@xterm/xterm 6.1.0-beta.302 → 6.1.0-beta.304

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.302",
4
+ "version": "6.1.0-beta.304",
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": "d6cfe3db5dfdbfd2a08c3f9bcfcde8a283aa479d"
122
+ "commit": "c58ea3637f3968e0e6e79cd92cf9aace7ef89ee2"
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
 
@@ -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.302';
9
+ export const XTERM_VERSION = '6.1.0-beta.304';
@@ -139,6 +139,7 @@ export class AttributeData implements IAttributeData {
139
139
  */
140
140
  export class ExtendedAttrs implements IExtendedAttrs {
141
141
  private _ext: number = 0;
142
+ public payload: Object | undefined;
142
143
  public get ext(): number {
143
144
  if (this._urlId) {
144
145
  return (
@@ -207,6 +208,6 @@ export class ExtendedAttrs implements IExtendedAttrs {
207
208
  * that needs to be persistant in the buffer.
208
209
  */
209
210
  public isEmpty(): boolean {
210
- return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0;
211
+ return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0 && this.payload === undefined;
211
212
  }
212
213
  }
@@ -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();
51
+ const $extended = DEFAULT_ATTR_DATA.extended.clone() as IExtendedAttrsExt;
46
52
 
47
- export interface IBufferLineStringCacheEntry {
48
- value: string | undefined;
49
- isTrimmed: boolean;
50
- generation: number;
51
- }
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];
@@ -210,21 +208,30 @@ export class BufferLine implements IBufferLine {
210
208
  } else {
211
209
  cell.combinedData = '';
212
210
  }
213
- if (cell.bg & BgFlags.HAS_EXTENDED) {
214
- cell.extended = this._extendedAttrs[index]!;
215
- } else {
216
- // Do not mutate cell.extended in place: it may still reference this line's map entry from a
217
- // prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
218
- cell.extended = DEFAULT_ATTR_DATA.extended.clone();
219
- }
211
+ cell.extended = this.getExtended(index);
220
212
  return cell;
221
213
  }
222
214
 
215
+ public getExtended(index: number): IExtendedAttrs {
216
+ $startIndex = index * Constants.CELL_INDICIES;
217
+ if (this._data[$startIndex + Cell.BG] & BgFlags.HAS_EXTENDED) {
218
+ return this._extendedAttrs[index]!;
219
+ }
220
+ // Do not mutate cell.extended in place: it may still reference this line's map entry from a
221
+ // prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
222
+ // We use $extended as blueprint and reset the internals
223
+ // mimicking the ctor to avoid a new allocation.
224
+ $extended._ext = 0;
225
+ $extended._urlId = 0;
226
+ $extended.payload = undefined;
227
+ return $extended;
228
+ }
229
+
223
230
  /**
224
231
  * Set data at `index` to `cell`.
225
232
  */
226
233
  public setCell(index: number, cell: ICellData): void {
227
- this._invalidateStringCache();
234
+ this._cacheValid = false;
228
235
  if (cell.content & Content.IS_COMBINED_MASK) {
229
236
  this._combined[index] = cell.combinedData;
230
237
  }
@@ -242,13 +249,14 @@ export class BufferLine implements IBufferLine {
242
249
  * it gets an optimized access method.
243
250
  */
244
251
  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
245
- this._invalidateStringCache();
252
+ this._cacheValid = false;
246
253
  if (attrs.bg & BgFlags.HAS_EXTENDED) {
247
254
  this._extendedAttrs[index] = attrs.extended;
248
255
  }
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;
256
+ const $idx = index * Constants.CELL_INDICIES;
257
+ this._data[$idx + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
258
+ this._data[$idx + Cell.FG] = attrs.fg;
259
+ this._data[$idx + Cell.BG] = attrs.bg;
252
260
  }
253
261
 
254
262
  /**
@@ -258,7 +266,7 @@ export class BufferLine implements IBufferLine {
258
266
  * by the previous `setDataFromCodePoint` call, we can omit it here.
259
267
  */
260
268
  public addCodepointToCell(index: number, codePoint: number, width: number): void {
261
- this._invalidateStringCache();
269
+ this._cacheValid = false;
262
270
  let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
263
271
  if (content & Content.IS_COMBINED_MASK) {
264
272
  // we already have a combined string, simply add
@@ -285,7 +293,7 @@ export class BufferLine implements IBufferLine {
285
293
  }
286
294
 
287
295
  public insertCells(pos: number, n: number, fillCellData: ICellData): void {
288
- this._invalidateStringCache();
296
+ this._cacheValid = false;
289
297
  pos %= this.length;
290
298
 
291
299
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
@@ -313,7 +321,7 @@ export class BufferLine implements IBufferLine {
313
321
  }
314
322
 
315
323
  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
316
- this._invalidateStringCache();
324
+ this._cacheValid = false;
317
325
  pos %= this.length;
318
326
  if (n < this.length - pos) {
319
327
  for (let i = 0; i < this.length - pos - n; ++i) {
@@ -340,7 +348,7 @@ export class BufferLine implements IBufferLine {
340
348
  }
341
349
 
342
350
  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
343
- this._invalidateStringCache();
351
+ this._cacheValid = false;
344
352
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
345
353
  if (respectProtect) {
346
354
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
@@ -380,7 +388,7 @@ export class BufferLine implements IBufferLine {
380
388
  * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD).
381
389
  */
382
390
  public resize(cols: number, fillCellData: ICellData): boolean {
383
- this._invalidateStringCache();
391
+ this._cacheValid = false;
384
392
  if (cols === this.length) {
385
393
  return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
386
394
  }
@@ -440,7 +448,7 @@ export class BufferLine implements IBufferLine {
440
448
 
441
449
  /** fill a line with fillCharData */
442
450
  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {
443
- this._invalidateStringCache();
451
+ this._cacheValid = false;
444
452
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
445
453
  if (respectProtect) {
446
454
  for (let i = 0; i < this.length; ++i) {
@@ -458,8 +466,7 @@ export class BufferLine implements IBufferLine {
458
466
  }
459
467
 
460
468
  /** alter to a full copy of line */
461
- public copyFrom(line: BufferLine): void {
462
- this._invalidateStringCache();
469
+ public copyFrom(line: BufferLine, blank?: boolean): void {
463
470
  if (this.length !== line.length) {
464
471
  this._data = new Uint32Array(line._data);
465
472
  } else {
@@ -467,16 +474,29 @@ export class BufferLine implements IBufferLine {
467
474
  this._data.set(line._data);
468
475
  }
469
476
  this.length = line.length;
470
- this._copySparseMapsFrom(line);
477
+ if (blank) {
478
+ // a blank line may never hold combined or extended attrs,
479
+ // thus we can skip handling them
480
+ this._combined = {};
481
+ this._extendedAttrs = {};
482
+ } else {
483
+ this._copySparseMapsFrom(line);
484
+ }
485
+ this._cache = '';
486
+ this._cacheValid = false;
471
487
  this.isWrapped = line.isWrapped;
472
488
  }
473
489
 
474
490
  /** create a new clone */
475
- public clone(): IBufferLine {
476
- const newLine = new BufferLine(this._stringCache, 0, undefined, false);
491
+ public clone(blank?: boolean): IBufferLine {
492
+ const newLine = new BufferLine(0, undefined, false);
477
493
  newLine._data = new Uint32Array(this._data);
478
494
  newLine.length = this.length;
479
- newLine._copySparseMapsFrom(this);
495
+ if (!blank) {
496
+ // a blank line may never hold combined or extended attrs,
497
+ // thus we can skip handling them
498
+ newLine._copySparseMapsFrom(this);
499
+ }
480
500
  newLine.isWrapped = this.isWrapped;
481
501
  return newLine;
482
502
  }
@@ -500,7 +520,7 @@ export class BufferLine implements IBufferLine {
500
520
  }
501
521
 
502
522
  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
503
- this._invalidateStringCache();
523
+ this._cacheValid = false;
504
524
  const srcData = src._data;
505
525
  if (applyInReverse) {
506
526
  for (let cell = length - 1; cell >= 0; cell--) {
@@ -534,17 +554,13 @@ export class BufferLine implements IBufferLine {
534
554
  * returned string, the corresponding entries in `outColumns` will have the same column number.
535
555
  */
536
556
  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) {
557
+ const isCanonical = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
558
+ if (isCanonical && this._cacheValid) {
543
559
  if (trimRight) {
544
- return stringCacheEntry.isTrimmed ? stringCacheEntry.value : stringCacheEntry.value.trimEnd();
560
+ return this._cacheTrimmed ? this._cache : this._cache.trimEnd();
545
561
  }
546
- if (!stringCacheEntry.isTrimmed) {
547
- return stringCacheEntry.value;
562
+ if (!this._cacheTrimmed) {
563
+ return this._cache;
548
564
  }
549
565
  }
550
566
  startCol = startCol ?? 0;
@@ -555,12 +571,12 @@ export class BufferLine implements IBufferLine {
555
571
  if (outColumns) {
556
572
  outColumns.length = 0;
557
573
  }
558
- $translateToStringBuilder.reset();
574
+ const cellContents: string[] = [];
559
575
  while (startCol < endCol) {
560
576
  const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT];
561
577
  const cp = content & Content.CODEPOINT_MASK;
562
578
  const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
563
- $translateToStringBuilder.append(chars);
579
+ cellContents.push(chars);
564
580
  if (outColumns) {
565
581
  for (let i = 0; i < chars.length; ++i) {
566
582
  outColumns.push(startCol);
@@ -571,39 +587,15 @@ export class BufferLine implements IBufferLine {
571
587
  if (outColumns) {
572
588
  outColumns.push(startCol);
573
589
  }
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;
590
+ const result = cellContents.join('');
591
+ if (isCanonical) {
592
+ this._cache = result;
593
+ this._cacheValid = true;
594
+ this._cacheTrimmed = !!trimRight;
580
595
  }
581
596
  return result;
582
597
  }
583
598
 
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
599
  /** Copy sparse map entries for a single cell when `_data` flags require them. */
608
600
  private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void {
609
601
  const srcStart = srcCol * Constants.CELL_INDICIES;
@@ -20,6 +20,7 @@ export interface IExtendedAttrs {
20
20
  underlineColor: number;
21
21
  underlineVariantOffset: number;
22
22
  urlId: number;
23
+ payload: Object | undefined;
23
24
  clone(): IExtendedAttrs;
24
25
  isEmpty(): boolean;
25
26
  }
@@ -131,8 +132,8 @@ export interface IBufferLine {
131
132
  resize(cols: number, fill: ICellData): boolean;
132
133
  cleanupMemory(): number;
133
134
  fill(fillCellData: ICellData, respectProtect?: boolean): void;
134
- copyFrom(line: IBufferLine): void;
135
- clone(): IBufferLine;
135
+ copyFrom(line: IBufferLine, blank?: boolean): void;
136
+ clone(blank?: boolean): IBufferLine;
136
137
  getTrimmedLength(): number;
137
138
  getNoBgTrimmedLength(): number;
138
139
  translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
@@ -146,6 +147,7 @@ export interface IBufferLine {
146
147
  getCodePoint(index: number): number;
147
148
  isCombined(index: number): number;
148
149
  getString(index: number): string;
150
+ getExtended(index: number): IExtendedAttrs;
149
151
  }
150
152
 
151
153
  export interface IMarker extends IDisposable {
@@ -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
- }