@xterm/xterm 5.6.0-beta.99 → 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 +8 -8
- 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/buffer/Buffer.ts +1 -74
- 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,16 +87,15 @@
|
|
|
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",
|
|
98
|
-
"node-pty": "
|
|
98
|
+
"node-pty": "1.1.0-beta19",
|
|
99
99
|
"nyc": "^15.1.0",
|
|
100
100
|
"source-map-loader": "^3.0.0",
|
|
101
101
|
"source-map-support": "^0.5.20",
|
|
@@ -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
|
@@ -320,37 +320,7 @@ export class Buffer implements IBuffer {
|
|
|
320
320
|
if (toRemove.length > 0) {
|
|
321
321
|
const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
|
|
322
322
|
reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
|
|
323
|
-
|
|
324
|
-
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
|
325
|
-
// when reflowing larger is to insert empty lines at the bottom of the buffer as when lines
|
|
326
|
-
// unwrap conpty's view cannot pull scrollback down, so it adds empty lines at the end.
|
|
327
|
-
let removedInViewport = 0;
|
|
328
|
-
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
|
329
|
-
if (isWindowsMode) {
|
|
330
|
-
for (let i = (toRemove.length / 2) - 1; i >= 0; i--) {
|
|
331
|
-
if (toRemove[i * 2 + 0] > this.ybase + removedInViewport) {
|
|
332
|
-
removedInViewport += toRemove[i * 2 + 1];
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
323
|
this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);
|
|
338
|
-
|
|
339
|
-
// Apply empty lines for any removed in viewport for conpty.
|
|
340
|
-
if (isWindowsMode) {
|
|
341
|
-
if (removedInViewport > 0) {
|
|
342
|
-
for (let i = 0; i < removedInViewport; i++) {
|
|
343
|
-
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
|
344
|
-
// view of the world. Once a line enters scrollback for conpty it remains there
|
|
345
|
-
this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR_DATA)));
|
|
346
|
-
}
|
|
347
|
-
if (this.ybase === this.ydisp) {
|
|
348
|
-
this.ydisp += removedInViewport;
|
|
349
|
-
}
|
|
350
|
-
this.ybase += removedInViewport;
|
|
351
|
-
this.y -= removedInViewport;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
324
|
}
|
|
355
325
|
}
|
|
356
326
|
|
|
@@ -382,7 +352,7 @@ export class Buffer implements IBuffer {
|
|
|
382
352
|
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
|
|
383
353
|
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
|
|
384
354
|
// batched up and only committed once
|
|
385
|
-
const toInsert
|
|
355
|
+
const toInsert = [];
|
|
386
356
|
let countToInsert = 0;
|
|
387
357
|
// Go backwards as many lines may be trimmed and this will avoid considering them
|
|
388
358
|
for (let y = this.lines.length - 1; y >= 0; y--) {
|
|
@@ -497,20 +467,6 @@ export class Buffer implements IBuffer {
|
|
|
497
467
|
this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);
|
|
498
468
|
}
|
|
499
469
|
|
|
500
|
-
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
|
501
|
-
// when reflowing smaller is to reflow all lines inside the viewport, and removing empty or
|
|
502
|
-
// whitespace only lines from the bottom, until non-whitespace is hit in order to prevent
|
|
503
|
-
// content from being pushed into the scrollback.
|
|
504
|
-
let addedInViewport = 0;
|
|
505
|
-
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
|
506
|
-
if (isWindowsMode) {
|
|
507
|
-
for (let i = toInsert.length - 1; i >= 0; i--) {
|
|
508
|
-
if (toInsert[i].start > this.ybase + addedInViewport) {
|
|
509
|
-
addedInViewport += toInsert[i].newLines.length;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
470
|
// Rearrange lines in the buffer if there are any insertions, this is done at the end rather
|
|
515
471
|
// than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
|
|
516
472
|
// costly calls to CircularList.splice.
|
|
@@ -564,35 +520,6 @@ export class Buffer implements IBuffer {
|
|
|
564
520
|
this.lines.onTrimEmitter.fire(amountToTrim);
|
|
565
521
|
}
|
|
566
522
|
}
|
|
567
|
-
|
|
568
|
-
// Apply empty lines to remove calculated earlier for conpty.
|
|
569
|
-
if (isWindowsMode) {
|
|
570
|
-
if (addedInViewport > 0) {
|
|
571
|
-
let emptyLinesAtBottom = 0;
|
|
572
|
-
for (let i = this.lines.length - 1; i >= this.ybase + this.y; i--) {
|
|
573
|
-
const line = this.lines.get(i) as BufferLine;
|
|
574
|
-
if (line.isWrapped || line.getTrimmedLength() > 0) {
|
|
575
|
-
break;
|
|
576
|
-
}
|
|
577
|
-
emptyLinesAtBottom++;
|
|
578
|
-
}
|
|
579
|
-
const emptyLinesToRemove = Math.min(addedInViewport, emptyLinesAtBottom);
|
|
580
|
-
if (emptyLinesToRemove > 0) {
|
|
581
|
-
for (let i = 0; i < emptyLinesToRemove; i++) {
|
|
582
|
-
this.lines.pop();
|
|
583
|
-
}
|
|
584
|
-
if (this.ybase === this.ydisp) {
|
|
585
|
-
this.ydisp -= emptyLinesToRemove;
|
|
586
|
-
}
|
|
587
|
-
this.ybase -= emptyLinesToRemove;
|
|
588
|
-
this.y += emptyLinesToRemove;
|
|
589
|
-
this.lines.onDeleteEmitter.fire({
|
|
590
|
-
index: this.lines.length - emptyLinesToRemove,
|
|
591
|
-
amount: emptyLinesToRemove
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
523
|
}
|
|
597
524
|
|
|
598
525
|
/**
|