@xterm/xterm 6.1.0-beta.9 → 6.1.0-beta.91

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.
Files changed (36) hide show
  1. package/README.md +27 -28
  2. package/css/xterm.css +5 -11
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +17 -17
  6. package/lib/xterm.mjs.map +4 -4
  7. package/package.json +26 -19
  8. package/src/browser/AccessibilityManager.ts +4 -1
  9. package/src/browser/CoreBrowserTerminal.ts +49 -8
  10. package/src/browser/OscLinkProvider.ts +1 -1
  11. package/src/browser/Types.ts +4 -1
  12. package/src/browser/Viewport.ts +16 -1
  13. package/src/browser/input/CompositionHelper.ts +10 -1
  14. package/src/browser/public/Terminal.ts +6 -5
  15. package/src/browser/renderer/dom/DomRenderer.ts +74 -3
  16. package/src/browser/renderer/dom/WidthCache.ts +54 -52
  17. package/src/browser/renderer/shared/Constants.ts +7 -0
  18. package/src/browser/renderer/shared/Types.ts +5 -0
  19. package/src/browser/services/MouseService.ts +2 -1
  20. package/src/browser/services/RenderService.ts +9 -5
  21. package/src/browser/services/Services.ts +1 -1
  22. package/src/common/Color.ts +8 -0
  23. package/src/common/CoreTerminal.ts +2 -1
  24. package/src/common/InputHandler.ts +52 -9
  25. package/src/common/Platform.ts +4 -1
  26. package/src/common/Types.ts +1 -1
  27. package/src/common/Version.ts +9 -0
  28. package/src/common/buffer/Buffer.ts +4 -0
  29. package/src/common/buffer/Types.ts +4 -0
  30. package/src/common/data/Charsets.ts +1 -1
  31. package/src/common/input/Keyboard.ts +7 -6
  32. package/src/common/services/CharsetService.ts +4 -0
  33. package/src/common/services/DecorationService.ts +17 -3
  34. package/src/common/services/OptionsService.ts +2 -2
  35. package/src/common/services/Services.ts +6 -1
  36. package/typings/xterm.d.ts +165 -34
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": "6.1.0-beta.9",
4
+ "version": "6.1.0-beta.91",
5
5
  "main": "lib/xterm.js",
6
6
  "module": "lib/xterm.mjs",
7
7
  "style": "css/xterm.css",
@@ -27,8 +27,11 @@
27
27
  "xterm"
28
28
  ],
29
29
  "scripts": {
30
- "setup": "npm run build",
30
+ "presetup": "npm run build",
31
+ "setup": "npm run esbuild",
32
+ "postsetup": "npm run esbuild-demo-server",
31
33
  "start": "node demo/start",
34
+ "dev": "concurrently -k -p [{name}] -n tsc,esbuild,esbuild-demo-client,esbuild-demo-server,server -c blue,yellow,cyan,green,magenta \"npm:tsc-watch\" \"npm:esbuild-watch\" \"npm:esbuild-demo-client-watch\" \"npm:esbuild-demo-server-watch\" \"npm:start\"",
32
35
  "build": "npm run tsc",
33
36
  "watch": "npm run tsc-watch",
34
37
  "tsc": "tsc -b ./tsconfig.all.json",
@@ -38,13 +41,15 @@
38
41
  "esbuild-package": "node bin/esbuild_all.mjs --prod",
39
42
  "esbuild-package-watch": "node bin/esbuild_all.mjs --prod --watch",
40
43
  "esbuild-package-headless-only": "node bin/esbuild.mjs --prod --headless",
41
- "esbuild-demo": "node bin/esbuild.mjs --demo-client",
42
- "esbuild-demo-watch": "node bin/esbuild.mjs --demo-client --watch",
44
+ "esbuild-demo-client": "node bin/esbuild.mjs --demo-client",
45
+ "esbuild-demo-client-watch": "node bin/esbuild.mjs --demo-client --watch",
46
+ "esbuild-demo-server": "node bin/esbuild.mjs --demo-server",
47
+ "esbuild-demo-server-watch": "node bin/esbuild.mjs --demo-server --watch",
43
48
  "test": "npm run test-unit",
44
49
  "posttest": "npm run lint",
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/",
47
- "lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/",
50
+ "lint": "eslint --max-warnings 0 src/ addons/ demo/",
51
+ "lint-fix": "eslint --fix src/ addons/ demo/",
52
+ "lint-api": "eslint --config eslint.config.typings.mjs --max-warnings 0 typings/",
48
53
  "test-unit": "node ./bin/test_unit.js",
49
54
  "test-unit-coverage": "node ./bin/test_unit.js --coverage",
50
55
  "test-unit-dev": "cross-env NODE_PATH='./out' mocha",
@@ -68,44 +73,46 @@
68
73
  },
