diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts index fdcd9d133199a6cd6ba9bea9606a02c03ad03b3d..0558a8873f680e8fb2a27cc833c49c5ba658e1dd 100644 --- a/src/browser/ColorContrastCache.ts +++ b/src/browser/ColorContrastCache.ts @@ -7,11 +7,17 @@ import { IColorContrastCache } from './Types'; import { IColor } from '../common/Types'; import { TwoKeyMap } from '../common/MultiKeyMap'; +const CONTRAST_CACHE_MAX_ENTRIES = 4096; + export class ColorContrastCache implements IColorContrastCache { private _color: TwoKeyMap = new TwoKeyMap(); private _css: TwoKeyMap = new TwoKeyMap(); + private _entryCount = 0; public setCss(bg: number, fg: number, value: string | null): void { + if (this._css.get(bg, fg) === undefined) { + this._admitNewEntry(); + } this._css.set(bg, fg, value); } @@ -20,6 +26,9 @@ export class ColorContrastCache implements IColorContrastCache { } public setColor(bg: number, fg: number, value: IColor | null): void { + if (this._color.get(bg, fg) === undefined) { + this._admitNewEntry(); + } this._color.set(bg, fg, value); } @@ -30,5 +39,14 @@ export class ColorContrastCache implements IColorContrastCache { public clear(): void { this._color.clear(); this._css.clear(); + this._entryCount = 0; + } + + private _admitNewEntry(): void { + // Color pairs outlive atlas pages, including cached misses and DOM-rendered colors. + if (this._entryCount >= CONTRAST_CACHE_MAX_ENTRIES) { + this.clear(); + } + this._entryCount++; } } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index e3a88bfeb4bbf79214d799adec30cba8e4dc1b49..f11e55306dde553d8f68b17340c9022cd749e914 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -504,7 +504,7 @@ export class Buffer extends Disposable implements IBuffer { for (let i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) { if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) { // Insert extra lines here, adjusting i as needed - for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) { + for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0 && i >= 0; nextI--) { this.lines.set(i--, nextToInsert.newLines[nextI]); } i++; diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 4984f3a49abe8437475c206db1345f5f5fe3979c..43fd5c5b286f23f11d407c9098062e970b6a2ec6 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -116,12 +116,16 @@ export class BufferLine implements IBufferLine { * @deprecated */ public set(index: number, value: CharData): void { + this._cache = ''; this._cacheValid = false; this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX]; if (value[CHAR_DATA_CHAR_INDEX].length > 1) { this._combined[index] = value[1]; this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } else { + if ((this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK)) { + delete this._combined[index]; + } this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } @@ -226,12 +230,21 @@ export class BufferLine implements IBufferLine { * Set data at `index` to `cell`. */ public setCell(index: number, cell: ICellData): void { + this._cache = ''; this._cacheValid = false; if (cell.content & Content.IS_COMBINED_MASK) { this._combined[index] = cell.combinedData; + } else { + if ((this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK)) { + delete this._combined[index]; + } } if (cell.bg & BgFlags.HAS_EXTENDED) { this._extendedAttrs[index] = cell.extended; + } else { + if ((this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED)) { + delete this._extendedAttrs[index]; + } } this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content; this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg; @@ -244,9 +257,17 @@ export class BufferLine implements IBufferLine { * it gets an optimized access method. */ public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void { + this._cache = ''; this._cacheValid = false; + if ((this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK)) { + delete this._combined[index]; + } if (attrs.bg & BgFlags.HAS_EXTENDED) { this._extendedAttrs[index] = attrs.extended; + } else { + if ((this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED)) { + delete this._extendedAttrs[index]; + } } const $idx = index * Constants.CELL_INDICIES; this._data[$idx + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT); @@ -261,6 +282,7 @@ export class BufferLine implements IBufferLine { * by the previous `setDataFromCodePoint` call, we can omit it here. */ public addCodepointToCell(index: number, codePoint: number, width: number): void { + this._cache = ''; this._cacheValid = false; let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT]; if (content & Content.IS_COMBINED_MASK) { @@ -288,6 +310,7 @@ export class BufferLine implements IBufferLine { } public insertCells(pos: number, n: number, fillCellData: ICellData): void { + this._cache = ''; this._cacheValid = false; pos %= this.length; @@ -309,6 +332,8 @@ export class BufferLine implements IBufferLine { } } + $workCell.combinedData = ''; + // handle fullwidth at line end: reset last cell if it is first cell of a wide char if (this.getWidth(this.length - 1) === 2) { this.setCellFromCodepoint(this.length - 1, 0, 1, fillCellData); @@ -316,6 +341,7 @@ export class BufferLine implements IBufferLine { } public deleteCells(pos: number, n: number, fillCellData: ICellData): void { + this._cache = ''; this._cacheValid = false; pos %= this.length; if (n < this.length - pos) { @@ -331,6 +357,8 @@ export class BufferLine implements IBufferLine { } } + $workCell.combinedData = ''; + // handle fullwidth at pos: // - reset pos-1 if wide char // - reset pos if width==0 (previous second cell of a wide char) @@ -343,6 +371,7 @@ export class BufferLine implements IBufferLine { } public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void { + this._cache = ''; this._cacheValid = false; // full branching on respectProtect==true, hopefully getting fast JIT for standard case if (respectProtect) { @@ -383,6 +412,7 @@ export class BufferLine implements IBufferLine { * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD). */ public resize(cols: number, fillCellData: ICellData): boolean { + this._cache = ''; this._cacheValid = false; if (cols === this.length) { return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength; @@ -443,6 +473,7 @@ export class BufferLine implements IBufferLine { /** fill a line with fillCharData */ public fill(fillCellData: ICellData, respectProtect: boolean = false): void { + this._cache = ''; this._cacheValid = false; // full branching on respectProtect==true, hopefully getting fast JIT for standard case if (respectProtect) { @@ -515,6 +546,7 @@ export class BufferLine implements IBufferLine { } public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void { + this._cache = ''; this._cacheValid = false; const srcData = src._data; if (applyInReverse) { @@ -596,9 +628,17 @@ 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 { + if (this._combined[destCol] !== undefined) { + delete this._combined[destCol]; + } } if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) { this._extendedAttrs[destCol] = src._extendedAttrs[srcCol]; + } else { + if (this._extendedAttrs[destCol] !== undefined) { + delete this._extendedAttrs[destCol]; + } } }