@xterm/xterm 6.1.0-beta.17 → 6.1.0-beta.170

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 (156) hide show
  1. package/README.md +27 -28
  2. package/css/xterm.css +29 -22
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +8 -34
  6. package/lib/xterm.mjs.map +4 -4
  7. package/package.json +26 -15
  8. package/src/browser/AccessibilityManager.ts +6 -3
  9. package/src/browser/Clipboard.ts +6 -3
  10. package/src/browser/CoreBrowserTerminal.ts +142 -62
  11. package/src/browser/Dom.ts +178 -0
  12. package/src/browser/Linkifier.ts +3 -3
  13. package/src/browser/OscLinkProvider.ts +3 -1
  14. package/src/browser/RenderDebouncer.ts +2 -2
  15. package/src/browser/TimeBasedDebouncer.ts +2 -2
  16. package/src/browser/Types.ts +12 -11
  17. package/src/browser/Viewport.ts +40 -17
  18. package/src/browser/decorations/BufferDecorationRenderer.ts +1 -1
  19. package/src/browser/decorations/OverviewRulerRenderer.ts +15 -16
  20. package/src/browser/input/CompositionHelper.ts +10 -1
  21. package/src/browser/public/Terminal.ts +24 -27
  22. package/src/browser/renderer/dom/DomRenderer.ts +128 -8
  23. package/src/browser/renderer/dom/DomRendererRowFactory.ts +19 -13
  24. package/src/browser/renderer/dom/WidthCache.ts +54 -52
  25. package/src/browser/renderer/shared/Constants.ts +7 -0
  26. package/src/browser/renderer/shared/TextBlinkStateManager.ts +97 -0
  27. package/src/browser/renderer/shared/Types.ts +8 -2
  28. package/src/browser/scrollable/abstractScrollbar.ts +300 -0
  29. package/src/browser/scrollable/fastDomNode.ts +126 -0
  30. package/src/browser/scrollable/globalPointerMoveMonitor.ts +90 -0
  31. package/src/browser/scrollable/horizontalScrollbar.ts +85 -0
  32. package/src/browser/scrollable/mouseEvent.ts +292 -0
  33. package/src/browser/scrollable/scrollable.ts +486 -0
  34. package/src/browser/scrollable/scrollableElement.ts +579 -0
  35. package/src/browser/scrollable/scrollableElementOptions.ts +161 -0
  36. package/src/browser/scrollable/scrollbarArrow.ts +110 -0
  37. package/src/browser/scrollable/scrollbarState.ts +246 -0
  38. package/src/browser/scrollable/scrollbarVisibilityController.ts +113 -0
  39. package/src/browser/scrollable/touch.ts +481 -0
  40. package/src/browser/scrollable/verticalScrollbar.ts +143 -0
  41. package/src/browser/scrollable/widget.ts +23 -0
  42. package/src/browser/services/CharSizeService.ts +2 -2
  43. package/src/browser/services/CoreBrowserService.ts +7 -5
  44. package/src/browser/services/KeyboardService.ts +67 -0
  45. package/src/browser/services/LinkProviderService.ts +1 -1
  46. package/src/browser/services/MouseService.ts +2 -1
  47. package/src/browser/services/RenderService.ts +22 -15
  48. package/src/browser/services/SelectionService.ts +12 -4
  49. package/src/browser/services/Services.ts +24 -15
  50. package/src/browser/services/ThemeService.ts +2 -2
  51. package/src/common/Async.ts +105 -0
  52. package/src/common/CircularList.ts +2 -2
  53. package/src/common/Color.ts +8 -0
  54. package/src/common/CoreTerminal.ts +21 -11
  55. package/src/common/Event.ts +118 -0
  56. package/src/common/InputHandler.ts +244 -24
  57. package/src/common/Lifecycle.ts +113 -0
  58. package/src/common/Platform.ts +13 -3
  59. package/src/common/SortedList.ts +7 -3
  60. package/src/common/TaskQueue.ts +9 -3
  61. package/src/common/Types.ts +27 -7
  62. package/src/common/Version.ts +9 -0
  63. package/src/common/buffer/Buffer.ts +20 -14
  64. package/src/common/buffer/BufferLine.ts +4 -5
  65. package/src/common/buffer/BufferSet.ts +7 -6
  66. package/src/common/buffer/CellData.ts +57 -0
  67. package/src/common/buffer/Marker.ts +2 -2
  68. package/src/common/buffer/Types.ts +6 -2
  69. package/src/common/data/EscapeSequences.ts +71 -70
  70. package/src/common/input/Keyboard.ts +7 -6
  71. package/src/common/input/KittyKeyboard.ts +491 -0
  72. package/src/common/input/Win32InputMode.ts +297 -0
  73. package/src/common/input/WriteBuffer.ts +34 -2
  74. package/src/common/input/XParseColor.ts +2 -2
  75. package/src/common/parser/ApcParser.ts +245 -0
  76. package/src/common/parser/Constants.ts +22 -4
  77. package/src/common/parser/DcsParser.ts +5 -5
  78. package/src/common/parser/EscapeSequenceParser.ts +75 -22
  79. package/src/common/parser/OscParser.ts +5 -5
  80. package/src/common/parser/Types.ts +34 -1
  81. package/src/common/public/BufferLineApiView.ts +2 -2
  82. package/src/common/public/BufferNamespaceApi.ts +2 -2
  83. package/src/common/public/ParserApi.ts +3 -0
  84. package/src/common/services/BufferService.ts +8 -5
  85. package/src/common/services/CharsetService.ts +4 -0
  86. package/src/common/services/CoreMouseService.ts +2 -2
  87. package/src/common/services/CoreService.ts +18 -4
  88. package/src/common/services/DecorationService.ts +24 -8
  89. package/src/common/services/LogService.ts +1 -31
  90. package/src/common/services/OptionsService.ts +13 -4
  91. package/src/common/services/Services.ts +39 -16
  92. package/src/common/services/UnicodeService.ts +1 -1
  93. package/typings/xterm.d.ts +319 -35
  94. package/src/common/TypedArrayUtils.ts +0 -17
  95. package/src/vs/base/browser/browser.ts +0 -141
  96. package/src/vs/base/browser/canIUse.ts +0 -49
  97. package/src/vs/base/browser/dom.ts +0 -2369
  98. package/src/vs/base/browser/fastDomNode.ts +0 -316
  99. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  100. package/src/vs/base/browser/iframe.ts +0 -135
  101. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  102. package/src/vs/base/browser/mouseEvent.ts +0 -229
  103. package/src/vs/base/browser/touch.ts +0 -372
  104. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  105. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  106. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  107. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  108. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  109. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  110. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  111. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  112. package/src/vs/base/browser/ui/widget.ts +0 -57
  113. package/src/vs/base/browser/window.ts +0 -14
  114. package/src/vs/base/common/arrays.ts +0 -887
  115. package/src/vs/base/common/arraysFind.ts +0 -202
  116. package/src/vs/base/common/assert.ts +0 -71
  117. package/src/vs/base/common/async.ts +0 -1992
  118. package/src/vs/base/common/cancellation.ts +0 -148
  119. package/src/vs/base/common/charCode.ts +0 -450
  120. package/src/vs/base/common/collections.ts +0 -140
  121. package/src/vs/base/common/decorators.ts +0 -130
  122. package/src/vs/base/common/equals.ts +0 -146
  123. package/src/vs/base/common/errors.ts +0 -303
  124. package/src/vs/base/common/event.ts +0 -1778
  125. package/src/vs/base/common/functional.ts +0 -32
  126. package/src/vs/base/common/hash.ts +0 -316
  127. package/src/vs/base/common/iterator.ts +0 -159
  128. package/src/vs/base/common/keyCodes.ts +0 -526
  129. package/src/vs/base/common/keybindings.ts +0 -284
  130. package/src/vs/base/common/lazy.ts +0 -47
  131. package/src/vs/base/common/lifecycle.ts +0 -801
  132. package/src/vs/base/common/linkedList.ts +0 -142
  133. package/src/vs/base/common/map.ts +0 -202
  134. package/src/vs/base/common/numbers.ts +0 -98
  135. package/src/vs/base/common/observable.ts +0 -76
  136. package/src/vs/base/common/observableInternal/api.ts +0 -31
  137. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  138. package/src/vs/base/common/observableInternal/base.ts +0 -489
  139. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  140. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  141. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  142. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  143. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  144. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  145. package/src/vs/base/common/platform.ts +0 -281
  146. package/src/vs/base/common/scrollable.ts +0 -522
  147. package/src/vs/base/common/sequence.ts +0 -34
  148. package/src/vs/base/common/stopwatch.ts +0 -43
  149. package/src/vs/base/common/strings.ts +0 -557
  150. package/src/vs/base/common/symbols.ts +0 -9
  151. package/src/vs/base/common/uint.ts +0 -59
  152. package/src/vs/patches/nls.ts +0 -90
  153. package/src/vs/typings/base-common.d.ts +0 -20
  154. package/src/vs/typings/require.d.ts +0 -42
  155. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  156. package/src/vs/typings/vscode-globals-product.d.ts +0 -33
