@@ -377,3 +377,115 @@ func TestPrintCombiningCharacters(t *testing.T) {
377377 }
378378 })
379379}
380+
381+ // ---------------------------------------------------------------------------
382+ // Print wrap + combining character (issue #41)
383+ // ---------------------------------------------------------------------------
384+
385+ func TestPrintWrapCombiningCharWidensCell (t * testing.T ) {
386+ t .Parallel ()
387+
388+ t .Run ("combining_at_last_column_no_wrap" , func (t * testing.T ) {
389+ // A combining mark on the last character of a line should NOT wrap;
390+ // it should join in-place.
391+ t .Parallel ()
392+ term := newTestTerminal (5 , 5 )
393+ defer term .Dispose ()
394+ // Fill 5 columns: "abcde", then add combining accent to 'e'.
395+ term .WriteString ("abcde\u0301 " )
396+ cell := getCellInfo (term , 0 , 4 )
397+ want := cellInfo {Chars : "e\u0301 " , Width : 1 , Combined : true }
398+ if diff := cmp .Diff (want , cell ); diff != "" {
399+ t .Errorf ("last col cell (-want +got):\n %s" , diff )
400+ }
401+ // Cursor should still be at col 5 (past end), row 0.
402+ if term .CursorX () != 5 {
403+ t .Errorf ("cursorX = %d, want 5" , term .CursorX ())
404+ }
405+ if term .CursorY () != 0 {
406+ t .Errorf ("cursorY = %d, want 0" , term .CursorY ())
407+ }
408+ })
409+
410+ t .Run ("wide_char_wrap_then_combining" , func (t * testing.T ) {
411+ // A wide (width-2) char that wraps to the next line, followed by a
412+ // combining mark, should show the combined character on the new line.
413+ t .Parallel ()
414+ term := newTestTerminal (5 , 5 )
415+ defer term .Dispose ()
416+ // Fill 4 columns, then write a wide char (U+4E16 '世', width 2).
417+ // The wide char doesn't fit at col 4 (needs 2 cells), so it wraps.
418+ term .WriteString ("abcd\u4e16 \u0301 " )
419+ // Row 0 should be "abcd" (wide char wrapped away).
420+ line0 := term .GetLine (0 )
421+ if line0 != "abcd" {
422+ t .Errorf ("row 0 = %q, want %q" , line0 , "abcd" )
423+ }
424+ // Row 1 should have the wide char with combining mark at col 0.
425+ cell := getCellInfo (term , 1 , 0 )
426+ want := cellInfo {Chars : "世\u0301 " , Width : 2 , Combined : true }
427+ if diff := cmp .Diff (want , cell ); diff != "" {
428+ t .Errorf ("row 1 col 0 (-want +got):\n %s" , diff )
429+ }
430+ })
431+
432+ t .Run ("oldWidth_combining_joins_in_place" , func (t * testing.T ) {
433+ // When cursor is at cols (past end) and a combining mark joins the
434+ // preceding width-1 char, no wrap should occur (chWidth - oldWidth = 0).
435+ t .Parallel ()
436+ term := newTestTerminal (5 , 5 )
437+ defer term .Dispose ()
438+ term .WriteString ("1234A" )
439+ if term .CursorX () != 5 {
440+ t .Fatalf ("setup: cursorX = %d, want 5" , term .CursorX ())
441+ }
442+ term .WriteString ("\u0301 " )
443+ cell := getCellInfo (term , 0 , 4 )
444+ want := cellInfo {Chars : "A\u0301 " , Width : 1 , Combined : true }
445+ if diff := cmp .Diff (want , cell ); diff != "" {
446+ t .Errorf ("cell at (0,4) (-want +got):\n %s" , diff )
447+ }
448+ if term .CursorX () != 5 {
449+ t .Errorf ("cursorX = %d, want 5" , term .CursorX ())
450+ }
451+ if term .CursorY () != 0 {
452+ t .Errorf ("cursorY = %d, want 0" , term .CursorY ())
453+ }
454+ })
455+
456+ t .Run ("wrap_sets_bufX_to_oldWidth" , func (t * testing.T ) {
457+ // Verify that after a normal (non-combining) wrap, buf.X is set
458+ // correctly. The fix changes buf.X = 0 to buf.X = oldWidth; for
459+ // non-combining chars oldWidth is 0, so behavior is identical.
460+ t .Parallel ()
461+ term := newTestTerminal (5 , 5 )
462+ defer term .Dispose ()
463+ term .WriteString ("12345X" )
464+ line0 := term .GetLine (0 )
465+ if line0 != "12345" {
466+ t .Errorf ("row 0 = %q, want %q" , line0 , "12345" )
467+ }
468+ cell := getCellInfo (term , 1 , 0 )
469+ want := cellInfo {Chars : "X" , Width : 1 , Combined : false }
470+ if diff := cmp .Diff (want , cell ); diff != "" {
471+ t .Errorf ("row 1 col 0 (-want +got):\n %s" , diff )
472+ }
473+ if term .CursorX () != 1 {
474+ t .Errorf ("cursorX = %d, want 1" , term .CursorX ())
475+ }
476+ if term .CursorY () != 1 {
477+ t .Errorf ("cursorY = %d, want 1" , term .CursorY ())
478+ }
479+ // Verify the second line is marked as wrapped.
480+ buf := term .bufferService .Buffer ()
481+ wrappedLine := buf .Lines .Get (buf .YBase + 1 )
482+ if wrappedLine == nil {
483+ t .Fatal ("row 1 line is nil" )
484+ }
485+ if ! wrappedLine .IsWrapped {
486+ t .Error ("row 1 should be marked as wrapped" )
487+ }
488+ })
489+ }
490+
491+
0 commit comments