11using System . Collections . Concurrent ;
2+ using System . Globalization ;
3+ using System . Text ;
24using Spectre . Console ;
35using Spectre . Console . Rendering ;
46
@@ -291,7 +293,7 @@ private void FlushLogs()
291293 {
292294 _printedLogHistory . Add ( new PrintedLogLine . Structured ( line ) ) ;
293295 var markup = FormatLogMarkup ( line ) ;
294- var visualLength = Markup . Remove ( markup ) . Length ;
296+ var visualLength = CellCount ( Markup . Remove ( markup ) ) ;
295297 int width = LogLineWidth ( ) ;
296298 if ( markup . Contains ( '\n ' ) || ( ! Console . IsOutputRedirected && visualLength >= width ) )
297299 {
@@ -328,7 +330,7 @@ private void ReplayPrintedLogHistory()
328330 private static void WriteStructuredLogLine ( TerminalLogLine line )
329331 {
330332 var markup = FormatLogMarkup ( line ) ;
331- var visualLength = Markup . Remove ( markup ) . Length ;
333+ var visualLength = CellCount ( Markup . Remove ( markup ) ) ;
332334 int width = LogLineWidth ( ) ;
333335 if ( markup . Contains ( '\n ' ) || ( ! Console . IsOutputRedirected && visualLength >= width ) )
334336 {
@@ -345,7 +347,7 @@ private static void WritePlainLogLines(string text)
345347 foreach ( var line in normalized . Split ( '\n ' ) )
346348 {
347349 foreach ( var visualLine in WrapPlainLogLine ( line ) )
348- AnsiConsole . WriteLine ( visualLine + PaddingFor ( visualLine . Length ) ) ;
350+ AnsiConsole . WriteLine ( visualLine + PaddingFor ( CellCount ( visualLine ) ) ) ;
349351 }
350352 }
351353
@@ -388,7 +390,7 @@ void WriteWrappedMarkupContent(
388390 string contPrefixText = continuationPrefixText ?? continuationPrefix ;
389391 string contPrefixMarkup = Markup . Escape ( contPrefixText ) ;
390392
391- int lineWidth = LogLineWidth ( ) - firstPrefixText . Length ;
393+ int lineWidth = LogLineWidth ( ) - CellCount ( firstPrefixText ) ;
392394 var chunks = WrapContent ( content , lineWidth ) . ToList ( ) ;
393395
394396 for ( int i = 0 ; i < chunks . Count ; i ++ )
@@ -399,7 +401,7 @@ void WriteWrappedMarkupContent(
399401 var prefixMarkupForChunk = isFirst ? firstPrefixMarkup : contPrefixMarkup ;
400402
401403 string contentMarkup = ( isFirst ? first : continuation ) ( chunk ) ;
402- AnsiConsole . MarkupLine ( prefixMarkupForChunk + contentMarkup + PaddingFor ( prefixTextForChunk . Length + chunk . Length ) ) ;
404+ AnsiConsole . MarkupLine ( prefixMarkupForChunk + contentMarkup + PaddingFor ( CellCount ( prefixTextForChunk ) + CellCount ( chunk ) ) ) ;
403405 }
404406 }
405407 }
@@ -421,13 +423,34 @@ private static IEnumerable<string> WrapContent(string content, int availableWidt
421423 if ( Console . IsOutputRedirected )
422424 return [ content ] ;
423425
426+ return WrapContentByCellWidth ( content , availableWidth ) ;
427+ }
428+
429+ private static IEnumerable < string > WrapContentByCellWidth ( string content , int availableWidth )
430+ {
424431 int width = Math . Max ( 1 , availableWidth ) ;
425- if ( content . Length < width )
432+ if ( CellCount ( content ) < width )
426433 return [ content ] ;
427434
428435 var wrapped = new List < string > ( ) ;
429- for ( int offset = 0 ; offset < content . Length ; offset += width )
430- wrapped . Add ( content . Substring ( offset , Math . Min ( width , content . Length - offset ) ) ) ;
436+ var current = new StringBuilder ( ) ;
437+ var currentWidth = 0 ;
438+ foreach ( var element in TextElements ( content ) )
439+ {
440+ var elementWidth = CellCount ( element ) ;
441+ if ( current . Length > 0 && currentWidth + elementWidth > width )
442+ {
443+ wrapped . Add ( current . ToString ( ) ) ;
444+ current . Clear ( ) ;
445+ currentWidth = 0 ;
446+ }
447+
448+ current . Append ( element ) ;
449+ currentWidth += elementWidth ;
450+ }
451+
452+ if ( current . Length > 0 )
453+ wrapped . Add ( current . ToString ( ) ) ;
431454 return wrapped ;
432455 }
433456
@@ -437,18 +460,28 @@ private static IEnumerable<string> WrapPlainLogLine(string line)
437460 return [ line ] ;
438461
439462 int width = LogLineWidth ( ) ;
440- if ( line . Length < width )
463+ if ( CellCount ( line ) < width )
441464 return [ line ] ;
442465
443- var wrapped = new List < string > ( ) ;
444- for ( int offset = 0 ; offset < line . Length ; offset += width )
445- wrapped . Add ( line . Substring ( offset , Math . Min ( width , line . Length - offset ) ) ) ;
446- return wrapped ;
466+ return WrapContent ( line , width ) ;
447467 }
448468
449469 private static string PaddingFor ( int visualLength )
450470 => Console . IsOutputRedirected ? "" : new string ( ' ' , Math . Max ( 0 , LogLineWidth ( ) - visualLength ) ) ;
451471
472+ internal static int CellCount ( string text )
473+ => new Segment ( text ) . CellCount ( ) ;
474+
475+ internal static IReadOnlyList < string > WrapContentForWidth ( string content , int availableWidth )
476+ => WrapContentByCellWidth ( content , availableWidth ) . ToList ( ) ;
477+
478+ private static IEnumerable < string > TextElements ( string text )
479+ {
480+ var enumerator = StringInfo . GetTextElementEnumerator ( text ) ;
481+ while ( enumerator . MoveNext ( ) )
482+ yield return enumerator . GetTextElement ( ) ;
483+ }
484+
452485 private static int LogLineWidth ( )
453486 {
454487 if ( Console . IsOutputRedirected )
0 commit comments