diff --git a/src/Markdig/Helpers/LazySubstring.cs b/src/Markdig/Helpers/LazySubstring.cs new file mode 100644 index 00000000..5cf15bd2 --- /dev/null +++ b/src/Markdig/Helpers/LazySubstring.cs @@ -0,0 +1,45 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Diagnostics; + +namespace Markdig.Helpers +{ + internal struct LazySubstring + { + public string Text; + public int Offset; + public int Length; + + public LazySubstring(string text) + { + Text = text; + Offset = 0; + Length = text.Length; + } + + public LazySubstring(string text, int offset, int length) + { + Debug.Assert((ulong)offset + (ulong)length < (ulong)text.Length, $"{offset}-{length} in {text}"); + Text = text; + Offset = offset; + Length = length; + } + + public ReadOnlySpan AsSpan() => Text.AsSpan(Offset, Length); + + public override string ToString() + { + if (Offset != 0 || Length != Text.Length) + { + Text = Text.Substring(Offset, Length); + Offset = 0; + } + + return Text; + + } + } +} diff --git a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs index e3432e79..da6474f8 100644 --- a/src/Markdig/Parsers/Inlines/CodeInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/CodeInlineParser.cs @@ -6,6 +6,7 @@ using Markdig.Helpers; using Markdig.Syntax; using Markdig.Syntax.Inlines; using System; +using System.Diagnostics; namespace Markdig.Parsers.Inlines { @@ -54,6 +55,7 @@ namespace Markdig.Parsers.Inlines // whitespace from the opening or closing backtick strings. bool allSpace = true; + bool containsNewLine = false; var contentEnd = -1; while (c != '\0') @@ -61,10 +63,12 @@ namespace Markdig.Parsers.Inlines // Transform '\n' into a single space if (c == '\n') { + containsNewLine = true; c = ' '; } else if (c == '\r') { + containsNewLine = true; slice.SkipChar(); c = slice.CurrentChar; continue; @@ -100,14 +104,19 @@ namespace Markdig.Parsers.Inlines { ReadOnlySpan contentSpan = builder.AsSpan(); + var content = containsNewLine + ? new LazySubstring(contentSpan.ToString()) + : new LazySubstring(slice.Text, contentStart, contentSpan.Length); + + Debug.Assert(contentSpan.SequenceEqual(content.AsSpan())); + // Remove one space from front and back if the string is not all spaces if (!allSpace && contentSpan.Length > 2 && contentSpan[0] == ' ' && contentSpan[contentSpan.Length - 1] == ' ') { - contentSpan = contentSpan.Slice(1, contentSpan.Length - 2); + content.Offset++; + content.Length -= 2; } - string content = contentSpan.ToString(); - int delimiterCount = Math.Min(openSticks, closeSticks); var spanStart = processor.GetSourcePosition(startPosition, out int line, out int column); var spanEnd = processor.GetSourcePosition(slice.Start - 1); diff --git a/src/Markdig/Renderers/Html/Inlines/CodeInlineRenderer.cs b/src/Markdig/Renderers/Html/Inlines/CodeInlineRenderer.cs index 696d1514..d4d1dc5c 100644 --- a/src/Markdig/Renderers/Html/Inlines/CodeInlineRenderer.cs +++ b/src/Markdig/Renderers/Html/Inlines/CodeInlineRenderer.cs @@ -22,11 +22,11 @@ namespace Markdig.Renderers.Html.Inlines } if (renderer.EnableHtmlEscape) { - renderer.WriteEscape(obj.Content); + renderer.WriteEscape(obj.ContentSpan); } else { - renderer.Write(obj.Content); + renderer.Write(obj.ContentSpan); } if (renderer.EnableHtmlForInline) { diff --git a/src/Markdig/Renderers/Normalize/Inlines/CodeInlineRenderer.cs b/src/Markdig/Renderers/Normalize/Inlines/CodeInlineRenderer.cs index c598f64d..9560075f 100644 --- a/src/Markdig/Renderers/Normalize/Inlines/CodeInlineRenderer.cs +++ b/src/Markdig/Renderers/Normalize/Inlines/CodeInlineRenderer.cs @@ -15,15 +15,16 @@ namespace Markdig.Renderers.Normalize.Inlines protected override void Write(NormalizeRenderer renderer, CodeInline obj) { var delimiterCount = 0; - for (var i = 0; i < obj.Content!.Length; i++) + string content = obj.Content; + for (var i = 0; i < content.Length; i++) { - var index = obj.Content.IndexOf(obj.Delimiter, i); + var index = content.IndexOf(obj.Delimiter, i); if (index == -1) break; var count = 1; - for (i = index + 1; i < obj.Content.Length; i++) + for (i = index + 1; i < content.Length; i++) { - if (obj.Content[i] == obj.Delimiter) count++; + if (content[i] == obj.Delimiter) count++; else break; } @@ -32,14 +33,14 @@ namespace Markdig.Renderers.Normalize.Inlines } var delimiterRun = new string(obj.Delimiter, delimiterCount + 1); renderer.Write(delimiterRun); - if (obj.Content.Length != 0) + if (content.Length != 0) { - if (obj.Content[0] == obj.Delimiter) + if (content[0] == obj.Delimiter) { renderer.Write(' '); } - renderer.Write(obj.Content); - if (obj.Content[obj.Content.Length - 1] == obj.Delimiter) + renderer.Write(content); + if (content[content.Length - 1] == obj.Delimiter) { renderer.Write(' '); } diff --git a/src/Markdig/Renderers/Roundtrip/Inlines/CodeInlineRenderer.cs b/src/Markdig/Renderers/Roundtrip/Inlines/CodeInlineRenderer.cs index 8343e158..be1b9c72 100644 --- a/src/Markdig/Renderers/Roundtrip/Inlines/CodeInlineRenderer.cs +++ b/src/Markdig/Renderers/Roundtrip/Inlines/CodeInlineRenderer.cs @@ -16,7 +16,7 @@ namespace Markdig.Renderers.Roundtrip.Inlines { var delimiterRun = new string(obj.Delimiter, obj.DelimiterCount); renderer.Write(delimiterRun); - if (obj.Content is { Length: > 0 }) + if (!obj.ContentSpan.IsEmpty) { renderer.Write(obj.ContentWithTrivia); } diff --git a/src/Markdig/Syntax/Inlines/CodeInline.cs b/src/Markdig/Syntax/Inlines/CodeInline.cs index 9d0fb0c2..00a524d2 100644 --- a/src/Markdig/Syntax/Inlines/CodeInline.cs +++ b/src/Markdig/Syntax/Inlines/CodeInline.cs @@ -3,6 +3,7 @@ // See the license.txt file in the project root for more information. using Markdig.Helpers; +using System; using System.Diagnostics; namespace Markdig.Syntax.Inlines @@ -17,9 +18,13 @@ namespace Markdig.Syntax.Inlines private TriviaProperties? _trivia => GetTrivia(); private TriviaProperties Trivia => GetOrSetTrivia(); - public CodeInline(string content) + private LazySubstring _content; + + public CodeInline(string content) : this(new LazySubstring(content)) { } + + internal CodeInline(LazySubstring content) { - Content = content; + _content = content; } /// @@ -35,7 +40,13 @@ namespace Markdig.Syntax.Inlines /// /// Gets or sets the content of the span. /// - public string Content { get; set; } + public string Content + { + get => _content.ToString(); + set => _content = new LazySubstring(value ?? string.Empty); + } + + public ReadOnlySpan ContentSpan => _content.AsSpan(); /// /// Gets or sets the content with trivia and whitespace.