@xterm/xterm 5.6.0-beta.140 → 5.6.0-beta.141
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/README.md +1 -0
- package/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/lib/xterm.mjs +15 -15
- package/lib/xterm.mjs.map +3 -3
- package/package.json +3 -4
- package/src/browser/public/Terminal.ts +1 -0
- package/src/browser/services/RenderService.ts +101 -10
- package/src/common/InputHandler.ts +8 -0
- package/src/common/Types.ts +1 -0
- package/src/common/services/CoreService.ts +1 -0
- package/typings/xterm.d.ts +7 -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": "5.6.0-beta.
|
|
4
|
+
"version": "5.6.0-beta.141",
|
|
5
5
|
"main": "lib/xterm.js",
|
|
6
6
|
"module": "lib/xterm.mjs",
|
|
7
7
|
"style": "css/xterm.css",
|
|
@@ -75,7 +75,6 @@
|
|
|
75
75
|
"@types/deep-equal": "^1.0.1",
|
|
76
76
|
"@types/express": "4",
|
|
77
77
|
"@types/express-ws": "^3.0.1",
|
|
78
|
-
"@types/glob": "^7.2.0",
|
|
79
78
|
"@types/jsdom": "^16.2.13",
|
|
80
79
|
"@types/mocha": "^9.0.0",
|
|
81
80
|
"@types/node": "^18.16.0",
|
|
@@ -93,7 +92,7 @@
|
|
|
93
92
|
"eslint-plugin-jsdoc": "^46.9.1",
|
|
94
93
|
"express": "^4.19.2",
|
|
95
94
|
"express-ws": "^5.0.2",
|
|
96
|
-
"glob": "^
|
|
95
|
+
"glob": "^13.0.0",
|
|
97
96
|
"jsdom": "^18.0.1",
|
|
98
97
|
"mocha": "^10.1.0",
|
|
99
98
|
"mustache": "^4.2.0",
|
|
@@ -109,5 +108,5 @@
|
|
|
109
108
|
"ws": "^8.2.3",
|
|
110
109
|
"xterm-benchmark": "^0.3.1"
|
|
111
110
|
},
|
|
112
|
-
"commit": "
|
|
111
|
+
"commit": "a9d3ca19f407c221447d30ea30d3a4c7953b1a4a"
|
|
113
112
|
}
|
|
@@ -123,6 +123,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
|
|
123
123
|
originMode: m.origin,
|
|
124
124
|
reverseWraparoundMode: m.reverseWraparound,
|
|
125
125
|
sendFocusMode: m.sendFocus,
|
|
126
|
+
synchronizedOutputMode: m.synchronizedOutput,
|
|
126
127
|
wraparoundMode: m.wraparound
|
|
127
128
|
};
|
|
128
129
|
}
|
|
@@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
|
|
9
9
|
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
|
10
10
|
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
11
11
|
import { DebouncedIdleTask } from 'common/TaskQueue';
|
|
12
|
-
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
12
|
+
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
13
13
|
import { Emitter } from 'vs/base/common/event';
|
|
14
14
|
|
|
15
15
|
interface ISelectionState {
|
|
@@ -18,6 +18,10 @@ interface ISelectionState {
|
|
|
18
18
|
columnSelectMode: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const enum Constants {
|
|
22
|
+
SYNCHRONIZED_OUTPUT_TIMEOUT_MS = 1000
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
export class RenderService extends Disposable implements IRenderService {
|
|
22
26
|
public serviceBrand: undefined;
|
|
23
27
|
|
|
@@ -32,6 +36,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
32
36
|
private _needsSelectionRefresh: boolean = false;
|
|
33
37
|
private _canvasWidth: number = 0;
|
|
34
38
|
private _canvasHeight: number = 0;
|
|
39
|
+
private _syncOutputHandler: SynchronizedOutputHandler;
|
|
35
40
|
private _selectionState: ISelectionState = {
|
|
36
41
|
start: undefined,
|
|
37
42
|
end: undefined,
|
|
@@ -52,23 +57,31 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
52
57
|
constructor(
|
|
53
58
|
private _rowCount: number,
|
|
54
59
|
screenElement: HTMLElement,
|
|
55
|
-
@IOptionsService
|
|
60
|
+
@IOptionsService private readonly _optionsService: IOptionsService,
|
|
56
61
|
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
|
62
|
+
@ICoreService private readonly _coreService: ICoreService,
|
|
57
63
|
@IDecorationService decorationService: IDecorationService,
|
|
58
64
|
@IBufferService bufferService: IBufferService,
|
|
59
|
-
@ICoreBrowserService
|
|
65
|
+
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
|
60
66
|
@IThemeService themeService: IThemeService
|
|
61
67
|
) {
|
|
62
68
|
super();
|
|
63
69
|
|
|
64
|
-
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end),
|
|
70
|
+
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end), this._coreBrowserService);
|
|
65
71
|
this._register(this._renderDebouncer);
|
|
66
72
|
|
|
67
|
-
this.
|
|
73
|
+
this._syncOutputHandler = new SynchronizedOutputHandler(
|
|
74
|
+
this._coreBrowserService,
|
|
75
|
+
this._coreService,
|
|
76
|
+
() => this._fullRefresh()
|
|
77
|
+
);
|
|
78
|
+
this._register(toDisposable(() => this._syncOutputHandler.dispose()));
|
|
79
|
+
|
|
80
|
+
this._register(this._coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));
|
|
68
81
|
|
|
69
82
|
this._register(bufferService.onResize(() => this._fullRefresh()));
|
|
70
83
|
this._register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear()));
|
|
71
|
-
this._register(
|
|
84
|
+
this._register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
|
72
85
|
this._register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged()));
|
|
73
86
|
|
|
74
87
|
// Do a full refresh whenever any decoration is added or removed. This may not actually result
|
|
@@ -78,7 +91,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
78
91
|
this._register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
|
|
79
92
|
|
|
80
93
|
// Clear the renderer when the a change that could affect glyphs occurs
|
|
81
|
-
this._register(
|
|
94
|
+
this._register(this._optionsService.onMultipleOptionChange([
|
|
82
95
|
'customGlyphs',
|
|
83
96
|
'drawBoldTextInBrightColors',
|
|
84
97
|
'letterSpacing',
|
|
@@ -96,15 +109,15 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
96
109
|
}));
|
|
97
110
|
|
|
98
111
|
// Refresh the cursor line when the cursor changes
|
|
99
|
-
this._register(
|
|
112
|
+
this._register(this._optionsService.onMultipleOptionChange([
|
|
100
113
|
'cursorBlink',
|
|
101
114
|
'cursorStyle'
|
|
102
115
|
], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true)));
|
|
103
116
|
|
|
104
117
|
this._register(themeService.onChangeColors(() => this._fullRefresh()));
|
|
105
118
|
|
|
106
|
-
this._registerIntersectionObserver(
|
|
107
|
-
this._register(
|
|
119
|
+
this._registerIntersectionObserver(this._coreBrowserService.window, screenElement);
|
|
120
|
+
this._register(this._coreBrowserService.onWindowChange((w) => this._registerIntersectionObserver(w, screenElement)));
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
private _registerIntersectionObserver(w: Window & typeof globalThis, screenElement: HTMLElement): void {
|
|
@@ -137,6 +150,18 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
137
150
|
this._needsFullRefresh = true;
|
|
138
151
|
return;
|
|
139
152
|
}
|
|
153
|
+
|
|
154
|
+
if (this._coreService.decPrivateModes.synchronizedOutput) {
|
|
155
|
+
this._syncOutputHandler.bufferRows(start, end);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const buffered = this._syncOutputHandler.flush();
|
|
160
|
+
if (buffered) {
|
|
161
|
+
start = Math.min(start, buffered.start);
|
|
162
|
+
end = Math.max(end, buffered.end);
|
|
163
|
+
}
|
|
164
|
+
|
|
140
165
|
if (!isRedrawOnly) {
|
|
141
166
|
this._isNextRenderRedrawOnly = false;
|
|
142
167
|
}
|
|
@@ -148,6 +173,13 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
148
173
|
return;
|
|
149
174
|
}
|
|
150
175
|
|
|
176
|
+
// Skip rendering if synchronized output mode is enabled. This check must happen here
|
|
177
|
+
// (in addition to refreshRows) to handle renders that were queued before the mode was enabled.
|
|
178
|
+
if (this._coreService.decPrivateModes.synchronizedOutput) {
|
|
179
|
+
this._syncOutputHandler.bufferRows(start, end);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
151
183
|
// Since this is debounced, a resize event could have happened between the time a refresh was
|
|
152
184
|
// requested and when this triggers. Clamp the values of start and end to ensure they're valid
|
|
153
185
|
// given the current viewport state.
|
|
@@ -283,3 +315,62 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
283
315
|
this._renderer.value?.clear();
|
|
284
316
|
}
|
|
285
317
|
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Buffers row refresh requests during synchronized output mode (DEC mode 2026).
|
|
321
|
+
* When the mode is disabled, the accumulated row range is flushed for rendering.
|
|
322
|
+
* A safety timeout ensures rendering occurs even if the end sequence is not received.
|
|
323
|
+
*/
|
|
324
|
+
class SynchronizedOutputHandler {
|
|
325
|
+
private _start: number = 0;
|
|
326
|
+
private _end: number = 0;
|
|
327
|
+
private _timeout: number | undefined;
|
|
328
|
+
private _isBuffering: boolean = false;
|
|
329
|
+
|
|
330
|
+
constructor(
|
|
331
|
+
private readonly _coreBrowserService: ICoreBrowserService,
|
|
332
|
+
private readonly _coreService: ICoreService,
|
|
333
|
+
private readonly _onTimeout: () => void
|
|
334
|
+
) {}
|
|
335
|
+
|
|
336
|
+
public bufferRows(start: number, end: number): void {
|
|
337
|
+
if (!this._isBuffering) {
|
|
338
|
+
this._start = start;
|
|
339
|
+
this._end = end;
|
|
340
|
+
this._isBuffering = true;
|
|
341
|
+
} else {
|
|
342
|
+
this._start = Math.min(this._start, start);
|
|
343
|
+
this._end = Math.max(this._end, end);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (this._timeout === undefined) {
|
|
347
|
+
this._timeout = this._coreBrowserService.window.setTimeout(() => {
|
|
348
|
+
this._timeout = undefined;
|
|
349
|
+
this._coreService.decPrivateModes.synchronizedOutput = false;
|
|
350
|
+
this._onTimeout();
|
|
351
|
+
}, Constants.SYNCHRONIZED_OUTPUT_TIMEOUT_MS);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
public flush(): { start: number, end: number } | undefined {
|
|
356
|
+
if (this._timeout !== undefined) {
|
|
357
|
+
this._coreBrowserService.window.clearTimeout(this._timeout);
|
|
358
|
+
this._timeout = undefined;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!this._isBuffering) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const result = { start: this._start, end: this._end };
|
|
366
|
+
this._isBuffering = false;
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
public dispose(): void {
|
|
371
|
+
if (this._timeout !== undefined) {
|
|
372
|
+
this._coreBrowserService.window.clearTimeout(this._timeout);
|
|
373
|
+
this._timeout = undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -1969,6 +1969,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1969
1969
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
|
1970
1970
|
this._coreService.decPrivateModes.bracketedPasteMode = true;
|
|
1971
1971
|
break;
|
|
1972
|
+
case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)
|
|
1973
|
+
this._coreService.decPrivateModes.synchronizedOutput = true;
|
|
1974
|
+
break;
|
|
1972
1975
|
}
|
|
1973
1976
|
}
|
|
1974
1977
|
return true;
|
|
@@ -2197,6 +2200,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
2197
2200
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
|
2198
2201
|
this._coreService.decPrivateModes.bracketedPasteMode = false;
|
|
2199
2202
|
break;
|
|
2203
|
+
case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)
|
|
2204
|
+
this._coreService.decPrivateModes.synchronizedOutput = false;
|
|
2205
|
+
this._onRequestRefreshRows.fire(undefined);
|
|
2206
|
+
break;
|
|
2200
2207
|
}
|
|
2201
2208
|
}
|
|
2202
2209
|
return true;
|
|
@@ -2291,6 +2298,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
2291
2298
|
if (p === 1048) return f(p, V.SET); // xterm always returns SET here
|
|
2292
2299
|
if (p === 47 || p === 1047 || p === 1049) return f(p, b2v(active === alt));
|
|
2293
2300
|
if (p === 2004) return f(p, b2v(dm.bracketedPasteMode));
|
|
2301
|
+
if (p === 2026) return f(p, b2v(dm.synchronizedOutput));
|
|
2294
2302
|
return f(p, V.NOT_RECOGNIZED);
|
|
2295
2303
|
}
|
|
2296
2304
|
|
package/src/common/Types.ts
CHANGED
package/typings/xterm.d.ts
CHANGED
|
@@ -1968,6 +1968,13 @@ declare module '@xterm/xterm' {
|
|
|
1968
1968
|
* Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h`
|
|
1969
1969
|
*/
|
|
1970
1970
|
readonly sendFocusMode: boolean;
|
|
1971
|
+
/**
|
|
1972
|
+
* Synchronized Output Mode: `CSI ? 2 0 2 6 h`
|
|
1973
|
+
*
|
|
1974
|
+
* When enabled, output is buffered and only rendered when the mode is
|
|
1975
|
+
* disabled, allowing for atomic screen updates without tearing.
|
|
1976
|
+
*/
|
|
1977
|
+
readonly synchronizedOutputMode: boolean;
|
|
1971
1978
|
/**
|
|
1972
1979
|
* Auto-Wrap Mode (DECAWM): `CSI ? 7 h`
|
|
1973
1980
|
*/
|