@xterm/xterm 5.4.0-beta.1

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/css/xterm.css +209 -0
  4. package/lib/xterm.js +2 -0
  5. package/lib/xterm.js.map +1 -0
  6. package/package.json +101 -0
  7. package/src/browser/AccessibilityManager.ts +278 -0
  8. package/src/browser/Clipboard.ts +93 -0
  9. package/src/browser/ColorContrastCache.ts +34 -0
  10. package/src/browser/Lifecycle.ts +33 -0
  11. package/src/browser/Linkifier2.ts +416 -0
  12. package/src/browser/LocalizableStrings.ts +12 -0
  13. package/src/browser/OscLinkProvider.ts +128 -0
  14. package/src/browser/RenderDebouncer.ts +83 -0
  15. package/src/browser/Terminal.ts +1317 -0
  16. package/src/browser/TimeBasedDebouncer.ts +86 -0
  17. package/src/browser/Types.d.ts +181 -0
  18. package/src/browser/Viewport.ts +401 -0
  19. package/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
  20. package/src/browser/decorations/ColorZoneStore.ts +117 -0
  21. package/src/browser/decorations/OverviewRulerRenderer.ts +218 -0
  22. package/src/browser/input/CompositionHelper.ts +246 -0
  23. package/src/browser/input/Mouse.ts +54 -0
  24. package/src/browser/input/MoveToCell.ts +249 -0
  25. package/src/browser/public/Terminal.ts +260 -0
  26. package/src/browser/renderer/dom/DomRenderer.ts +509 -0
  27. package/src/browser/renderer/dom/DomRendererRowFactory.ts +526 -0
  28. package/src/browser/renderer/dom/WidthCache.ts +160 -0
  29. package/src/browser/renderer/shared/CellColorResolver.ts +137 -0
  30. package/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
  31. package/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
  32. package/src/browser/renderer/shared/Constants.ts +14 -0
  33. package/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
  34. package/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
  35. package/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
  36. package/src/browser/renderer/shared/README.md +1 -0
  37. package/src/browser/renderer/shared/RendererUtils.ts +58 -0
  38. package/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
  39. package/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
  40. package/src/browser/renderer/shared/Types.d.ts +173 -0
  41. package/src/browser/selection/SelectionModel.ts +144 -0
  42. package/src/browser/selection/Types.d.ts +15 -0
  43. package/src/browser/services/CharSizeService.ts +102 -0
  44. package/src/browser/services/CharacterJoinerService.ts +339 -0
  45. package/src/browser/services/CoreBrowserService.ts +137 -0
  46. package/src/browser/services/MouseService.ts +46 -0
  47. package/src/browser/services/RenderService.ts +279 -0
  48. package/src/browser/services/SelectionService.ts +1031 -0
  49. package/src/browser/services/Services.ts +147 -0
  50. package/src/browser/services/ThemeService.ts +237 -0
  51. package/src/common/CircularList.ts +241 -0
  52. package/src/common/Clone.ts +23 -0
  53. package/src/common/Color.ts +357 -0
  54. package/src/common/CoreTerminal.ts +284 -0
  55. package/src/common/EventEmitter.ts +78 -0
  56. package/src/common/InputHandler.ts +3461 -0
  57. package/src/common/Lifecycle.ts +108 -0
  58. package/src/common/MultiKeyMap.ts +42 -0
  59. package/src/common/Platform.ts +44 -0
  60. package/src/common/SortedList.ts +118 -0
  61. package/src/common/TaskQueue.ts +166 -0
  62. package/src/common/TypedArrayUtils.ts +17 -0
  63. package/src/common/Types.d.ts +553 -0
  64. package/src/common/WindowsMode.ts +27 -0
  65. package/src/common/buffer/AttributeData.ts +196 -0
  66. package/src/common/buffer/Buffer.ts +654 -0
  67. package/src/common/buffer/BufferLine.ts +524 -0
  68. package/src/common/buffer/BufferRange.ts +13 -0
  69. package/src/common/buffer/BufferReflow.ts +223 -0
  70. package/src/common/buffer/BufferSet.ts +134 -0
  71. package/src/common/buffer/CellData.ts +94 -0
  72. package/src/common/buffer/Constants.ts +149 -0
  73. package/src/common/buffer/Marker.ts +43 -0
  74. package/src/common/buffer/Types.d.ts +52 -0
  75. package/src/common/data/Charsets.ts +256 -0
  76. package/src/common/data/EscapeSequences.ts +153 -0
  77. package/src/common/input/Keyboard.ts +398 -0
  78. package/src/common/input/TextDecoder.ts +346 -0
  79. package/src/common/input/UnicodeV6.ts +145 -0
  80. package/src/common/input/WriteBuffer.ts +246 -0
  81. package/src/common/input/XParseColor.ts +80 -0
  82. package/src/common/parser/Constants.ts +58 -0
  83. package/src/common/parser/DcsParser.ts +192 -0
  84. package/src/common/parser/EscapeSequenceParser.ts +792 -0
  85. package/src/common/parser/OscParser.ts +238 -0
  86. package/src/common/parser/Params.ts +229 -0
  87. package/src/common/parser/Types.d.ts +275 -0
  88. package/src/common/public/AddonManager.ts +53 -0
  89. package/src/common/public/BufferApiView.ts +35 -0
  90. package/src/common/public/BufferLineApiView.ts +29 -0
  91. package/src/common/public/BufferNamespaceApi.ts +36 -0
  92. package/src/common/public/ParserApi.ts +37 -0
  93. package/src/common/public/UnicodeApi.ts +27 -0
  94. package/src/common/services/BufferService.ts +151 -0
  95. package/src/common/services/CharsetService.ts +34 -0
  96. package/src/common/services/CoreMouseService.ts +318 -0
  97. package/src/common/services/CoreService.ts +87 -0
  98. package/src/common/services/DecorationService.ts +140 -0
  99. package/src/common/services/InstantiationService.ts +85 -0
  100. package/src/common/services/LogService.ts +124 -0
  101. package/src/common/services/OptionsService.ts +202 -0
  102. package/src/common/services/OscLinkService.ts +115 -0
  103. package/src/common/services/ServiceRegistry.ts +49 -0
  104. package/src/common/services/Services.ts +373 -0
  105. package/src/common/services/UnicodeService.ts +111 -0
  106. package/src/headless/Terminal.ts +136 -0
  107. package/src/headless/public/Terminal.ts +195 -0
  108. package/typings/xterm.d.ts +1857 -0
