mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35:27 +08:00 
			
		
		
		
	Fix bug when splitting text containing CJK chars
In Segment.Split, we didn't take cell width into account when calculating the offset, which resulted in some "fun" bugs. I've added a new overload for Segment.Split and obsoleted the old one. Closes #150
This commit is contained in:
		
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							ee305702e8
						
					
				
				
					commit
					6932c95731
				
			@@ -0,0 +1,7 @@
 | 
			
		||||
┌──────────┐
 | 
			
		||||
│ ┌──────┐ │
 | 
			
		||||
│ │ 测试 │ │
 | 
			
		||||
│ ├──────┤ │
 | 
			
		||||
│ │ 测试 │ │
 | 
			
		||||
│ └──────┘ │
 | 
			
		||||
└──────────┘
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,10 @@ namespace Spectre.Console.Internal
 | 
			
		||||
    {
 | 
			
		||||
        public static int GetCellLength(RenderContext context, string text)
 | 
			
		||||
        {
 | 
			
		||||
            return text.Sum(rune =>
 | 
			
		||||
            return text.Sum(rune => GetCellLength(context, rune));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static int GetCellLength(RenderContext context, char rune)
 | 
			
		||||
        {
 | 
			
		||||
            if (context.LegacyConsole)
 | 
			
		||||
            {
 | 
			
		||||
@@ -33,7 +36,6 @@ namespace Spectre.Console.Internal
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return UnicodeCalculator.GetWidth(rune);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="offset">The offset where to split the segment.</param>
 | 
			
		||||
        /// <returns>One or two new segments representing the split.</returns>
 | 
			
		||||
        [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));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Splits the segment at the offset.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="context">The render context.</param>
 | 
			
		||||
        /// <param name="offset">The offset where to split the segment.</param>
 | 
			
		||||
        /// <returns>One or two new segments representing the split.</returns>
 | 
			
		||||
        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));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clones the segment.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user