Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,10 @@ public void Should_Not_Apply_Link_To_Text_After_Link_Close_Tag()
var console = new TestConsole()
.EmitAnsiSequences();

// When - text after the [/] closing tag should NOT have the link
// When
console.Markup("Before [link=https://example.com]LINK[/] After");

// Then
// The link should only wrap "LINK", not " After"
var output = console.Output;
output.ShouldMatch(@"Before \e\]8;id=\d+;https://example\.com\e\\LINK\e\]8;;\e\\ After");
}
Expand All @@ -247,17 +246,38 @@ public void Should_Properly_Handle_Nested_Link_With_Styles()
var console = new TestConsole()
.EmitAnsiSequences();

// When - link with styled text inside
// When
console.Markup("[link=https://example.com][bold]Bold Link[/][/] Plain");

// Then
// The link should only wrap "Bold Link", not " Plain"
var output = console.Output;

// Check that "Plain" is NOT inside a link
var linkEndIndex = output.LastIndexOf("\u001b]8;;\u001b\\");
var plainIndex = output.IndexOf("Plain");
var linkEndIndex = output.LastIndexOf("\e]8;;\e\\", StringComparison.Ordinal);
var plainIndex = output.IndexOf("Plain", StringComparison.Ordinal);
plainIndex.ShouldBeGreaterThan(linkEndIndex, "Plain should appear after the link ends");
}

[Fact]
[GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2124")]
public void Should_Preserve_Link_When_Wrapped_Inside_Grid_Cell()
{
// Given
var console = new TestConsole()
.Width(10)
.SupportsAnsi(true)
.EmitAnsiSequences();

var grid = new Grid();
grid.AddColumn();
grid.AddRow("[link=https://example.com/readme.md]pneumonoultramicroscopicsilicovolcanoconiosis[/]");

// When
console.Write(grid);

// Then
Regex.Matches(
console.Output.NormalizeLineEndings(),
"https://example.com/readme.md")
.Count.ShouldBeGreaterThan(1);
}
}
}
94 changes: 93 additions & 1 deletion src/Spectre.Console.Tests/Unit/Rendering/SegmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ public void Should_Split_Segment_Correctly(string text, int offset, string expec
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
var segment = new Segment(text, style);
var link = new Link("https://example.com");
var segment = new Segment(text, style, link);

// When
var (first, second) = segment.Split(offset);

// Then
first.Text.ShouldBe(expectedFirst);
first.Style.ShouldBe(style);
first.Link.ShouldBe(link);
second?.Text.ShouldBe(expectedSecond);
second?.Style.ShouldBe(style);
second?.Link.ShouldBe(link);
}
}

Expand Down Expand Up @@ -268,4 +271,93 @@ public void Should_Handle_Fullwidth_Text_When_Using_Crop()
result[0].Text.EndsWith("…", StringComparison.Ordinal).ShouldBeFalse();
}
}

public sealed class LinkPreservation
{
private static readonly Link _link = new("https://example.com/readme.md");

[Fact]
[GitHubIssue("https://github.com/spectreconsole/spectre.console/issues/2124")]
public void Should_Preserve_Link_When_Splitting_Lines_By_Width()
{
// Given
var segment = new Segment("abcdefghijklmnop", Style.Plain, _link);

// When
var lines = Segment.SplitLines([segment], maxWidth: 4);

// Then
lines.Count.ShouldBeGreaterThan(1);
lines.ShouldAllBe(line => line.All(p => p.Link == _link));
}

[Fact]
public void Should_Preserve_Link_When_Folding_Overflow()
{
// Given
var segment = new Segment("abcdefghijklmnop", Style.Plain, _link);

// When
var result = Segment.SplitOverflow(segment, Overflow.Fold, maxWidth: 4);

// Then
result.ShouldAllBe(part => part.Link == _link);
}

[Fact]
public void Should_Preserve_Link_On_Ellipsis_When_Overflowing()
{
// Given
var segment = new Segment("abcdefghijklmnop", Style.Plain, _link);

// When
var result = Segment.SplitOverflow(segment, Overflow.Ellipsis, maxWidth: 5);

// Then
result.Count.ShouldBe(1);
result[0].Text.ShouldEndWith("…");
result[0].Link.ShouldBe(_link);
}

[Fact]
public void Should_Preserve_Link_When_Truncating()
{
// Given
var segment = new Segment("abcdefghijklmnop", Style.Plain, _link);

// When
var truncated = Segment.Truncate(segment, maxWidth: 4);

// Then
truncated.ShouldNotBeNull();
truncated.Text.ShouldBe("abcd");
truncated.Link.ShouldBe(_link);
}

[Fact]
public void Should_Preserve_Link_When_Cloning()
{
// Given
var segment = new Segment("abc", Style.Plain, _link);

// When
var result = segment.Clone();

// Then
result.Link.ShouldBe(_link);
}

[Fact]
public void Should_Preserve_Link_When_Stripping_Line_Endings()
{
// Given
var segment = new Segment("abc\n", Style.Plain, _link);

// When
var result = segment.StripLineEndings();

// Then
result.Link.ShouldBe(_link);
}
}
}
24 changes: 12 additions & 12 deletions src/Spectre.Console/Rendering/Segment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public static int CellCount(IEnumerable<Segment> segments)
/// <returns>A new segment without any trailing line endings.</returns>
public Segment StripLineEndings()
{
return new Segment(Text.TrimEnd('\n').TrimEnd('\r'), Style);
return new Segment(Text.TrimEnd('\n').TrimEnd('\r'), Style, Link);
}

