From 60bae9528dd552b7b4917497396d8fd0477dda4d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:44:07 -0700 Subject: [PATCH] fix(BufferLine): limit copyCellsFrom metadata to copied range copyCellsFrom copied Uint32 cell data only for the requested column range but migrated every combined entry with key >= srcCol, which could attach grapheme metadata to unrelated destination columns during partial line copies in reflow and wraparound. Copy combined and extended attributes only per copied cell and clear stale entries on the destination when the source cell does not use them. Co-authored-by: Cursor --- src/common/buffer/BufferLine.test.ts | 15 +++++++++++++++ src/common/buffer/BufferLine.ts | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 7fc12ad76..ca80c7535 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -495,6 +495,21 @@ describe('BufferLine', function(): void { assert.deepEqual(columns, [0]); }); }); + describe('copyCellsFrom', () => { + it('should only move combined data within the copied range', () => { + const src = new TestBufferLine(10, NULL_CELL_DATA, false); + src.setCell(1, createCellData(1, 'a', 1)); + src.addCodepointToCell(1, '\u0301'.charCodeAt(0), 0); + src.setCell(5, createCellData(1, 'z', 1)); + src.addCodepointToCell(5, '\u0301'.charCodeAt(0), 0); + + const dest = new TestBufferLine(10, NULL_CELL_DATA, false); + dest.copyCellsFrom(src, 0, 0, 2, false); + + assert.property(dest.combined, '1'); + assert.notProperty(dest.combined, '5'); + }); + }); describe('addCharToCell', () => { it('should set width to 1 for empty cell', () => { const line = new TestBufferLine(3, NULL_CELL_DATA, false); diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 30b4ed3bb..83931b05c 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -609,9 +609,13 @@ export class BufferLine implements IBufferLine { const srcStart = srcCol * Constants.CELL_INDICIES; if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) { this._combined[destCol] = src._combined[srcCol]; + } else { + delete this._combined[destCol]; } if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) { this._extendedAttrs[destCol] = src._extendedAttrs[srcCol]; + } else { + delete this._extendedAttrs[destCol]; } }