@@ -0,0 +1,509 @@
1
+ /**
2
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory';
7
+ import { WidthCache } from 'browser/renderer/dom/WidthCache';
8
+ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
9
+ import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
10
+ import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
11
+ import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
12
+ import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
13
+ import { color } from 'common/Color';
14
+ import { EventEmitter } from 'common/EventEmitter';
15
+ import { Disposable, toDisposable } from 'common/Lifecycle';
16
+ import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services';
17
+
18
+
19
+ const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
20
+ const ROW_CONTAINER_CLASS = 'xterm-rows';
21
+ const FG_CLASS_PREFIX = 'xterm-fg-';
22
+ const BG_CLASS_PREFIX = 'xterm-bg-';
23
+ const FOCUS_CLASS = 'xterm-focus';
24
+ const SELECTION_CLASS = 'xterm-selection';
25
+
26
+ let nextTerminalId = 1;
27
+
28
+
29
+ /**
30
+ * A fallback renderer for when canvas is slow. This is not meant to be
31
+ * particularly fast or feature complete, more just stable and usable for when
32
+ * canvas is not an option.
33
+ */
34
+ export class DomRenderer extends Disposable implements IRenderer {
35
+ private _rowFactory: DomRendererRowFactory;
36
+ private _terminalClass: number = nextTerminalId++;
37
+
38
+ private _themeStyleElement!: HTMLStyleElement;
39
+ private _dimensionsStyleElement!: HTMLStyleElement;
40
+ private _rowContainer: HTMLElement;
41
+ private _rowElements: HTMLElement[] = [];
42
+ private _selectionContainer: HTMLElement;
43
+ private _widthCache: WidthCache;
44
+
45
+ public dimensions: IRenderDimensions;
46
+
47
+ public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
48
+
49
+ constructor(
50
+ private readonly _document: Document,
51
+ private readonly _element: HTMLElement,
52
+ private readonly _screenElement: HTMLElement,
53
+ private readonly _viewportElement: HTMLElement,
54
+ private readonly _helperContainer: HTMLElement,
55
+ private readonly _linkifier2: ILinkifier2,
56
+ @IInstantiationService instantiationService: IInstantiationService,
57
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
58
+ @IOptionsService private readonly _optionsService: IOptionsService,
59
+ @IBufferService private readonly _bufferService: IBufferService,
60
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
61
+ @IThemeService private readonly _themeService: IThemeService
62
+ ) {
63
+ super();
64
+ this._rowContainer = this._document.createElement('div');
65
+ this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
66
+ this._rowContainer.style.lineHeight = 'normal';
67
+ this._rowContainer.setAttribute('aria-hidden', 'true');
68
+ this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);
69
+ this._selectionContainer = this._document.createElement('div');
70
+ this._selectionContainer.classList.add(SELECTION_CLASS);
71
+ this._selectionContainer.setAttribute('aria-hidden', 'true');
72
+
73
+ this.dimensions = createRenderDimensions();
74
+ this._updateDimensions();
75
+ this.register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));
76
+
77
+ this.register(this._themeService.onChangeColors(e => this._injectCss(e)));
78
+ this._injectCss(this._themeService.colors);
79
+
80
+ this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document);
81
+
82
+ this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
83
+ this._screenElement.appendChild(this._rowContainer);
84
+ this._screenElement.appendChild(this._selectionContainer);
85
+
86
+ this.register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));
87
+ this.register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));
88
+
89
+ this.register(toDisposable(() => {
90
+ this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
91
+
92
+ // Outside influences such as React unmounts may manipulate the DOM before our disposal.
93
+ // https://github.com/xtermjs/xterm.js/issues/2960
94
+ this._rowContainer.remove();
95
+ this._selectionContainer.remove();
96
+ this._widthCache.dispose();
97
+ this._themeStyleElement.remove();
98
+ this._dimensionsStyleElement.remove();
99
+ }));
100
+
101
+ this._widthCache = new WidthCache(this._document, this._helperContainer);
102
+ this._widthCache.setFont(
103
+ this._optionsService.rawOptions.fontFamily,
104
+ this._optionsService.rawOptions.fontSize,
105
+ this._optionsService.rawOptions.fontWeight,
106
+ this._optionsService.rawOptions.fontWeightBold
107
+ );
108
+ this._setDefaultSpacing();
109
+ }
110
+
111
+ private _updateDimensions(): void {
112
+ const dpr = this._coreBrowserService.dpr;
113
+ this.dimensions.device.char.width = this._charSizeService.width * dpr;
114
+ this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);
115
+ this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
116
+ this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
117
+ this.dimensions.device.char.left = 0;
118
+ this.dimensions.device.char.top = 0;
119
+ this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols;
120
+ this.dimensions.device.canvas.height = this.dimensions.device.cell.height * this._bufferService.rows;
121
+ this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr);
122
+ this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr);
123
+ this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols;
124
+ this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows;
125
+
126
+ for (const element of this._rowElements) {
127
+ element.style.width = `${this.dimensions.css.canvas.width}px`;
128
+ element.style.height = `${this.dimensions.css.cell.height}px`;
129
+ element.style.lineHeight = `${this.dimensions.css.cell.height}px`;
130
+ // Make sure rows don't overflow onto following row
131
+ element.style.overflow = 'hidden';
132
+ }
133
+
134
+ if (!this._dimensionsStyleElement) {
135
+ this._dimensionsStyleElement = this._document.createElement('style');
136
+ this._screenElement.appendChild(this._dimensionsStyleElement);
137
+ }
138
+
139
+ const styles =
140
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
141
+ ` display: inline-block;` + // TODO: find workaround for inline-block (creates ~20% render penalty)
142
+ ` height: 100%;` +
143
+ ` vertical-align: top;` +
144
+ `}`;
145
+
146
+ this._dimensionsStyleElement.textContent = styles;
147
+
148
+ this._selectionContainer.style.height = this._viewportElement.style.height;
149
+ this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`;
150
+ this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`;
151
+ }
152
+
153
+ private _injectCss(colors: ReadonlyColorSet): void {
154
+ if (!this._themeStyleElement) {
155
+ this._themeStyleElement = this._document.createElement('style');
156
+ this._screenElement.appendChild(this._themeStyleElement);
157
+ }
158
+
159
+ // Base CSS
160
+ let styles =
161
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
162
+ ` color: ${colors.foreground.css};` +
163
+ ` font-family: ${this._optionsService.rawOptions.fontFamily};` +
164
+ ` font-size: ${this._optionsService.rawOptions.fontSize}px;` +
165
+ ` font-kerning: none;` +
166
+ ` white-space: pre` +
167
+ `}`;
168
+ styles +=
169
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .xterm-dim {` +
170
+ ` color: ${color.multiplyOpacity(colors.foreground, 0.5).css};` +
171
+ `}`;
172
+ // Text styles
173
+ styles +=
174
+ `${this._terminalSelector} span:not(.${RowCss.BOLD_CLASS}) {` +
175
+ ` font-weight: ${this._optionsService.rawOptions.fontWeight};` +
176
+ `}` +
177
+ `${this._terminalSelector} span.${RowCss.BOLD_CLASS} {` +
178
+ ` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +
179
+ `}` +
180
+ `${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` +
181
+ ` font-style: italic;` +
182
+ `}`;
183
+ // Blink animation
184
+ styles +=
185
+ `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
186
+ ` 50% {` +
187
+ ` border-bottom-style: hidden;` +
188
+ ` }` +
189
+ `}`;
190
+ styles +=
191
+ `@keyframes blink_block` + `_` + this._terminalClass + ` {` +
192
+ ` 0% {` +
193
+ ` background-color: ${colors.cursor.css};` +
194
+ ` color: ${colors.cursorAccent.css};` +
195
+ ` }` +
196
+ ` 50% {` +
197
+ ` background-color: inherit;` +
198
+ ` color: ${colors.cursor.css};` +
199
+ ` }` +
200
+ `}`;
201
+ // Cursor
202
+ styles +=
203
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` +
204
+ ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
205
+ `}` +
206
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
207
+ ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
208
+ `}` +
209
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
210
+ ` background-color: ${colors.cursor.css} !important;` +
211
+ ` color: ${colors.cursorAccent.css} !important;` +
212
+ `}` +
213
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` +
214
+ ` outline: 1px solid ${colors.cursor.css};` +
215
+ ` outline-offset: -1px;` +
216
+ `}` +
217
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +
218
+ ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` +
219
+ `}` +
220
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +
221
+ ` border-bottom: 1px ${colors.cursor.css};` +
222
+ ` border-bottom-style: solid;` +
223
+ ` height: calc(100% - 1px);` +
224
+ `}`;
225
+ // Selection
226
+ styles +=
227
+ `${this._terminalSelector} .${SELECTION_CLASS} {` +
228
+ ` position: absolute;` +
229
+ ` top: 0;` +
230
+ ` left: 0;` +
231
+ ` z-index: 1;` +
232
+ ` pointer-events: none;` +
233
+ `}` +
234
+ `${this._terminalSelector}.focus .${SELECTION_CLASS} div {` +
235
+ ` position: absolute;` +
236
+ ` background-color: ${colors.selectionBackgroundOpaque.css};` +
237
+ `}` +
238
+ `${this._terminalSelector} .${SELECTION_CLASS} div {` +
239
+ ` position: absolute;` +
240
+ ` background-color: ${colors.selectionInactiveBackgroundOpaque.css};` +
241
+ `}`;
242
+ // Colors
243
+ for (const [i, c] of colors.ansi.entries()) {
244
+ styles +=
245
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
246
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` +
247
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
248
+ }
249
+ styles +=
250
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` +
251
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` +
252
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`;
253
+
254
+ this._themeStyleElement.textContent = styles;
255
+ }
256
+
257
+ /**
258
+ * default letter spacing
259
+ * Due to rounding issues in dimensions dpr calc glyph might render
260
+ * slightly too wide or too narrow. The method corrects the stacking offsets
261
+ * by applying a default letter-spacing for all chars.
262
+ * The value gets passed to the row factory to avoid setting this value again
263
+ * (render speedup is roughly 10%).
264
+ */
265
+ private _setDefaultSpacing(): void {
266
+ // measure same char as in CharSizeService to get the base deviation
267
+ const spacing = this.dimensions.css.cell.width - this._widthCache.get('W', false, false);
268
+ this._rowContainer.style.letterSpacing = `${spacing}px`;
269
+ this._rowFactory.defaultSpacing = spacing;
270
+ }
271
+
272
+ public handleDevicePixelRatioChange(): void {
273
+ this._updateDimensions();
274
+ this._widthCache.clear();
275
+ this._setDefaultSpacing();
276
+ }
277
+
278
+ private _refreshRowElements(cols: number, rows: number): void {
279
+ // Add missing elements
280
+ for (let i = this._rowElements.length; i <= rows; i++) {
281
+ const row = this._document.createElement('div');
282
+ this._rowContainer.appendChild(row);
283
+ this._rowElements.push(row);
284
+ }
285
+ // Remove excess elements
286
+ while (this._rowElements.length > rows) {
287
+ this._rowContainer.removeChild(this._rowElements.pop()!);
288
+ }
289
+ }
290
+
291
+ public handleResize(cols: number, rows: number): void {
292
+ this._refreshRowElements(cols, rows);
293
+ this._updateDimensions();
294
+ }
295
+
296
+ public handleCharSizeChanged(): void {
297
+ this._updateDimensions();
298
+ this._widthCache.clear();
299
+ this._setDefaultSpacing();
300
+ }
301
+
302
+ public handleBlur(): void {
303
+ this._rowContainer.classList.remove(FOCUS_CLASS);
304
+ this.renderRows(0, this._bufferService.rows - 1);
305
+ }
306
+
307
+ public handleFocus(): void {
308
+ this._rowContainer.classList.add(FOCUS_CLASS);
309
+ this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
310
+ }
311
+
312
+ public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
313
+ // Remove all selections
314
+ this._selectionContainer.replaceChildren();
315
+ this._rowFactory.handleSelectionChanged(start, end, columnSelectMode);
316
+ this.renderRows(0, this._bufferService.rows - 1);
317
+
318
+ // Selection does not exist
319
+ if (!start || !end) {
320
+ return;
321
+ }
322
+
323
+ // Translate from buffer position to viewport position
324
+ const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
325
+ const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
326
+ const viewportCappedStartRow = Math.max(viewportStartRow, 0);
327
+ const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
328
+
329
+ // No need to draw the selection
330
+ if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
331
+ return;
332
+ }
333
+
334
+ // Create the selections
335
+ const documentFragment = this._document.createDocumentFragment();
336
+
337
+ if (columnSelectMode) {
338
+ const isXFlipped = start[0] > end[0];
339
+ documentFragment.appendChild(
340
+ this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
341
+ );
342
+ } else {
343
+ // Draw first row
344
+ const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
345
+ const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
346
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
347
+ // Draw middle rows
348
+ const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
349
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
350
+ // Draw final row
351
+ if (viewportCappedStartRow !== viewportCappedEndRow) {
352
+ // Only draw viewportEndRow if it's not the same as viewporttartRow
353
+ const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
354
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
355
+ }
356
+ }
357
+ this._selectionContainer.appendChild(documentFragment);
358
+ }
359
+
360
+ /**
361
+ * Creates a selection element at the specified position.
362
+ * @param row The row of the selection.
363
+ * @param colStart The start column.
364
+ * @param colEnd The end columns.
365
+ */
366
+ private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
367
+ const element = this._document.createElement('div');
368
+ element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;
369
+ element.style.top = `${row * this.dimensions.css.cell.height}px`;
370
+ element.style.left = `${colStart * this.dimensions.css.cell.width}px`;
371
+ element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`;
372
+ return element;
373
+ }
374
+
375
+ public handleCursorMove(): void {
376
+ // No-op, the cursor is drawn when rows are drawn
377
+ }
378
+
379
+ private _handleOptionsChanged(): void {
380
+ // Force a refresh
381
+ this._updateDimensions();
382
+ // Refresh CSS
383
+ this._injectCss(this._themeService.colors);
384
+ // update spacing cache
385
+ this._widthCache.setFont(
386
+ this._optionsService.rawOptions.fontFamily,
387
+ this._optionsService.rawOptions.fontSize,
388
+ this._optionsService.rawOptions.fontWeight,
389
+ this._optionsService.rawOptions.fontWeightBold
390
+ );
391
+ this._setDefaultSpacing();
392
+ }
393
+
394
+ public clear(): void {
395
+ for (const e of this._rowElements) {
396
+ /**
397
+ * NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and
398
+ * `@testing-library/react`
399
+ *
400
+ * references:
401
+ * - https://github.com/testing-library/react-testing-library/issues/1146
402
+ * - https://github.com/jsdom/jsdom/issues/1245
403
+ */
404
+ e.replaceChildren();
405
+ }
406
+ }
407
+
408
+ public renderRows(start: number, end: number): void {
409
+ const buffer = this._bufferService.buffer;
410
+ const cursorAbsoluteY = buffer.ybase + buffer.y;
411
+ const cursorX = Math.min(buffer.x, this._bufferService.cols - 1);
412
+ const cursorBlink = this._optionsService.rawOptions.cursorBlink;
413
+ const cursorStyle = this._optionsService.rawOptions.cursorStyle;
414
+ const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
415
+
416
+ for (let y = start; y <= end; y++) {
417
+ const row = y + buffer.ydisp;
418
+ const rowElement = this._rowElements[y];
419
+ const lineData = buffer.lines.get(row);
420
+ if (!rowElement || !lineData) {
421
+ break;
422
+ }
423
+ rowElement.replaceChildren(
424
+ ...this._rowFactory.createRow(
425
+ lineData,
426
+ row,
427
+ row === cursorAbsoluteY,
428
+ cursorStyle,
429
+ cursorInactiveStyle,
430
+ cursorX,
431
+ cursorBlink,
432
+ this.dimensions.css.cell.width,
433
+ this._widthCache,
434
+ -1,
435
+ -1
436
+ )
437
+ );
438
+ }
439
+ }
440
+
441
+ private get _terminalSelector(): string {
442
+ return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
443
+ }
444
+
445
+ private _handleLinkHover(e: ILinkifierEvent): void {
446
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
447
+ }
448
+
449
+ private _handleLinkLeave(e: ILinkifierEvent): void {
450
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);
451
+ }
452
+
453
+ private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
454
+ /**
455
+ * NOTE: The linkifier may send out of viewport y-values if:
456
+ * - negative y-value: the link started at a higher line
457
+ * - y-value >= maxY: the link ends at a line below viewport
458
+ *
459
+ * For negative y-values we can simply adjust x = 0,
460
+ * as higher up link start means, that everything from
461
+ * (0,0) is a link under top-down-left-right char progression
462
+ *
463
+ * Additionally there might be a small chance of out-of-sync x|y-values
464
+ * from a race condition of render updates vs. link event handler execution:
465
+ * - (sync) resize: chances terminal buffer in sync, schedules render update async
466
+ * - (async) link handler race condition: new buffer metrics, but still on old render state
467
+ * - (async) render update: brings term metrics and render state back in sync
468
+ */
469
+ // clip coords into viewport
470
+ if (y < 0) x = 0;
471
+ if (y2 < 0) x2 = 0;
472
+ const maxY = this._bufferService.rows - 1;
473
+ y = Math.max(Math.min(y, maxY), 0);
474
+ y2 = Math.max(Math.min(y2, maxY), 0);
475
+
476
+ cols = Math.min(cols, this._bufferService.cols);
477
+ const buffer = this._bufferService.buffer;
478
+ const cursorAbsoluteY = buffer.ybase + buffer.y;
479
+ const cursorX = Math.min(buffer.x, cols - 1);
480
+ const cursorBlink = this._optionsService.rawOptions.cursorBlink;
481
+ const cursorStyle = this._optionsService.rawOptions.cursorStyle;
482
+ const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
483
+
484
+ // refresh rows within link range
485
+ for (let i = y; i <= y2; ++i) {
486
+ const row = i + buffer.ydisp;
487
+ const rowElement = this._rowElements[i];
488
+ const bufferline = buffer.lines.get(row);
489
+ if (!rowElement || !bufferline) {
490
+ break;
491
+ }
492
+ rowElement.replaceChildren(
493
+ ...this._rowFactory.createRow(
494
+ bufferline,
495
+ row,
496
+ row === cursorAbsoluteY,
497
+ cursorStyle,
498
+ cursorInactiveStyle,
499
+ cursorX,
500
+ cursorBlink,
501
+ this.dimensions.css.cell.width,
502
+ this._widthCache,
503
+ enabled ? (i === y ? x : 0) : -1,
504
+ enabled ? ((i === y2 ? x2 : cols) - 1) : -1
505
+ )
506
+ );
507
+ }
508
+ }
509
+ }