@xterm/xterm 6.1.0-beta.301 → 6.1.0-beta.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xterm/xterm",
3
3
  "description": "Full xterm terminal, in your browser",
4
- "version": "6.1.0-beta.301",
4
+ "version": "6.1.0-beta.303",
5
5
  "main": "lib/xterm.js",
6
6
  "module": "lib/xterm.mjs",
7
7
  "style": "css/xterm.css",
@@ -119,5 +119,5 @@
119
119
  "ws": "^8.20.0",
120
120
  "xterm-benchmark": "^0.3.2"
121
121
  },
122
- "commit": "8c9b9fdb9ba7b72b677173225f69c2a47f807600"
122
+ "commit": "d3e32b344dfe7dd6015cff6a9aeaaeaeccdc2789"
123
123
  }
@@ -27,11 +27,13 @@ export class Emitter<T> {
27
27
  }
28
28
 
29
29
  const entry = { fn: listener, thisArgs };
30
+ this._listeners = this._listeners.slice();
30
31
  this._listeners.push(entry);
31
32
 
32
33
  const result = toDisposable(() => {
33
34
  const idx = this._listeners.indexOf(entry);
34
35
  if (idx !== -1) {
36
+ this._listeners = this._listeners.slice();
35
37
  this._listeners.splice(idx, 1);
36
38
  }
37
39
  });
@@ -50,23 +52,16 @@ export class Emitter<T> {
50
52
  }
51
53
 
52
54
  public fire(event: T): void {
53
- if (this._disposed) {
55
+ if (this._disposed || !this._listeners.length) {
54
56
  return;
55
57
  }
56
- switch (this._listeners.length) {
57
- case 0: return;
58
- case 1: {
59
- const { fn, thisArgs } = this._listeners[0];
60
- fn.call(thisArgs, event);
61
- return;
62
- }
63
- default: {
64
- // Snapshot listeners to allow modifications during iteration (2+ listeners)
65
- const listeners = this._listeners.slice();
66
- for (const { fn, thisArgs } of listeners) {
67
- fn.call(thisArgs, event);
68
- }
69
- }
58
+ if (this._listeners.length === 1) {
59
+ this._listeners[0].fn.call(this._listeners[0].thisArgs, event);
60
+ return;
61
+ }
62
+ const listeners = this._listeners;
63
+ for (let i = 0, len = listeners.length; i < len; ++i) {
64
+ listeners[i].fn.call(listeners[i].thisArgs, event);
70
65
  }
71
66
  }
72
67
 
@@ -1286,6 +1286,11 @@ export class InputHandler extends Disposable implements IInputHandler {
1286
1286
  this._activeBuffer.lines.trimStart(scrollBackSize);
1287
1287
  this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0);
1288
1288
  this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0);
1289
+ // isUserScrolling tracks the normal buffer's viewport, so ED3 on the alt
1290
+ // screen must not touch it
1291
+ if (this._activeBuffer === this._bufferService.buffers.normal) {
1292
+ this._bufferService.isUserScrolling = false;
1293
+ }
1289
1294
  // Force a scroll event to refresh viewport
1290
1295
  this._onScroll.fire(0);
1291
1296
  }
@@ -6,4 +6,4 @@
6
6
  /**
7
7
  * The xterm.js version. This is updated by the publish script from package.json.
8
8
  */
9
- export const XTERM_VERSION = '6.1.0-beta.301';
9
+ export const XTERM_VERSION = '6.1.0-beta.303';
@@ -10,7 +10,6 @@ import { ICharset } from '../Types';
10
10
  import { IAttributeData, IBuffer, IBufferLine, ICellData } from './Types';
11
11
  import { ExtendedAttrs } from './AttributeData';
12
12
  import { BufferLine, DEFAULT_ATTR_DATA } from './BufferLine';
13
- import { BufferLineStringCache } from './BufferLineStringCache';
14
13
  import { getWrappedLineTrimmedLength, reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
15
14
  import { CellData } from './CellData';
16
15
  import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, WHITESPACE_CELL_WIDTH } from './Constants';
