Skip to content

Commit bd4af19

Browse files
Should preserve auto links when wrapped in grid
Closes #2145
1 parent 2ac9ee8 commit bd4af19

3 files changed

Lines changed: 106 additions & 6 deletions

File tree

src/Spectre.Console.Ansi/AnsiMarkup.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,60 @@ public static IEnumerable<AnsiMarkupSegment> Parse(string markup, Style? style =
106106
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
107107
}
108108

109+
// Try resolving auto links (if any)
110+
ResolveAutoLinks(result);
111+
109112
return result;
110113
}
111114

115+
private static void ResolveAutoLinks(List<AnsiMarkupSegment> segments)
116+
{
117+
for (var segmentIndex = 0; segmentIndex < segments.Count; segmentIndex++)
118+
{
119+
var currentLink = segments[segmentIndex].Link;
120+
if (currentLink?.Url.Equals(Constants.EmptyLink, StringComparison.Ordinal) != true)
121+
{
122+
// Not a link
123+
continue;
124+
}
125+
126+
// Find out where the link ends
127+
var lastKnownLinkIndex = segmentIndex + 1;
128+
while (lastKnownLinkIndex < segments.Count && ReferenceEquals(segments[lastKnownLinkIndex].Link, currentLink))
129+
{
130+
lastKnownLinkIndex++;
131+
}
132+
133+
string url;
134+
if (lastKnownLinkIndex - segmentIndex == 1)
135+
{
136+
// This is a plain [link]url[/], so just grab the text
137+
url = segments[segmentIndex].Text;
138+
}
139+
else
140+
{
141+
// Build the URL using the text from the segments
142+
var builder = new StringBuilder();
143+
for (var i = segmentIndex; i < lastKnownLinkIndex; i++)
144+
{
145+
builder.Append(segments[i].Text);
146+
}
147+
148+
url = builder.ToString();
149+
}
150+
151+
// Set all segments to the same link
152+
var resolved = new Link(url);
153+
for (var i = segmentIndex; i < lastKnownLinkIndex; i++)
154+
{
155+
segments[i].Link = resolved;
156+
}
157+
158+
// Continue at the next part
159+
segmentIndex = lastKnownLinkIndex - 1;
160+
}
161+
}
162+
112163
/// <summary>
113164
/// Escapes the specified text so that it won’t be interpreted as markup.
114165
/// </summary>
@@ -183,7 +234,7 @@ public sealed class AnsiMarkupSegment
183234
/// <summary>
184235
/// Gets the segment link.
185236
/// </summary>
186-
public Link? Link { get; }
237+
public Link? Link { get; internal set; }
187238

188239
/// <summary>
189240
/// Initializes a new instance of the <see cref="AnsiMarkupSegment"/> class.

src/Spectre.Console.Ansi/AnsiWriter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ public AnsiWriter Write(string text, Style style, Link? link = null)
7979
{
8080
if (link != null)
8181
{
82-
var url = link.Url.Equals(Constants.EmptyLink) ? text : link.Url;
83-
BeginLink(url, link.Id);
82+
BeginLink(link.Url, link.Id);
8483
}
8584

8685
_styleBuffer.Clear();

src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ public void Should_Preserve_Link_When_Wrapped_Inside_Grid_Cell()
266266
.SupportsAnsi(true)
267267
.EmitAnsiSequences();
268268

269-
var grid = new Grid();
270-
grid.AddColumn();
271-
grid.AddRow("[link=https://example.com/readme.md]pneumonoultramicroscopicsilicovolcanoconiosis[/]");
269+
var grid = new Grid()
270+
.AddColumn()
271+
.AddRow("[link=https://example.com/readme.md]pneumonoultramicroscopicsilicovolcanoconiosis[/]");
272272

273273
// When
274274
console.Write(grid);
@@ -279,5 +279,55 @@ public void Should_Preserve_Link_When_Wrapped_Inside_Grid_Cell()
279279
"https://example.com/readme.md")
280280
.Count.ShouldBeGreaterThan(1);
281281
}
282+
283+
[Fact]
284+
[GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")]
285+
public void Should_Preserve_Auto_Link_When_Wrapped_Inside_Grid_Cell()
286+
{
287+
// Given
288+
var console = new TestConsole()
289+
.Width(10)
290+
.SupportsAnsi(true)
291+
.EmitAnsiSequences();
292+
293+
var grid = new Grid()
294+
.AddColumn()
295+
.AddRow("[link]https://example.com/readme.md[/]");
296+
297+
// When
298+
console.Write(grid);
299+
300+
// Then
301+
Regex.Matches(
302+
console.Output.NormalizeLineEndings(),
303+
"https://example.com/readme.md")
304+
.Count.ShouldBeGreaterThan(1);
305+
}
306+
307+
[Fact]
308+
[GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")]
309+
public void Should_Resolve_Auto_Link_Url_From_Display_Text()
310+
{
311+
// Given, When
312+
var segments = AnsiMarkup.Parse("[link]https://example.com/readme.md[/]").ToList();
313+
314+
// Then
315+
segments.Count.ShouldBe(1);
316+
segments[0].Link.ShouldNotBeNull();
317+
segments[0].Link!.Url.ShouldBe("https://example.com/readme.md");
318+
}
319+
320+
[Fact]
321+
[GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2145")]
322+
public void Should_Resolve_Auto_Link_Url_From_Full_Text_When_Nested()
323+
{
324+
// Given, When
325+
var segments = AnsiMarkup.Parse("[link]https://[bold]example[/].com[/]").ToList();
326+
327+
// Then
328+
segments.Count.ShouldBeGreaterThan(1);
329+
segments.ShouldAllBe(segment => segment.Link!.Url == "https://example.com");
330+
segments.Select(segment => segment.Link!.Id).Distinct().Count().ShouldBe(1);
331+
}
282332
}
283333
}

0 commit comments

Comments
 (0)