69
74
  "devDependencies": {
70
75
  "@lunapaint/png-codec": "^0.2.0",
71
- "@playwright/test": "^1.37.1",
72
- "@stylistic/eslint-plugin": "^2.3.0",
76
+ "@playwright/test": "^1.57.0",
77
+ "@stylistic/eslint-plugin": "^4.4.1",
73
78
  "@types/chai": "^4.2.22",
74
79
  "@types/debug": "^4.1.7",
75
80
  "@types/deep-equal": "^1.0.1",
76
81
  "@types/express": "4",
77
82
  "@types/express-ws": "^3.0.1",
78
- "@types/jsdom": "^16.2.13",
83
+ "@types/jsdom": "^27.0.0",
79
84
  "@types/mocha": "^9.0.0",
80
- "@types/node": "^18.16.0",
85
+ "@types/node": "^22.19.3",
81
86
  "@types/trusted-types": "^1.0.6",
82
87
  "@types/utf8": "^3.0.0",
83
88
  "@types/webpack": "^5.28.0",
84
89
  "@types/ws": "^8.2.0",
85
- "@typescript-eslint/eslint-plugin": "^6.2.00",
86
- "@typescript-eslint/parser": "^6.2.00",
90
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
91
+ "@typescript-eslint/parser": "^8.50.1",
87
92
  "chai": "^4.3.4",
93
+ "concurrently": "^9.1.2",
88
94
  "cross-env": "^7.0.3",
89
95
  "deep-equal": "^2.0.5",
90
96
  "esbuild": "~0.25.2",
91
- "eslint": "^8.56.0",
92
- "eslint-plugin-jsdoc": "^46.9.1",
97
+ "eslint": "^9.39.2",
98
+ "eslint-plugin-jsdoc": "^50.8.0",
93
99
  "express": "^4.19.2",
94
100
  "express-ws": "^5.0.2",
95
- "jsdom": "^18.0.1",
101
+ "jsdom": "^27.3.0",
96
102
  "mocha": "^10.1.0",
97
103
  "mustache": "^4.2.0",
98
104
  "node-pty": "1.1.0-beta19",
99
- "nyc": "^15.1.0",
105
+ "nyc": "^17.1.0",
100
106
  "source-map-loader": "^3.0.0",
101
107
  "source-map-support": "^0.5.20",
102
108
  "ts-loader": "^9.3.1",
103
- "typescript": "5.5",
109
+ "typescript": "^5.9.3",
110
+ "typescript-eslint": "^8.50.1",
104
111
  "utf8": "^3.0.0",
105
112
  "webpack": "^5.61.0",
106
113
  "webpack-cli": "^4.9.1",
107
114
  "ws": "^8.2.3",
108
115
  "xterm-benchmark": "^0.3.1"
109
116
  },
110
- "commit": "269d8c20aed41b71c743690b1fe70d82dbd420d6"
117
+ "commit": "94a264679e6bd51b7021ca59e3b2e1e80d02c633"
111
118
  }
@@ -151,7 +151,7 @@ export class AccessibilityManager extends Disposable {
151
151
  if (char === '\n') {
152
152
  this._liveRegionLineCount++;
153
153
  if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {
154
- this._liveRegion.textContent += Strings.tooMuchOutput.get();
154
+ this._liveRegion.textContent = Strings.tooMuchOutput.get();
155
155
  }
156
156
  }
157
157
  }