@@ -5,16 +5,18 @@
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
+ import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
11
12
  import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
12
13
  import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
13
14
  import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
14
15
  import { color } from 'common/Color';
15
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
16
+ import { Disposable, toDisposable } from 'common/Lifecycle';
16
17
  import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services';
17
- import { Emitter } from 'vs/base/common/event';
18
+ import { Emitter } from 'common/Event';
19
+ import { addDisposableListener } from 'browser/Dom';
18
20
 
19
21
 
20
22
  const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -23,6 +25,7 @@ const FG_CLASS_PREFIX = 'xterm-fg-';
23
25
  const BG_CLASS_PREFIX = 'xterm-bg-';
24
26
  const FOCUS_CLASS = 'xterm-focus';
25
27
  const SELECTION_CLASS = 'xterm-selection';
28
+ const CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle';
26
29
 
27
30
  let nextTerminalId = 1;
28
31
 
@@ -42,10 +45,15 @@ export class DomRenderer extends Disposable implements IRenderer {
42
45
  private _selectionContainer: HTMLElement;
43
46
  private _widthCache: WidthCache;
44
47
  private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
48
+ private _cursorBlinkStateManager: CursorBlinkStateManager;
49
+ private _textBlinkStateManager: TextBlinkStateManager;
50
+ private _rowHasBlinkingCells: boolean[] = [];
51
+ private _rowHasBlinkingCellsCount: number = 0;
45
52
 
46
53
  public dimensions: IRenderDimensions;
47
54
 
48
- public readonly onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>()).event;
55
+ private readonly _onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>());
56
+ public readonly onRequestRedraw = this._onRequestRedraw.event;
49
57
 
