spectreconsole / spectre.console

A .NET library that makes it easier to create beautiful console applications.
https://spectreconsole.net
MIT License
9.17k stars 472 forks source link

Unhandled exception. System.ArgumentOutOfRangeException: Index and length must refer to a location within the string. (Parameter 'length') #1604

Open zwluoqi opened 1 month ago

zwluoqi commented 1 month ago

Information

Describe the bug

To Reproduce

Expected behavior Unhandled exception. System.ArgumentOutOfRangeException: Index and length must refer to a location within the string. (Parameter 'length') at System.String.ThrowSubstringArgumentOutOfRange(Int32, Int32) at System.String.Substring(Int32, Int32) at Spectre.Console.Rendering.Segment.SplitOverflow(Segment, Nullable1, Int32) in /_/src/Spectre.Console/Rendering/Segment.cs:line 371 at Spectre.Console.Paragraph.SplitLines(Int32) in /_/src/Spectre.Console/Widgets/Paragraph.cs:line 232 at Spectre.Console.Paragraph.Render(RenderOptions, Int32) in /_/src/Spectre.Console/Widgets/Paragraph.cs:line 141 at Spectre.Console.TableRenderer.Render(TableRendererContext, List1) in //src/Spectre.Console/Widgets/Table/TableRenderer.cs:line 11 at Spectre.Console.TableRenderer.Render(TableRendererContext, List`1) in //src/Spectre.Console/Widgets/Table/TableRenderer.cs:line 31 at Spectre.Console.Padder.Render(RenderOptions, Int32) in //src/Spectre.Console/Widgets/Padder.cs:line 45 at Spectre.Console.Rendering.LiveRenderable.Render(RenderOptions, Int32)+MoveNext() at System.Collections.Generic.List1.AddRange(IEnumerable1) at Spectre.Console.RenderableExtensions.GetSegments(IAnsiConsole, RenderOptions, IEnumerable`1) in //src/Spectre.Console/Extensions/RenderableExtensions.cs:line 34 at Spectre.Console.AnsiBuilder.Build(IAnsiConsole, IRenderable) in //src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs:line 17 at Spectre.Console.AnsiConsoleFacade.Write(IRenderable) in //src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs:line 38 at Spectre.Console.ProgressRefreshThread.Run() in /_/src/Spectre.Console/Live/Progress/ProgressRefreshThread.cs:line 45 at System.Threading.Thread.StartCallback()

Screenshots

Additional context


Please upvote :+1: this issue if you are interested in it.

patriksvensson commented 1 month ago

@zwluoqi Thanks for reporting this. We would however need a reproducable example to even know how to fix this and to make sure it doesn't happen again.

elgonzo commented 1 month ago

@patriksvensson

looking at the source code the posted stack trace, it seems the issue is occurring in Segment.SplitOverflow when the overflow mode is Overflow.Ellipsis or Overflow.Crop and Segment.Text is a string shorter than maxWidth-1.

Which then poses the question as to why the Segment.SplitOverflow method is being called despite the Segment.Text string being shorter than maxWidth-1. Looking at the calling method (Paragraph.SplitLines), its logic decides that Segment.SplitOverflow is being called when Segment.CellCount() is larger than maxWidth.

Ergo, reproducing the problem requires a Segment.Text string whose length is shorter than maxWidth-1 but its corresponding Segment.CellCount() is larger than maxWidth. This condition would be satisfied by strings mainly composed of characters that occupy more than one console cell (like CJK characters for example), rendered by Paragraph elements in either Overflow.Ellipsis or Overflow.Crop mode .

Based on this, i made some quick'n'dirty reproduction code (which i wrote and tested using Spectre.Console 0.49.2-preview.0.16):

using Spectre.Console;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

int stringWidth = 20;

var table = new Table() { Width = 50 };
table.AddColumn("Foo");
table.AddColumn("Bar");

//
// Adding paragraphs with strings mainly composed of single-cell characters is fine...
//
table.AddRow(
    new Paragraph(new string('a', stringWidth)) { Overflow = Overflow.Ellipsis },
    new Paragraph(new string('b', stringWidth)) { Overflow = Overflow.Ellipsis }
);
AnsiConsole.Write(table);

//
// Exception happens here when adding the paragraphs with the CJK strings to the table
//
table.AddRow(
    new Paragraph(new string('한', stringWidth)) { Overflow = Overflow.Ellipsis },
    new Paragraph(new string('글', stringWidth)) { Overflow = Overflow.Ellipsis }
);
//
// Using Overflow.Crop would get the same kind of exception
//
// table.AddRow(
//    new Paragraph(new string('한', stringWidth)) { Overflow = Overflow.Crop },
//    new Paragraph(new string('글', stringWidth)) { Overflow = Overflow.Crop }
// );
AnsiConsole.Write(table);

(dotnetfiddle: https://dotnetfiddle.net/HREpNo)