@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/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/Version.ts +1 -1
- package/src/common/buffer/AttributeData.ts +2 -1
- package/src/common/buffer/Buffer.ts +4 -9
- package/src/common/buffer/BufferLine.ts +73 -81
- package/src/common/buffer/Types.ts +4 -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.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": "
|
|
122
|
+
"commit": "c58ea3637f3968e0e6e79cd92cf9aace7ef89ee2"
|
|
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
|
|
package/src/common/Version.ts
CHANGED
|
@@ -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.
|
|
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 $
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
252
|
+
this._cacheValid = false;
|
|
246
253
|
if (attrs.bg & BgFlags.HAS_EXTENDED) {
|
|
247
254
|
this._extendedAttrs[index] = attrs.extended;
|
|
248
255
|
}
|
|
249
|
-
|
|
250
|
-
this._data[
|
|
251
|
-
this._data[
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
538
|
-
if (
|
|
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
|
|
560
|
+
return this._cacheTrimmed ? this._cache : this._cache.trimEnd();
|
|
545
561
|
}
|
|
546
|
-
if (!
|
|
547
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
}
|