@xterm/xterm 5.6.0-beta.98 → 6.0.0
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 +7 -7
- package/src/browser/AccessibilityManager.ts +1 -1
- package/src/browser/CoreBrowserTerminal.ts +24 -1
- package/src/browser/TimeBasedDebouncer.ts +2 -2
- package/src/browser/Viewport.ts +9 -4
- package/src/browser/input/MoveToCell.ts +3 -1
- package/src/browser/public/Terminal.ts +1 -0
- package/src/browser/services/RenderService.ts +101 -10
- package/src/browser/services/SelectionService.ts +8 -0
- package/src/common/InputHandler.ts +34 -8
- package/src/common/TaskQueue.ts +7 -7
- package/src/common/Types.ts +1 -0
- package/src/common/input/Keyboard.ts +0 -24
- package/src/common/input/WriteBuffer.ts +6 -5
- package/src/common/services/BufferService.ts +8 -5
- package/src/common/services/CoreMouseService.ts +48 -3
- package/src/common/services/CoreService.ts +5 -2
- package/src/common/services/OptionsService.ts +1 -0
- package/src/common/services/Services.ts +14 -1
- package/src/vs/base/common/async.ts +1 -1
- package/typings/xterm.d.ts +26 -28
- package/src/vs/typings/thenable.d.ts +0 -12
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xterm/xterm",
|
|
3
3
|
"description": "Full xterm terminal, in your browser",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"main": "lib/xterm.js",
|
|
6
6
|
"module": "lib/xterm.mjs",
|
|
7
7
|
"style": "css/xterm.css",
|
|
8
8
|
"types": "typings/xterm.d.ts",
|
|
9
9
|
"repository": "https://github.com/xtermjs/xterm.js",
|
|
10
10
|
"license": "MIT",
|
|
11
|
+
"workspaces": [
|
|
12
|
+
"addons/*"
|
|
13
|
+
],
|
|
11
14
|
"keywords": [
|
|
12
15
|
"cli",
|
|
13
16
|
"command-line",
|
|
@@ -25,8 +28,6 @@
|
|
|
25
28
|
],
|
|
26
29
|
"scripts": {
|
|
27
30
|
"setup": "npm run build",
|
|
28
|
-
"presetup": "npm run install-addons",
|
|
29
|
-
"install-addons": "node ./bin/install-addons.js",
|
|
30
31
|
"start": "node demo/start",
|
|
31
32
|
"build": "npm run tsc",
|
|
32
33
|
"watch": "npm run tsc-watch",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"test": "npm run test-unit",
|
|
43
44
|
"posttest": "npm run lint",
|
|
44
45
|
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
|
|
46
|
+
"lint-fix": "eslint -c .eslintrc.json --fix --ext .ts src/ addons/",
|
|
45
47
|
"lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/",
|
|
46
48
|
"test-unit": "node ./bin/test_unit.js",
|
|
47
49
|
"test-unit-coverage": "node ./bin/test_unit.js --coverage",
|
|
@@ -73,7 +75,6 @@
|
|
|
73
75
|
"@types/deep-equal": "^1.0.1",
|
|
74
76
|
"@types/express": "4",
|
|
75
77
|
"@types/express-ws": "^3.0.1",
|
|
76
|
-
"@types/glob": "^7.2.0",
|
|
77
78
|
"@types/jsdom": "^16.2.13",
|
|
78
79
|
"@types/mocha": "^9.0.0",
|
|
79
80
|
"@types/node": "^18.16.0",
|
|
@@ -86,12 +87,11 @@
|
|
|
86
87
|
"chai": "^4.3.4",
|
|
87
88
|
"cross-env": "^7.0.3",
|
|
88
89
|
"deep-equal": "^2.0.5",
|
|
89
|
-
"esbuild": "
|
|
90
|
+
"esbuild": "~0.25.2",
|
|
90
91
|
"eslint": "^8.56.0",
|
|
91
92
|
"eslint-plugin-jsdoc": "^46.9.1",
|
|
92
93
|
"express": "^4.19.2",
|
|
93
94
|
"express-ws": "^5.0.2",
|
|
94
|
-
"glob": "^7.2.0",
|
|
95
95
|
"jsdom": "^18.0.1",
|
|
96
96
|
"mocha": "^10.1.0",
|
|
97
97
|
"mustache": "^4.2.0",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"ws": "^8.2.3",
|
|
108
108
|
"xterm-benchmark": "^0.3.1"
|
|
109
109
|
},
|
|
110
|
-
"commit": "
|
|
110
|
+
"commit": "f447274f430fd22513f6adbf9862d19524471c04"
|
|
111
111
|
}
|
|
@@ -185,7 +185,7 @@ export class AccessibilityManager extends Disposable {
|
|
|
185
185
|
const element = this._rowElements[i];
|
|
186
186
|
if (element) {
|
|
187
187
|
if (lineData.length === 0) {
|
|
188
|
-
element.
|
|
188
|
+
element.textContent = '\u00a0';
|
|
189
189
|
this._rowColumns.set(element, [0, 1]);
|
|
190
190
|
} else {
|
|
191
191
|
element.textContent = lineData;
|
|
@@ -532,7 +532,13 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|
|
532
532
|
this.textarea!.focus();
|
|
533
533
|
this.textarea!.select();
|
|
534
534
|
}));
|
|
535
|
-
this._register(
|
|
535
|
+
this._register(Event.any(
|
|
536
|
+
this._onScroll.event,
|
|
537
|
+
this._inputHandler.onScroll
|
|
538
|
+
)(() => {
|
|
539
|
+
this._selectionService!.refresh();
|
|
540
|
+
this._viewport?.queueSync();
|
|
541
|
+
}));
|
|
536
542
|
|
|
537
543
|
this._register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
|
|
538
544
|
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
|
@@ -640,6 +646,14 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|
|
640
646
|
if (deltaY === 0) {
|
|
641
647
|
return false;
|
|
642
648
|
}
|
|
649
|
+
const lines = self.coreMouseService.consumeWheelEvent(
|
|
650
|
+
ev as WheelEvent,
|
|
651
|
+
self._renderService?.dimensions?.device?.cell?.height,
|
|
652
|
+
self._coreBrowserService?.dpr
|
|
653
|
+
);
|
|
654
|
+
if (lines === 0) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
643
657
|
action = deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
|
|
644
658
|
but = CoreMouseButton.WHEEL;
|
|
645
659
|
break;
|
|
@@ -811,6 +825,15 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|
|
811
825
|
return false;
|
|
812
826
|
}
|
|
813
827
|
|
|
828
|
+
const lines = self.coreMouseService.consumeWheelEvent(
|
|
829
|
+
ev as WheelEvent,
|
|
830
|
+
self._renderService?.dimensions?.device?.cell?.height,
|
|
831
|
+
self._coreBrowserService?.dpr
|
|
832
|
+
);
|
|
833
|
+
if (lines === 0) {
|
|
834
|
+
return this.cancel(ev, true);
|
|
835
|
+
}
|
|
836
|
+
|
|
814
837
|
// Construct and send sequences
|
|
815
838
|
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
|
|
816
839
|
this.coreService.triggerDataEvent(sequence, true);
|
|
@@ -45,7 +45,7 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
|
|
|
45
45
|
|
|
46
46
|
// Only refresh if the time since last refresh is above a threshold, otherwise wait for
|
|
47
47
|
// enough time to pass before refreshing again.
|
|
48
|
-
const refreshRequestTime: number =
|
|
48
|
+
const refreshRequestTime: number = performance.now();
|
|
49
49
|
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
|
|
50
50
|
// Enough time has lapsed since the last refresh; refresh immediately
|
|
51
51
|
this._lastRefreshMs = refreshRequestTime;
|
|
@@ -57,7 +57,7 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
|
|
|
57
57
|
this._additionalRefreshRequested = true;
|
|
58
58
|
|
|
59
59
|
this._refreshTimeoutID = window.setTimeout(() => {
|
|
60
|
-
this._lastRefreshMs =
|
|
60
|
+
this._lastRefreshMs = performance.now();
|
|
61
61
|
this._innerRefresh();
|
|
62
62
|
this._additionalRefreshRequested = false;
|
|
63
63
|
this._refreshTimeoutID = undefined; // No longer need to clear the timeout
|
package/src/browser/Viewport.ts
CHANGED
|
@@ -93,8 +93,13 @@ export class Viewport extends Disposable {
|
|
|
93
93
|
].join('\n');
|
|
94
94
|
}));
|
|
95
95
|
|
|
96
|
-
this._register(this._bufferService.onResize(() => this.
|
|
97
|
-
this._register(this._bufferService.buffers.onBufferActivate(() =>
|
|
96
|
+
this._register(this._bufferService.onResize(() => this.queueSync()));
|
|
97
|
+
this._register(this._bufferService.buffers.onBufferActivate(() => {
|
|
98
|
+
// Reset _latestYDisp when switching buffers to prevent stale scroll position
|
|
99
|
+
// from alt buffer contaminating normal buffer scroll position
|
|
100
|
+
this._latestYDisp = undefined;
|
|
101
|
+
this.queueSync();
|
|
102
|
+
}));
|
|
98
103
|
this._register(this._bufferService.onScroll(() => this._sync()));
|
|
99
104
|
|
|
100
105
|
this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
|
|
@@ -126,7 +131,7 @@ export class Viewport extends Disposable {
|
|
|
126
131
|
};
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
public queueSync(ydisp?: number): void {
|
|
130
135
|
// Update state
|
|
131
136
|
if (ydisp !== undefined) {
|
|
132
137
|
this._latestYDisp = ydisp;
|
|
@@ -157,7 +162,7 @@ export class Viewport extends Disposable {
|
|
|
157
162
|
});
|
|
158
163
|
this._suppressOnScrollHandler = false;
|
|
159
164
|
|
|
160
|
-
// If ydisp has been changed by some other
|
|
165
|
+
// If ydisp has been changed by some other component (input/buffer), then stop animating smooth
|
|
161
166
|
// scroll and scroll there immediately.
|
|
162
167
|
if (ydisp !== this._latestYDisp) {
|
|
163
168
|
this._scrollableElement.setScrollPosition({
|
|
@@ -199,7 +199,9 @@ function bufferLine(
|
|
|
199
199
|
let currentRow = startRow;
|
|
200
200
|
let bufferStr = '';
|
|
201
201
|
|
|
202
|
-
while (currentCol !== endCol || currentRow !== endRow)
|
|
202
|
+
while ((currentCol !== endCol || currentRow !== endRow) &&
|
|
203
|
+
currentRow >= 0 &&
|
|
204
|
+
currentRow < bufferService.buffer.lines.length) {
|
|
203
205
|
currentCol += forward ? 1 : -1;
|
|
204
206
|
|
|
205
207
|
if (forward && currentCol > bufferService.cols - 1) {
|
|
@@ -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
|
+
}
|
|
@@ -152,6 +152,14 @@ export class SelectionService extends Disposable implements ISelectionService {
|
|
|
152
152
|
this._register(toDisposable(() => {
|
|
153
153
|
this._removeMouseDownListeners();
|
|
154
154
|
}));
|
|
155
|
+
|
|
156
|
+
// Clear selection when resizing vertically. This experience could be improved, this is the
|
|
157
|
+
// simple option to fix the buggy behavior. https://github.com/xtermjs/xterm.js/issues/5300
|
|
158
|
+
this._register(this._bufferService.onResize(e => {
|
|
159
|
+
if (e.rowsChanged) {
|
|
160
|
+
this.clearSelection();
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
public reset(): void {
|
|
@@ -445,7 +445,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
445
445
|
|
|
446
446
|
// Log debug data, the log level gate is to prevent extra work in this hot path
|
|
447
447
|
if (this._logService.logLevel <= LogLevelEnum.DEBUG) {
|
|
448
|
-
this._logService.debug(`parsing data${typeof data === 'string' ? ` "${data}"` : ` "${Array.prototype.map.call(data, e => String.fromCharCode(e)).join('')}"`}
|
|
448
|
+
this._logService.debug(`parsing data ${typeof data === 'string' ? ` "${data}"` : ` "${Array.prototype.map.call(data, e => String.fromCharCode(e)).join('')}"`}`);
|
|
449
|
+
}
|
|
450
|
+
if (this._logService.logLevel === LogLevelEnum.TRACE) {
|
|
451
|
+
this._logService.trace(`parsing data (codes)`, typeof data === 'string'
|
|
449
452
|
? data.split('').map(e => e.charCodeAt(0))
|
|
450
453
|
: data
|
|
451
454
|
);
|
|
@@ -606,7 +609,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
606
609
|
// since an empty cell is only set by fullwidth chars
|
|
607
610
|
bufferRow.addCodepointToCell(this._activeBuffer.x - offset,
|
|
608
611
|
code, chWidth);
|
|
609
|
-
for (let delta = chWidth - oldWidth; --delta >= 0;
|
|
612
|
+
for (let delta = chWidth - oldWidth; --delta >= 0;) {
|
|
610
613
|
bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);
|
|
611
614
|
}
|
|
612
615
|
continue;
|
|
@@ -1220,12 +1223,27 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1220
1223
|
this._dirtyRowTracker.markDirty(0);
|
|
1221
1224
|
break;
|
|
1222
1225
|
case 2:
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1226
|
+
if (this._optionsService.rawOptions.scrollOnEraseInDisplay) {
|
|
1227
|
+
j = this._bufferService.rows;
|
|
1228
|
+
this._dirtyRowTracker.markRangeDirty(0, j - 1);
|
|
1229
|
+
while (j--) {
|
|
1230
|
+
const currentLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + j);
|
|
1231
|
+
if (currentLine?.getTrimmedLength()) {
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
for (; j >= 0; j--) {
|
|
1236
|
+
this._bufferService.scroll(this._eraseAttrData());
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
j = this._bufferService.rows;
|
|
1241
|
+
this._dirtyRowTracker.markDirty(j - 1);
|
|
1242
|
+
while (j--) {
|
|
1243
|
+
this._resetBufferLine(j, respectProtect);
|
|
1244
|
+
}
|
|
1245
|
+
this._dirtyRowTracker.markDirty(0);
|
|
1227
1246
|
}
|
|
1228
|
-
this._dirtyRowTracker.markDirty(0);
|
|
1229
1247
|
break;
|
|
1230
1248
|
case 3:
|
|
1231
1249
|
// Clear scrollback (everything not in viewport)
|
|
@@ -1607,7 +1625,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1607
1625
|
const text = bufferRow.getString(x);
|
|
1608
1626
|
const data = new Uint32Array(text.length * length);
|
|
1609
1627
|
let idata = 0;
|
|
1610
|
-
for (let itext = 0; itext < text.length;
|
|
1628
|
+
for (let itext = 0; itext < text.length;) {
|
|
1611
1629
|
const ch = text.codePointAt(itext) || 0;
|
|
1612
1630
|
data[idata++] = ch;
|
|
1613
1631
|
itext += ch > 0xffff ? 2 : 1;
|
|
@@ -1951,6 +1969,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1951
1969
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
|
1952
1970
|
this._coreService.decPrivateModes.bracketedPasteMode = true;
|
|
1953
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;
|
|
1954
1975
|
}
|
|
1955
1976
|
}
|
|
1956
1977
|
return true;
|
|
@@ -2179,6 +2200,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
2179
2200
|
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
|
2180
2201
|
this._coreService.decPrivateModes.bracketedPasteMode = false;
|
|
2181
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;
|
|
2182
2207
|
}
|
|
2183
2208
|
}
|
|
2184
2209
|
return true;
|
|
@@ -2273,6 +2298,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
2273
2298
|
if (p === 1048) return f(p, V.SET); // xterm always returns SET here
|
|
2274
2299
|
if (p === 47 || p === 1047 || p === 1049) return f(p, b2v(active === alt));
|
|
2275
2300
|
if (p === 2004) return f(p, b2v(dm.bracketedPasteMode));
|
|
2301
|
+
if (p === 2026) return f(p, b2v(dm.synchronizedOutput));
|
|
2276
2302
|
return f(p, V.NOT_RECOGNIZED);
|
|
2277
2303
|
}
|
|
2278
2304
|
|
package/src/common/TaskQueue.ts
CHANGED
|
@@ -74,14 +74,14 @@ abstract class TaskQueue implements ITaskQueue {
|
|
|
74
74
|
let lastDeadlineRemaining = deadline.timeRemaining();
|
|
75
75
|
let deadlineRemaining = 0;
|
|
76
76
|
while (this._i < this._tasks.length) {
|
|
77
|
-
taskDuration =
|
|
77
|
+
taskDuration = performance.now();
|
|
78
78
|
if (!this._tasks[this._i]()) {
|
|
79
79
|
this._i++;
|
|
80
80
|
}
|
|
81
|
-
// other than performance.now,
|
|
82
|
-
// this is not an issue here as a clock change during a short running task is very
|
|
83
|
-
// in case it still happened and leads to negative duration, simply assume 1 msec
|
|
84
|
-
taskDuration = Math.max(1,
|
|
81
|
+
// other than performance.now, performance.now might not be stable (changes on wall clock
|
|
82
|
+
// changes), this is not an issue here as a clock change during a short running task is very
|
|
83
|
+
// unlikely in case it still happened and leads to negative duration, simply assume 1 msec
|
|
84
|
+
taskDuration = Math.max(1, performance.now() - taskDuration);
|
|
85
85
|
longestTask = Math.max(taskDuration, longestTask);
|
|
86
86
|
// Guess the following task will take a similar time to the longest task in this batch, allow
|
|
87
87
|
// additional room to try avoid exceeding the deadline
|
|
@@ -116,9 +116,9 @@ export class PriorityTaskQueue extends TaskQueue {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
private _createDeadline(duration: number): ITaskDeadline {
|
|
119
|
-
const end =
|
|
119
|
+
const end = performance.now() + duration;
|
|
120
120
|
return {
|
|
121
|
-
timeRemaining: () => Math.max(0, end -
|
|
121
|
+
timeRemaining: () => Math.max(0, end - performance.now())
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
}
|
package/src/common/Types.ts
CHANGED
|
@@ -117,12 +117,6 @@ export function evaluateKeyboardEvent(
|
|
|
117
117
|
}
|
|
118
118
|
if (modifiers) {
|
|
119
119
|
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
|
|
120
|
-
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
|
121
|
-
// http://unix.stackexchange.com/a/108106
|
|
122
|
-
// macOS uses different escape sequences than linux
|
|
123
|
-
if (result.key === C0.ESC + '[1;3D') {
|
|
124
|
-
result.key = C0.ESC + (isMac ? 'b' : '[1;5D');
|
|
125
|
-
}
|
|
126
120
|
} else if (applicationCursorMode) {
|
|
127
121
|
result.key = C0.ESC + 'OD';
|
|
128
122
|
} else {
|
|
@@ -136,12 +130,6 @@ export function evaluateKeyboardEvent(
|
|
|
136
130
|
}
|
|
137
131
|
if (modifiers) {
|
|
138
132
|
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
|
|
139
|
-
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
|
140
|
-
// http://unix.stackexchange.com/a/108106
|
|
141
|
-
// macOS uses different escape sequences than linux
|
|
142
|
-
if (result.key === C0.ESC + '[1;3C') {
|
|
143
|
-
result.key = C0.ESC + (isMac ? 'f' : '[1;5C');
|
|
144
|
-
}
|
|
145
133
|
} else if (applicationCursorMode) {
|
|
146
134
|
result.key = C0.ESC + 'OC';
|
|
147
135
|
} else {
|
|
@@ -155,12 +143,6 @@ export function evaluateKeyboardEvent(
|
|
|
155
143
|
}
|
|
156
144
|
if (modifiers) {
|
|
157
145
|
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
|
|
158
|
-
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
|
159
|
-
// http://unix.stackexchange.com/a/108106
|
|
160
|
-
// macOS uses different escape sequences than linux
|
|
161
|
-
if (!isMac && result.key === C0.ESC + '[1;3A') {
|
|
162
|
-
result.key = C0.ESC + '[1;5A';
|
|
163
|
-
}
|
|
164
146
|
} else if (applicationCursorMode) {
|
|
165
147
|
result.key = C0.ESC + 'OA';
|
|
166
148
|
} else {
|
|
@@ -174,12 +156,6 @@ export function evaluateKeyboardEvent(
|
|
|
174
156
|
}
|
|
175
157
|
if (modifiers) {
|
|
176
158
|
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
|
|
177
|
-
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
|
178
|
-
// http://unix.stackexchange.com/a/108106
|
|
179
|
-
// macOS uses different escape sequences than linux
|
|
180
|
-
if (!isMac && result.key === C0.ESC + '[1;3B') {
|
|
181
|
-
result.key = C0.ESC + '[1;5B';
|
|
182
|
-
}
|
|
183
159
|
} else if (applicationCursorMode) {
|
|
184
160
|
result.key = C0.ESC + 'OB';
|
|
185
161
|
} else {
|
|
@@ -137,7 +137,7 @@ export class WriteBuffer extends Disposable {
|
|
|
137
137
|
* effectively lowering the redrawing needs, schematically:
|
|
138
138
|
*
|
|
139
139
|
* macroTask _innerWrite:
|
|
140
|
-
* if (
|
|
140
|
+
* if (performance.now() - (lastTime | 0) < WRITE_TIMEOUT_MS):
|
|
141
141
|
* schedule microTask _innerWrite(lastTime)
|
|
142
142
|
* else:
|
|
143
143
|
* schedule macroTask _innerWrite(0)
|
|
@@ -158,7 +158,7 @@ export class WriteBuffer extends Disposable {
|
|
|
158
158
|
* Note, for pure sync code `lastTime` and `promiseResult` have no meaning.
|
|
159
159
|
*/
|
|
160
160
|
protected _innerWrite(lastTime: number = 0, promiseResult: boolean = true): void {
|
|
161
|
-
const startTime = lastTime ||
|
|
161
|
+
const startTime = lastTime || performance.now();
|
|
162
162
|
while (this._writeBuffer.length > this._bufferOffset) {
|
|
163
163
|
const data = this._writeBuffer[this._bufferOffset];
|
|
164
164
|
const result = this._action(data, promiseResult);
|
|
@@ -186,7 +186,7 @@ export class WriteBuffer extends Disposable {
|
|
|
186
186
|
* responsibility to slice hard work), but we can at least schedule a screen update as we
|
|
187
187
|
* gain control.
|
|
188
188
|
*/
|
|
189
|
-
const continuation: (r: boolean) => void = (r: boolean) =>
|
|
189
|
+
const continuation: (r: boolean) => void = (r: boolean) => performance.now() - startTime >= WRITE_TIMEOUT_MS
|
|
190
190
|
? setTimeout(() => this._innerWrite(0, r))
|
|
191
191
|
: this._innerWrite(startTime, r);
|
|
192
192
|
|
|
@@ -202,7 +202,8 @@ export class WriteBuffer extends Disposable {
|
|
|
202
202
|
* throughput by eval'ing `startTime` upfront pulling at least one more chunk into the
|
|
203
203
|
* current microtask queue (executed before setTimeout).
|
|
204
204
|
*/
|
|
205
|
-
// const continuation: (r: boolean) => void =
|
|
205
|
+
// const continuation: (r: boolean) => void = performance.now() - startTime >=
|
|
206
|
+
// WRITE_TIMEOUT_MS
|
|
206
207
|
// ? r => setTimeout(() => this._innerWrite(0, r))
|
|
207
208
|
// : r => this._innerWrite(startTime, r);
|
|
208
209
|
|
|
@@ -222,7 +223,7 @@ export class WriteBuffer extends Disposable {
|
|
|
222
223
|
this._bufferOffset++;
|
|
223
224
|
this._pendingData -= data.length;
|
|
224
225
|
|
|
225
|
-
if (
|
|
226
|
+
if (performance.now() - startTime >= WRITE_TIMEOUT_MS) {
|
|
226
227
|
break;
|
|
227
228
|
}
|
|
228
229
|
}
|
|
@@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|
|
7
7
|
import { IAttributeData, IBufferLine } from 'common/Types';
|
|
8
8
|
import { BufferSet } from 'common/buffer/BufferSet';
|
|
9
9
|
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
|
10
|
-
import { IBufferService, IOptionsService } from 'common/services/Services';
|
|
10
|
+
import { IBufferService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services';
|
|
11
11
|
import { Emitter } from 'vs/base/common/event';
|
|
12
12
|
|
|
13
13
|
export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
|
|
@@ -22,7 +22,7 @@ export class BufferService extends Disposable implements IBufferService {
|
|
|
22
22
|
/** Whether the user is scrolling (locks the scroll position) */
|
|
23
23
|
public isUserScrolling: boolean = false;
|
|
24
24
|
|
|
25
|
-
private readonly _onResize = this._register(new Emitter<
|
|
25
|
+
private readonly _onResize = this._register(new Emitter<IBufferResizeEvent>());
|
|
26
26
|
public readonly onResize = this._onResize.event;
|
|
27
27
|
private readonly _onScroll = this._register(new Emitter<number>());
|
|
28
28
|
public readonly onScroll = this._onScroll.event;
|
|
@@ -37,15 +37,18 @@ export class BufferService extends Disposable implements IBufferService {
|
|
|
37
37
|
this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS);
|
|
38
38
|
this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
|
|
39
39
|
this.buffers = this._register(new BufferSet(optionsService, this));
|
|
40
|
+
this._register(this.buffers.onBufferActivate(e => {
|
|
41
|
+
this._onScroll.fire(e.activeBuffer.ydisp);
|
|
42
|
+
}));
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
public resize(cols: number, rows: number): void {
|
|
46
|
+
const colsChanged = this.cols !== cols;
|
|
47
|
+
const rowsChanged = this.rows !== rows;
|
|
43
48
|
this.cols = cols;
|
|
44
49
|
this.rows = rows;
|
|
45
50
|
this.buffers.resize(cols, rows);
|
|
46
|
-
|
|
47
|
-
// event
|
|
48
|
-
this._onResize.fire({ cols, rows });
|
|
51
|
+
this._onResize.fire({ cols, rows, colsChanged, rowsChanged });
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
public reset(): void {
|