diff --git a/src/Spectre.Console.Tests/Expectations/PanelTests.Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly.verified.txt b/src/Spectre.Console.Tests/Expectations/PanelTests.Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly.verified.txt
new file mode 100644
index 0000000..d3ce79c
--- /dev/null
+++ b/src/Spectre.Console.Tests/Expectations/PanelTests.Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly.verified.txt
@@ -0,0 +1,7 @@
+┌──────────┐
+│ ┌──────┐ │
+│ │ 测试 │ │
+│ ├──────┤ │
+│ │ 测试 │ │
+│ └──────┘ │
+└──────────┘
diff --git a/src/Spectre.Console.Tests/Unit/PanelTests.cs b/src/Spectre.Console.Tests/Unit/PanelTests.cs
index a32d052..ef89490 100644
--- a/src/Spectre.Console.Tests/Unit/PanelTests.cs
+++ b/src/Spectre.Console.Tests/Unit/PanelTests.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
+using System.Text;
using System.Threading.Tasks;
+using Shouldly;
using Spectre.Console.Rendering;
using VerifyXunit;
using Xunit;
@@ -267,5 +269,23 @@ namespace Spectre.Console.Tests.Unit
// Then
return Verifier.Verify(console.Output);
}
+
+ [Fact]
+ public Task Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly()
+ {
+ // Given
+ var console = new PlainConsole(width: 80);
+
+ var table = new Table();
+ table.AddColumn("测试");
+ table.AddRow("测试");
+ var panel = new Panel(table);
+
+ // When
+ console.Render(panel);
+
+ // Then
+ return Verifier.Verify(console.Output);
+ }
}
}
diff --git a/src/Spectre.Console.Tests/Unit/SegmentTests.cs b/src/Spectre.Console.Tests/Unit/SegmentTests.cs
index 5177747..6e1d241 100644
--- a/src/Spectre.Console.Tests/Unit/SegmentTests.cs
+++ b/src/Spectre.Console.Tests/Unit/SegmentTests.cs
@@ -22,18 +22,43 @@ namespace Spectre.Console.Tests.Unit
[UsesVerify]
public sealed class TheSplitMethod
{
- [Fact]
- public Task Should_Split_Segment_Correctly()
+ [Theory]
+ [InlineData("Foo Bar", 0, "", "Foo Bar")]
+ [InlineData("Foo Bar", 1, "F", "oo Bar")]
+ [InlineData("Foo Bar", 2, "Fo", "o Bar")]
+ [InlineData("Foo Bar", 3, "Foo", " Bar")]
+ [InlineData("Foo Bar", 4, "Foo ", "Bar")]
+ [InlineData("Foo Bar", 5, "Foo B", "ar")]
+ [InlineData("Foo Bar", 6, "Foo Ba", "r")]
+ [InlineData("Foo Bar", 7, "Foo Bar", null)]
+ [InlineData("Foo 测试 Bar", 0, "", "Foo 测试 Bar")]
+ [InlineData("Foo 测试 Bar", 1, "F", "oo 测试 Bar")]
+ [InlineData("Foo 测试 Bar", 2, "Fo", "o 测试 Bar")]
+ [InlineData("Foo 测试 Bar", 3, "Foo", " 测试 Bar")]
+ [InlineData("Foo 测试 Bar", 4, "Foo ", "测试 Bar")]
+ [InlineData("Foo 测试 Bar", 5, "Foo 测", "试 Bar")]
+ [InlineData("Foo 测试 Bar", 6, "Foo 测", "试 Bar")]
+ [InlineData("Foo 测试 Bar", 7, "Foo 测试", " Bar")]
+ [InlineData("Foo 测试 Bar", 8, "Foo 测试", " Bar")]
+ [InlineData("Foo 测试 Bar", 9, "Foo 测试 ", "Bar")]
+ [InlineData("Foo 测试 Bar", 10, "Foo 测试 B", "ar")]
+ [InlineData("Foo 测试 Bar", 11, "Foo 测试 Ba", "r")]
+ [InlineData("Foo 测试 Bar", 12, "Foo 测试 Bar", null)]
+ public void Should_Split_Segment_Correctly(string text, int offset, string expectedFirst, string expectedSecond)
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
- var segment = new Segment("Foo Bar", style);
+ var context = new RenderContext(Encoding.UTF8, false);
+ var segment = new Segment(text, style);
// When
- var result = segment.Split(3);
+ var (first, second) = segment.Split(context, offset);
// Then
- return Verifier.Verify(result);
+ first.Text.ShouldBe(expectedFirst);
+ first.Style.ShouldBe(style);
+ second?.Text?.ShouldBe(expectedSecond);
+ second?.Style?.ShouldBe(style);
}
}
diff --git a/src/Spectre.Console/Internal/Text/Cell.cs b/src/Spectre.Console/Internal/Text/Cell.cs
index 2f0d3d8..e4f724a 100644
--- a/src/Spectre.Console/Internal/Text/Cell.cs
+++ b/src/Spectre.Console/Internal/Text/Cell.cs
@@ -8,32 +8,34 @@ namespace Spectre.Console.Internal
{
public static int GetCellLength(RenderContext context, string text)
{
- return text.Sum(rune =>
- {
- if (context.LegacyConsole)
- {
- // Is it represented by a single byte?
- // In that case we don't have to calculate the
- // actual cell width.
- if (context.Encoding.GetByteCount(new[] { rune }) == 1)
- {
- return 1;
- }
- }
+ return text.Sum(rune => GetCellLength(context, rune));
+ }
- // TODO: We need to figure out why Segment.SplitLines fails
- // if we let wcwidth (which returns -1 instead of 1)
- // calculate the size for new line characters.
- // That is correct from a Unicode perspective, but the
- // algorithm was written before wcwidth was added and used
- // to work with string length and not cell length.
- if (rune == '\n')
+ public static int GetCellLength(RenderContext context, char rune)
+ {
+ if (context.LegacyConsole)
+ {
+ // Is it represented by a single byte?
+ // In that case we don't have to calculate the
+ // actual cell width.
+ if (context.Encoding.GetByteCount(new[] { rune }) == 1)
{
return 1;
}
+ }
- return UnicodeCalculator.GetWidth(rune);
- });
+ // TODO: We need to figure out why Segment.SplitLines fails
+ // if we let wcwidth (which returns -1 instead of 1)
+ // calculate the size for new line characters.
+ // That is correct from a Unicode perspective, but the
+ // algorithm was written before wcwidth was added and used
+ // to work with string length and not cell length.
+ if (rune == '\n')
+ {
+ return 1;
+ }
+
+ return UnicodeCalculator.GetWidth(rune);
}
}
}
diff --git a/src/Spectre.Console/Rendering/Segment.cs b/src/Spectre.Console/Rendering/Segment.cs
index 87ab08c..616b939 100644
--- a/src/Spectre.Console/Rendering/Segment.cs
+++ b/src/Spectre.Console/Rendering/Segment.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
+using Spectre.Console.Internal;
namespace Spectre.Console.Rendering
{
@@ -145,6 +146,7 @@ namespace Spectre.Console.Rendering
///
/// The offset where to split the segment.
/// One or two new segments representing the split.
+ [Obsolete("Use Split(RenderContext, Int32) instead")]
public (Segment First, Segment? Second) Split(int offset)
{
if (offset < 0)
@@ -162,6 +164,44 @@ namespace Spectre.Console.Rendering
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
+ ///
+ /// Splits the segment at the offset.
+ ///
+ /// The render context.
+ /// The offset where to split the segment.
+ /// One or two new segments representing the split.
+ public (Segment First, Segment? Second) Split(RenderContext context, int offset)
+ {
+ if (offset < 0)
+ {
+ return (this, null);
+ }
+
+ if (offset >= CellCount(context))
+ {
+ return (this, null);
+ }
+
+ var index = 0;
+ if (offset > 0)
+ {
+ var accumulated = 0;
+ foreach (var character in Text)
+ {
+ index++;
+ accumulated += Cell.GetCellLength(context, character);
+ if (accumulated >= offset)
+ {
+ break;
+ }
+ }
+ }
+
+ return (
+ new Segment(Text.Substring(0, index), Style),
+ new Segment(Text.Substring(index, Text.Length - index), Style));
+ }
+
///
/// Clones the segment.
///
@@ -219,14 +259,16 @@ namespace Spectre.Console.Rendering
while (stack.Count > 0)
{
var segment = stack.Pop();
+ var segmentLength = segment.CellCount(context);
// Does this segment make the line exceed the max width?
- if (line.CellCount(context) + segment.CellCount(context) > maxWidth)
+ var lineLength = line.CellCount(context);
+ if (lineLength + segmentLength > maxWidth)
{
- var diff = -(maxWidth - (line.Length + segment.Text.Length));
+ var diff = -(maxWidth - (lineLength + segmentLength));
var offset = segment.Text.Length - diff;
- var (first, second) = segment.Split(offset);
+ var (first, second) = segment.Split(context, offset);
line.Add(first);
lines.Add(line);