@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/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": "5.6.0-beta.98",
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": "^0.23.0",
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": "2042bb85023714e55c0c2e986b5000e33b17c414"
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.innerText = '\u00a0';
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(this._onScroll.event(() => this._selectionService!.refresh()));
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 = Date.now();
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 = Date.now();
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
@@ -93,8 +93,13 @@ export class Viewport extends Disposable {
93
93
  ].join('\n');
94
94
  }));
95
95
 
96
- this._register(this._bufferService.onResize(() => this._queueSync()));
97
- this._register(this._bufferService.buffers.onBufferActivate(() => this._queueSync()));
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
- private _queueSync(ydisp?: number): void {
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 copmonent (input/buffer), then stop animating smooth
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 optionsService: 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 coreBrowserService: 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), coreBrowserService);
70
+ this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end), this._coreBrowserService);
65
71
  this._register(this._renderDebouncer);
66
72
 
67
- this._register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));
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(optionsService.onOptionChange(() => this._handleOptionsChanged()));
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(optionsService.onMultipleOptionChange([
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(optionsService.onMultipleOptionChange([
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(coreBrowserService.window, screenElement);
107
- this._register(coreBrowserService.onWindowChange((w) => this._registerIntersectionObserver(w, screenElement)));
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('')}"`}`, typeof data === 'string'
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
- j = this._bufferService.rows;
1224
- this._dirtyRowTracker.markDirty(j - 1);
1225
- while (j--) {
1226
- this._resetBufferLine(j, respectProtect);
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
 
@@ -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 = Date.now();
77
+ taskDuration = performance.now();
78
78
  if (!this._tasks[this._i]()) {
79
79
  this._i++;
80
80
  }
81
- // other than performance.now, Date.now might not be stable (changes on wall clock changes),
82
- // this is not an issue here as a clock change during a short running task is very unlikely
83
- // in case it still happened and leads to negative duration, simply assume 1 msec
84
- taskDuration = Math.max(1, Date.now() - taskDuration);
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 = Date.now() + duration;
119
+ const end = performance.now() + duration;
120
120
  return {
121
- timeRemaining: () => Math.max(0, end - Date.now())
121
+ timeRemaining: () => Math.max(0, end - performance.now())
122
122
  };
123
123
  }
124
124
  }
@@ -273,6 +273,7 @@ export interface IDecPrivateModes {
273
273
  origin: boolean;
274
274
  reverseWraparound: boolean;
275
275
  sendFocus: boolean;
276
+ synchronizedOutput: boolean;
276
277
  wraparound: boolean; // defaults: xterm - true, vt100 - false
277
278
  }
278
279
 
@@ -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 (Date.now() - (lastTime | 0) < WRITE_TIMEOUT_MS):
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 || Date.now();
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) => Date.now() - startTime >= WRITE_TIMEOUT_MS
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 = Date.now() - startTime >= WRITE_TIMEOUT_MS
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 (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
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<{ cols: number, rows: number }>());
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
- // TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward
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 {