/// <summary>
Expand Down Expand Up @@ -179,8 +179,8 @@ public Segment StripLineEndings()
}

return (
new Segment(Text.Substring(0, index), Style),
new Segment(Text.Substring(index, Text.Length - index), Style));
new Segment(Text.Substring(0, index), Style, Link),
new Segment(Text.Substring(index, Text.Length - index), Style, Link));
}

/// <summary>
Expand All @@ -189,7 +189,7 @@ public Segment StripLineEndings()
/// <returns>A new segment that's identical to this one.</returns>
public Segment Clone()
{
return new Segment(Text, Style);
return new Segment(Text, Style, Link);
}

/// <summary>
Expand Down Expand Up @@ -268,7 +268,7 @@ public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int ma
{
if (parts[0].Length > 0)
{
line.Add(new Segment(parts[0], segment.Style));
line.Add(new Segment(parts[0], segment.Style, segment.Link));
}
}

Expand Down Expand Up @@ -347,32 +347,32 @@ public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, i
var splitted = SplitSegment(segment.Text, maxWidth);
foreach (var str in splitted)
{
result.Add(new Segment(str, segment.Style));
result.Add(new Segment(str, segment.Style, segment.Link));
}
}
else if (overflow == Overflow.Crop)
{
if (maxWidth <= 0)
{
result.Add(new Segment(string.Empty, segment.Style));
result.Add(new Segment(string.Empty, segment.Style, segment.Link));
}
else
{
var truncated = Truncate(segment, maxWidth);
result.Add(truncated ?? new Segment(string.Empty, segment.Style));
result.Add(truncated ?? new Segment(string.Empty, segment.Style, segment.Link));
}
}
else if (overflow == Overflow.Ellipsis)
{
if (Math.Max(0, maxWidth - 1) == 0)
{
result.Add(new Segment("…", segment.Style));
result.Add(new Segment("…", segment.Style, segment.Link));
}
else
{
var truncated = Truncate(segment, maxWidth - 1);
var prefix = truncated?.Text ?? string.Empty;
result.Add(new Segment(prefix + "…", segment.Style));
result.Add(new Segment(prefix + "…", segment.Style, segment.Link));
}
}

Expand Down Expand Up @@ -455,7 +455,7 @@ public static List<Segment> Truncate(IEnumerable<Segment> segments, int maxWidth
return null;
}

return new Segment(builder.ToString(), segment.Style);
return new Segment(builder.ToString(), segment.Style, segment.Link);
}

internal static IEnumerable<Segment> Merge(IEnumerable<Segment> segments)
Expand Down Expand Up @@ -512,7 +512,7 @@ internal static List<Segment> TruncateWithEllipsis(IEnumerable<Segment> segments
}

var result = new List<Segment>(segments);
result.Add(new Segment("…", result.Last().Style));
result.Add(new Segment("…", result.Last().Style, result.Last().Link));
return result;
}

Expand Down