@@ -52,7 +51,6 @@ export class Buffer extends Disposable implements IBuffer {
52
51
  private _isClearing: boolean = false;
53
52
  private _memoryCleanupQueue: InstanceType<typeof IdleTaskQueue>;
54
53
  private _memoryCleanupPosition = 0;
55
- private readonly _stringCache: BufferLineStringCache;
56
54
 
57
55
  constructor(
58
56
  private _hasScrollback: boolean,
@@ -70,7 +68,6 @@ export class Buffer extends Disposable implements IBuffer {
70
68
  this._memoryCleanupQueue = new IdleTaskQueue(this._logService);
71
69
  this._register(toDisposable(() => this._memoryCleanupQueue.clear()));
72
70
  this._register(toDisposable(() => this.clearAllMarkers()));
73
- this._stringCache = this._register(new BufferLineStringCache());
74
71
  }
75
72
 
76
73
  public getNullCell(attr?: IAttributeData): ICellData {
@@ -100,7 +97,7 @@ export class Buffer extends Disposable implements IBuffer {
100
97
  }
101
98
 
102
99
  public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
103
- return new BufferLine(this._stringCache, this._bufferService.cols, this.getNullCell(attr), isWrapped);
100
+ return new BufferLine(this._bufferService.cols, this.getNullCell(attr), isWrapped);
104
101
  }
105
102
 
106
103
  public get hasScrollback(): boolean {
@@ -145,7 +142,6 @@ export class Buffer extends Disposable implements IBuffer {
145
142
  * Clears the buffer to its initial state, discarding all previous data.
146
143
  */
147
144
  public clear(): void {
148
- this._stringCache.clear();
149
145
  this.ydisp = 0;
150
146
  this.ybase = 0;
151
147
  this.y = 0;
@@ -164,7 +160,6 @@ export class Buffer extends Disposable implements IBuffer {
164
160
  public resize(newCols: number, newRows: number): void {
165
161
  // store reference to null cell with default attrs
166
162
  const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
167
- this._stringCache.clear();
168
163
 
169
164
  // count bufferlines with overly big memory to be cleaned afterwards
170
165
  let dirtyMemoryLines = 0;
@@ -199,7 +194,7 @@ export class Buffer extends Disposable implements IBuffer {
199
194
  if (this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) {
200
195
  // Just add the new missing rows on Windows as conpty reprints the screen with its
201
196
  // view of the world. Once a line enters scrollback for conpty it remains there
202
- this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
197
+ this.lines.push(new BufferLine(newCols, nullCell, false));
203
198
  } else {
204
199
  if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
205
200
  // There is room above the buffer and there are no empty elements below the line,
@@ -213,7 +208,7 @@ export class Buffer extends Disposable implements IBuffer {
213
208
  } else {
214
209
  // Add a blank line if there is no buffer left at the top to scroll to, or if there
215
210
  // are blank lines after the cursor
216
- this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
211
+ this.lines.push(new BufferLine(newCols, nullCell, false));
217
212
  }
218
213
  }
219
214
  }
@@ -354,7 +349,7 @@ export class Buffer extends Disposable implements IBuffer {
354
349
  }
355
350
  if (this.lines.length < newRows) {
356
351
  // Add an extra row at the bottom of the viewport
357
- this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
352
+ this.lines.push(new BufferLine(newCols, nullCell, false));
358
353
  }
359
354
  } else {
360
355
  if (this.ydisp === this.ybase) {
@@ -8,7 +8,6 @@ import { AttributeData } from './AttributeData';
8
8
  import { CellData } from './CellData';
9
9
  import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from './Constants';
10
10
  import { stringFromCodePoint } from '../input/TextDecoder';
11
- import { StringBuilder } from '../StringBuilder';
12
11
 
13
12
  // Buffer memory layout:
14
13
  //
@@ -37,24 +36,20 @@ const enum Cell {
37
36
  BG = 2 // currently unused
38
37
  }
39
38
 
39
+
40
+ interface IExtendedAttrsExt extends IExtendedAttrs {
41
+ _ext: number;
42
+ _urlId: number;
43
+ }
44
+
45
+
40
46
  export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
41
47
 
42
48
  // Work variables to avoid garbage collection
43
49
  let $startIndex = 0;
44
50
  const $workCell = new CellData();
45
- const $translateToStringBuilder = new StringBuilder();
46
-
47
- export interface IBufferLineStringCacheEntry {
48
- value: string | undefined;
49
- isTrimmed: boolean;
50
- generation: number;
51
- }
51
+ const $extended = DEFAULT_ATTR_DATA.extended.clone() as IExtendedAttrsExt;
52
52
 
53
- export interface IBufferLineStringCache {
54
- generation: number;
55
- allocateEntry(): IBufferLineStringCacheEntry;
56
- touch?(): void;
57
- }
58
53
 
59
54
  /**
60
55
  * Typed array based bufferline implementation.
@@ -77,11 +72,14 @@ export class BufferLine implements IBufferLine {
77
72
  protected _combined: {[index: number]: string} = {};
78
73
  /** Sparse cache; only read when `HAS_EXTENDED` is set in `_data`. */
79
74
  protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
80
- protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
81
75
  public length: number;
82
76
 
77
+ /** line text cache */
78
+ protected _cacheValid = false;
79
+ protected _cache: string = '';
80
+ protected _cacheTrimmed = false;
81
+
83
82
  constructor(
84
- protected readonly _stringCache: IBufferLineStringCache,
85
83
  cols: number,
86
84
  fillCellData?: ICellData,
87
85
  public isWrapped: boolean = false
@@ -118,7 +116,7 @@ export class BufferLine implements IBufferLine {
118
116
  * @deprecated
119
117
  */
120
118
  public set(index: number, value: CharData): void {
121
- this._invalidateStringCache();
119
+ this._cacheValid = false;
122
120
  this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
123
121
  if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
124
122
  this._combined[index] = value[1];
@@ -215,7 +213,11 @@ export class BufferLine implements IBufferLine {
215
213
  } else {
216
214
  // Do not mutate cell.extended in place: it may still reference this line's map entry from a
217
215
  // prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
218
- cell.extended = DEFAULT_ATTR_DATA.extended.clone();
216
+ // We use $extended as blueprint and reset the internals
217
+ // mimicking the ctor to avoid a new allocation.
218
+ $extended._ext = 0;
219
+ $extended._urlId = 0;
220
+ cell.extended = $extended;
219
221
  }
220
222
  return cell;
221
223
  }
@@ -224,7 +226,7 @@ export class BufferLine implements IBufferLine {
224
226
  * Set data at `index` to `cell`.
225
227
  */
226
228
  public setCell(index: number, cell: ICellData): void {
227
- this._invalidateStringCache();
229
+ this._cacheValid = false;
228
230
  if (cell.content & Content.IS_COMBINED_MASK) {
229
231
  this._combined[index] = cell.combinedData;
230
232
  }
@@ -242,13 +244,14 @@ export class BufferLine implements IBufferLine {
242
244
  * it gets an optimized access method.
243
245
  */
244
246
  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
245
- this._invalidateStringCache();
247
+ this._cacheValid = false;
246
248
  if (attrs.bg & BgFlags.HAS_EXTENDED) {
247
249
  this._extendedAttrs[index] = attrs.extended;
248
250
  }
249
- this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
250
- this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
251
- this._data[index * Constants.CELL_INDICIES + Cell.BG] = attrs.bg;
251
+ const $idx = index * Constants.CELL_INDICIES;
252
+ this._data[$idx + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
253
+ this._data[$idx + Cell.FG] = attrs.fg;
254
+ this._data[$idx + Cell.BG] = attrs.bg;
252
255
  }
253
256
 
254
257
  /**
@@ -258,7 +261,7 @@ export class BufferLine implements IBufferLine {
258
261
  * by the previous `setDataFromCodePoint` call, we can omit it here.
259
262
  */
260
263
  public addCodepointToCell(index: number, codePoint: number, width: number): void {
261
- this._invalidateStringCache();
264
+ this._cacheValid = false;
262
265
  let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
263
266
  if (content & Content.IS_COMBINED_MASK) {
264
267
  // we already have a combined string, simply add
@@ -285,7 +288,7 @@ export class BufferLine implements IBufferLine {
285
288
  }
286
289
 
287
290
  public insertCells(pos: number, n: number, fillCellData: ICellData): void {
288
- this._invalidateStringCache();
291
+ this._cacheValid = false;
289
292
  pos %= this.length;
290
293
 
291
294
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
@@ -313,7 +316,7 @@ export class BufferLine implements IBufferLine {
313
316
  }
314
317
 
315
318
  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
316
- this._invalidateStringCache();
319
+ this._cacheValid = false;
317
320
  pos %= this.length;
318
321
  if (n < this.length - pos) {
319
322
  for (let i = 0; i < this.length - pos - n; ++i) {
@@ -340,7 +343,7 @@ export class BufferLine implements IBufferLine {
340
343
  }
341
344
 
342
345
  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
343
- this._invalidateStringCache();
346
+ this._cacheValid = false;
344
347
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
345
348
  if (respectProtect) {
346
349
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
@@ -380,7 +383,7 @@ export class BufferLine implements IBufferLine {
380
383
  * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD).
381
384
  */
382
385
  public resize(cols: number, fillCellData: ICellData): boolean {
383
- this._invalidateStringCache();
386
+ this._cacheValid = false;
384
387
  if (cols === this.length) {
385
388
  return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
386
389
  }
@@ -440,7 +443,7 @@ export class BufferLine implements IBufferLine {
440
443
 
441
444
  /** fill a line with fillCharData */
442
445
  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {
443
- this._invalidateStringCache();
446
+ this._cacheValid = false;
444
447
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
445
448
  if (respectProtect) {
446
449
  for (let i = 0; i < this.length; ++i) {
@@ -458,8 +461,7 @@ export class BufferLine implements IBufferLine {
458
461
  }
459
462
 
460
463
  /** alter to a full copy of line */
461
- public copyFrom(line: BufferLine): void {
462
- this._invalidateStringCache();
464
+ public copyFrom(line: BufferLine, blank?: boolean): void {
463
465
  if (this.length !== line.length) {
464
466
  this._data = new Uint32Array(line._data);
465
467
  } else {
@@ -467,16 +469,29 @@ export class BufferLine implements IBufferLine {
467
469
  this._data.set(line._data);
468
470
  }
469
471
  this.length = line.length;
470
- this._copySparseMapsFrom(line);
472
+ if (blank) {
473
+ // a blank line may never hold combined or extended attrs,
474
+ // thus we can skip handling them
475
+ this._combined = {};
476
+ this._extendedAttrs = {};
477
+ } else {
478
+ this._copySparseMapsFrom(line);
479
+ }
480
+ this._cache = '';
481
+ this._cacheValid = false;
471
482
  this.isWrapped = line.isWrapped;
472
483
  }
473
484
 
474
485
  /** create a new clone */
475
- public clone(): IBufferLine {
476
- const newLine = new BufferLine(this._stringCache, 0, undefined, false);
486
+ public clone(blank?: boolean): IBufferLine {
487
+ const newLine = new BufferLine(0, undefined, false);
477
488
  newLine._data = new Uint32Array(this._data);
478
489
  newLine.length = this.length;
479
- newLine._copySparseMapsFrom(this);
490
+ if (!blank) {
491
+ // a blank line may never hold combined or extended attrs,
492
+ // thus we can skip handling them
493
+ newLine._copySparseMapsFrom(this);
494
+ }
480
495
  newLine.isWrapped = this.isWrapped;
481
496
  return newLine;
482
497
  }
@@ -500,7 +515,7 @@ export class BufferLine implements IBufferLine {
500
515
  }
501
516
 
502
517
  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
503
- this._invalidateStringCache();
518
+ this._cacheValid = false;
504
519
  const srcData = src._data;
505
520
  if (applyInReverse) {
506
521
  for (let cell = length - 1; cell >= 0; cell--) {
@@ -534,17 +549,13 @@ export class BufferLine implements IBufferLine {
534
549
  * returned string, the corresponding entries in `outColumns` will have the same column number.
535
550
  */
536
551
  public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {
537
- const isCanonicalRequest = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
538
- if (isCanonicalRequest) {
539
- this._stringCache.touch?.();
540
- }
541
- const stringCacheEntry = isCanonicalRequest ? this._getStringCacheEntry(false) : undefined;
542
- if (isCanonicalRequest && stringCacheEntry?.value !== undefined) {
552
+ const isCanonical = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
553
+ if (isCanonical && this._cacheValid) {
543
554
  if (trimRight) {
544
- return stringCacheEntry.isTrimmed ? stringCacheEntry.value : stringCacheEntry.value.trimEnd();
555
+ return this._cacheTrimmed ? this._cache : this._cache.trimEnd();
545
556
  }
546
- if (!stringCacheEntry.isTrimmed) {
547
- return stringCacheEntry.value;
557
+ if (!this._cacheTrimmed) {
558
+ return this._cache;
548
559
  }
549
560
  }
550
561
  startCol = startCol ?? 0;
@@ -555,12 +566,12 @@ export class BufferLine implements IBufferLine {
555
566
  if (outColumns) {
556
567
  outColumns.length = 0;
557
568
  }
558
- $translateToStringBuilder.reset();
569
+ const cellContents: string[] = [];
559
570
  while (startCol < endCol) {
560
571
  const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT];
561
572
  const cp = content & Content.CODEPOINT_MASK;
562
573
  const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
563
- $translateToStringBuilder.append(chars);
574
+ cellContents.push(chars);
564
575
  if (outColumns) {
565
576
  for (let i = 0; i < chars.length; ++i) {
566
577
  outColumns.push(startCol);
@@ -571,39 +582,15 @@ export class BufferLine implements IBufferLine {
571
582
  if (outColumns) {
572
583
  outColumns.push(startCol);
573
584
  }
574
- const result = $translateToStringBuilder.toString();
575
- $translateToStringBuilder.reset();
576
- if (isCanonicalRequest) {
577
- const cacheEntry = this._getStringCacheEntry(true)!;
578
- cacheEntry.value = result;
579
- cacheEntry.isTrimmed = !!trimRight;
585
+ const result = cellContents.join('');
586
+ if (isCanonical) {
587
+ this._cache = result;
588
+ this._cacheValid = true;
589
+ this._cacheTrimmed = !!trimRight;
580
590
  }
581
591
  return result;
582
592
  }
583
593
 
584
- protected _getStringCacheEntry(createIfNeeded: boolean): IBufferLineStringCacheEntry | undefined {
585
- const cachedEntry = this._stringCacheEntryRef?.deref();
586
- if (cachedEntry) {
587
- if (cachedEntry.generation === this._stringCache.generation) {
588
- return cachedEntry;
589
- }
590
- }
591
- if (!createIfNeeded) {
592
- return undefined;
593
- }
594
- const cacheEntry = this._stringCache.allocateEntry();
595
- this._stringCacheEntryRef = new WeakRef(cacheEntry);
596
- return cacheEntry;
597
- }
598
-
599
- private _invalidateStringCache(): void {
600
- const cacheEntry = this._getStringCacheEntry(false);
601
- if (cacheEntry) {
602
- cacheEntry.value = undefined;
603
- cacheEntry.isTrimmed = false;
604
- }
605
- }
606
-
607
594
  /** Copy sparse map entries for a single cell when `_data` flags require them. */
608
595
  private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void {
609
596
  const srcStart = srcCol * Constants.CELL_INDICIES;
@@ -131,8 +131,8 @@ export interface IBufferLine {
131
131
  resize(cols: number, fill: ICellData): boolean;
132
132
  cleanupMemory(): number;
133
133
  fill(fillCellData: ICellData, respectProtect?: boolean): void;
134
- copyFrom(line: IBufferLine): void;
135
- clone(): IBufferLine;
134
+ copyFrom(line: IBufferLine, blank?: boolean): void;
135
+ clone(blank?: boolean): IBufferLine;
136
136
  getTrimmedLength(): number;
137
137
  getNoBgTrimmedLength(): number;
138
138
  translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
@@ -86,12 +86,12 @@ export class BufferService extends Disposable implements IBufferService {
86
86
  // Insert the line using the fastest method
87
87
  if (bottomRow === buffer.lines.length - 1) {
88
88
  if (willBufferBeTrimmed) {
89
- buffer.lines.recycle().copyFrom(newLine);
89
+ buffer.lines.recycle().copyFrom(newLine, true);
90
90
  } else {
91
- buffer.lines.push(newLine.clone());
91
+ buffer.lines.push(newLine.clone(true));
92
92
  }
93
93
  } else {
94
- buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
94
+ buffer.lines.splice(bottomRow + 1, 0, newLine.clone(true));
95
95
  }
96
96
 
97
97
  // Only adjust ybase and ydisp when the buffer is not trimmed
@@ -113,7 +113,7 @@ export class BufferService extends Disposable implements IBufferService {
113
113
  // scrollback, instead we can just shift them in-place.
114
114
  const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */;
115
115
  buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
116
- buffer.lines.set(bottomRow, newLine.clone());
116
+ buffer.lines.set(bottomRow, newLine.clone(true));
117
117
  }
118
118
 
119
119
  // Move the viewport to the bottom of the buffer unless the user is
@@ -214,7 +214,7 @@ export class DecorationLineCache extends Disposable {
214
214
  }
215
215
 
216
216
  private _handleBufferLinesTrim(amount: number): void {
217
- if (amount <= 0) {
217
+ if (amount <= 0 || !this._decorationsByLine.size) {
218
218
  return;
219
219
  }
220
220
  const newMap = new Map<number, IInternalDecoration[]>();
@@ -1,69 +0,0 @@
1
- /**
2
- * Copyright (c) 2026 The xterm.js authors. All rights reserved.
3
- * @license MIT
4
- */
5
-
6
- import type { IBufferLineStringCache, IBufferLineStringCacheEntry } from './BufferLine';
7
- import { disposableTimeout } from '../Async';
8
- import { Disposable, MutableDisposable, toDisposable, type IDisposable } from '../Lifecycle';
9
-
10
- const enum Constants {
11
- CACHE_TTL_MS = 15000
12
- }
13
-
14
- export class BufferLineStringCache extends Disposable implements IBufferLineStringCache {
15
- public generation: number = 0;
16
- public readonly entries: Set<IBufferLineStringCacheEntry> = new Set();
17
- private readonly _clearTimeout = this._register(new MutableDisposable<IDisposable>());
18
- private _lastAccessTimestamp: number = 0;
19
-
20
- constructor() {
21
- super();
22
- this._register(toDisposable(() => this.entries.clear()));
23
- }
24
-
25
- public touch(): void {
26
- this._scheduleClear();
27
- }
28
-
29
- public allocateEntry(): IBufferLineStringCacheEntry {
30
- const entry: IBufferLineStringCacheEntry = {
31
- value: undefined,
32
- isTrimmed: false,
33
- generation: this.generation
34
- };
35
- this.entries.add(entry);
36
- this._scheduleClear();
37
- return entry;
38
- }
39
-
40
- public clear(): void {
41
- this._clearTimeout.clear();
42
- this._lastAccessTimestamp = 0;
43
- this.generation++;
44
- for (const entry of this.entries) {
45
- entry.value = undefined;
46
- entry.isTrimmed = false;
47
- }
48
- this.entries.clear();
49
- }
50
-
51
- private _scheduleClear(): void {
52
- this._lastAccessTimestamp = Date.now();
53
- if (this._clearTimeout.value) {
54
- return;
55
- }
56
- this._scheduleClearTimeout(Constants.CACHE_TTL_MS);
57
- }
58
-
59
- private _scheduleClearTimeout(timeoutMs: number): void {
60
- this._clearTimeout.value = disposableTimeout(() => {
61
- const elapsed = Date.now() - this._lastAccessTimestamp;
62
- if (elapsed >= Constants.CACHE_TTL_MS) {
63
- this.clear();
64
- return;
65
- }
66
- this._scheduleClearTimeout(Constants.CACHE_TTL_MS - elapsed);
67
- }, timeoutMs);
68
- }
69
- }