@@ -203,6 +203,9 @@ export class AccessibilityManager extends Disposable {
203
203
  if (this._charsToAnnounce.length === 0) {
204
204
  return;
205
205
  }
206
+ if (this._liveRegion.textContent === Strings.tooMuchOutput.get()) {
207
+ this._clearLiveRegion();
208
+ }
206
209
  this._liveRegion.textContent += this._charsToAnnounce;
207
210
  this._charsToAnnounce = '';
208
211
  }
@@ -21,7 +21,7 @@
21
21
  * http://linux.die.net/man/7/urxvt
22
22
  */
23
23
 
24
- import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm';
24
+ import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
25
25
  import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';
26
26
  import * as Strings from 'browser/LocalizableStrings';
27
27
  import { OscLinkProvider } from 'browser/OscLinkProvider';
@@ -126,8 +126,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
126
126
  public readonly onCursorMove = this._onCursorMove.event;
127
127
  private readonly _onKey = this._register(new Emitter<{ key: string, domEvent: KeyboardEvent }>());
128
128
  public readonly onKey = this._onKey.event;
129
- private readonly _onRender = this._register(new Emitter<{ start: number, end: number }>());
130
- public readonly onRender = this._onRender.event;
131
129
  private readonly _onSelectionChange = this._register(new Emitter<void>());
132
130
  public readonly onSelectionChange = this._onSelectionChange.event;
133
131
  private readonly _onTitleChange = this._register(new Emitter<string>());
@@ -145,6 +143,26 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
145
143
  public get onA11yTab(): Event<number> { return this._onA11yTabEmitter.event; }
146
144
  private _onWillOpen = this._register(new Emitter<HTMLElement>());
147
145
  public get onWillOpen(): Event<HTMLElement> { return this._onWillOpen.event; }
146
+ private readonly _onDimensionsChange = this._register(new Emitter<IRenderDimensionsApi>());
147
+ public readonly onDimensionsChange = this._onDimensionsChange.event;
148
+
149
+ public get dimensions(): IRenderDimensionsApi | undefined {
150
+ if (!this._renderService) {
151
+ return undefined;
152
+ }
153
+ const dimensions = this._renderService.dimensions;
154
+ return {
155
+ css: {
156
+ canvas: { ...dimensions.css.canvas },
157
+ cell: { ...dimensions.css.cell }
158
+ },
159
+ device: {
160
+ canvas: { ...dimensions.device.canvas },
161
+ cell: { ...dimensions.device.cell },
162
+ char: { ...dimensions.device.char }
163
+ }
164
+ };
165
+ }
148
166
 
