@@ -294,14 +294,40 @@ public void SetCellsFromBuffer(int destX, int destY, CharacterBuffer source, int
294294 if ( sx >= 0 && sx < sourceWidth && srcY >= 0 && srcY < sourceHeight )
295295 {
296296 var srcCell = source . GetCell ( sx , srcY ) ;
297- string ansi = FormatCellAnsi ( srcCell . Foreground , srcCell . Background , srcCell . Decorations ) ;
298297
299- if ( destCell . Character != srcCell . Character || destCell . AnsiEscape != ansi || destCell . IsWideContinuation != srcCell . IsWideContinuation || destCell . Combiners != srcCell . Combiners )
298+ // Fix clipped wide char at left edge of copy region: if the first source
299+ // cell is a continuation, its base is outside our copy range (under another
300+ // window). Write a space instead of an orphaned continuation that would be
301+ // skipped during rendering, leaving stale terminal content visible.
302+ // Also fix clipped wide char at right edge: if this is a wide base
303+ // but its continuation would be outside our copy range, write space.
304+ bool isOrphanedContinuation = srcCell . IsWideContinuation && i == 0 ;
305+ bool isClippedWideBase = ! srcCell . IsWideContinuation &&
306+ sx + 1 < sourceWidth && source . GetCell ( sx + 1 , srcY ) . IsWideContinuation &&
307+ i == maxWidth - 1 ;
308+ if ( isOrphanedContinuation || isClippedWideBase )
300309 {
301- destCell . Character = srcCell . Character ;
302- destCell . AnsiEscape = ansi ;
303- destCell . IsWideContinuation = srcCell . IsWideContinuation ;
304- destCell . Combiners = srcCell . Combiners ;
310+ string ansi = FormatCellAnsi ( srcCell . Foreground , srcCell . Background , srcCell . Decorations ) ;
311+ var spaceRune = new Rune ( ' ' ) ;
312+ if ( destCell . Character != spaceRune || destCell . AnsiEscape != ansi || destCell . IsWideContinuation || destCell . Combiners != null )
313+ {
314+ destCell . Character = spaceRune ;
315+ destCell . AnsiEscape = ansi ;
316+ destCell . IsWideContinuation = false ;
317+ destCell . Combiners = null ;
318+ }
319+ }
320+ else
321+ {
322+ string ansi = FormatCellAnsi ( srcCell . Foreground , srcCell . Background , srcCell . Decorations ) ;
323+
324+ if ( destCell . Character != srcCell . Character || destCell . AnsiEscape != ansi || destCell . IsWideContinuation != srcCell . IsWideContinuation || destCell . Combiners != srcCell . Combiners )
325+ {
326+ destCell . Character = srcCell . Character ;
327+ destCell . AnsiEscape = ansi ;
328+ destCell . IsWideContinuation = srcCell . IsWideContinuation ;
329+ destCell . Combiners = srcCell . Combiners ;
330+ }
305331 }
306332 }
307333 else
@@ -733,6 +759,34 @@ private void AppendRegionToBuilder(int y, int startX, int endX, StringBuilder bu
733759 continue ;
734760 }
735761
762+ // Wide char terminal safety: when emitting a wide character, the terminal
763+ // auto-advances past the continuation cell at x+1. But if the terminal was
764+ // previously showing a different character at x+1 (e.g., a border │), some
765+ // terminals don't reliably clear it — the old content persists as a ghost.
766+ // Fix: emit a space at x+1 first to explicitly clear the old content, then
767+ // reposition cursor back to x before emitting the wide char.
768+ bool isWideChar = x + 1 < _width && _backBuffer [ x + 1 , y ] . IsWideContinuation ;
769+ if ( isWideChar )
770+ {
771+ // Check if the terminal (old front buffer, before sync) had something
772+ // other than this wide char's continuation at x+1
773+ ref var nextFront = ref _frontBuffer [ x + 1 , y ] ;
774+ bool terminalHadDifferentContent = ! nextFront . Equals ( _backBuffer [ x + 1 , y ] ) ;
775+
776+ if ( terminalHadDifferentContent )
777+ {
778+ // Emit space at x+1 to clear old terminal content
779+ builder . Append ( backCell . AnsiEscape ) ;
780+ lastOutputAnsi = backCell . AnsiEscape ;
781+ builder . Append ( ' ' ) ;
782+ // Reposition cursor back to x
783+ builder . Append ( $ "\x1b [{ y + 1 } ;{ x + 1 } H") ;
784+ }
785+
786+ // Sync the continuation cell's front buffer now
787+ nextFront . CopyFrom ( _backBuffer [ x + 1 , y ] ) ;
788+ }
789+
736790 // Output ANSI only if it changed
737791 if ( backCell . AnsiEscape != lastOutputAnsi )
738792 {
@@ -744,6 +798,16 @@ private void AppendRegionToBuilder(int y, int startX, int endX, StringBuilder bu
744798 builder . AppendRune ( backCell . Character ) ;
745799 if ( backCell . Combiners != null )
746800 builder . Append ( backCell . Combiners ) ;
801+
802+ // Skip past continuation cell — we already synced it above
803+ if ( isWideChar )
804+ {
805+ // Emit any combiners on the continuation
806+ ref readonly var contCell = ref _backBuffer [ x + 1 , y ] ;
807+ if ( contCell . Combiners != null )
808+ builder . Append ( contCell . Combiners ) ;
809+ x ++ ; // Skip continuation in loop
810+ }
747811 }
748812
749813 // Reset ANSI at end of region
@@ -798,6 +862,28 @@ private void AppendLineToBuilder(int y, StringBuilder builder)
798862 consecutiveUnchanged = 0 ;
799863 }
800864
865+ // Wide char terminal safety: clear old content at continuation position
866+ // before emitting the wide char (see AppendRegionToBuilder for full explanation)
867+ bool isWideChar = x + 1 < maxWidth && _backBuffer [ x + 1 , y ] . IsWideContinuation ;
868+ if ( isWideChar )
869+ {
870+ ref var nextFront = ref _frontBuffer [ x + 1 , y ] ;
871+ if ( ! nextFront . Equals ( _backBuffer [ x + 1 , y ] ) )
872+ {
873+ // Emit ANSI + space at x+1 to clear old terminal content
874+ if ( backCell . AnsiEscape != lastOutputAnsi )
875+ {
876+ builder . Append ( backCell . AnsiEscape ) ;
877+ lastOutputAnsi = backCell . AnsiEscape ;
878+ }
879+ builder . Append ( ' ' ) ;
880+ // Reposition cursor back to x
881+ builder . Append ( $ "\x1b [{ y + 1 } ;{ x + 1 } H") ;
882+ }
883+ // Sync continuation front buffer
884+ nextFront . CopyFrom ( _backBuffer [ x + 1 , y ] ) ;
885+ }
886+
801887 // Only output ANSI if it's different from the last one we output
802888 if ( backCell . AnsiEscape != lastOutputAnsi )
803889 {
@@ -823,7 +909,7 @@ private void AppendLineToBuilder(int y, StringBuilder builder)
823909 builder . Append ( backCell . Combiners ) ;
824910
825911 // Track if this was a wide char — terminal advances cursor by 2
826- lastOutputWasWide = x + 1 < maxWidth && _backBuffer [ x + 1 , y ] . IsWideContinuation ;
912+ lastOutputWasWide = isWideChar ;
827913 }
828914 else
829915 {
0 commit comments