@xterm/xterm 5.6.0-beta.140 → 5.6.0-beta.142

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@xterm/xterm",
3
3
  "description": "Full xterm terminal, in your browser",
4
- "version": "5.6.0-beta.140",
4
+ "version": "5.6.0-beta.142",
5
5
  "main": "lib/xterm.js",
6
6
  "module": "lib/xterm.mjs",
7
7
  "style": "css/xterm.css",
@@ -75,7 +75,6 @@
75
75
  "@types/deep-equal": "^1.0.1",
76
76
  "@types/express": "4",
77
77
  "@types/express-ws": "^3.0.1",
78
- "@types/glob": "^7.2.0",
79
78
  "@types/jsdom": "^16.2.13",
80
79
  "@types/mocha": "^9.0.0",
81
80
  "@types/node": "^18.16.0",
@@ -93,7 +92,6 @@
93
92
  "eslint-plugin-jsdoc": "^46.9.1",
94
93
  "express": "^4.19.2",
95
94
  "express-ws": "^5.0.2",
96
- "glob": "^7.2.0",
97
95
  "jsdom": "^18.0.1",
98
96
  "mocha": "^10.1.0",
99
97
  "mustache": "^4.2.0",
@@ -109,5 +107,5 @@
109
107
  "ws": "^8.2.3",
110
108
  "xterm-benchmark": "^0.3.1"
111
109
  },
112
- "commit": "80c31640706f547275658b59c9b0b912ec183370"
110
+ "commit": "d1c50c14e3ce3df6c078a06ebc6cf49d58b28326"
113
111
  }
@@ -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
+ }
@@ -1969,6 +1969,9 @@ export class InputHandler extends Disposable implements IInputHandler {
1969
1969
  case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
1970
1970
  this._coreService.decPrivateModes.bracketedPasteMode = true;
1971
1971
  break;
1972
+ case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)
1973
+ this._coreService.decPrivateModes.synchronizedOutput = true;
1974
+ break;
1972
1975
  }
1973
1976
  }
1974
1977
  return true;
@@ -2197,6 +2200,10 @@ export class InputHandler extends Disposable implements IInputHandler {
2197
2200
  case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
2198
2201
  this._coreService.decPrivateModes.bracketedPasteMode = false;
2199
2202
  break;
2203
+ case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)
2204
+ this._coreService.decPrivateModes.synchronizedOutput = false;
2205
+ this._onRequestRefreshRows.fire(undefined);
2206
+ break;
2200
2207
  }
2201
2208
  }
2202
2209
  return true;
@@ -2291,6 +2298,7 @@ export class InputHandler extends Disposable implements IInputHandler {
2291
2298
  if (p === 1048) return f(p, V.SET); // xterm always returns SET here
2292
2299
  if (p === 47 || p === 1047 || p === 1049) return f(p, b2v(active === alt));
2293
2300
  if (p === 2004) return f(p, b2v(dm.bracketedPasteMode));
2301
+ if (p === 2026) return f(p, b2v(dm.synchronizedOutput));
2294
2302
  return f(p, V.NOT_RECOGNIZED);
2295
2303
  }
2296
2304
 
@@ -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
 
@@ -22,6 +22,7 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
22
22
  origin: false,
23
23
  reverseWraparound: false,
24
24
  sendFocus: false,
25
+ synchronizedOutput: false,
25
26
  wraparound: true // defaults: xterm - true, vt100 - false
26
27
  });
27
28
 
@@ -1968,6 +1968,13 @@ declare module '@xterm/xterm' {
1968
1968
  * Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h`
1969
1969
  */
1970
1970
  readonly sendFocusMode: boolean;
1971
+ /**
1972
+ * Synchronized Output Mode: `CSI ? 2 0 2 6 h`
1973
+ *
1974
+ * When enabled, output is buffered and only rendered when the mode is
1975
+ * disabled, allowing for atomic screen updates without tearing.
1976
+ */
1977
+ readonly synchronizedOutputMode: boolean;
1971
1978
  /**
1972
1979
  * Auto-Wrap Mode (DECAWM): `CSI ? 7 h`
1973
1980
  */