diff --git a/src/Spectre.Console.Ansi/AnsiMarkup.cs b/src/Spectre.Console.Ansi/AnsiMarkup.cs index 8fe5aae31..b3e08864d 100644 --- a/src/Spectre.Console.Ansi/AnsiMarkup.cs +++ b/src/Spectre.Console.Ansi/AnsiMarkup.cs @@ -106,9 +106,60 @@ public static IEnumerable Parse(string markup, Style? style = throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?"); } + // Try resolving auto links (if any) + ResolveAutoLinks(result); + return result; } + private static void ResolveAutoLinks(List segments) + { + for (var segmentIndex = 0; segmentIndex < segments.Count; segmentIndex++) + { + var currentLink = segments[segmentIndex].Link; + if (currentLink?.Url.Equals(Constants.EmptyLink, StringComparison.Ordinal) != true) + { + // Not a link + continue; + } + + // Find out where the link ends + var lastKnownLinkIndex = segmentIndex + 1; + while (lastKnownLinkIndex < segments.Count && ReferenceEquals(segments[lastKnownLinkIndex].Link, currentLink)) + { + lastKnownLinkIndex++; + } + + string url; + if (lastKnownLinkIndex - segmentIndex == 1) + { + // This is a plain [link]url[/], so just grab the text + url = segments[segmentIndex].Text; + } + else + { + // Build the URL using the text from the segments + var builder = new StringBuilder(); + for (var i = segmentIndex; i < lastKnownLinkIndex; i++) + { + builder.Append(segments[i].Text); + } + + url = builder.ToString(); + } + + // Set all segments to the same link + var resolved = new Link(url); + for (var i = segmentIndex; i < lastKnownLinkIndex; i++) + { + segments[i].Link = resolved; + } + + // Continue at the next part + segmentIndex = lastKnownLinkIndex - 1; + } + } + /// /// Escapes the specified text so that it won’t be interpreted as markup. /// @@ -183,7 +234,7 @@ public sealed class AnsiMarkupSegment /// /// Gets the segment link. /// - public Link? Link { get; } + public Link? Link { get; internal set; } /// /// Initializes a new instance of the class. diff --git a/src/Spectre.Console.Ansi/AnsiWriter.cs b/src/Spectre.Console.Ansi/AnsiWriter.cs index 41c5a6c7d..3d39073c4 100644 --- a/src/Spectre.Console.Ansi/AnsiWriter.cs +++ b/src/Spectre.Console.Ansi/AnsiWriter.cs @@ -79,8 +79,7 @@ public AnsiWriter Write(string text, Style style, Link? link = null) { if (link != null) { - var url = link.Url.Equals(Constants.EmptyLink) ? text : link.Url; - BeginLink(url, link.Id); + BeginLink(link.Url, link.Id); } _styleBuffer.Clear(); diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs index 7faf0fd39..b9e10894e 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs @@ -266,9 +266,9 @@ public void Should_Preserve_Link_When_Wrapped_Inside_Grid_Cell() .SupportsAnsi(true) .EmitAnsiSequences(); - var grid = new Grid(); - grid.AddColumn(); - grid.AddRow("[link=https://example.com/readme.md]pneumonoultramicroscopicsilicovolcanoconiosis[/]"); + var grid = new Grid() + .AddColumn() + .AddRow("[link=https://example.com/readme.md]pneumonoultramicroscopicsilicovolcanoconiosis[/]"); // When console.Write(grid); @@ -279,5 +279,55 @@ public void Should_Preserve_Link_When_Wrapped_Inside_Grid_Cell() "https://example.com/readme.md") .Count.ShouldBeGreaterThan(1); } + + [Fact] + [GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")] + public void Should_Preserve_Auto_Link_When_Wrapped_Inside_Grid_Cell() + { + // Given + var console = new TestConsole() + .Width(10) + .SupportsAnsi(true) + .EmitAnsiSequences(); + + var grid = new Grid() + .AddColumn() + .AddRow("[link]https://example.com/readme.md[/]"); + + // When + console.Write(grid); + + // Then + Regex.Matches( + console.Output.NormalizeLineEndings(), + "https://example.com/readme.md") + .Count.ShouldBeGreaterThan(1); + } + + [Fact] + [GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")] + public void Should_Resolve_Auto_Link_Url_From_Display_Text() + { + // Given, When + var segments = AnsiMarkup.Parse("[link]https://example.com/readme.md[/]").ToList(); + + // Then + segments.Count.ShouldBe(1); + segments[0].Link.ShouldNotBeNull(); + segments[0].Link!.Url.ShouldBe("https://example.com/readme.md"); + } + + [Fact] + [GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")] + public void Should_Resolve_Auto_Link_Url_From_Full_Text_When_Nested() + { + // Given, When + var segments = AnsiMarkup.Parse("[link]https://[bold]example[/].com[/]").ToList(); + + // Then + segments.Count.ShouldBeGreaterThan(1); + segments.ShouldAllBe(segment => segment.Link!.Url == "https://example.com"); + segments.Select(segment => segment.Link!.Id).Distinct().Count().ShouldBe(1); + } } } \ No newline at end of file