149
167
  constructor(
150
168
  options: Partial<ITerminalOptions> = {}
@@ -418,6 +436,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
418
436
  this.element.dir = 'ltr'; // xterm.css assumes LTR
419
437
  this.element.classList.add('terminal');
420
438
  this.element.classList.add('xterm');
439
+ this.element.classList.toggle('allow-transparency', this.options.allowTransparency);
440
+ this._register(this.optionsService.onSpecificOptionChange('allowTransparency', value => this.element!.classList.toggle('allow-transparency', value)));
421
441
  parent.appendChild(this.element);
422
442
 
423
443
  // Performance: Use a document fragment to build the terminal
@@ -478,6 +498,17 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
478
498
  this._renderService = this._register(this._instantiationService.createInstance(RenderService, this.rows, this.screenElement));
479
499
  this._instantiationService.setService(IRenderService, this._renderService);
480
500
  this._register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
501
+ this._register(this._renderService.onDimensionsChange(e => this._onDimensionsChange.fire({
502
+ css: {
503
+ canvas: { ...e.css.canvas },
504
+ cell: { ...e.css.cell }
505
+ },
506
+ device: {
507
+ canvas: { ...e.device.canvas },
508
+ cell: { ...e.device.cell },
509
+ char: { ...e.device.char }
510
+ }
511
+ })));
481
512
  this.onResize(e => this._renderService!.resize(e.cols, e.rows));
482
513
 
483
514
  this._compositionView = this._document.createElement('div');
@@ -605,8 +636,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
605
636
 
606
637
  // send event to CoreMouseService
607
638
  function sendEvent(ev: MouseEvent | WheelEvent): boolean {
608
- // get mouse coordinates
609
- const pos = self._mouseService!.getMouseReportCoords(ev, self.screenElement!);
639
+ // Get mouse coordinates
640
+ const pos = self._mouseService?.getMouseReportCoords(ev, self.screenElement!);
610
641
  if (!pos) {
611
642
  return false;
612
643
  }
@@ -773,6 +804,16 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
773
804
  // force initial onProtocolChange so we dont miss early mouse requests
774
805
  this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol;
775
806
 
807
+ // Ensure document-level listeners are removed on dispose
808
+ this._register(toDisposable(() => {
809
+ if (requestedEvents.mouseup) {
810
+ this._document!.removeEventListener('mouseup', requestedEvents.mouseup);
811
+ }
812
+ if (requestedEvents.mousedrag) {
813
+ this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);
814
+ }
815
+ }));
816
+
776
817
  /**
777
818
  * "Always on" event listeners.
778
819
  */
@@ -849,8 +890,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
849
890
  * @param start The row to start from (between 0 and this.rows - 1).
850
891
  * @param end The row to end at (between start and this.rows - 1).
851
892
  */
852
- public refresh(start: number, end: number): void {
853
- this._renderService?.refreshRows(start, end);
893
+ public refresh(start: number, end: number, sync: boolean = false): void {
894
+ this._renderService?.refreshRows(start, end, sync);
854
895
  }
855
896
 
856
897
  /**
@@ -1283,7 +1324,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
1283
1324
  this._customKeyEventHandler = customKeyEventHandler;
1284
1325
 
1285
1326
  // do a full screen refresh
1286
- this.refresh(0, this.rows - 1);
1327
+ this.refresh(0, this.rows - 1, true);
1287
1328
  }
1288
1329
 
1289
1330
  public clearTextureAtlas(): void {
@@ -75,7 +75,7 @@ export class OscLinkProvider implements ILinkProvider {
75
75
  if (!['http:', 'https:'].includes(parsed.protocol)) {
76
76
  ignoreLink = true;
77
77
  }
78
- } catch (e) {
78
+ } catch {
79
79
  // Ignore invalid URLs to prevent unexpected behaviors
80
80
  ignoreLink = true;
81
81
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types';
7
7
  import { IBuffer } from 'common/buffer/Types';
8
- import { IDisposable, Terminal as ITerminalApi } from '@xterm/xterm';
8
+ import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';
9
9
  import { channels, css } from 'common/Color';
10
10
  import type { Event } from 'vs/base/common/event';
11
11
 
@@ -21,8 +21,11 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
21
21
  linkifier: ILinkifier2 | undefined;
22
22
  options: Required<ITerminalOptions>;
23
23
 
24
+ readonly dimensions: IRenderDimensionsApi | undefined;
25
+
24
26
  onBlur: Event<void>;
25
27
  onFocus: Event<void>;
28
+ onDimensionsChange: Event<IRenderDimensionsApi>;
26
29
  onA11yChar: Event<string>;
27
30
  onA11yTab: Event<number>;
28
31
  onWillOpen: Event<HTMLElement>;
@@ -8,11 +8,12 @@ import { ViewportConstants } from 'browser/shared/Constants';
8
8
  import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
9
9
  import { IBufferService, ICoreMouseService, IOptionsService } from 'common/services/Services';
10
10
  import { CoreMouseEventType } from 'common/Types';
11
- import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
11
+ import { addDisposableListener, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
12
12
  import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
13
13
  import type { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
14
14
  import { Emitter, Event } from 'vs/base/common/event';
15
15
  import { Scrollable, ScrollbarVisibility, type ScrollEvent } from 'vs/base/common/scrollable';
16
+ import { Gesture, EventType as GestureEventType, type GestureEvent } from 'vs/base/browser/touch';
16
17
 
17
18
  export class Viewport extends Disposable {
18
19
 
@@ -71,6 +72,7 @@ export class Viewport extends Disposable {
71
72
 
72
73
  this._scrollableElement.setScrollDimensions({ height: 0, scrollHeight: 0 });
73
74
  this._register(Event.runAndSubscribe(themeService.onChangeColors, () => {
75
+ element.style.backgroundColor = themeService.colors.background.css;
74
76
  this._scrollableElement.getDomNode().style.backgroundColor = themeService.colors.background.css;
75
77
  }));
76
78
  element.appendChild(this._scrollableElement.getDomNode());
@@ -103,6 +105,10 @@ export class Viewport extends Disposable {
103
105
  this._register(this._bufferService.onScroll(() => this._sync()));
104
106
 
105
107
  this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
108
+
109
+ // Touch/gesture scrolling support
110
+ this._register(Gesture.addTarget(screenElement));
111
+ this._register(addDisposableListener(screenElement, GestureEventType.Change, (e: GestureEvent) => this._handleGestureChange(e)));
106
112
  }
107
113
 
108
114
  public scrollLines(disp: number): void {
@@ -189,4 +195,13 @@ export class Viewport extends Disposable {
189
195
  }
190
196
  this._isHandlingScroll = false;
191
197
  }
198
+
199
+ private _handleGestureChange(e: GestureEvent): void {
200
+ e.preventDefault();
201
+ e.stopPropagation();
202
+ const pos = this._scrollableElement.getScrollPosition();
203
+ this._scrollableElement.setScrollPosition({
204
+ scrollTop: pos.scrollTop - e.translationY
205
+ });
206
+ }
192
207
  }
@@ -41,6 +41,11 @@ export class CompositionHelper {
41
41
  */
42
42
  private _dataAlreadySent: string;
43
43
 
44
+ /**
45
+ * The pending textarea change timer, if any.
46
+ */
47
+ private _textareaChangeTimer?: number;
48
+
44
49
  constructor(
45
50
  private readonly _textarea: HTMLTextAreaElement,
46
51
  private readonly _compositionView: HTMLElement,
@@ -184,8 +189,12 @@ export class CompositionHelper {
184
189
  * IME is active.
185
190
  */
186
191
  private _handleAnyTextareaChanges(): void {
192
+ if (this._textareaChangeTimer) {
193
+ return;
194
+ }
187
195
  const oldValue = this._textarea.value;
188
- setTimeout(() => {
196
+ this._textareaChangeTimer = window.setTimeout(() => {
197
+ this._textareaChangeTimer = undefined;
189
198
  // Ignore if a composition has started since the timeout
190
199
  if (!this._isComposing) {
191
200
  const newValue = this._textarea.value;
@@ -12,7 +12,7 @@ import { AddonManager } from 'common/public/AddonManager';
12
12
  import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
13
13
  import { ParserApi } from 'common/public/ParserApi';
14
14
  import { UnicodeApi } from 'common/public/UnicodeApi';
15
- import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';
15
+ import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, IRenderDimensions, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';
16
16
  import type { Event } from 'vs/base/common/event';
17
17
 
18
18
  /**
@@ -80,6 +80,7 @@ export class Terminal extends Disposable implements ITerminalApi {
80
80
  public get onSelectionChange(): Event<void> { return this._core.onSelectionChange; }
81
81
  public get onTitleChange(): Event<string> { return this._core.onTitleChange; }
82
82
  public get onWriteParsed(): Event<void> { return this._core.onWriteParsed; }
83
+ public get onDimensionsChange(): Event<IRenderDimensions> { return this._core.onDimensionsChange; }
83
84
 
84
85
  public get element(): HTMLElement | undefined { return this._core.element; }
85
86
  public get parser(): IParser {
@@ -102,7 +103,6 @@ export class Terminal extends Disposable implements ITerminalApi {
102
103
  return this._buffer;
103
104
  }
104
105
  public get markers(): ReadonlyArray<IMarker> {
105
- this._checkProposedApi();
106
106
  return this._core.markers;
107
107
  }
108
108
  public get modes(): IModes {
@@ -123,10 +123,14 @@ export class Terminal extends Disposable implements ITerminalApi {
123
123
  originMode: m.origin,
124
124
  reverseWraparoundMode: m.reverseWraparound,
125
125
  sendFocusMode: m.sendFocus,
126
+ showCursor: !this._core.coreService.isCursorHidden,
126
127
  synchronizedOutputMode: m.synchronizedOutput,
127
128
  wraparoundMode: m.wraparound
128
129
  };
129
130
  }
131
+ public get dimensions(): IRenderDimensions | undefined {
132
+ return this._core.dimensions;
133
+ }
130
134
  public get options(): Required<ITerminalOptions> {
131
135
  return this._publicOptions;
132
136
  }
@@ -161,11 +165,9 @@ export class Terminal extends Disposable implements ITerminalApi {
161
165
  return this._core.registerLinkProvider(linkProvider);
162
166
  }
163
167
  public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
164
- this._checkProposedApi();
165
168
  return this._core.registerCharacterJoiner(handler);
166
169
  }
167
170
  public deregisterCharacterJoiner(joinerId: number): void {
168
- this._checkProposedApi();
169
171
  this._core.deregisterCharacterJoiner(joinerId);
170
172
  }
171
173
  public registerMarker(cursorYOffset: number = 0): IMarker {
@@ -173,7 +175,6 @@ export class Terminal extends Disposable implements ITerminalApi {
173
175
  return this._core.registerMarker(cursorYOffset);
174
176
  }
175
177
  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
176
- this._checkProposedApi();
177
178
  this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0);
178
179
  return this._core.registerDecoration(decorationOptions);
179
180
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory';
7
7
  import { WidthCache } from 'browser/renderer/dom/WidthCache';
8
- import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
8
+ import { INVERTED_DEFAULT_COLOR, RendererConstants } from 'browser/renderer/shared/Constants';
9
9
  import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
10
10
  import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
11
11
  import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
@@ -15,6 +15,7 @@ import { color } from 'common/Color';
15
15
  import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
16
16
  import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services';
17
17
  import { Emitter } from 'vs/base/common/event';
18
+ import { addDisposableListener } from 'vs/base/browser/dom';
18
19
 
19
20
 
20
21
  const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -23,6 +24,7 @@ const FG_CLASS_PREFIX = 'xterm-fg-';
23
24
  const BG_CLASS_PREFIX = 'xterm-bg-';
24
25
  const FOCUS_CLASS = 'xterm-focus';
25
26
  const SELECTION_CLASS = 'xterm-selection';
27
+ const CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle';
26
28
 
27
29
  let nextTerminalId = 1;
28
30
 
@@ -42,6 +44,7 @@ export class DomRenderer extends Disposable implements IRenderer {
42
44
  private _selectionContainer: HTMLElement;
43
45
  private _widthCache: WidthCache;
44
46
  private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
47
+ private _cursorBlinkStateManager: CursorBlinkStateManager;
45
48
 
46
49
  public dimensions: IRenderDimensions;
47
50
 
@@ -89,6 +92,10 @@ export class DomRenderer extends Disposable implements IRenderer {
89
92
  this._register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));
90
93
  this._register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));
91
94
 
95
+ this._cursorBlinkStateManager = new CursorBlinkStateManager(this._rowContainer, this._coreBrowserService);
96
+ this._register(addDisposableListener(this._document, 'mousedown', () => this._cursorBlinkStateManager.restartBlinkAnimation()));
97
+ this._register(toDisposable(() => this._cursorBlinkStateManager.dispose()));
98
+
92
99
  this._register(toDisposable(() => {
93
100
  this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
94
101
 
@@ -101,7 +108,7 @@ export class DomRenderer extends Disposable implements IRenderer {
101
108
  this._dimensionsStyleElement.remove();
102
109
  }));
103
110
 
104
- this._widthCache = new WidthCache(this._document, this._helperContainer);
111
+ this._widthCache = new WidthCache();
105
112
  this._widthCache.setFont(
106
113
  this._optionsService.rawOptions.fontFamily,
107
114
  this._optionsService.rawOptions.fontSize,
@@ -225,6 +232,10 @@ export class DomRenderer extends Disposable implements IRenderer {
225
232
  `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
226
233
  ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +
227
234
  `}` +
235
+ // Disable cursor blinking when idle
236
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` +
237
+ ` animation: none !important;` +
238
+ `}` +
228
239
  // !important helps fix an issue where the cursor will not render on top of the selection,
229
240
  // however it's very hard to fix this issue and retain the blink animation without the use of
230
241
  // !important. So this edge case fails when cursor blink is on.
@@ -328,11 +339,13 @@ export class DomRenderer extends Disposable implements IRenderer {
328
339
 
329
340
  public handleBlur(): void {
330
341
  this._rowContainer.classList.remove(FOCUS_CLASS);
342
+ this._cursorBlinkStateManager.pause();
331
343
  this.renderRows(0, this._bufferService.rows - 1);
332
344
  }
333
345
 
334
346
  public handleFocus(): void {
335
347
  this._rowContainer.classList.add(FOCUS_CLASS);
348
+ this._cursorBlinkStateManager.resume();
336
349
  this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
337
350
  }
338
351
 
@@ -406,7 +419,8 @@ export class DomRenderer extends Disposable implements IRenderer {
406
419
  }
407
420
 
408
421
  public handleCursorMove(): void {
409
- // No-op, the cursor is drawn when rows are drawn
422
+ // Reset idle timer on cursor movement (which happens on input)
423
+ this._cursorBlinkStateManager.restartBlinkAnimation();
410
424
  }
411
425
 
412
426
  private _handleOptionsChanged(): void {
@@ -540,3 +554,60 @@ export class DomRenderer extends Disposable implements IRenderer {
540
554
  }
541
555
  }
542
556
  }
557
+
558
+ class CursorBlinkStateManager {
559
+ private _idleTimeout: number | undefined;
560
+ private _isIdlePaused: boolean = false;
561
+
562
+ constructor(
563
+ private readonly _rowContainer: HTMLElement,
564
+ private readonly _coreBrowserService: ICoreBrowserService
565
+ ) {
566
+ if (this._coreBrowserService.isFocused) {
567
+ this._resetIdleTimer();
568
+ }
569
+ }
570
+
571
+ public dispose(): void {
572
+ this._clearIdleTimer();
573
+ }
574
+
575
+ public restartBlinkAnimation(): void {
576
+ if (this._isIdlePaused) {
577
+ this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
578
+ }
579
+ this._resetIdleTimer();
580
+ }
581
+
582
+ public pause(): void {
583
+ this._isIdlePaused = false;
584
+ this._clearIdleTimer();
585
+ }
586
+
587
+ public resume(): void {
588
+ this._isIdlePaused = false;
589
+ this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
590
+ this._resetIdleTimer();
591
+ }
592
+
593
+ private _resetIdleTimer(): void {
594
+ this._isIdlePaused = false;
595
+ this._clearIdleTimer();
596
+ this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {
597
+ this._stopBlinkingDueToIdle();
598
+ }, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);
599
+ }
600
+
601
+ private _clearIdleTimer(): void {
602
+ if (this._idleTimeout) {
603
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
604
+ this._idleTimeout = undefined;
605
+ }
606
+ }
607
+
608
+ private _stopBlinkingDueToIdle(): void {
609
+ this._rowContainer.classList.add(CURSOR_BLINK_IDLE_CLASS);
610
+ this._isIdlePaused = true;
611
+ this._idleTimeout = undefined;
612
+ }
613
+ }