@xterm/xterm 6.1.0-beta.220 → 6.1.0-beta.221
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 +7 -7
- package/lib/xterm.mjs.map +3 -3
- package/package.json +2 -2
- package/src/common/Async.ts +36 -0
- package/src/common/Version.ts +1 -1
- package/src/common/services/DecorationService.ts +254 -20
- package/src/common/services/Services.ts +2 -0
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.221",
|
|
5
5
|
"main": "lib/xterm.js",
|
|
6
6
|
"module": "lib/xterm.mjs",
|
|
7
7
|
"style": "css/xterm.css",
|
|
@@ -111,5 +111,5 @@
|
|
|
111
111
|
"ws": "^8.2.3",
|
|
112
112
|
"xterm-benchmark": "^0.3.1"
|
|
113
113
|
},
|
|
114
|
-
"commit": "
|
|
114
|
+
"commit": "4e5994d8a2ae575eaac2c355c54f97a9f4c3b928"
|
|
115
115
|
}
|
package/src/common/Async.ts
CHANGED
|
@@ -73,6 +73,42 @@ export class TimeoutTimer implements IDisposable {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Schedules a single runner on the microtask queue. Unlike {@link TimeoutTimer}, a scheduled
|
|
78
|
+
* microtask cannot be unqueued; {@link cancel} prevents the runner from executing if it has not
|
|
79
|
+
* run yet.
|
|
80
|
+
*/
|
|
81
|
+
export class MicrotaskTimer implements IDisposable {
|
|
82
|
+
private _isScheduled = false;
|
|
83
|
+
private _isDisposed = false;
|
|
84
|
+
|
|
85
|
+
public dispose(): void {
|
|
86
|
+
this.cancel();
|
|
87
|
+
this._isDisposed = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public cancel(): void {
|
|
91
|
+
this._isScheduled = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public set(runner: () => void): void {
|
|
95
|
+
if (this._isDisposed) {
|
|
96
|
+
throw new Error('Calling set on a disposed MicrotaskTimer');
|
|
97
|
+
}
|
|
98
|
+
if (this._isScheduled) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this._isScheduled = true;
|
|
102
|
+
queueMicrotask(() => {
|
|
103
|
+
if (!this._isScheduled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this._isScheduled = false;
|
|
107
|
+
runner();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
76
112
|
export class IntervalTimer implements IDisposable {
|
|
77
113
|
private _disposable: IDisposable | undefined;
|
|
78
114
|
private _isDisposed = false;
|
package/src/common/Version.ts
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* @license MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { IDeleteEvent, IInsertEvent } from 'common/CircularList';
|
|
7
|
+
import { MicrotaskTimer } from 'common/Async';
|
|
6
8
|
import { css } from 'common/Color';
|
|
7
|
-
import { Disposable, DisposableStore, toDisposable } from 'common/Lifecycle';
|
|
8
|
-
import { IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
|
|
9
|
+
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
|
10
|
+
import { IBufferService, IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
|
|
9
11
|
import { SortedList } from 'common/SortedList';
|
|
10
|
-
import { IColor } from 'common/Types';
|
|
12
|
+
import { IColor, ICircularList } from 'common/Types';
|
|
11
13
|
import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';
|
|
12
14
|
import { Emitter } from 'common/Event';
|
|
13
15
|
|
|
14
16
|
// Work variables to avoid garbage collection
|
|
15
17
|
let $xmin = 0;
|
|
16
18
|
let $xmax = 0;
|
|
17
|
-
let $ymin = 0;
|
|
18
|
-
let $ymax = 0;
|
|
19
19
|
|
|
20
20
|
export class DecorationService extends Disposable implements IDecorationService {
|
|
21
21
|
public serviceBrand: any;
|
|
@@ -27,6 +27,8 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
27
27
|
*/
|
|
28
28
|
private readonly _decorations: SortedList<IInternalDecoration>;
|
|
29
29
|
|
|
30
|
+
private readonly _lineCache = this._register(new DecorationLineCache());
|
|
31
|
+
|
|
30
32
|
private readonly _onDecorationRegistered = this._register(new Emitter<IInternalDecoration>());
|
|
31
33
|
public readonly onDecorationRegistered = this._onDecorationRegistered.event;
|
|
32
34
|
private readonly _onDecorationRemoved = this._register(new Emitter<IInternalDecoration>());
|
|
@@ -34,12 +36,19 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
34
36
|
|
|
35
37
|
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
|
|
36
38
|
|
|
37
|
-
constructor(
|
|
39
|
+
constructor(
|
|
40
|
+
@ILogService private readonly _logService: ILogService,
|
|
41
|
+
@IBufferService private readonly _bufferService: IBufferService
|
|
42
|
+
) {
|
|
38
43
|
super();
|
|
39
44
|
|
|
40
45
|
this._decorations = new SortedList(e => e?.marker.line, this._logService);
|
|
41
46
|
|
|
42
47
|
this._register(toDisposable(() => this.reset()));
|
|
48
|
+
this._register(this._bufferService.buffers.onBufferActivate(() => {
|
|
49
|
+
this._lineCache.attachToBufferLines(this._bufferService.buffer.lines);
|
|
50
|
+
}));
|
|
51
|
+
this._lineCache.attachToBufferLines(this._bufferService.buffer.lines);
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
|
|
@@ -53,12 +62,14 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
53
62
|
listener.dispose();
|
|
54
63
|
if (decoration) {
|
|
55
64
|
if (this._decorations.delete(decoration)) {
|
|
65
|
+
this._lineCache.remove(decoration);
|
|
56
66
|
this._onDecorationRemoved.fire(decoration);
|
|
57
67
|
}
|
|
58
68
|
markerDispose.dispose();
|
|
59
69
|
}
|
|
60
70
|
});
|
|
61
71
|
this._decorations.insert(decoration);
|
|
72
|
+
this._lineCache.add(decoration);
|
|
62
73
|
this._onDecorationRegistered.fire(decoration);
|
|
63
74
|
}
|
|
64
75
|
return decoration;
|
|
@@ -69,19 +80,17 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
69
80
|
d.dispose();
|
|
70
81
|
}
|
|
71
82
|
this._decorations.clear();
|
|
83
|
+
this._lineCache.clear();
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {
|
|
87
|
+
const bucket = this._lineCache.getDecorationsOnLine(line);
|
|
88
|
+
if (!bucket) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
75
91
|
let xmin = 0;
|
|
76
92
|
let xmax = 0;
|
|
77
|
-
|
|
78
|
-
let ymax = 0;
|
|
79
|
-
for (const d of this._decorations.values()) {
|
|
80
|
-
ymin = d.marker.line;
|
|
81
|
-
ymax = ymin + (d.options.height ?? 1);
|
|
82
|
-
if (line < ymin || line >= ymax) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
93
|
+
for (const d of bucket) {
|
|
85
94
|
xmin = d.options.x ?? 0;
|
|
86
95
|
xmax = xmin + (d.options.width ?? 1);
|
|
87
96
|
if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
|
|
@@ -91,12 +100,11 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
103
|
+
const bucket = this._lineCache.getDecorationsOnLine(line);
|
|
104
|
+
if (!bucket) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const d of bucket) {
|
|
100
108
|
$xmin = d.options.x ?? 0;
|
|
101
109
|
$xmax = $xmin + (d.options.width ?? 1);
|
|
102
110
|
if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
|
|
@@ -106,10 +114,235 @@ export class DecorationService extends Disposable implements IDecorationService
|
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Per-logical-line index of decorations for fast cell lookup.
|
|
119
|
+
*
|
|
120
|
+
* Keys are marker.line coordinates (logical buffer lines), not CircularList ring slots.
|
|
121
|
+
* Multi-line decorations appear in every line bucket they span. The index is kept aligned
|
|
122
|
+
* with marker.line updates via buffer line trim/insert/delete events.
|
|
123
|
+
*/
|
|
124
|
+
export class DecorationLineCache extends Disposable {
|
|
125
|
+
private readonly _decorationsByLine: Map<number, IInternalDecoration[]> = new Map();
|
|
126
|
+
private readonly _decorations = new Set<IInternalDecoration>();
|
|
127
|
+
private readonly _bufferLineListeners = this._register(new MutableDisposable<DisposableStore>());
|
|
128
|
+
private readonly _lineIndexSyncTimer = this._register(new MicrotaskTimer());
|
|
129
|
+
private _lineIndexSyncCallbacks: (() => void)[] = [];
|
|
130
|
+
|
|
131
|
+
public clear(): void {
|
|
132
|
+
this._lineIndexSyncCallbacks.length = 0;
|
|
133
|
+
this._lineIndexSyncTimer.cancel();
|
|
134
|
+
this._decorationsByLine.clear();
|
|
135
|
+
this._decorations.clear();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public add(decoration: IInternalDecoration): void {
|
|
139
|
+
this._decorations.add(decoration);
|
|
140
|
+
this._addToLineBuckets(decoration);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public remove(decoration: IInternalDecoration): void {
|
|
144
|
+
this._decorations.delete(decoration);
|
|
145
|
+
this._removeFromLineBuckets(decoration);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public getDecorationsOnLine(line: number): ReadonlyArray<IInternalDecoration> | undefined {
|
|
149
|
+
return this._decorationsByLine.get(line);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public attachToBufferLines(lines: ICircularList<unknown>): void {
|
|
153
|
+
const store = new DisposableStore();
|
|
154
|
+
this._bufferLineListeners.value = store;
|
|
155
|
+
store.add(lines.onTrim(amount => this._handleBufferLinesTrim(amount)));
|
|
156
|
+
store.add(lines.onInsert(event => this._handleBufferLinesInsert(event)));
|
|
157
|
+
store.add(lines.onDelete(event => this._handleBufferLinesDelete(event)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private _getDecorationHeight(decoration: IInternalDecoration): number {
|
|
161
|
+
return decoration.options.height ?? 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private _addToLineBuckets(decoration: IInternalDecoration): void {
|
|
165
|
+
const start = decoration.marker.line;
|
|
166
|
+
if (start < 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
decoration._indexedStartLine = start;
|
|
170
|
+
const height = this._getDecorationHeight(decoration);
|
|
171
|
+
for (let line = start; line < start + height; line++) {
|
|
172
|
+
let bucket = this._decorationsByLine.get(line);
|
|
173
|
+
if (!bucket) {
|
|
174
|
+
bucket = [];
|
|
175
|
+
this._decorationsByLine.set(line, bucket);
|
|
176
|
+
}
|
|
177
|
+
bucket.push(decoration);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private _removeFromLineBuckets(decoration: IInternalDecoration): void {
|
|
182
|
+
const start = decoration._indexedStartLine;
|
|
183
|
+
const height = this._getDecorationHeight(decoration);
|
|
184
|
+
for (let line = start; line < start + height; line++) {
|
|
185
|
+
const bucket = this._decorationsByLine.get(line);
|
|
186
|
+
if (!bucket) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const index = bucket.indexOf(decoration);
|
|
190
|
+
if (index !== -1) {
|
|
191
|
+
bucket.splice(index, 1);
|
|
192
|
+
}
|
|
193
|
+
if (bucket.length === 0) {
|
|
194
|
+
this._decorationsByLine.delete(line);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private _reindexDecoration(decoration: IInternalDecoration): void {
|
|
200
|
+
this._removeFromLineBuckets(decoration);
|
|
201
|
+
if (!decoration.marker.isDisposed && decoration.marker.line >= 0) {
|
|
202
|
+
this._addToLineBuckets(decoration);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Re-index after marker line updates (buffer listeners may run before markers). */
|
|
207
|
+
private _scheduleLineIndexSync(callback: () => void): void {
|
|
208
|
+
this._lineIndexSyncCallbacks.push(callback);
|
|
209
|
+
this._lineIndexSyncTimer.set(() => {
|
|
210
|
+
const callbacks = this._lineIndexSyncCallbacks;
|
|
211
|
+
this._lineIndexSyncCallbacks = [];
|
|
212
|
+
for (const cb of callbacks) {
|
|
213
|
+
cb();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private _handleBufferLinesTrim(amount: number): void {
|
|
219
|
+
if (amount <= 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const newMap = new Map<number, IInternalDecoration[]>();
|
|
223
|
+
for (const [line, bucket] of this._decorationsByLine) {
|
|
224
|
+
const newLine = line - amount;
|
|
225
|
+
if (newLine < 0) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const existing = newMap.get(newLine);
|
|
229
|
+
if (existing) {
|
|
230
|
+
existing.push(...bucket);
|
|
231
|
+
} else {
|
|
232
|
+
newMap.set(newLine, bucket.slice());
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this._decorationsByLine.clear();
|
|
236
|
+
for (const [line, bucket] of newMap) {
|
|
237
|
+
this._decorationsByLine.set(line, bucket);
|
|
238
|
+
}
|
|
239
|
+
for (const d of this._decorations) {
|
|
240
|
+
if (!d.marker.isDisposed) {
|
|
241
|
+
d._indexedStartLine -= amount;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private _handleBufferLinesInsert(event: IInsertEvent): void {
|
|
247
|
+
this._scheduleLineIndexSync(() => this._applyBufferLinesInsert(event));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private _handleBufferLinesDelete(event: IDeleteEvent): void {
|
|
251
|
+
this._scheduleLineIndexSync(() => this._applyBufferLinesDelete(event));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private _mergeLineBucket(newMap: Map<number, IInternalDecoration[]>, line: number, bucket: IInternalDecoration[]): void {
|
|
255
|
+
const existing = newMap.get(line);
|
|
256
|
+
if (existing) {
|
|
257
|
+
existing.push(...bucket);
|
|
258
|
+
} else {
|
|
259
|
+
newMap.set(line, bucket.slice());
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Shift indexed line keys and sync start lines. O(unique indexed lines), not O(decoration count).
|
|
265
|
+
* Decorations that span the insert point are re-indexed individually (rare vs single-line hits).
|
|
266
|
+
*/
|
|
267
|
+
private _applyBufferLinesInsert(event: IInsertEvent): void {
|
|
268
|
+
const { index, amount } = event;
|
|
269
|
+
const spanCrossers: IInternalDecoration[] = [];
|
|
270
|
+
for (const d of this._decorations) {
|
|
271
|
+
if (d.marker.isDisposed) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const start = d._indexedStartLine;
|
|
275
|
+
if (start < index && start + this._getDecorationHeight(d) > index) {
|
|
276
|
+
spanCrossers.push(d);
|
|
277
|
+
this._removeFromLineBuckets(d);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const newMap = new Map<number, IInternalDecoration[]>();
|
|
281
|
+
for (const [line, bucket] of this._decorationsByLine) {
|
|
282
|
+
const newLine = line >= index ? line + amount : line;
|
|
283
|
+
this._mergeLineBucket(newMap, newLine, bucket);
|
|
284
|
+
}
|
|
285
|
+
this._decorationsByLine.clear();
|
|
286
|
+
for (const [line, bucket] of newMap) {
|
|
287
|
+
this._decorationsByLine.set(line, bucket);
|
|
288
|
+
}
|
|
289
|
+
for (const d of this._decorations) {
|
|
290
|
+
if (d.marker.isDisposed) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (d._indexedStartLine >= index) {
|
|
294
|
+
d._indexedStartLine = d.marker.line;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const d of spanCrossers) {
|
|
298
|
+
this._addToLineBuckets(d);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Drop deleted line keys, shift keys below, sync start lines. Full re-index only when a
|
|
304
|
+
* multi-line decoration spans across the deleted range but survives.
|
|
305
|
+
*/
|
|
306
|
+
private _applyBufferLinesDelete(event: IDeleteEvent): void {
|
|
307
|
+
const deleteEnd = event.index + event.amount;
|
|
308
|
+
const newMap = new Map<number, IInternalDecoration[]>();
|
|
309
|
+
for (const [line, bucket] of this._decorationsByLine) {
|
|
310
|
+
if (line >= event.index && line < deleteEnd) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const newLine = line >= deleteEnd ? line - event.amount : line;
|
|
314
|
+
this._mergeLineBucket(newMap, newLine, bucket);
|
|
315
|
+
}
|
|
316
|
+
this._decorationsByLine.clear();
|
|
317
|
+
for (const [line, bucket] of newMap) {
|
|
318
|
+
this._decorationsByLine.set(line, bucket);
|
|
319
|
+
}
|
|
320
|
+
const toReindex: IInternalDecoration[] = [];
|
|
321
|
+
for (const d of this._decorations) {
|
|
322
|
+
if (d.marker.isDisposed) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const start = d._indexedStartLine;
|
|
326
|
+
const height = this._getDecorationHeight(d);
|
|
327
|
+
if (start >= deleteEnd) {
|
|
328
|
+
d._indexedStartLine = d.marker.line;
|
|
329
|
+
} else if (start < event.index && start + height > deleteEnd) {
|
|
330
|
+
toReindex.push(d);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const d of toReindex) {
|
|
334
|
+
this._reindexDecoration(d);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
109
339
|
class Decoration extends DisposableStore implements IInternalDecoration {
|
|
110
340
|
public readonly marker: IMarker;
|
|
111
341
|
public element: HTMLElement | undefined;
|
|
112
342
|
|
|
343
|
+
/** Start line used for line-index removal when marker.line is cleared on dispose. */
|
|
344
|
+
public _indexedStartLine: number;
|
|
345
|
+
|
|
113
346
|
public readonly onRenderEmitter = this.add(new Emitter<HTMLElement>());
|
|
114
347
|
public readonly onRender = this.onRenderEmitter.event;
|
|
115
348
|
private readonly _onDispose = this.add(new Emitter<void>());
|
|
@@ -144,6 +377,7 @@ class Decoration extends DisposableStore implements IInternalDecoration {
|
|
|
144
377
|
) {
|
|
145
378
|
super();
|
|
146
379
|
this.marker = options.marker;
|
|
380
|
+
this._indexedStartLine = options.marker.line;
|
|
147
381
|
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
|
|
148
382
|
this.options.overviewRulerOptions.position = 'full';
|
|
149
383
|
}
|
|
@@ -396,4 +396,6 @@ export interface IInternalDecoration extends IDecoration {
|
|
|
396
396
|
readonly backgroundColorRGB: IColor | undefined;
|
|
397
397
|
readonly foregroundColorRGB: IColor | undefined;
|
|
398
398
|
readonly onRenderEmitter: Emitter<HTMLElement>;
|
|
399
|
+
/** @internal Start line for line-index removal; kept in sync on buffer line shifts. */
|
|
400
|
+
_indexedStartLine: number;
|
|
399
401
|
}
|