@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/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/lib/xterm.mjs +8 -8
- package/lib/xterm.mjs.map +4 -4
- package/package.json +2 -2
- package/src/common/Event.ts +10 -15
- package/src/common/InputHandler.ts +5 -0
- package/src/common/Version.ts +1 -1
- package/src/common/buffer/Buffer.ts +4 -9
- package/src/common/buffer/BufferLine.ts +62 -75
- package/src/common/buffer/Types.ts +2 -2
- package/src/common/services/BufferService.ts +4 -4
- package/src/common/services/DecorationService.ts +1 -1
- package/src/common/buffer/BufferLineStringCache.ts +0 -69
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.
|
|
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": "
|
|
122
|
+
"commit": "d3e32b344dfe7dd6015cff6a9aeaaeaeccdc2789"
|
|
123
123
|
}
|
package/src/common/Event.ts
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
}
|
package/src/common/Version.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
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(
|
|
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(
|
|
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 $
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
247
|
+
this._cacheValid = false;
|
|
246
248
|
if (attrs.bg & BgFlags.HAS_EXTENDED) {
|
|
247
249
|
this._extendedAttrs[index] = attrs.extended;
|
|
248
250
|
}
|
|
249
|
-
|
|
250
|
-
this._data[
|
|
251
|
-
this._data[
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
538
|
-
if (
|
|
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
|
|
555
|
+
return this._cacheTrimmed ? this._cache : this._cache.trimEnd();
|
|
545
556
|
}
|
|
546
|
-
if (!
|
|
547
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
}
|