50
58
  constructor(
51
59
  private readonly _terminal: ITerminal,
@@ -89,6 +97,15 @@ export class DomRenderer extends Disposable implements IRenderer {
89
97
  this._register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));
90
98
  this._register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));
91
99
 
100
+ this._cursorBlinkStateManager = new CursorBlinkStateManager(this._rowContainer, this._coreBrowserService);
101
+ this._register(addDisposableListener(this._document, 'mousedown', () => this._cursorBlinkStateManager.restartBlinkAnimation()));
102
+ this._register(toDisposable(() => this._cursorBlinkStateManager.dispose()));
103
+ this._textBlinkStateManager = this._register(new TextBlinkStateManager(
104
+ () => this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }),
105
+ this._coreBrowserService,
106
+ this._optionsService
107
+ ));
108
+
92
109
  this._register(toDisposable(() => {
93
110
  this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
94
111
 
@@ -101,7 +118,7 @@ export class DomRenderer extends Disposable implements IRenderer {
101
118
  this._dimensionsStyleElement.remove();
102
119
  }));
103
120
 
104
- this._widthCache = new WidthCache(this._document, this._helperContainer);
121
+ this._widthCache = new WidthCache();
105
122
  this._widthCache.setFont(
106
123
  this._optionsService.rawOptions.fontFamily,
107
124
  this._optionsService.rawOptions.fontSize,
@@ -186,6 +203,9 @@ export class DomRenderer extends Disposable implements IRenderer {
186
203
  `}` +
187
204
  `${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` +
188
205
  ` font-style: italic;` +
206
+ `}` +
207
+ `${this._terminalSelector} span.${RowCss.BLINK_HIDDEN_CLASS} {` +
208
+ ` visibility: hidden;` +
189
209
  `}`;
190
210
  // Blink animation
191
211
  const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`;
@@ -225,6 +245,10 @@ export class DomRenderer extends Disposable implements IRenderer {
225
245
  `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
226
246
  ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +
227
247
  `}` +
248
+ // Disable cursor blinking when idle
249
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` +
250
+ ` animation: none !important;` +
251
+ `}` +
228
252
  // !important helps fix an issue where the cursor will not render on top of the selection,
229
253
  // however it's very hard to fix this issue and retain the blink animation without the use of
230
254
  // !important. So this edge case fails when cursor blink is on.
@@ -307,10 +331,14 @@ export class DomRenderer extends Disposable implements IRenderer {
307
331
  const row = this._document.createElement('div');
308
332
  this._rowContainer.appendChild(row);
309
333
  this._rowElements.push(row);
334
+ this._rowHasBlinkingCells.push(false);
310
335
  }
311
336
  // Remove excess elements
312
337
  while (this._rowElements.length > rows) {
313
338
  this._rowContainer.removeChild(this._rowElements.pop()!);
339
+ if (this._rowHasBlinkingCells.pop()) {
340
+ this._rowHasBlinkingCellsCount--;
341
+ }
314
342
  }
315
343
  }
316
344
 
@@ -328,14 +356,20 @@ export class DomRenderer extends Disposable implements IRenderer {
328
356
 
329
357
  public handleBlur(): void {
330
358
  this._rowContainer.classList.remove(FOCUS_CLASS);
359
+ this._cursorBlinkStateManager.pause();
331
360
  this.renderRows(0, this._bufferService.rows - 1);
332
361
  }
333
362
 
334
363
  public handleFocus(): void {
335
364
  this._rowContainer.classList.add(FOCUS_CLASS);
365
+ this._cursorBlinkStateManager.resume();
336
366
  this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
337
367
  }
338
368
 
369
+ public handleViewportVisibilityChange(isVisible: boolean): void {
370
+ this._textBlinkStateManager.setViewportVisible(isVisible);
371
+ }
372
+
339
373
  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
340
374
  // Remove all selections
341
375
  this._selectionContainer.replaceChildren();
@@ -406,7 +440,8 @@ export class DomRenderer extends Disposable implements IRenderer {
406
440
  }
407
441
 
408
442
  public handleCursorMove(): void {
409
- // No-op, the cursor is drawn when rows are drawn
443
+ // Reset idle timer on cursor movement (which happens on input)
444
+ this._cursorBlinkStateManager.restartBlinkAnimation();
410
445
  }
411
446
 
412
447
  private _handleOptionsChanged(): void {
@@ -436,6 +471,11 @@ export class DomRenderer extends Disposable implements IRenderer {
436
471
  */
437
472
  e.replaceChildren();
438
473
  }
474
+ if (this._rowHasBlinkingCellsCount > 0) {
475
+ this._rowHasBlinkingCells.fill(false);
476
+ this._rowHasBlinkingCellsCount = 0;
477
+ this._textBlinkStateManager.setNeedsBlinkInViewport(false);
478
+ }
439
479
  }
440
480
 
441
481
  public renderRows(start: number, end: number): void {
@@ -445,6 +485,7 @@ export class DomRenderer extends Disposable implements IRenderer {
445
485
  const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink;
446
486
  const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle;
447
487
  const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
488
+ const rowInfo = { hasBlinkingCells: false };
448
489
 
449
490
  for (let y = start; y <= end; y++) {
450
491
  const row = y + buffer.ydisp;
@@ -462,13 +503,17 @@ export class DomRenderer extends Disposable implements IRenderer {
462
503
  cursorInactiveStyle,
463
504
  cursorX,
464
505
  cursorBlink,
506
+ this._textBlinkStateManager.isBlinkOn,
465
507
  this.dimensions.css.cell.width,
466
508
  this._widthCache,
467
509
  -1,
468
- -1
510
+ -1,
511
+ rowInfo
469
512
  )
470
513
  );
514
+ this._setRowBlinkState(y, rowInfo.hasBlinkingCells);
471
515
  }
516
+ this._updateTextBlinkState();
472
517
  }
473
518
 
474
519
  private get _terminalSelector(): string {
@@ -513,6 +558,7 @@ export class DomRenderer extends Disposable implements IRenderer {
513
558
  const cursorBlink = this._optionsService.rawOptions.cursorBlink;
514
559
  const cursorStyle = this._optionsService.rawOptions.cursorStyle;
515
560
  const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
561
+ const rowInfo = { hasBlinkingCells: false };
516
562
 
517
563
  // refresh rows within link range
518
564
  for (let i = y; i <= y2; ++i) {
@@ -531,12 +577,86 @@ export class DomRenderer extends Disposable implements IRenderer {
531
577
  cursorInactiveStyle,
532
578
  cursorX,
533
579
  cursorBlink,
580
+ this._textBlinkStateManager.isBlinkOn,
534
581
  this.dimensions.css.cell.width,
535
582
  this._widthCache,
536
583
  enabled ? (i === y ? x : 0) : -1,
537
- enabled ? ((i === y2 ? x2 : cols) - 1) : -1
584
+ enabled ? ((i === y2 ? x2 : cols) - 1) : -1,
585
+ rowInfo
538
586
  )
539
587
  );
588
+ this._setRowBlinkState(i, rowInfo.hasBlinkingCells);
540
589
  }
590
+ this._updateTextBlinkState();
591
+ }
592
+
593
+ private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
594
+ const previous = this._rowHasBlinkingCells[row];
595
+ if (previous === hasBlinkingCells) {
596
+ return;
597
+ }
598
+ this._rowHasBlinkingCells[row] = hasBlinkingCells;
599
+ this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
600
+ }
601
+
602
+ private _updateTextBlinkState(): void {
603
+ this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
604
+ }
605
+ }
606
+
607
+ class CursorBlinkStateManager {
608
+ private _idleTimeout: number | undefined;
609
+ private _isIdlePaused: boolean = false;
610
+
611
+ constructor(
612
+ private readonly _rowContainer: HTMLElement,
613
+ private readonly _coreBrowserService: ICoreBrowserService
614
+ ) {
615
+ if (this._coreBrowserService.isFocused) {
616
+ this._resetIdleTimer();
617
+ }
618
+ }
619
+
620
+ public dispose(): void {
621
+ this._clearIdleTimer();
622
+ }
623
+
624
+ public restartBlinkAnimation(): void {
625
+ if (this._isIdlePaused) {
626
+ this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
627
+ }
628
+ this._resetIdleTimer();
629
+ }
630
+
631
+ public pause(): void {
632
+ this._isIdlePaused = false;
633
+ this._clearIdleTimer();
634
+ }
635
+
636
+ public resume(): void {
637
+ this._isIdlePaused = false;
638
+ this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
639
+ this._resetIdleTimer();
640
+ }
641
+
642
+ private _resetIdleTimer(): void {
643
+ this._isIdlePaused = false;
644
+ this._clearIdleTimer();
645
+ this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {
646
+ this._stopBlinkingDueToIdle();
647
+ }, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);
648
+ }
649
+
650
+ private _clearIdleTimer(): void {
651
+ if (this._idleTimeout) {
652
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
653
+ this._idleTimeout = undefined;
654
+ }
655
+ }
656
+
657
+ private _stopBlinkingDueToIdle(): void {
658
+ this._rowContainer.classList.add(CURSOR_BLINK_IDLE_CLASS);
659
+ this._isIdlePaused = true;
660
+ this._idleTimeout = undefined;
541
661
  }
542
662
  }
@@ -24,6 +24,7 @@ export const enum RowCss {
24
24
  UNDERLINE_CLASS = 'xterm-underline',
25
25
  OVERLINE_CLASS = 'xterm-overline',
26
26
  STRIKETHROUGH_CLASS = 'xterm-strikethrough',
27
+ BLINK_HIDDEN_CLASS = 'xterm-blink-hidden',
27
28
  CURSOR_CLASS = 'xterm-cursor',
28
29
  CURSOR_BLINK_CLASS = 'xterm-cursor-blink',
29
30
  CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block',
@@ -66,13 +67,18 @@ export class DomRendererRowFactory {
66
67
  cursorInactiveStyle: string | undefined,
67
68
  cursorX: number,
68
69
  cursorBlink: boolean,
70
+ blinkOn: boolean,
69
71
  cellWidth: number,
70
72
  widthCache: WidthCache,
71
73
  linkStart: number,
72
- linkEnd: number
74
+ linkEnd: number,
75
+ rowInfo?: { hasBlinkingCells: boolean }
73
76
  ): HTMLSpanElement[] {
74
77
 
75
78
  const elements: HTMLSpanElement[] = [];
79
+ if (rowInfo) {
80
+ rowInfo.hasBlinkingCells = false;
81
+ }
76
82
  const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
77
83
  const colors = this._themeService.colors;
78
84
 
@@ -118,7 +124,7 @@ export class DomRendererRowFactory {
118
124
  // Process any joined character ranges as needed. Because of how the
119
125
  // ranges are produced, we know that they are valid for the characters
120
126
  // and attributes of our input.
121
- let cell = this._workCell;
127
+ let cell: ICellData = this._workCell;
122
128
  if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
123
129
  const range = joinedRanges.shift()!;
124
130
  // If the ligature's selection state is not consistent, don't join it. This helps the
@@ -153,6 +159,13 @@ export class DomRendererRowFactory {
153
159
  const isInSelection = this._isCellInSelection(x, row);
154
160
  const isCursorCell = isCursorRow && x === cursorX;
155
161
  const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;
162
+ if (rowInfo && cell.isBlink()) {
163
+ rowInfo.hasBlinkingCells = true;
164
+ }
165
+ const isBlinkHidden = !blinkOn && cell.isBlink();
166
+ if (isBlinkHidden) {
167
+ classes.push(RowCss.BLINK_HIDDEN_CLASS);
168
+ }
156
169
 
157
170
  let isDecorated = false;
158
171
  this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {
@@ -397,7 +410,7 @@ export class DomRendererRowFactory {
397
410
  break;
398
411
  case Attributes.CM_RGB:
399
412
  resolvedBg = channels.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
400
- this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
413
+ this._addStyle(charElement, `background-color:#${(bg >>> 0).toString(16).padStart(6, '0')}`);
401
414
  break;
402
415
  case Attributes.CM_DEFAULT:
403
416
  default:
@@ -434,7 +447,7 @@ export class DomRendererRowFactory {
434
447
  (fg ) & 0xFF
435
448
  );
436
449
  if (!this._applyMinimumContrast(charElement, resolvedBg, color, cell, bgOverride, fgOverride)) {
437
- this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
450
+ this._addStyle(charElement, `color:#${fg.toString(16).padStart(6, '0')}`);
438
451
  }
439
452
  break;
440
453
  case Attributes.CM_DEFAULT:
@@ -494,8 +507,8 @@ export class DomRendererRowFactory {
494
507
  // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
495
508
  // non-dim cells
496
509
  const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1);
497
- adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, ratio);
498
- cache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
510
+ adjustedColor = color.ensureContrastRatio(bgOverride ?? bg, fgOverride ?? fg, ratio);
511
+ cache.setColor((bgOverride ?? bg).rgba, (fgOverride ?? fg).rgba, adjustedColor ?? null);
499
512
  }
