From 620cb457df2c0b8422e602fbb5eb1a6e7fd985db Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:00:15 -0500 Subject: [PATCH 1/7] Fix rendering of various elements --- src/FencedCodeBlockRenderer.cs | 7 +- src/HeaderBlockRenderer.cs | 18 +- src/LeafInlineRenderer.cs | 12 +- src/ListBlockRenderer.cs | 49 ++-- src/ListItemBlockRenderer.cs | 102 ++----- src/MarkdownConverter.cs | 2 +- src/MarkdownDocumentRenderer.cs | 29 ++ src/ParagraphBlockRenderer.cs | 44 +-- src/QuoteBlockRenderer.cs | 16 +- src/TableRenderer.cs | 62 ++++ src/VT100Renderer.cs | 26 ++ test/BasicTests.cs | 2 +- test/VT100Tests.cs | 504 +++++++++++++++++++------------- 13 files changed, 508 insertions(+), 365 deletions(-) create mode 100644 src/MarkdownDocumentRenderer.cs create mode 100644 src/TableRenderer.cs diff --git a/src/FencedCodeBlockRenderer.cs b/src/FencedCodeBlockRenderer.cs index d878ae9..3c31881 100644 --- a/src/FencedCodeBlockRenderer.cs +++ b/src/FencedCodeBlockRenderer.cs @@ -24,17 +24,14 @@ protected override void Write(VT100Renderer renderer, FencedCodeBlock obj) // This specifically helps for parameters help content. if (string.Equals(obj.Info, "yaml", StringComparison.OrdinalIgnoreCase)) { - renderer.Write("\t").WriteLine(codeLine.ToString()); + renderer.Write("\t").Write(codeLine.ToString()); } else { - renderer.WriteLine(renderer.EscapeSequences.FormatCode(codeLine.ToString(), isInline: false)); + renderer.Write(renderer.EscapeSequences.FormatCode(codeLine.ToString(), isInline: false)); } } } - - // Add a blank line after the code block for better readability. - renderer.WriteLine(); } } } diff --git a/src/HeaderBlockRenderer.cs b/src/HeaderBlockRenderer.cs index 454000a..46a5b0b 100644 --- a/src/HeaderBlockRenderer.cs +++ b/src/HeaderBlockRenderer.cs @@ -20,33 +20,27 @@ protected override void Write(VT100Renderer renderer, HeadingBlock obj) switch (obj.Level) { case 1: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader1(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader1(headerText)); break; case 2: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader2(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader2(headerText)); break; case 3: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader3(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader3(headerText)); break; case 4: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader4(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader4(headerText)); break; case 5: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader5(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader5(headerText)); break; case 6: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader6(headerText)); - renderer.WriteLine(); + renderer.Write(renderer.EscapeSequences.FormatHeader6(headerText)); break; } } diff --git a/src/LeafInlineRenderer.cs b/src/LeafInlineRenderer.cs index d1af034..780cbd3 100644 --- a/src/LeafInlineRenderer.cs +++ b/src/LeafInlineRenderer.cs @@ -12,17 +12,7 @@ internal class LeafInlineRenderer : VT100ObjectRenderer { protected override void Write(VT100Renderer renderer, LeafInline obj) { - // If the next sibling is null, then this is the last line in the paragraph. - // Add new line character at the end. - // Else just write without newline at the end. - if (obj.NextSibling == null) - { - renderer.WriteLine(obj.ToString()); - } - else - { - renderer.Write(obj.ToString()); - } + renderer.Write(obj.ToString()); } } } diff --git a/src/ListBlockRenderer.cs b/src/ListBlockRenderer.cs index 100565b..c35aab5 100644 --- a/src/ListBlockRenderer.cs +++ b/src/ListBlockRenderer.cs @@ -12,36 +12,25 @@ internal class ListBlockRenderer : VT100ObjectRenderer { protected override void Write(VT100Renderer renderer, ListBlock obj) { - // start index of a numbered block. - int index = 1; - - foreach (var item in obj) - { - if (item is ListItemBlock listItem) - { - if (obj.IsOrdered) - { - RenderNumberedList(renderer, listItem, index++); - } - else - { - renderer.Write(listItem); - } - } - } - - renderer.WriteLine(); - } - - private static void RenderNumberedList(VT100Renderer renderer, ListItemBlock block, int index) - { - // For a numbered list, we need to make sure the index is incremented. - foreach (var line in block) - { - if (line is ParagraphBlock paragraphBlock) - { - renderer.Write(index.ToString()).Write(". ").Write(paragraphBlock.Inline); - } + for (int idx = 0; idx < obj.Count; idx++) + { + + Block block = obj[idx]; + + if (obj.IsOrdered) + { + renderer.Write((idx + 1).ToString()).Write(". "); + } + else + { + renderer.Write(obj.BulletType).Write(" "); + } + + renderer.Write(block); + + if (idx == obj.Count - 1) break; + + renderer.WriteLine(); } } } diff --git a/src/ListItemBlockRenderer.cs b/src/ListItemBlockRenderer.cs index 54cdeab..61ff863 100644 --- a/src/ListItemBlockRenderer.cs +++ b/src/ListItemBlockRenderer.cs @@ -1,80 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Threading; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for items in a list block. - /// - internal class ListItemBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ListItemBlock obj) - { - if (obj.Parent is ListBlock parent) - { - if (!parent.IsOrdered) - { - foreach (var line in obj) - { - RenderWithIndent(renderer, line, parent.BulletType, 0); - } - } - } - } - - private static void RenderWithIndent(VT100Renderer renderer, MarkdownObject block, char listBullet, int indentLevel) - { - // Indent left by 2 for each level on list. - string indent = Padding(indentLevel * 2); - - if (block is ParagraphBlock paragraphBlock) - { - renderer.Write(indent).Write(listBullet).Write(" ").Write(paragraphBlock.Inline); - } - else - { - // If there is a sublist, the block is a ListBlock instead of ParagraphBlock. - if (block is ListBlock subList) - { - foreach (var subListItem in subList) - { - if (subListItem is ListItemBlock subListItemBlock) - { - foreach (var line in subListItemBlock) - { - // Increment indent level for sub list. - RenderWithIndent(renderer, line, listBullet, indentLevel + 1); - } - } - } - } - } - } - - // Typical padding is at most a screen's width, any more than that and we won't bother caching. - private const int IndentCacheMax = 120; - - private static readonly string[] IndentCache = new string[IndentCacheMax]; - - internal static string Padding(int countOfSpaces) - { - if (countOfSpaces >= IndentCacheMax) - { - return new string(' ', countOfSpaces); - } - - var result = IndentCache[countOfSpaces]; - - if (result == null) - { - Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), comparand: null); - result = IndentCache[countOfSpaces]; - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; +using Markdig.Syntax; + +namespace Microsoft.PowerShell.MarkdownRender +{ + /// + /// Renderer for adding VT100 escape sequences for items in a list block. + /// + internal class ListItemBlockRenderer : VT100ObjectRenderer + { + protected override void Write(VT100Renderer renderer, ListItemBlock obj) + { + // 2 spaces for indentation + renderer.PushIndent(" "); + renderer.WriteChildrenJoinNewLine(obj); + renderer.PopIndent(); + } + } +} diff --git a/src/MarkdownConverter.cs b/src/MarkdownConverter.cs index bb4a3e8..1cb32f5 100644 --- a/src/MarkdownConverter.cs +++ b/src/MarkdownConverter.cs @@ -73,7 +73,7 @@ public static MarkdownInfo Convert(string markdownString, MarkdownConversionType if (conversionType.HasFlag(MarkdownConversionType.VT100)) { - pipeline = new MarkdownPipelineBuilder().Build(); + pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); // Use the VT100 renderer. var renderer = new VT100Renderer(writer, optionInfo); diff --git a/src/MarkdownDocumentRenderer.cs b/src/MarkdownDocumentRenderer.cs new file mode 100644 index 0000000..c03e78b --- /dev/null +++ b/src/MarkdownDocumentRenderer.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Markdig.Syntax; +using Markdig.Syntax.Inlines; + +namespace Microsoft.PowerShell.MarkdownRender +{ + /// + /// Renderer for adding VT100 escape sequences for paragraphs. + /// + internal class MarkdownDocumentRenderer : VT100ObjectRenderer + { + protected override void Write(VT100Renderer renderer, MarkdownDocument obj) + { + bool f = true; + foreach (Block item in obj) + { + if (item.Span.IsEmpty) continue; + + if (f) f = false; + else renderer.WriteLine(); + + renderer.Write(item); + renderer.WriteLine(); + } + } + } +} diff --git a/src/ParagraphBlockRenderer.cs b/src/ParagraphBlockRenderer.cs index 3b854cb..0040707 100644 --- a/src/ParagraphBlockRenderer.cs +++ b/src/ParagraphBlockRenderer.cs @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for paragraphs. - /// - internal class ParagraphBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ParagraphBlock obj) - { - // Call the renderer for children, leaf inline or line breaks. - renderer.WriteChildren(obj.Inline); - - // Add new line at the end of the paragraph. - renderer.WriteLine(); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Markdig.Syntax; + +namespace Microsoft.PowerShell.MarkdownRender +{ + /// + /// Renderer for adding VT100 escape sequences for paragraphs. + /// + internal class ParagraphBlockRenderer : VT100ObjectRenderer + { + /** + * Does not append newline after rendering the paragraph. + */ + protected override void Write(VT100Renderer renderer, ParagraphBlock obj) + { + // Call the renderer for children, leaf inline or line breaks. + renderer.WriteChildren(obj.Inline); + } + } +} diff --git a/src/QuoteBlockRenderer.cs b/src/QuoteBlockRenderer.cs index 7e0bda6..59725ec 100644 --- a/src/QuoteBlockRenderer.cs +++ b/src/QuoteBlockRenderer.cs @@ -12,14 +12,16 @@ internal class QuoteBlockRenderer : VT100ObjectRenderer { protected override void Write(VT100Renderer renderer, QuoteBlock obj) { - // Iterate through each item and add the quote character before the content. - foreach (var item in obj) - { - renderer.Write(obj.QuoteChar).Write(" ").Write(item); + renderer.PushIndent(obj.QuoteChar + " "); + for (int i = 0; i < obj.Count; i++) + { + Block b = obj[i]; + renderer.Write(b); + + if (i == obj.Count - 1) break; + renderer.WriteLine(); } - - // Add blank line after the quote block. - renderer.WriteLine(); + renderer.PopIndent(); } } } diff --git a/src/TableRenderer.cs b/src/TableRenderer.cs new file mode 100644 index 0000000..5187578 --- /dev/null +++ b/src/TableRenderer.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Markdig.Extensions.Tables; +using Markdig.Syntax; + +namespace Microsoft.PowerShell.MarkdownRender +{ + /// + /// Renderer for adding VT100 escape sequences for quote blocks. + /// + internal class TableRenderer : VT100ObjectRenderer + { + protected override void Write(VT100Renderer renderer, Table table) + { + // TODO: improve table rendering + // Probably have to create a new renderer, render the content as string, + // and then process the string to table in order to align the columns + // Additionally, the new rendered content will not have proper length because of + // VT100 escape sequences, therefore a sanitization step is needed + // The sanitization regex is currently in VT100Tests.cs:VT100Tests.MarkdownDocument + // Console column width can be exposed by renderer in order + // to prevent tables from being wider than console + + for (int i = 0; i < table.Count; i++) + { + Block b = table[i]; + if (b.Span.IsEmpty) continue; + + if (i > 0) renderer.WriteLine(); + if (i == 1) + { + if (table[0] is TableRow head) + { + foreach (var cell in head) + { + renderer.Write("|-"); + renderer.Write(new string('-', cell.Span.End - cell.Span.Start)); + } + renderer.Write("|"); + } + renderer.WriteLine(); + } + + if (b is ContainerBlock cb) + { + foreach (var item in cb) + { + renderer.Write("| "); + renderer.Write(item); + renderer.Write(" "); + } + + renderer.Write("|"); + } else + { + renderer.Write(b); + } + } + } + } +} diff --git a/src/VT100Renderer.cs b/src/VT100Renderer.cs index 1f5da62..b4efbf0 100644 --- a/src/VT100Renderer.cs +++ b/src/VT100Renderer.cs @@ -3,6 +3,7 @@ using System.IO; using Markdig.Renderers; +using Markdig.Syntax; namespace Microsoft.PowerShell.MarkdownRender { @@ -20,6 +21,9 @@ public VT100Renderer(TextWriter writer, PSMarkdownOptionInfo optionInfo) : base( { EscapeSequences = new VT100EscapeSequences(optionInfo); + // Root Renderer to handle element spacing + ObjectRenderers.Add(new MarkdownDocumentRenderer()); + // Add the various element renderers. ObjectRenderers.Add(new HeaderBlockRenderer()); ObjectRenderers.Add(new LineBreakRenderer()); @@ -32,11 +36,33 @@ public VT100Renderer(TextWriter writer, PSMarkdownOptionInfo optionInfo) : base( ObjectRenderers.Add(new ListBlockRenderer()); ObjectRenderers.Add(new ListItemBlockRenderer()); ObjectRenderers.Add(new QuoteBlockRenderer()); + + // Table + ObjectRenderers.Add(new TableRenderer()); } /// /// Gets the current escape sequences. /// public VT100EscapeSequences EscapeSequences { get; } + + /// + /// Modify the container block to remove empty children and write the children + /// + /// Then write children excluding last line break + /// + public void WriteChildrenJoinNewLine(ContainerBlock obj) + { + bool f = true; + foreach (Block b in obj) + { + if (b.Span.IsEmpty) continue; + + if (f) f = false; + else WriteLine(); + + Write(b); + } + } } } diff --git a/test/BasicTests.cs b/test/BasicTests.cs index 5b11ffd..a1a2195 100644 --- a/test/BasicTests.cs +++ b/test/BasicTests.cs @@ -12,7 +12,7 @@ public class BasicTests public void VT100Renderer() { var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("# Heading1", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[7mHeading1{Esc}[0m\n\n"; + string expected = $"{Esc}[7mHeading1{Esc}[0m\n"; Assert.Equal(expected, m.VT100EncodedString); } diff --git a/test/VT100Tests.cs b/test/VT100Tests.cs index 6f7a51d..02c7988 100644 --- a/test/VT100Tests.cs +++ b/test/VT100Tests.cs @@ -1,197 +1,309 @@ -using System; -using System.Runtime.InteropServices; -using Microsoft.PowerShell.MarkdownRender; -using Xunit; - -namespace Microsoft.PowerShell.MarkdownRender.Tests -{ - public class VT100Tests - { - private const char Esc = (char)0x1b; - - [Fact] - public void CodeInline() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("`Hello`", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95mHello{Esc}[0m\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void EmphasisInlineBold() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("**Hello**", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[1mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void EmphasisInlineItalics() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("*Hello*", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[36mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void FencedCodeBlock() - { - string inputString = "```PowerShell\n$a = 1\n```"; - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95m$a = 1{Esc}[500@{Esc}[0m\n\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30m$a = 1{Esc}[500@{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header1() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("# Heading1", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[7mHeading1{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header2() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("## Heading2", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;93mHeading2{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header3() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("### Heading3", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;94mHeading3{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header4() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("#### Heading4", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;95mHeading4{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header5() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("##### Heading5", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;96mHeading5{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header6() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("###### Heading6", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;97mHeading6{Esc}[0m\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void LinkUrl() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("[Bing](https://www.bing.com)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;38;5;117m\"Bing\"{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void LinkImage() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("![](image.png)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[33m[Image]{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void OrderedList() - { - string inputString = "1. A\n2. B\n3. C\n"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"1. A\n2. B\n3. C\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void UnorderedList() - { - string inputString = "* A\n* B\n* C\n"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"* A\n* B\n* C\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void QuoteBlock() - { - string inputString = "> Hello"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"> Hello\n\n\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - } - - public class PSMarkdownOptionInfoTests - { - private void ValidateDarkTheme(PSMarkdownOptionInfo opt) - { - bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; - - Assert.Equal("[1m", opt.EmphasisBold); - Assert.Equal("[36m", opt.EmphasisItalics); - Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); - Assert.Equal("[7m", opt.Header1); - Assert.Equal("[4;93m", opt.Header2); - Assert.Equal("[4;94m", opt.Header3); - Assert.Equal("[4;95m", opt.Header4); - Assert.Equal("[4;96m", opt.Header5); - Assert.Equal("[4;97m", opt.Header6); - Assert.Equal("[33m", opt.Image); - Assert.Equal("[4;38;5;117m", opt.Link); - - string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; - Assert.Equal(expectedCode, opt.Code); - } - - private void ValidateLightTheme(PSMarkdownOptionInfo opt) - { - bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; - - Assert.Equal("[1m", opt.EmphasisBold); - Assert.Equal("[36m", opt.EmphasisItalics); - Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); - Assert.Equal("[7m", opt.Header1); - Assert.Equal("[4;33m", opt.Header2); - Assert.Equal("[4;34m", opt.Header3); - Assert.Equal("[4;35m", opt.Header4); - Assert.Equal("[4;36m", opt.Header5); - Assert.Equal("[4;30m", opt.Header6); - Assert.Equal("[33m", opt.Image); - Assert.Equal("[4;38;5;117m", opt.Link); - - string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; - Assert.Equal(expectedCode, opt.Code); - } - - [Fact] - public void PSMarkdownOptionInfo_Default() - { - var opt = new PSMarkdownOptionInfo(); - ValidateDarkTheme(opt); - } - - [Fact] - public void PSMarkdownOptionInfo_LightTheme() - { - var opt = new PSMarkdownOptionInfo(); - opt.SetLightTheme(); - ValidateLightTheme(opt); - } - } +using System; +using System.Runtime.InteropServices; +using Microsoft.PowerShell.MarkdownRender; +using Xunit; +using System.Text.RegularExpressions; + +namespace Microsoft.PowerShell.MarkdownRender.Tests +{ + public class VT100Tests + { + private const char Esc = (char)0x1b; + + [Fact] + public void CodeInline() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("`Hello`", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95mHello{Esc}[0m\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void EmphasisInlineBold() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("**Hello**", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[1mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void EmphasisInlineItalics() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("*Hello*", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[36mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void FencedCodeBlock() + { + string inputString = "```PowerShell\n$a = 1\n```"; + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95m$a = 1{Esc}[500@{Esc}[0m\n\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30m$a = 1{Esc}[500@{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header1() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("# Heading1", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[7mHeading1{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header2() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("## Heading2", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;93mHeading2{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header3() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("### Heading3", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;94mHeading3{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header4() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("#### Heading4", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;95mHeading4{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header5() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("##### Heading5", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;96mHeading5{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header6() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("###### Heading6", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;97mHeading6{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Inline() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("Hello\n\nWorld", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = "Hello\n\nWorld\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void LinkUrl() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("[Bing](https://www.bing.com)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;38;5;117m\"Bing\"{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void LinkImage() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("![](image.png)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[33m[Image]{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void OrderedList() + { + string inputString = "1. `A`\n2. B\n3. C\n"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"1. {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n2. B\n3. C\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void UnorderedList() + { + string inputString = "* `A`\n* B\n* C\n"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"* {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n* B\n* C\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void QuoteBlock() + { + string inputString = "> Hello"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"> Hello\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void MarkdownDocument() + { + string md = @" + +# Heading1 + +- li +- `hello` +- [link](linker) + +`solo inline` + +code `code inline` should work + +```ps1 +$a = 1 +``` + +> > double quote + +> quote +> > double quote +> > > triple quote +> > +> > back a quote +> > +> still quote + +1. `a` +2. [b](l) +3. c + + +- l1.0 + 1. l2.0 + - l3.0 + 3. l2.1 + - l3.1 + 3. l2.2 +- l1.1 + + +| a | bc | +|---|---| +| 1 | 2 | +| 3 | 4 | + +--- + +end +".Replace("\r\n", "\n"); + string expected = @"Heading1 + +- li +- hello +- ""link"" + +solo inline + +code code inline should work + +$a = 1 + +> > double quote + +> quote +> > double quote +> > > triple quote +> > back a quote +> still quote + +1. a +2. ""b"" +3. c + +- l1.0 + 1. l2.0 + - l3.0 + 2. l2.1 + - l3.1 + 3. l2.2 +- l1.1 + +| a | bc | +|---|----| +| 1 | 2 | +| 3 | 4 | + + + +end +".Replace("\r\n", "\n"); + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(md, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string pattern = @"\x1B\[[0-?]*[@-~]"; + + // Replace all occurrences of VT100 escape sequences with an empty string + string actual = Regex.Replace(m.VT100EncodedString, pattern, ""); + + Assert.Equal(expected, actual); + } + } + + public class PSMarkdownOptionInfoTests + { + private void ValidateDarkTheme(PSMarkdownOptionInfo opt) + { + bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; + + Assert.Equal("[1m", opt.EmphasisBold); + Assert.Equal("[36m", opt.EmphasisItalics); + Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); + Assert.Equal("[7m", opt.Header1); + Assert.Equal("[4;93m", opt.Header2); + Assert.Equal("[4;94m", opt.Header3); + Assert.Equal("[4;95m", opt.Header4); + Assert.Equal("[4;96m", opt.Header5); + Assert.Equal("[4;97m", opt.Header6); + Assert.Equal("[33m", opt.Image); + Assert.Equal("[4;38;5;117m", opt.Link); + + string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; + Assert.Equal(expectedCode, opt.Code); + } + + private void ValidateLightTheme(PSMarkdownOptionInfo opt) + { + bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; + + Assert.Equal("[1m", opt.EmphasisBold); + Assert.Equal("[36m", opt.EmphasisItalics); + Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); + Assert.Equal("[7m", opt.Header1); + Assert.Equal("[4;33m", opt.Header2); + Assert.Equal("[4;34m", opt.Header3); + Assert.Equal("[4;35m", opt.Header4); + Assert.Equal("[4;36m", opt.Header5); + Assert.Equal("[4;30m", opt.Header6); + Assert.Equal("[33m", opt.Image); + Assert.Equal("[4;38;5;117m", opt.Link); + + string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; + Assert.Equal(expectedCode, opt.Code); + } + + [Fact] + public void PSMarkdownOptionInfo_Default() + { + var opt = new PSMarkdownOptionInfo(); + ValidateDarkTheme(opt); + } + + [Fact] + public void PSMarkdownOptionInfo_LightTheme() + { + var opt = new PSMarkdownOptionInfo(); + opt.SetLightTheme(); + ValidateLightTheme(opt); + } + } } \ No newline at end of file From a50667fae455f0f0f82b65b65b3360e121fb5ce5 Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:04:49 -0500 Subject: [PATCH 2/7] Use WriteChildrenJoinNewLine --- src/QuoteBlockRenderer.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/QuoteBlockRenderer.cs b/src/QuoteBlockRenderer.cs index 59725ec..806f486 100644 --- a/src/QuoteBlockRenderer.cs +++ b/src/QuoteBlockRenderer.cs @@ -13,14 +13,7 @@ internal class QuoteBlockRenderer : VT100ObjectRenderer protected override void Write(VT100Renderer renderer, QuoteBlock obj) { renderer.PushIndent(obj.QuoteChar + " "); - for (int i = 0; i < obj.Count; i++) - { - Block b = obj[i]; - renderer.Write(b); - - if (i == obj.Count - 1) break; - renderer.WriteLine(); - } + renderer.WriteChildrenJoinNewLine(obj); renderer.PopIndent(); } } From 096dea84f4f9d356e01c3dd8b5342ea835d6b5df Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:06:59 -0500 Subject: [PATCH 3/7] fix line ending --- test/VT100Tests.cs | 616 ++++++++++++++++++++++----------------------- 1 file changed, 308 insertions(+), 308 deletions(-) diff --git a/test/VT100Tests.cs b/test/VT100Tests.cs index 02c7988..5972e6d 100644 --- a/test/VT100Tests.cs +++ b/test/VT100Tests.cs @@ -1,309 +1,309 @@ -using System; -using System.Runtime.InteropServices; -using Microsoft.PowerShell.MarkdownRender; -using Xunit; -using System.Text.RegularExpressions; - -namespace Microsoft.PowerShell.MarkdownRender.Tests -{ - public class VT100Tests - { - private const char Esc = (char)0x1b; - - [Fact] - public void CodeInline() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("`Hello`", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95mHello{Esc}[0m\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void EmphasisInlineBold() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("**Hello**", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[1mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void EmphasisInlineItalics() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("*Hello*", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[36mHello{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void FencedCodeBlock() - { - string inputString = "```PowerShell\n$a = 1\n```"; - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95m$a = 1{Esc}[500@{Esc}[0m\n\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30m$a = 1{Esc}[500@{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header1() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("# Heading1", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[7mHeading1{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header2() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("## Heading2", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;93mHeading2{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header3() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("### Heading3", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;94mHeading3{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header4() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("#### Heading4", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;95mHeading4{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header5() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("##### Heading5", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;96mHeading5{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Header6() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("###### Heading6", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;97mHeading6{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void Inline() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("Hello\n\nWorld", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = "Hello\n\nWorld\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void LinkUrl() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("[Bing](https://www.bing.com)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[4;38;5;117m\"Bing\"{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void LinkImage() - { - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("![](image.png)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"{Esc}[33m[Image]{Esc}[0m\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void OrderedList() - { - string inputString = "1. `A`\n2. B\n3. C\n"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"1. {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n2. B\n3. C\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void UnorderedList() - { - string inputString = "* `A`\n* B\n* C\n"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"* {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n* B\n* C\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void QuoteBlock() - { - string inputString = "> Hello"; - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string expected = $"> Hello\n"; - Assert.Equal(expected, m.VT100EncodedString); - } - - [Fact] - public void MarkdownDocument() - { - string md = @" - -# Heading1 - -- li -- `hello` -- [link](linker) - -`solo inline` - -code `code inline` should work - -```ps1 -$a = 1 -``` - -> > double quote - -> quote -> > double quote -> > > triple quote -> > -> > back a quote -> > -> still quote - -1. `a` -2. [b](l) -3. c - - -- l1.0 - 1. l2.0 - - l3.0 - 3. l2.1 - - l3.1 - 3. l2.2 -- l1.1 - - -| a | bc | -|---|---| -| 1 | 2 | -| 3 | 4 | - ---- - -end -".Replace("\r\n", "\n"); - string expected = @"Heading1 - -- li -- hello -- ""link"" - -solo inline - -code code inline should work - -$a = 1 - -> > double quote - -> quote -> > double quote -> > > triple quote -> > back a quote -> still quote - -1. a -2. ""b"" -3. c - -- l1.0 - 1. l2.0 - - l3.0 - 2. l2.1 - - l3.1 - 3. l2.2 -- l1.1 - -| a | bc | -|---|----| -| 1 | 2 | -| 3 | 4 | - - - -end -".Replace("\r\n", "\n"); - - var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(md, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); - string pattern = @"\x1B\[[0-?]*[@-~]"; - - // Replace all occurrences of VT100 escape sequences with an empty string - string actual = Regex.Replace(m.VT100EncodedString, pattern, ""); - - Assert.Equal(expected, actual); - } - } - - public class PSMarkdownOptionInfoTests - { - private void ValidateDarkTheme(PSMarkdownOptionInfo opt) - { - bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; - - Assert.Equal("[1m", opt.EmphasisBold); - Assert.Equal("[36m", opt.EmphasisItalics); - Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); - Assert.Equal("[7m", opt.Header1); - Assert.Equal("[4;93m", opt.Header2); - Assert.Equal("[4;94m", opt.Header3); - Assert.Equal("[4;95m", opt.Header4); - Assert.Equal("[4;96m", opt.Header5); - Assert.Equal("[4;97m", opt.Header6); - Assert.Equal("[33m", opt.Image); - Assert.Equal("[4;38;5;117m", opt.Link); - - string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; - Assert.Equal(expectedCode, opt.Code); - } - - private void ValidateLightTheme(PSMarkdownOptionInfo opt) - { - bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; - - Assert.Equal("[1m", opt.EmphasisBold); - Assert.Equal("[36m", opt.EmphasisItalics); - Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); - Assert.Equal("[7m", opt.Header1); - Assert.Equal("[4;33m", opt.Header2); - Assert.Equal("[4;34m", opt.Header3); - Assert.Equal("[4;35m", opt.Header4); - Assert.Equal("[4;36m", opt.Header5); - Assert.Equal("[4;30m", opt.Header6); - Assert.Equal("[33m", opt.Image); - Assert.Equal("[4;38;5;117m", opt.Link); - - string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; - Assert.Equal(expectedCode, opt.Code); - } - - [Fact] - public void PSMarkdownOptionInfo_Default() - { - var opt = new PSMarkdownOptionInfo(); - ValidateDarkTheme(opt); - } - - [Fact] - public void PSMarkdownOptionInfo_LightTheme() - { - var opt = new PSMarkdownOptionInfo(); - opt.SetLightTheme(); - ValidateLightTheme(opt); - } - } +using System; +using System.Runtime.InteropServices; +using Microsoft.PowerShell.MarkdownRender; +using Xunit; +using System.Text.RegularExpressions; + +namespace Microsoft.PowerShell.MarkdownRender.Tests +{ + public class VT100Tests + { + private const char Esc = (char)0x1b; + + [Fact] + public void CodeInline() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("`Hello`", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95mHello{Esc}[0m\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void EmphasisInlineBold() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("**Hello**", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[1mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void EmphasisInlineItalics() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("*Hello*", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[36mHello{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void FencedCodeBlock() + { + string inputString = "```PowerShell\n$a = 1\n```"; + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{Esc}[107;95m$a = 1{Esc}[500@{Esc}[0m\n\n" : $"{Esc}[48;2;155;155;155;38;2;30;30;30m$a = 1{Esc}[500@{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header1() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("# Heading1", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[7mHeading1{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header2() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("## Heading2", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;93mHeading2{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header3() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("### Heading3", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;94mHeading3{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header4() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("#### Heading4", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;95mHeading4{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header5() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("##### Heading5", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;96mHeading5{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Header6() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("###### Heading6", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;97mHeading6{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void Inline() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("Hello\n\nWorld", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = "Hello\n\nWorld\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void LinkUrl() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("[Bing](https://www.bing.com)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[4;38;5;117m\"Bing\"{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void LinkImage() + { + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert("![](image.png)", MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"{Esc}[33m[Image]{Esc}[0m\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void OrderedList() + { + string inputString = "1. `A`\n2. B\n3. C\n"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"1. {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n2. B\n3. C\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void UnorderedList() + { + string inputString = "* `A`\n* B\n* C\n"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"* {Esc}[48;2;155;155;155;38;2;30;30;30mA{Esc}[0m\n* B\n* C\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void QuoteBlock() + { + string inputString = "> Hello"; + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(inputString, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string expected = $"> Hello\n"; + Assert.Equal(expected, m.VT100EncodedString); + } + + [Fact] + public void MarkdownDocument() + { + string md = @" + +# Heading1 + +- li +- `hello` +- [link](linker) + +`solo inline` + +code `code inline` should work + +```ps1 +$a = 1 +``` + +> > double quote + +> quote +> > double quote +> > > triple quote +> > +> > back a quote +> > +> still quote + +1. `a` +2. [b](l) +3. c + + +- l1.0 + 1. l2.0 + - l3.0 + 3. l2.1 + - l3.1 + 3. l2.2 +- l1.1 + + +| a | bc | +|---|---| +| 1 | 2 | +| 3 | 4 | + +--- + +end +".Replace("\r\n", "\n"); + string expected = @"Heading1 + +- li +- hello +- ""link"" + +solo inline + +code code inline should work + +$a = 1 + +> > double quote + +> quote +> > double quote +> > > triple quote +> > back a quote +> still quote + +1. a +2. ""b"" +3. c + +- l1.0 + 1. l2.0 + - l3.0 + 2. l2.1 + - l3.1 + 3. l2.2 +- l1.1 + +| a | bc | +|---|----| +| 1 | 2 | +| 3 | 4 | + + + +end +".Replace("\r\n", "\n"); + + var m = Microsoft.PowerShell.MarkdownRender.MarkdownConverter.Convert(md, MarkdownConversionType.VT100, new PSMarkdownOptionInfo() ); + string pattern = @"\x1B\[[0-?]*[@-~]"; + + // Replace all occurrences of VT100 escape sequences with an empty string + string actual = Regex.Replace(m.VT100EncodedString, pattern, ""); + + Assert.Equal(expected, actual); + } + } + + public class PSMarkdownOptionInfoTests + { + private void ValidateDarkTheme(PSMarkdownOptionInfo opt) + { + bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; + + Assert.Equal("[1m", opt.EmphasisBold); + Assert.Equal("[36m", opt.EmphasisItalics); + Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); + Assert.Equal("[7m", opt.Header1); + Assert.Equal("[4;93m", opt.Header2); + Assert.Equal("[4;94m", opt.Header3); + Assert.Equal("[4;95m", opt.Header4); + Assert.Equal("[4;96m", opt.Header5); + Assert.Equal("[4;97m", opt.Header6); + Assert.Equal("[33m", opt.Image); + Assert.Equal("[4;38;5;117m", opt.Link); + + string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; + Assert.Equal(expectedCode, opt.Code); + } + + private void ValidateLightTheme(PSMarkdownOptionInfo opt) + { + bool expectedEnableVT100 = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version.Major >= 10; + + Assert.Equal("[1m", opt.EmphasisBold); + Assert.Equal("[36m", opt.EmphasisItalics); + Assert.Equal(expectedEnableVT100, opt.EnableVT100Encoding); + Assert.Equal("[7m", opt.Header1); + Assert.Equal("[4;33m", opt.Header2); + Assert.Equal("[4;34m", opt.Header3); + Assert.Equal("[4;35m", opt.Header4); + Assert.Equal("[4;36m", opt.Header5); + Assert.Equal("[4;30m", opt.Header6); + Assert.Equal("[33m", opt.Image); + Assert.Equal("[4;38;5;117m", opt.Link); + + string expectedCode = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "[107;95m" : "[48;2;155;155;155;38;2;30;30;30m"; + Assert.Equal(expectedCode, opt.Code); + } + + [Fact] + public void PSMarkdownOptionInfo_Default() + { + var opt = new PSMarkdownOptionInfo(); + ValidateDarkTheme(opt); + } + + [Fact] + public void PSMarkdownOptionInfo_LightTheme() + { + var opt = new PSMarkdownOptionInfo(); + opt.SetLightTheme(); + ValidateLightTheme(opt); + } + } } \ No newline at end of file From 9ef233248c111333550633ebaa426ed4f30df070 Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:13:33 -0500 Subject: [PATCH 4/7] fix codeblock newline (oops) --- src/FencedCodeBlockRenderer.cs | 4 ++++ test/VT100Tests.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/FencedCodeBlockRenderer.cs b/src/FencedCodeBlockRenderer.cs index 3c31881..cee551d 100644 --- a/src/FencedCodeBlockRenderer.cs +++ b/src/FencedCodeBlockRenderer.cs @@ -16,10 +16,14 @@ protected override void Write(VT100Renderer renderer, FencedCodeBlock obj) { if (obj?.Lines.Lines != null) { + bool f = true; foreach (StringLine codeLine in obj.Lines.Lines) { if (!string.IsNullOrWhiteSpace(codeLine.ToString())) { + if (f) f = false; + else renderer.WriteLine(); + // If the code block is of type YAML, then tab to right to improve readability. // This specifically helps for parameters help content. if (string.Equals(obj.Info, "yaml", StringComparison.OrdinalIgnoreCase)) diff --git a/test/VT100Tests.cs b/test/VT100Tests.cs index 5972e6d..162a2ac 100644 --- a/test/VT100Tests.cs +++ b/test/VT100Tests.cs @@ -162,6 +162,7 @@ public void MarkdownDocument() ```ps1 $a = 1 +$b = 2 ``` > > double quote @@ -208,6 +209,7 @@ solo inline code code inline should work $a = 1 +$b = 2 > > double quote From b477ecbf8906a25770b448bd7d1b1683b51c9e8e Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:39:48 -0500 Subject: [PATCH 5/7] add multiline test --- test/VT100Tests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/VT100Tests.cs b/test/VT100Tests.cs index 162a2ac..42bb0f5 100644 --- a/test/VT100Tests.cs +++ b/test/VT100Tests.cs @@ -182,7 +182,9 @@ 3. c - l1.0 1. l2.0 - - l3.0 + - l3.0\ + l3.0.line1\ + l3.0.line2 3. l2.1 - l3.1 3. l2.2 @@ -226,6 +228,8 @@ 3. c - l1.0 1. l2.0 - l3.0 + l3.0.line1 + l3.0.line2 2. l2.1 - l3.1 3. l2.2 From 3399046111d4abf5211bb06d69fd8996ab917b2a Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:48:23 -0500 Subject: [PATCH 6/7] Fix consistency --- src/ListBlockRenderer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ListBlockRenderer.cs b/src/ListBlockRenderer.cs index c35aab5..65277a7 100644 --- a/src/ListBlockRenderer.cs +++ b/src/ListBlockRenderer.cs @@ -14,6 +14,7 @@ protected override void Write(VT100Renderer renderer, ListBlock obj) { for (int idx = 0; idx < obj.Count; idx++) { + if (idx > 0) renderer.WriteLine(); Block block = obj[idx]; @@ -27,10 +28,7 @@ protected override void Write(VT100Renderer renderer, ListBlock obj) } renderer.Write(block); - - if (idx == obj.Count - 1) break; - - renderer.WriteLine(); + } } } From c2d556404f995d6f0538a53b7102bb71824c1cab Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:51:40 -0500 Subject: [PATCH 7/7] fix line endings --- src/ListItemBlockRenderer.cs | 44 ++++++++++++++++----------------- src/TableRenderer.cs | 48 ++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/ListItemBlockRenderer.cs b/src/ListItemBlockRenderer.cs index 61ff863..5edcb3e 100644 --- a/src/ListItemBlockRenderer.cs +++ b/src/ListItemBlockRenderer.cs @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Threading; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for items in a list block. - /// - internal class ListItemBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ListItemBlock obj) - { - // 2 spaces for indentation - renderer.PushIndent(" "); - renderer.WriteChildrenJoinNewLine(obj); - renderer.PopIndent(); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; +using Markdig.Syntax; + +namespace Microsoft.PowerShell.MarkdownRender +{ + /// + /// Renderer for adding VT100 escape sequences for items in a list block. + /// + internal class ListItemBlockRenderer : VT100ObjectRenderer + { + protected override void Write(VT100Renderer renderer, ListItemBlock obj) + { + // 2 spaces for indentation + renderer.PushIndent(" "); + renderer.WriteChildrenJoinNewLine(obj); + renderer.PopIndent(); + } + } +} diff --git a/src/TableRenderer.cs b/src/TableRenderer.cs index 5187578..a9fa18e 100644 --- a/src/TableRenderer.cs +++ b/src/TableRenderer.cs @@ -23,38 +23,38 @@ protected override void Write(VT100Renderer renderer, Table table) // to prevent tables from being wider than console for (int i = 0; i < table.Count; i++) - { - Block b = table[i]; + { + Block b = table[i]; if (b.Span.IsEmpty) continue; if (i > 0) renderer.WriteLine(); - if (i == 1) - { - if (table[0] is TableRow head) - { - foreach (var cell in head) - { - renderer.Write("|-"); - renderer.Write(new string('-', cell.Span.End - cell.Span.Start)); - } - renderer.Write("|"); - } - renderer.WriteLine(); + if (i == 1) + { + if (table[0] is TableRow head) + { + foreach (var cell in head) + { + renderer.Write("|-"); + renderer.Write(new string('-', cell.Span.End - cell.Span.Start)); + } + renderer.Write("|"); + } + renderer.WriteLine(); } - if (b is ContainerBlock cb) - { - foreach (var item in cb) - { - renderer.Write("| "); - renderer.Write(item); - renderer.Write(" "); + if (b is ContainerBlock cb) + { + foreach (var item in cb) + { + renderer.Write("| "); + renderer.Write(item); + renderer.Write(" "); } renderer.Write("|"); - } else - { - renderer.Write(b); + } else + { + renderer.Write(b); } } }