@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.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/css/xterm.css +209 -0
- package/lib/xterm.js +2 -0
- package/lib/xterm.js.map +1 -0
- package/package.json +101 -0
- package/src/browser/AccessibilityManager.ts +278 -0
- package/src/browser/Clipboard.ts +93 -0
- package/src/browser/ColorContrastCache.ts +34 -0
- package/src/browser/Lifecycle.ts +33 -0
- package/src/browser/Linkifier2.ts +416 -0
- package/src/browser/LocalizableStrings.ts +12 -0
- package/src/browser/OscLinkProvider.ts +128 -0
- package/src/browser/RenderDebouncer.ts +83 -0
- package/src/browser/Terminal.ts +1317 -0
- package/src/browser/TimeBasedDebouncer.ts +86 -0
- package/src/browser/Types.d.ts +181 -0
- package/src/browser/Viewport.ts +401 -0
- package/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
- package/src/browser/decorations/ColorZoneStore.ts +117 -0
- package/src/browser/decorations/OverviewRulerRenderer.ts +218 -0
- package/src/browser/input/CompositionHelper.ts +246 -0
- package/src/browser/input/Mouse.ts +54 -0
- package/src/browser/input/MoveToCell.ts +249 -0
- package/src/browser/public/Terminal.ts +260 -0
- package/src/browser/renderer/dom/DomRenderer.ts +509 -0
- package/src/browser/renderer/dom/DomRendererRowFactory.ts +526 -0
- package/src/browser/renderer/dom/WidthCache.ts +160 -0
- package/src/browser/renderer/shared/CellColorResolver.ts +137 -0
- package/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
- package/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
- package/src/browser/renderer/shared/Constants.ts +14 -0
- package/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
- package/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
- package/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
- package/src/browser/renderer/shared/README.md +1 -0
- package/src/browser/renderer/shared/RendererUtils.ts +58 -0
- package/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
- package/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
- package/src/browser/renderer/shared/Types.d.ts +173 -0
- package/src/browser/selection/SelectionModel.ts +144 -0
- package/src/browser/selection/Types.d.ts +15 -0
- package/src/browser/services/CharSizeService.ts +102 -0
- package/src/browser/services/CharacterJoinerService.ts +339 -0
- package/src/browser/services/CoreBrowserService.ts +137 -0
- package/src/browser/services/MouseService.ts +46 -0
- package/src/browser/services/RenderService.ts +279 -0
- package/src/browser/services/SelectionService.ts +1031 -0
- package/src/browser/services/Services.ts +147 -0
- package/src/browser/services/ThemeService.ts +237 -0
- package/src/common/CircularList.ts +241 -0
- package/src/common/Clone.ts +23 -0
- package/src/common/Color.ts +357 -0
- package/src/common/CoreTerminal.ts +284 -0
- package/src/common/EventEmitter.ts +78 -0
- package/src/common/InputHandler.ts +3461 -0
- package/src/common/Lifecycle.ts +108 -0
- package/src/common/MultiKeyMap.ts +42 -0
- package/src/common/Platform.ts +44 -0
- package/src/common/SortedList.ts +118 -0
- package/src/common/TaskQueue.ts +166 -0
- package/src/common/TypedArrayUtils.ts +17 -0
- package/src/common/Types.d.ts +553 -0
- package/src/common/WindowsMode.ts +27 -0
- package/src/common/buffer/AttributeData.ts +196 -0
- package/src/common/buffer/Buffer.ts +654 -0
- package/src/common/buffer/BufferLine.ts +524 -0
- package/src/common/buffer/BufferRange.ts +13 -0
- package/src/common/buffer/BufferReflow.ts +223 -0
- package/src/common/buffer/BufferSet.ts +134 -0
- package/src/common/buffer/CellData.ts +94 -0
- package/src/common/buffer/Constants.ts +149 -0
- package/src/common/buffer/Marker.ts +43 -0
- package/src/common/buffer/Types.d.ts +52 -0
- package/src/common/data/Charsets.ts +256 -0
- package/src/common/data/EscapeSequences.ts +153 -0
- package/src/common/input/Keyboard.ts +398 -0
- package/src/common/input/TextDecoder.ts +346 -0
- package/src/common/input/UnicodeV6.ts +145 -0
- package/src/common/input/WriteBuffer.ts +246 -0
- package/src/common/input/XParseColor.ts +80 -0
- package/src/common/parser/Constants.ts +58 -0
- package/src/common/parser/DcsParser.ts +192 -0
- package/src/common/parser/EscapeSequenceParser.ts +792 -0
- package/src/common/parser/OscParser.ts +238 -0
- package/src/common/parser/Params.ts +229 -0
- package/src/common/parser/Types.d.ts +275 -0
- package/src/common/public/AddonManager.ts +53 -0
- package/src/common/public/BufferApiView.ts +35 -0
- package/src/common/public/BufferLineApiView.ts +29 -0
- package/src/common/public/BufferNamespaceApi.ts +36 -0
- package/src/common/public/ParserApi.ts +37 -0
- package/src/common/public/UnicodeApi.ts +27 -0
- package/src/common/services/BufferService.ts +151 -0
- package/src/common/services/CharsetService.ts +34 -0
- package/src/common/services/CoreMouseService.ts +318 -0
- package/src/common/services/CoreService.ts +87 -0
- package/src/common/services/DecorationService.ts +140 -0
- package/src/common/services/InstantiationService.ts +85 -0
- package/src/common/services/LogService.ts +124 -0
- package/src/common/services/OptionsService.ts +202 -0
- package/src/common/services/OscLinkService.ts +115 -0
- package/src/common/services/ServiceRegistry.ts +49 -0
- package/src/common/services/Services.ts +373 -0
- package/src/common/services/UnicodeService.ts +111 -0
- package/src/headless/Terminal.ts +136 -0
- package/src/headless/public/Terminal.ts +195 -0
- 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'; // =
|
|
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'; // =
|
|
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
|
+
}
|