500
513
 
501
514
  if (adjustedColor) {
@@ -537,10 +550,3 @@ export class DomRendererRowFactory {
537
550
  (start[1] < end[1] && y === start[1] && x >= start[0]);
538
551
  }
539
552
  }
540
-
541
- function padStart(text: string, padChar: string, length: number): string {
542
- while (text.length < length) {
543
- text = padChar + text;
544
- }
545
- return text;
546
- }
@@ -3,6 +3,7 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
+ import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
6
7
  import { IDisposable } from 'common/Types';
7
8
  import { FontWeight } from 'common/services/Services';
8
9
 
@@ -24,6 +25,10 @@ const enum FontVariant {
24
25
  BOLD_ITALIC = 3
25
26
  }
26
27
 
28
+ export interface IWidthCacheFontVariantCanvas {
29
+ setFont(fontFamily: string, fontSize: number, fontWeight: FontWeight, italic: boolean): void;
30
+ measure(c: string): number;
31
+ }
27
32
 
28
33
  export class WidthCache implements IDisposable {
29
34
  // flat cache for regular variant up to CacheSettings.FLAT_SIZE
@@ -42,50 +47,24 @@ export class WidthCache implements IDisposable {
42
47
  private _fontSize = 0;
43
48
  private _weight: FontWeight = 'normal';
44
49
  private _weightBold: FontWeight = 'bold';
45
- private _container: HTMLDivElement;
46
- private _measureElements: HTMLSpanElement[] = [];
47
-
48
- constructor(_document: Document, _helperContainer: HTMLElement) {
49
- this._container = _document.createElement('div');
50
- this._container.classList.add('xterm-width-cache-measure-container');
51
- this._container.setAttribute('aria-hidden', 'true');
52
- // SP should stack in spans
53
- this._container.style.whiteSpace = 'pre';
54
- // avoid undercuts in non-monospace fonts from kerning
55
- this._container.style.fontKerning = 'none';
56
-
57
- const regular = _document.createElement('span');
58
- regular.classList.add('xterm-char-measure-element');
59
-
60
- const bold = _document.createElement('span');
61
- bold.classList.add('xterm-char-measure-element');
62
- bold.style.fontWeight = 'bold';
63
-
64
- const italic = _document.createElement('span');
65
- italic.classList.add('xterm-char-measure-element');
66
- italic.style.fontStyle = 'italic';
67
-
68
- const boldItalic = _document.createElement('span');
69
- boldItalic.classList.add('xterm-char-measure-element');
70
- boldItalic.style.fontWeight = 'bold';
71
- boldItalic.style.fontStyle = 'italic';
72
-
73
- // NOTE: must be in order of FontVariant
74
- this._measureElements = [regular, bold, italic, boldItalic];
75
- this._container.appendChild(regular);
76
- this._container.appendChild(bold);
77
- this._container.appendChild(italic);
78
- this._container.appendChild(boldItalic);
79
-
80
- _helperContainer.appendChild(this._container);
50
+ private _canvasElements: IWidthCacheFontVariantCanvas[] = [];
51
+
52
+ constructor(
53
+ canvasFactory: () => IWidthCacheFontVariantCanvas = () => new WidthCacheFontVariantCanvas()
54
+ ) {
55
+ this._canvasElements = [
56
+ canvasFactory(),
57
+ canvasFactory(),
58
+ canvasFactory(),
59
+ canvasFactory()
60
+ ];
81
61
 
82
62
  this.clear();
83
63
  }
84
64
 
85
65
  public dispose(): void {
86
- this._container.remove(); // remove elements from DOM
87
- this._measureElements.length = 0; // release element refs
88
- this._holey = undefined; // free cache memory via GC
66
+ this._canvasElements.length = 0;
67
+ this._holey = undefined; // free cache memory via GC
89
68
  }
90
69
 
91
70
  /**
@@ -104,10 +83,11 @@ export class WidthCache implements IDisposable {
104
83
  */
105
84
  public setFont(font: string, fontSize: number, weight: FontWeight, weightBold: FontWeight): void {
106
85
  // skip if nothing changed
107
- if (font === this._font
108
- && fontSize === this._fontSize
109
- && weight === this._weight
110
- && weightBold === this._weightBold
86
+ if (
87
+ font === this._font &&
88
+ fontSize === this._fontSize &&
89
+ weight === this._weight &&
90
+ weightBold === this._weightBold
111
91
  ) {
112
92
  return;
113
93
  }
@@ -117,12 +97,10 @@ export class WidthCache implements IDisposable {
117
97
  this._weight = weight;
118
98
  this._weightBold = weightBold;
119
99
 
120
- this._container.style.fontFamily = this._font;
121
- this._container.style.fontSize = `${this._fontSize}px`;
122
- this._measureElements[FontVariant.REGULAR].style.fontWeight = `${weight}`;
123
- this._measureElements[FontVariant.BOLD].style.fontWeight = `${weightBold}`;
124
- this._measureElements[FontVariant.ITALIC].style.fontWeight = `${weight}`;
125
- this._measureElements[FontVariant.BOLD_ITALIC].style.fontWeight = `${weightBold}`;
100
+ this._canvasElements[FontVariant.REGULAR].setFont(font, fontSize, weight, false);
101
+ this._canvasElements[FontVariant.BOLD].setFont(font, fontSize, weightBold, false);
102
+ this._canvasElements[FontVariant.ITALIC].setFont(font, fontSize, weight, true);
103
+ this._canvasElements[FontVariant.BOLD_ITALIC].setFont(font, fontSize, weightBold, true);
126
104
 
127
105
  this.clear();
128
106
  }
@@ -160,8 +138,32 @@ export class WidthCache implements IDisposable {
160
138
  }
161
139
 
162
140
  protected _measure(c: string, variant: FontVariant): number {
163
- const el = this._measureElements[variant];
164
- el.textContent = c.repeat(WidthCacheSettings.REPEAT);
165
- return el.offsetWidth / WidthCacheSettings.REPEAT;
141
+ return this._canvasElements[variant].measure(c);
142
+ }
143
+ }
144
+
145
+ class WidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas {
146
+ private _canvas: OffscreenCanvas | HTMLCanvasElement;
147
+ private _ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
148
+
149
+ constructor() {
150
+ if (typeof OffscreenCanvas !== 'undefined') {
151
+ this._canvas = new OffscreenCanvas(1, 1);
152
+ this._ctx = throwIfFalsy(this._canvas.getContext('2d'));
153
+ } else {
154
+ this._canvas = document.createElement('canvas');
155
+ this._canvas.width = 1;
156
+ this._canvas.height = 1;
157
+ this._ctx = throwIfFalsy(this._canvas.getContext('2d'));
158
+ }
159
+ }
160
+
161
+ public setFont(fontFamily: string, fontSize: number, fontWeight: FontWeight, italic: boolean): void {
162
+ const fontStyle = italic ? 'italic' : '';
163
+ this._ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`.trim();
164
+ }
165
+
166
+ public measure(c: string): number {
167
+ return this._ctx.measureText(c).width;
166
168
  }
167
169
  }
@@ -4,3 +4,10 @@
4
4
  */
5
5
 
6
6
  export const INVERTED_DEFAULT_COLOR = 257;
7
+
8
+ export const enum RendererConstants {
9
+ /**
10
+ * The idle time after which cursor blinking stops.
11
+ */
12
+ CURSOR_BLINK_IDLE_TIMEOUT = 5 * 60 * 1000
13
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Copyright (c) 2026 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { ICoreBrowserService } from 'browser/services/Services';
7
+ import { Disposable, toDisposable } from 'common/Lifecycle';
8
+ import { IOptionsService } from 'common/services/Services';
9
+
10
+ export class TextBlinkStateManager extends Disposable {
11
+ private _intervalDuration: number = 0;
12
+ private _interval: number | undefined;
13
+ private _blinkOn: boolean = true;
14
+ private _needsBlinkInViewport: boolean = false;
15
+ private _isViewportVisible: boolean = true;
16
+
17
+ constructor(
18
+ private readonly _renderCallback: () => void,
19
+ private readonly _coreBrowserService: ICoreBrowserService,
20
+ private readonly _optionsService: IOptionsService
21
+ ) {
22
+ super();
23
+ this._register(this._optionsService.onSpecificOptionChange('blinkIntervalDuration', duration => {
24
+ this.setIntervalDuration(duration);
25
+ }));
26
+ this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration);
27
+ this._register(toDisposable(() => this._clearInterval()));
28
+ }
29
+
30
+ public get isBlinkOn(): boolean {
31
+ return this._blinkOn;
32
+ }
33
+
34
+ public get isEnabled(): boolean {
35
+ return this._intervalDuration > 0;
36
+ }
37
+
38
+ public setNeedsBlinkInViewport(needsBlinkInViewport: boolean): void {
39
+ if (this._needsBlinkInViewport === needsBlinkInViewport) {
40
+ return;
41
+ }
42
+
43
+ this._needsBlinkInViewport = needsBlinkInViewport;
44
+ this._updateIntervalState();
45
+ }
46
+
47
+ public setViewportVisible(isVisible: boolean): void {
48
+ if (this._isViewportVisible === isVisible) {
49
+ return;
50
+ }
51
+
52
+ this._isViewportVisible = isVisible;
53
+ this._updateIntervalState();
54
+ }
55
+
56
+ public setIntervalDuration(duration: number): void {
57
+ if (duration === this._intervalDuration) {
58
+ return;
59
+ }
60
+
61
+ this._intervalDuration = duration;
62
+ this._clearInterval();
63
+ this._updateIntervalState();
64
+ }
65
+
66
+ private _updateIntervalState(): void {
67
+ const shouldBlink = this._intervalDuration > 0 && this._needsBlinkInViewport && this._isViewportVisible;
68
+ if (shouldBlink) {
69
+ if (this._interval !== undefined) {
70
+ return;
71
+ }
72
+ const wasBlinkOn = this._blinkOn;
73
+ this._blinkOn = true;
74
+ this._interval = this._coreBrowserService.window.setInterval(() => {
75
+ this._blinkOn = !this._blinkOn;
76
+ this._renderCallback();
77
+ }, this._intervalDuration);
78
+ if (!wasBlinkOn) {
79
+ this._renderCallback();
80
+ }
81
+ return;
82
+ }
83
+
84
+ this._clearInterval();
85
+ if (!this._blinkOn) {
86
+ this._blinkOn = true;
87
+ this._renderCallback();
88
+ }
89
+ }
90
+
91
+ private _clearInterval(): void {
92
+ if (this._interval !== undefined) {
93
+ this._coreBrowserService.window.clearInterval(this._interval);
94
+ this._interval = undefined;
95
+ }
96
+ }
97
+ }
@@ -6,7 +6,7 @@
6
6
  import { Terminal } from '@xterm/xterm';
7
7
  import { ITerminal } from 'browser/Types';
8
8
  import { IDisposable } from 'common/Types';
9
- import type { Event } from 'vs/base/common/event';
9
+ import type { IEvent } from 'common/Event';
10
10
 
11
11
  export interface IDimensions {
12
12
  width: number;
@@ -39,6 +39,11 @@ export interface IRenderDimensions {
39
39
  export interface IRequestRedrawEvent {
40
40
  start: number;
41
41
  end: number;
42
+ /**
43
+ * Whether the redraw should happen synchronously. This is used to avoid
44
+ * flicker when the canvas is resized.
45
+ */
46
+ sync?: boolean;
42
47
  }
43
48
 
44
49
  /**
@@ -52,7 +57,7 @@ export interface IRenderer extends IDisposable {
52
57
  * Fires when the renderer is requesting to be redrawn on the next animation
53
58
  * frame but is _not_ a result of content changing (eg. selection changes).
54
59
  */
55
- readonly onRequestRedraw: Event<IRequestRedrawEvent>;
60
+ readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
56
61
 
57
62
  dispose(): void;
58
63
  handleDevicePixelRatioChange(): void;
@@ -60,6 +65,7 @@ export interface IRenderer extends IDisposable {
60
65
  handleCharSizeChanged(): void;
61
66
  handleBlur(): void;
62
67
  handleFocus(): void;
68
+ handleViewportVisibilityChange?(isVisible: boolean): void;
63
69
  handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
64
70
  handleCursorMove(): void;
65
71
  clear(): void;