@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,526 @@
1
+ /**
2
+ * Copyright (c) 2018, 2023 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { IBufferLine, ICellData, IColor } from 'common/Types';
7
+ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
8
+ import { WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
9
+ import { CellData } from 'common/buffer/CellData';
10
+ import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
11
+ import { color, rgba } from 'common/Color';
12
+ import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
13
+ import { JoinedCellData } from 'browser/services/CharacterJoinerService';
14
+ import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils';
15
+ import { AttributeData } from 'common/buffer/AttributeData';
16
+ import { WidthCache } from 'browser/renderer/dom/WidthCache';
17
+ import { IColorContrastCache } from 'browser/Types';
18
+
19
+
20
+ export const enum RowCss {
21
+ BOLD_CLASS = 'xterm-bold',
22
+ DIM_CLASS = 'xterm-dim',
23
+ ITALIC_CLASS = 'xterm-italic',
24
+ UNDERLINE_CLASS = 'xterm-underline',
25
+ OVERLINE_CLASS = 'xterm-overline',
26
+ STRIKETHROUGH_CLASS = 'xterm-strikethrough',
27
+ CURSOR_CLASS = 'xterm-cursor',
28
+ CURSOR_BLINK_CLASS = 'xterm-cursor-blink',
29
+ CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block',
30
+ CURSOR_STYLE_OUTLINE_CLASS = 'xterm-cursor-outline',
31
+ CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar',
32
+ CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'
33
+ }
34
+
35
+
36
+ export class DomRendererRowFactory {
37
+ private _workCell: CellData = new CellData();
38
+
39
+ private _selectionStart: [number, number] | undefined;
40
+ private _selectionEnd: [number, number] | undefined;
41
+ private _columnSelectMode: boolean = false;
42
+
43
+ public defaultSpacing = 0;
44
+
45
+ constructor(
46
+ private readonly _document: Document,
47
+ @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
48
+ @IOptionsService private readonly _optionsService: IOptionsService,
49
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
50
+ @ICoreService private readonly _coreService: ICoreService,
51
+ @IDecorationService private readonly _decorationService: IDecorationService,
52
+ @IThemeService private readonly _themeService: IThemeService
53
+ ) {}
54
+
55
+ public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
56
+ this._selectionStart = start;
57
+ this._selectionEnd = end;
58
+ this._columnSelectMode = columnSelectMode;
59
+ }
60
+
61
+ public createRow(
62
+ lineData: IBufferLine,
63
+ row: number,
64
+ isCursorRow: boolean,
65
+ cursorStyle: string | undefined,
66
+ cursorInactiveStyle: string | undefined,
67
+ cursorX: number,
68
+ cursorBlink: boolean,
69
+ cellWidth: number,
70
+ widthCache: WidthCache,
71
+ linkStart: number,
72
+ linkEnd: number
73
+ ): HTMLSpanElement[] {
74
+
75
+ const elements: HTMLSpanElement[] = [];
76
+ const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
77
+ const colors = this._themeService.colors;
78
+
79
+ let lineLength = lineData.getNoBgTrimmedLength();
80
+ if (isCursorRow && lineLength < cursorX + 1) {
81
+ lineLength = cursorX + 1;
82
+ }
83
+
84
+ let charElement: HTMLSpanElement | undefined;
85
+ let cellAmount = 0;
86
+ let text = '';
87
+ let oldBg = 0;
88
+ let oldFg = 0;
89
+ let oldExt = 0;
90
+ let oldLinkHover: number | boolean = false;
91
+ let oldSpacing = 0;
92
+ let oldIsInSelection: boolean = false;
93
+ let spacing = 0;
94
+ const classes: string[] = [];
95
+
96
+ const hasHover = linkStart !== -1 && linkEnd !== -1;
97
+
98
+ for (let x = 0; x < lineLength; x++) {
99
+ lineData.loadCell(x, this._workCell);
100
+ let width = this._workCell.getWidth();
101
+
102
+ // The character to the left is a wide character, drawing is owned by the char at x-1
103
+ if (width === 0) {
104
+ continue;
105
+ }
106
+
107
+ // If true, indicates that the current character(s) to draw were joined.
108
+ let isJoined = false;
109
+ let lastCharX = x;
110
+
111
+ // Process any joined character ranges as needed. Because of how the
112
+ // ranges are produced, we know that they are valid for the characters
113
+ // and attributes of our input.
114
+ let cell = this._workCell;
115
+ if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
116
+ isJoined = true;
117
+ const range = joinedRanges.shift()!;
118
+
119
+ // We already know the exact start and end column of the joined range,
120
+ // so we get the string and width representing it directly
121
+ cell = new JoinedCellData(
122
+ this._workCell,
123
+ lineData.translateToString(true, range[0], range[1]),
124
+ range[1] - range[0]
125
+ );
126
+
127
+ // Skip over the cells occupied by this range in the loop
128
+ lastCharX = range[1] - 1;
129
+
130
+ // Recalculate width
131
+ width = cell.getWidth();
132
+ }
133
+
134
+ const isInSelection = this._isCellInSelection(x, row);
135
+ const isCursorCell = isCursorRow && x === cursorX;
136
+ const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;
137
+
138
+ let isDecorated = false;
139
+ this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {
140
+ isDecorated = true;
141
+ });
142
+
143
+ // get chars to render for this cell
144
+ let chars = cell.getChars() || WHITESPACE_CELL_CHAR;
145
+ if (chars === ' ' && (cell.isUnderline() || cell.isOverline())) {
146
+ chars = '\xa0';
147
+ }
148
+
149
+ // lookup char render width and calc spacing
150
+ spacing = width * cellWidth - widthCache.get(chars, cell.isBold(), cell.isItalic());
151
+
152
+ if (!charElement) {
153
+ charElement = this._document.createElement('span');
154
+ } else {
155
+ /**
156
+ * chars can only be merged on existing span if:
157
+ * - existing span only contains mergeable chars (cellAmount != 0)
158
+ * - bg did not change (or both are in selection)
159
+ * - fg did not change (or both are in selection and selection fg is set)
160
+ * - ext did not change
161
+ * - underline from hover state did not change
162
+ * - cell content renders to same letter-spacing
163
+ * - cell is not cursor
164
+ */
165
+ if (
166
+ cellAmount
167
+ && (
168
+ (isInSelection && oldIsInSelection)
169
+ || (!isInSelection && !oldIsInSelection && cell.bg === oldBg)
170
+ )
171
+ && (
172
+ (isInSelection && oldIsInSelection && colors.selectionForeground)
173
+ || cell.fg === oldFg
174
+ )
175
+ && cell.extended.ext === oldExt
176
+ && isLinkHover === oldLinkHover
177
+ && spacing === oldSpacing
178
+ && !isCursorCell
179
+ && !isJoined
180
+ && !isDecorated
181
+ ) {
182
+ // no span alterations, thus only account chars skipping all code below
183
+ if (cell.isInvisible()) {
184
+ text += WHITESPACE_CELL_CHAR;
185
+ } else {
186
+ text += chars;
187
+ }
188
+ cellAmount++;
189
+ continue;
190
+ } else {
191
+ /**
192
+ * cannot merge:
193
+ * - apply left-over text to old span
194
+ * - create new span, reset state holders cellAmount & text
195
+ */
196
+ if (cellAmount) {
197
+ charElement.textContent = text;
198
+ }
199
+ charElement = this._document.createElement('span');
200
+ cellAmount = 0;
201
+ text = '';
202
+ }
203
+ }
204
+ // preserve conditions for next merger eval round
205
+ oldBg = cell.bg;
206
+ oldFg = cell.fg;
207
+ oldExt = cell.extended.ext;
208
+ oldLinkHover = isLinkHover;
209
+ oldSpacing = spacing;
210
+ oldIsInSelection = isInSelection;
211
+
212
+ if (isJoined) {
213
+ // The DOM renderer colors the background of the cursor but for ligatures all cells are
214
+ // joined. The workaround here is to show a cursor around the whole ligature so it shows up,
215
+ // the cursor looks the same when on any character of the ligature though
216
+ if (cursorX >= x && cursorX <= lastCharX) {
217
+ cursorX = x;
218
+ }
219
+ }
220
+
221
+ if (!this._coreService.isCursorHidden && isCursorCell && this._coreService.isCursorInitialized) {
222
+ classes.push(RowCss.CURSOR_CLASS);
223
+ if (this._coreBrowserService.isFocused) {
224
+ if (cursorBlink) {
225
+ classes.push(RowCss.CURSOR_BLINK_CLASS);
226
+ }
227
+ classes.push(
228
+ cursorStyle === 'bar'
229
+ ? RowCss.CURSOR_STYLE_BAR_CLASS
230
+ : cursorStyle === 'underline'
231
+ ? RowCss.CURSOR_STYLE_UNDERLINE_CLASS
232
+ : RowCss.CURSOR_STYLE_BLOCK_CLASS
233
+ );
234
+ } else {
235
+ if (cursorInactiveStyle) {
236
+ switch (cursorInactiveStyle) {
237
+ case 'outline':
238
+ classes.push(RowCss.CURSOR_STYLE_OUTLINE_CLASS);
239
+ break;
240
+ case 'block':
241
+ classes.push(RowCss.CURSOR_STYLE_BLOCK_CLASS);
242
+ break;
243
+ case 'bar':
244
+ classes.push(RowCss.CURSOR_STYLE_BAR_CLASS);
245
+ break;
246
+ case 'underline':
247
+ classes.push(RowCss.CURSOR_STYLE_UNDERLINE_CLASS);
248
+ break;
249
+ default:
250
+ break;
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ if (cell.isBold()) {
257
+ classes.push(RowCss.BOLD_CLASS);
258
+ }
259
+
260
+ if (cell.isItalic()) {
261
+ classes.push(RowCss.ITALIC_CLASS);
262
+ }
263
+
264
+ if (cell.isDim()) {
265
+ classes.push(RowCss.DIM_CLASS);
266
+ }
267
+
268
+ if (cell.isInvisible()) {
269
+ text = WHITESPACE_CELL_CHAR;
270
+ } else {
271
+ text = cell.getChars() || WHITESPACE_CELL_CHAR;
272
+ }
273
+
274
+ if (cell.isUnderline()) {
275
+ classes.push(`${RowCss.UNDERLINE_CLASS}-${cell.extended.underlineStyle}`);
276
+ if (text === ' ') {
277
+ text = '\xa0'; // = &nbsp;
278
+ }
279
+ if (!cell.isUnderlineColorDefault()) {
280
+ if (cell.isUnderlineColorRGB()) {
281
+ charElement.style.textDecorationColor = `rgb(${AttributeData.toColorRGB(cell.getUnderlineColor()).join(',')})`;
282
+ } else {
283
+ let fg = cell.getUnderlineColor();
284
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
285
+ fg += 8;
286
+ }
287
+ charElement.style.textDecorationColor = colors.ansi[fg].css;
288
+ }
289
+ }
290
+ }
291
+
292
+ if (cell.isOverline()) {
293
+ classes.push(RowCss.OVERLINE_CLASS);
294
+ if (text === ' ') {
295
+ text = '\xa0'; // = &nbsp;
296
+ }
297
+ }
298
+
299
+ if (cell.isStrikethrough()) {
300
+ classes.push(RowCss.STRIKETHROUGH_CLASS);
301
+ }
302
+
303
+ // apply link hover underline late, effectively overrides any previous text-decoration
304
+ // settings
305
+ if (isLinkHover) {
306
+ charElement.style.textDecoration = 'underline';
307
+ }
308
+
309
+ let fg = cell.getFgColor();
310
+ let fgColorMode = cell.getFgColorMode();
311
+ let bg = cell.getBgColor();
312
+ let bgColorMode = cell.getBgColorMode();
313
+ const isInverse = !!cell.isInverse();
314
+ if (isInverse) {
315
+ const temp = fg;
316
+ fg = bg;
317
+ bg = temp;
318
+ const temp2 = fgColorMode;
319
+ fgColorMode = bgColorMode;
320
+ bgColorMode = temp2;
321
+ }
322
+
323
+ // Apply any decoration foreground/background overrides, this must happen after inverse has
324
+ // been applied
325
+ let bgOverride: IColor | undefined;
326
+ let fgOverride: IColor | undefined;
327
+ let isTop = false;
328
+ this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {
329
+ if (d.options.layer !== 'top' && isTop) {
330
+ return;
331
+ }
332
+ if (d.backgroundColorRGB) {
333
+ bgColorMode = Attributes.CM_RGB;
334
+ bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
335
+ bgOverride = d.backgroundColorRGB;
336
+ }
337
+ if (d.foregroundColorRGB) {
338
+ fgColorMode = Attributes.CM_RGB;
339
+ fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
340
+ fgOverride = d.foregroundColorRGB;
341
+ }
342
+ isTop = d.options.layer === 'top';
343
+ });
344
+
345
+ // Apply selection
346
+ if (!isTop && isInSelection) {
347
+ // If in the selection, force the element to be above the selection to improve contrast and
348
+ // support opaque selections. The applies background is not actually needed here as
349
+ // selection is drawn in a seperate container, the main purpose of this to ensuring minimum
350
+ // contrast ratio
351
+ bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque;
352
+ bg = bgOverride.rgba >> 8 & 0xFFFFFF;
353
+ bgColorMode = Attributes.CM_RGB;
354
+ // Since an opaque selection is being rendered, the selection pretends to be a decoration to
355
+ // ensure text is drawn above the selection.
356
+ isTop = true;
357
+ // Apply selection foreground if applicable
358
+ if (colors.selectionForeground) {
359
+ fgColorMode = Attributes.CM_RGB;
360
+ fg = colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
361
+ fgOverride = colors.selectionForeground;
362
+ }
363
+ }
364
+
365
+ // If it's a top decoration, render above the selection
366
+ if (isTop) {
367
+ classes.push('xterm-decoration-top');
368
+ }
369
+
370
+ // Background
371
+ let resolvedBg: IColor;
372
+ switch (bgColorMode) {
373
+ case Attributes.CM_P16:
374
+ case Attributes.CM_P256:
375
+ resolvedBg = colors.ansi[bg];
376
+ classes.push(`xterm-bg-${bg}`);
377
+ break;
378
+ case Attributes.CM_RGB:
379
+ resolvedBg = rgba.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
380
+ this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
381
+ break;
382
+ case Attributes.CM_DEFAULT:
383
+ default:
384
+ if (isInverse) {
385
+ resolvedBg = colors.foreground;
386
+ classes.push(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
387
+ } else {
388
+ resolvedBg = colors.background;
389
+ }
390
+ }
391
+
392
+ // If there is no background override by now it's the original color, so apply dim if needed
393
+ if (!bgOverride) {
394
+ if (cell.isDim()) {
395
+ bgOverride = color.multiplyOpacity(resolvedBg, 0.5);
396
+ }
397
+ }
398
+
399
+ // Foreground
400
+ switch (fgColorMode) {
401
+ case Attributes.CM_P16:
402
+ case Attributes.CM_P256:
403
+ if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {
404
+ fg += 8;
405
+ }
406
+ if (!this._applyMinimumContrast(charElement, resolvedBg, colors.ansi[fg], cell, bgOverride, undefined)) {
407
+ classes.push(`xterm-fg-${fg}`);
408
+ }
409
+ break;
410
+ case Attributes.CM_RGB:
411
+ const color = rgba.toColor(
412
+ (fg >> 16) & 0xFF,
413
+ (fg >> 8) & 0xFF,
414
+ (fg ) & 0xFF
415
+ );
416
+ if (!this._applyMinimumContrast(charElement, resolvedBg, color, cell, bgOverride, fgOverride)) {
417
+ this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
418
+ }
419
+ break;
420
+ case Attributes.CM_DEFAULT:
421
+ default:
422
+ if (!this._applyMinimumContrast(charElement, resolvedBg, colors.foreground, cell, bgOverride, fgOverride)) {
423
+ if (isInverse) {
424
+ classes.push(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
425
+ }
426
+ }
427
+ }
428
+
429
+ // apply CSS classes
430
+ // slightly faster than using classList by omitting
431
+ // checks for doubled entries (code above should not have doublets)
432
+ if (classes.length) {
433
+ charElement.className = classes.join(' ');
434
+ classes.length = 0;
435
+ }
436
+
437
+ // exclude conditions for cell merging - never merge these
438
+ if (!isCursorCell && !isJoined && !isDecorated) {
439
+ cellAmount++;
440
+ } else {
441
+ charElement.textContent = text;
442
+ }
443
+ // apply letter-spacing rule
444
+ if (spacing !== this.defaultSpacing) {
445
+ charElement.style.letterSpacing = `${spacing}px`;
446
+ }
447
+
448
+ elements.push(charElement);
449
+ x = lastCharX;
450
+ }
451
+
452
+ // postfix text of last merged span
453
+ if (charElement && cellAmount) {
454
+ charElement.textContent = text;
455
+ }
456
+
457
+ return elements;
458
+ }
459
+
460
+ private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {
461
+ if (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode())) {
462
+ return false;
463
+ }
464
+
465
+ // Try get from cache first, only use the cache when there are no decoration overrides
466
+ const cache = this._getContrastCache(cell);
467
+ let adjustedColor: IColor | undefined | null = undefined;
468
+ if (!bgOverride && !fgOverride) {
469
+ adjustedColor = cache.getColor(bg.rgba, fg.rgba);
470
+ }
471
+
472
+ // Calculate and store in cache
473
+ if (adjustedColor === undefined) {
474
+ // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
475
+ // non-dim cells
476
+ const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1);
477
+ adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, ratio);
478
+ cache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
479
+ }
480
+
481
+ if (adjustedColor) {
482
+ this._addStyle(element, `color:${adjustedColor.css}`);
483
+ return true;
484
+ }
485
+
486
+ return false;
487
+ }
488
+
489
+ private _getContrastCache(cell: ICellData): IColorContrastCache {
490
+ if (cell.isDim()) {
491
+ return this._themeService.colors.halfContrastCache;
492
+ }
493
+ return this._themeService.colors.contrastCache;
494
+ }
495
+
496
+ private _addStyle(element: HTMLElement, style: string): void {
497
+ element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
498
+ }
499
+
500
+ private _isCellInSelection(x: number, y: number): boolean {
501
+ const start = this._selectionStart;
502
+ const end = this._selectionEnd;
503
+ if (!start || !end) {
504
+ return false;
505
+ }
506
+ if (this._columnSelectMode) {
507
+ if (start[0] <= end[0]) {
508
+ return x >= start[0] && y >= start[1] &&
509
+ x < end[0] && y <= end[1];
510
+ }
511
+ return x < start[0] && y >= start[1] &&
512
+ x >= end[0] && y <= end[1];
513
+ }
514
+ return (y > start[1] && y < end[1]) ||
515
+ (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||
516
+ (start[1] < end[1] && y === end[1] && x < end[0]) ||
517
+ (start[1] < end[1] && y === start[1] && x >= start[0]);
518
+ }
519
+ }
520
+
521
+ function padStart(text: string, padChar: string, length: number): string {
522
+ while (text.length < length) {
523
+ text = padChar + text;
524
+ }
525
+ return text;
526
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Copyright (c) 2023 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { IDisposable } from 'common/Types';
7
+ import { FontWeight } from 'common/services/Services';
8
+
9
+
10
+ export const enum WidthCacheSettings {
11
+ /** sentinel for unset values in flat cache */
12
+ FLAT_UNSET = -9999,
13
+ /** size of flat cache, size-1 equals highest codepoint handled by flat */
14
+ FLAT_SIZE = 256,
15
+ /** char repeat for measuring */
16
+ REPEAT = 32
17
+ }
18
+
19
+
20
+ const enum FontVariant {
21
+ REGULAR = 0,
22
+ BOLD = 1,
23
+ ITALIC = 2,
24
+ BOLD_ITALIC = 3
25
+ }
26
+
27
+
28
+ export class WidthCache implements IDisposable {
29
+ // flat cache for regular variant up to CacheSettings.FLAT_SIZE
30
+ // NOTE: ~4x faster access than holey (serving >>80% of terminal content)
31
+ // It has a small memory footprint (only 1MB for full BMP caching),
32
+ // still the sweet spot is not reached before touching 32k different codepoints,
33
+ // thus we store the remaining <<20% of terminal data in a holey structure.
34
+ protected _flat = new Float32Array(WidthCacheSettings.FLAT_SIZE);
35
+
36
+ // holey cache for bold, italic and bold&italic for any string
37
+ // FIXME: can grow really big over time (~8.5 MB for full BMP caching),
38
+ // so a shared API across terminals is needed
39
+ protected _holey: Map<string, number> | undefined;
40
+
41
+ private _font = '';
42
+ private _fontSize = 0;
43
+ private _weight: FontWeight = 'normal';
44
+ 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);
81
+
82
+ this.clear();
83
+ }
84
+
85
+ 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
89
+ }
90
+
91
+ /**
92
+ * Clear the width cache.
93
+ */
94
+ public clear(): void {
95
+ this._flat.fill(WidthCacheSettings.FLAT_UNSET);
96
+ // .clear() has some overhead, re-assign instead (>3 times faster)
97
+ this._holey = new Map<string, number>();
98
+ }
99
+
100
+ /**
101
+ * Set the font for measuring.
102
+ * Must be called for any changes on font settings.
103
+ * Also clears the cache.
104
+ */
105
+ public setFont(font: string, fontSize: number, weight: FontWeight, weightBold: FontWeight): void {
106
+ // skip if nothing changed
107
+ if (font === this._font
108
+ && fontSize === this._fontSize
109
+ && weight === this._weight
110
+ && weightBold === this._weightBold
111
+ ) {
112
+ return;
113
+ }
114
+
115
+ this._font = font;
116
+ this._fontSize = fontSize;
117
+ this._weight = weight;
118
+ this._weightBold = weightBold;
119
+
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}`;
126
+
127
+ this.clear();
128
+ }
129
+
130
+ /**
131
+ * Get the render width for cell content `c` with current font settings.
132
+ * `variant` denotes the font variant to be used.
133
+ */
134
+ public get(c: string, bold: boolean | number, italic: boolean | number): number {
135
+ let cp = 0;
136
+ if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) {
137
+ return this._flat[cp] !== WidthCacheSettings.FLAT_UNSET
138
+ ? this._flat[cp]
139
+ : (this._flat[cp] = this._measure(c, 0));
140
+ }
141
+ let key = c;
142
+ if (bold) key += 'B';
143
+ if (italic) key += 'I';
144
+ let width = this._holey!.get(key);
145
+ if (width === undefined) {
146
+ let variant = 0;
147
+ if (bold) variant |= FontVariant.BOLD;
148
+ if (italic) variant |= FontVariant.ITALIC;
149
+ width = this._measure(c, variant);
150
+ this._holey!.set(key, width);
151
+ }
152
+ return width;
153
+ }
154
+
155
+ protected _measure(c: string, variant: FontVariant): number {
156
+ const el = this._measureElements[variant];
157
+ el.textContent = c.repeat(WidthCacheSettings.REPEAT);
158
+ return el.offsetWidth / WidthCacheSettings.REPEAT;
159
+ }
160
+ }