diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 4b6f6e8bb..30b4ed3bb 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -73,7 +73,9 @@ export interface IBufferLineStringCache { */ export class BufferLine implements IBufferLine { protected _data: Uint32Array; + /** Sparse cache; only read when `IS_COMBINED_MASK` is set in `_data`. */ protected _combined: {[index: number]: string} = {}; + /** Sparse cache; only read when `HAS_EXTENDED` is set in `_data`. */ protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {}; protected _stringCacheEntryRef: WeakRef | undefined; public length: number; @@ -205,9 +207,15 @@ export class BufferLine implements IBufferLine { cell.bg = this._data[$startIndex + Cell.BG]; if (cell.content & Content.IS_COMBINED_MASK) { cell.combinedData = this._combined[index]; + } else { + cell.combinedData = ''; } if (cell.bg & BgFlags.HAS_EXTENDED) { cell.extended = this._extendedAttrs[index]!; + } else { + // Do not mutate cell.extended in place: it may still reference this line's map entry from a + // prior loadCell into a reused CellData (e.g. $workCell during insert/delete). + cell.extended = DEFAULT_ATTR_DATA.extended.clone(); } return cell; } @@ -459,14 +467,7 @@ export class BufferLine implements IBufferLine { this._data.set(line._data); } this.length = line.length; - this._combined = {}; - for (const el in line._combined) { - this._combined[el] = line._combined[el]; - } - this._extendedAttrs = {}; - for (const el in line._extendedAttrs) { - this._extendedAttrs[el] = line._extendedAttrs[el]; - } + this._copySparseMapsFrom(line); this.isWrapped = line.isWrapped; } @@ -475,12 +476,7 @@ export class BufferLine implements IBufferLine { const newLine = new BufferLine(this._stringCache, 0, undefined, false); newLine._data = new Uint32Array(this._data); newLine.length = this.length; - for (const el in this._combined) { - newLine._combined[el] = this._combined[el]; - } - for (const el in this._extendedAttrs) { - newLine._extendedAttrs[el] = this._extendedAttrs[el]; - } + newLine._copySparseMapsFrom(this); newLine.isWrapped = this.isWrapped; return newLine; } @@ -511,27 +507,14 @@ export class BufferLine implements IBufferLine { for (let i = 0; i < Constants.CELL_INDICIES; i++) { this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i]; } - if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) { - this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell]; - } + this._copyCellMapsFrom(src, srcCol + cell, destCol + cell); } } else { for (let cell = 0; cell < length; cell++) { for (let i = 0; i < Constants.CELL_INDICIES; i++) { this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i]; } - if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) { - this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell]; - } - } - } - - // Move any combined data over as needed, FIXME: repeat for extended attrs - const srcCombinedKeys = Object.keys(src._combined); - for (let i = 0; i < srcCombinedKeys.length; i++) { - const key = parseInt(srcCombinedKeys[i], 10); - if (key >= srcCol) { - this._combined[key - srcCol + destCol] = src._combined[key]; + this._copyCellMapsFrom(src, srcCol + cell, destCol + cell); } } } @@ -620,4 +603,24 @@ export class BufferLine implements IBufferLine { cacheEntry.isTrimmed = false; } } + + /** Copy sparse map entries for a single cell when `_data` flags require them. */ + private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void { + const srcStart = srcCol * Constants.CELL_INDICIES; + if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) { + this._combined[destCol] = src._combined[srcCol]; + } + if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) { + this._extendedAttrs[destCol] = src._extendedAttrs[srcCol]; + } + } + + /** Rebuild sparse maps from another line, keyed only by `_data` flags. */ + private _copySparseMapsFrom(line: BufferLine): void { + this._combined = {}; + this._extendedAttrs = {}; + for (let i = 0; i < line.length; i++) { + this._copyCellMapsFrom(line, i, i); + } + } }