@xterm/xterm 5.4.0-beta.2 → 5.4.0-beta.20
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/css/xterm.css +10 -1
- package/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/package.json +1 -1
- package/src/browser/AccessibilityManager.ts +132 -3
- package/src/browser/Linkifier2.ts +3 -0
- package/src/browser/RenderDebouncer.ts +6 -5
- package/src/browser/Terminal.ts +18 -16
- package/src/browser/Types.d.ts +1 -0
- package/src/browser/public/Terminal.ts +3 -0
- package/src/browser/renderer/dom/DomRenderer.ts +20 -9
- package/src/browser/renderer/dom/DomRendererRowFactory.ts +2 -2
- package/src/browser/renderer/shared/CellColorResolver.ts +110 -11
- package/src/browser/renderer/shared/RendererUtils.ts +5 -1
- package/src/browser/renderer/shared/SelectionRenderModel.ts +5 -3
- package/src/browser/renderer/shared/TextureAtlas.ts +27 -8
- package/src/browser/renderer/shared/Types.d.ts +2 -2
- package/src/browser/services/RenderService.ts +13 -8
- package/src/common/Color.ts +17 -0
- package/src/common/InputHandler.ts +15 -19
- package/src/common/Types.d.ts +7 -5
- package/src/common/buffer/AttributeData.ts +15 -0
- package/src/common/buffer/BufferLine.ts +47 -20
- package/src/common/buffer/Constants.ts +10 -2
- package/src/common/input/Keyboard.ts +2 -3
- package/src/common/services/OptionsService.ts +8 -1
- package/typings/xterm.d.ts +22 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
|
2
2
|
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
3
3
|
import { ReadonlyColorSet } from 'browser/Types';
|
|
4
|
-
import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants';
|
|
5
|
-
import { IDecorationService } from 'common/services/Services';
|
|
4
|
+
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
|
5
|
+
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
|
6
6
|
import { ICellData } from 'common/Types';
|
|
7
7
|
import { Terminal } from '@xterm/xterm';
|
|
8
|
+
import { rgba } from 'common/Color';
|
|
9
|
+
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
|
8
10
|
|
|
9
11
|
// Work variables to avoid garbage collection
|
|
10
12
|
let $fg = 0;
|
|
@@ -13,6 +15,7 @@ let $hasFg = false;
|
|
|
13
15
|
let $hasBg = false;
|
|
14
16
|
let $isSelected = false;
|
|
15
17
|
let $colors: ReadonlyColorSet | undefined;
|
|
18
|
+
let $variantOffset = 0;
|
|
16
19
|
|
|
17
20
|
export class CellColorResolver {
|
|
18
21
|
/**
|
|
@@ -27,6 +30,7 @@ export class CellColorResolver {
|
|
|
27
30
|
|
|
28
31
|
constructor(
|
|
29
32
|
private readonly _terminal: Terminal,
|
|
33
|
+
private readonly _optionService: IOptionsService,
|
|
30
34
|
private readonly _selectionRenderModel: ISelectionRenderModel,
|
|
31
35
|
private readonly _decorationService: IDecorationService,
|
|
32
36
|
private readonly _coreBrowserService: ICoreBrowserService,
|
|
@@ -38,7 +42,7 @@ export class CellColorResolver {
|
|
|
38
42
|
* Resolves colors for the cell, putting the result into the shared {@link result}. This resolves
|
|
39
43
|
* overrides, inverse and selection for the cell which can then be used to feed into the renderer.
|
|
40
44
|
*/
|
|
41
|
-
public resolve(cell: ICellData, x: number, y: number): void {
|
|
45
|
+
public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void {
|
|
42
46
|
this.result.bg = cell.bg;
|
|
43
47
|
this.result.fg = cell.fg;
|
|
44
48
|
this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
|
|
@@ -52,15 +56,22 @@ export class CellColorResolver {
|
|
|
52
56
|
$hasFg = false;
|
|
53
57
|
$isSelected = false;
|
|
54
58
|
$colors = this._themeService.colors;
|
|
59
|
+
$variantOffset = 0;
|
|
60
|
+
|
|
61
|
+
const code = cell.getCode();
|
|
62
|
+
if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) {
|
|
63
|
+
const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));
|
|
64
|
+
$variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);
|
|
65
|
+
}
|
|
55
66
|
|
|
56
67
|
// Apply decorations on the bottom layer
|
|
57
68
|
this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
|
|
58
69
|
if (d.backgroundColorRGB) {
|
|
59
|
-
$bg = d.backgroundColorRGB.rgba >> 8 &
|
|
70
|
+
$bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;
|
|
60
71
|
$hasBg = true;
|
|
61
72
|
}
|
|
62
73
|
if (d.foregroundColorRGB) {
|
|
63
|
-
$fg = d.foregroundColorRGB.rgba >> 8 &
|
|
74
|
+
$fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;
|
|
64
75
|
$hasFg = true;
|
|
65
76
|
}
|
|
66
77
|
});
|
|
@@ -68,10 +79,94 @@ export class CellColorResolver {
|
|
|
68
79
|
// Apply the selection color if needed
|
|
69
80
|
$isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y);
|
|
70
81
|
if ($isSelected) {
|
|
71
|
-
|
|
82
|
+
// If the cell has a bg color, retain the color by blending it with the selection color
|
|
83
|
+
if (
|
|
84
|
+
(this.result.fg & FgFlags.INVERSE) ||
|
|
85
|
+
(this.result.bg & Attributes.CM_MASK) !== Attributes.CM_DEFAULT
|
|
86
|
+
) {
|
|
87
|
+
// Resolve the standard bg color
|
|
88
|
+
if (this.result.fg & FgFlags.INVERSE) {
|
|
89
|
+
switch (this.result.fg & Attributes.CM_MASK) {
|
|
90
|
+
case Attributes.CM_P16:
|
|
91
|
+
case Attributes.CM_P256:
|
|
92
|
+
$bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;
|
|
93
|
+
break;
|
|
94
|
+
case Attributes.CM_RGB:
|
|
95
|
+
$bg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF;
|
|
96
|
+
break;
|
|
97
|
+
case Attributes.CM_DEFAULT:
|
|
98
|
+
default:
|
|
99
|
+
$bg = this._themeService.colors.foreground.rgba;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
switch (this.result.bg & Attributes.CM_MASK) {
|
|
103
|
+
case Attributes.CM_P16:
|
|
104
|
+
case Attributes.CM_P256:
|
|
105
|
+
$bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;
|
|
106
|
+
break;
|
|
107
|
+
case Attributes.CM_RGB:
|
|
108
|
+
$bg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF;
|
|
109
|
+
break;
|
|
110
|
+
// No need to consider default bg color here as it's not possible
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Blend with selection bg color
|
|
114
|
+
$bg = rgba.blend(
|
|
115
|
+
$bg,
|
|
116
|
+
((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80
|
|
117
|
+
) >> 8 & Attributes.RGB_MASK;
|
|
118
|
+
} else {
|
|
119
|
+
$bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK;
|
|
120
|
+
}
|
|
72
121
|
$hasBg = true;
|
|
122
|
+
|
|
123
|
+
// Apply explicit selection foreground if present
|
|
73
124
|
if ($colors.selectionForeground) {
|
|
74
|
-
$fg = $colors.selectionForeground.rgba >> 8 &
|
|
125
|
+
$fg = $colors.selectionForeground.rgba >> 8 & Attributes.RGB_MASK;
|
|
126
|
+
$hasFg = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Overwrite fg as bg if it's a special decorative glyph (eg. powerline)
|
|
130
|
+
if (treatGlyphAsBackgroundColor(cell.getCode())) {
|
|
131
|
+
// Inverse default background should be treated as transparent
|
|
132
|
+
if (
|
|
133
|
+
(this.result.fg & FgFlags.INVERSE) &&
|
|
134
|
+
(this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT
|
|
135
|
+
) {
|
|
136
|
+
$fg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK;
|
|
137
|
+
} else {
|
|
138
|
+
|
|
139
|
+
if (this.result.fg & FgFlags.INVERSE) {
|
|
140
|
+
switch (this.result.bg & Attributes.CM_MASK) {
|
|
141
|
+
case Attributes.CM_P16:
|
|
142
|
+
case Attributes.CM_P256:
|
|
143
|
+
$fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;
|
|
144
|
+
break;
|
|
145
|
+
case Attributes.CM_RGB:
|
|
146
|
+
$fg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF;
|
|
147
|
+
break;
|
|
148
|
+
// No need to consider default bg color here as it's not possible
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
switch (this.result.fg & Attributes.CM_MASK) {
|
|
152
|
+
case Attributes.CM_P16:
|
|
153
|
+
case Attributes.CM_P256:
|
|
154
|
+
$fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;
|
|
155
|
+
break;
|
|
156
|
+
case Attributes.CM_RGB:
|
|
157
|
+
$fg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF;
|
|
158
|
+
break;
|
|
159
|
+
case Attributes.CM_DEFAULT:
|
|
160
|
+
default:
|
|
161
|
+
$fg = this._themeService.colors.foreground.rgba;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
$fg = rgba.blend(
|
|
166
|
+
$fg,
|
|
167
|
+
((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80
|
|
168
|
+
) >> 8 & Attributes.RGB_MASK;
|
|
169
|
+
}
|
|
75
170
|
$hasFg = true;
|
|
76
171
|
}
|
|
77
172
|
}
|
|
@@ -79,11 +174,11 @@ export class CellColorResolver {
|
|
|
79
174
|
// Apply decorations on the top layer
|
|
80
175
|
this._decorationService.forEachDecorationAtCell(x, y, 'top', d => {
|
|
81
176
|
if (d.backgroundColorRGB) {
|
|
82
|
-
$bg = d.backgroundColorRGB.rgba >> 8 &
|
|
177
|
+
$bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;
|
|
83
178
|
$hasBg = true;
|
|
84
179
|
}
|
|
85
180
|
if (d.foregroundColorRGB) {
|
|
86
|
-
$fg = d.foregroundColorRGB.rgba >> 8 &
|
|
181
|
+
$fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;
|
|
87
182
|
$hasFg = true;
|
|
88
183
|
}
|
|
89
184
|
});
|
|
@@ -110,7 +205,7 @@ export class CellColorResolver {
|
|
|
110
205
|
if ($hasBg && !$hasFg) {
|
|
111
206
|
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
|
112
207
|
if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
|
113
|
-
$fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 &
|
|
208
|
+
$fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
|
114
209
|
} else {
|
|
115
210
|
$fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
|
116
211
|
}
|
|
@@ -119,7 +214,7 @@ export class CellColorResolver {
|
|
|
119
214
|
if (!$hasBg && $hasFg) {
|
|
120
215
|
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
|
121
216
|
if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
|
122
|
-
$bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 &
|
|
217
|
+
$bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
|
123
218
|
} else {
|
|
124
219
|
$bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
|
125
220
|
}
|
|
@@ -133,5 +228,9 @@ export class CellColorResolver {
|
|
|
133
228
|
// Use the override if it exists
|
|
134
229
|
this.result.bg = $hasBg ? $bg : this.result.bg;
|
|
135
230
|
this.result.fg = $hasFg ? $fg : this.result.fg;
|
|
231
|
+
|
|
232
|
+
// Reset overrides variantOffset
|
|
233
|
+
this.result.ext &= ~ExtFlags.VARIANT_OFFSET;
|
|
234
|
+
this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET;
|
|
136
235
|
}
|
|
137
236
|
}
|
|
@@ -27,7 +27,7 @@ function isBoxOrBlockGlyph(codepoint: number): boolean {
|
|
|
27
27
|
return 0x2500 <= codepoint && codepoint <= 0x259F;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function
|
|
30
|
+
export function treatGlyphAsBackgroundColor(codepoint: number): boolean {
|
|
31
31
|
return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -56,3 +56,7 @@ function createDimension(): IDimensions {
|
|
|
56
56
|
height: 0
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
export function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number {
|
|
61
|
+
return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2);
|
|
62
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @license MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { ITerminal } from 'browser/Types';
|
|
6
7
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
|
7
8
|
import { Terminal } from '@xterm/xterm';
|
|
8
9
|
|
|
@@ -35,7 +36,7 @@ class SelectionRenderModel implements ISelectionRenderModel {
|
|
|
35
36
|
this.selectionEnd = undefined;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
public update(terminal:
|
|
39
|
+
public update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
|
39
40
|
this.selectionStart = start;
|
|
40
41
|
this.selectionEnd = end;
|
|
41
42
|
// Selection does not exist
|
|
@@ -45,8 +46,9 @@ class SelectionRenderModel implements ISelectionRenderModel {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// Translate from buffer position to viewport position
|
|
48
|
-
const
|
|
49
|
-
const
|
|
49
|
+
const viewportY = terminal.buffers.active.ydisp;
|
|
50
|
+
const viewportStartRow = start[1] - viewportY;
|
|
51
|
+
const viewportEndRow = end[1] - viewportY;
|
|
50
52
|
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
|
51
53
|
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
|
|
52
54
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { IColorContrastCache } from 'browser/Types';
|
|
7
7
|
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
|
8
8
|
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
|
9
|
-
import {
|
|
9
|
+
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
|
10
10
|
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types';
|
|
11
11
|
import { NULL_COLOR, color, rgba } from 'common/Color';
|
|
12
12
|
import { EventEmitter } from 'common/EventEmitter';
|
|
@@ -15,7 +15,6 @@ import { IdleTaskQueue } from 'common/TaskQueue';
|
|
|
15
15
|
import { IColor } from 'common/Types';
|
|
16
16
|
import { AttributeData } from 'common/buffer/AttributeData';
|
|
17
17
|
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
|
|
18
|
-
import { traceCall } from 'common/services/LogService';
|
|
19
18
|
import { IUnicodeService } from 'common/services/Services';
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -424,7 +423,6 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
424
423
|
return this._config.colors.contrastCache;
|
|
425
424
|
}
|
|
426
425
|
|
|
427
|
-
@traceCall
|
|
428
426
|
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph {
|
|
429
427
|
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
|
430
428
|
|
|
@@ -492,7 +490,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
492
490
|
|
|
493
491
|
const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
|
|
494
492
|
const restrictedPowerlineGlyph = chars.length === 1 && isRestrictedPowerlineGlyph(chars.charCodeAt(0));
|
|
495
|
-
const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold,
|
|
493
|
+
const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, treatGlyphAsBackgroundColor(chars.charCodeAt(0)));
|
|
496
494
|
this._tmpCtx.fillStyle = foregroundColor.css;
|
|
497
495
|
|
|
498
496
|
// For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
|
|
@@ -545,6 +543,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
545
543
|
const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
|
|
546
544
|
const yMid = yTop + lineWidth;
|
|
547
545
|
const yBot = yTop + lineWidth * 2;
|
|
546
|
+
let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
|
|
548
547
|
|
|
549
548
|
for (let i = 0; i < chWidth; i++) {
|
|
550
549
|
this._tmpCtx.save();
|
|
@@ -594,12 +593,32 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
594
593
|
);
|
|
595
594
|
break;
|
|
596
595
|
case UnderlineStyle.DOTTED:
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
596
|
+
const offsetWidth = nextOffset === 0 ? 0 :
|
|
597
|
+
(nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset);
|
|
598
|
+
// a line and a gap.
|
|
599
|
+
const isLineStart = nextOffset >= lineWidth ? false : true;
|
|
600
|
+
if (isLineStart === false || offsetWidth === 0) {
|
|
601
|
+
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
|
|
602
|
+
this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
|
|
603
|
+
this._tmpCtx.lineTo(xChRight, yTop);
|
|
604
|
+
} else {
|
|
605
|
+
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
|
|
606
|
+
this._tmpCtx.moveTo(xChLeft, yTop);
|
|
607
|
+
this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop);
|
|
608
|
+
this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop);
|
|
609
|
+
this._tmpCtx.lineTo(xChRight, yTop);
|
|
610
|
+
}
|
|
611
|
+
nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
|
|
600
612
|
break;
|
|
601
613
|
case UnderlineStyle.DASHED:
|
|
602
|
-
|
|
614
|
+
const lineRatio = 0.6;
|
|
615
|
+
const gapRatio = 0.3;
|
|
616
|
+
// End line ratio is approximately equal to 0.1
|
|
617
|
+
const xChWidth = xChRight - xChLeft;
|
|
618
|
+
const line = Math.floor(lineRatio * xChWidth);
|
|
619
|
+
const gap = Math.floor(gapRatio * xChWidth);
|
|
620
|
+
const end = xChWidth - line - gap;
|
|
621
|
+
this._tmpCtx.setLineDash([line, gap, end]);
|
|
603
622
|
this._tmpCtx.moveTo(xChLeft, yTop);
|
|
604
623
|
this._tmpCtx.lineTo(xChRight, yTop);
|
|
605
624
|
break;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { FontWeight, Terminal } from '@xterm/xterm';
|
|
7
|
-
import { IColorSet } from 'browser/Types';
|
|
7
|
+
import { IColorSet, ITerminal } from 'browser/Types';
|
|
8
8
|
import { IDisposable } from 'common/Types';
|
|
9
9
|
import { IEvent } from 'common/EventEmitter';
|
|
10
10
|
|
|
@@ -168,6 +168,6 @@ export interface ISelectionRenderModel {
|
|
|
168
168
|
readonly selectionStart: [number, number] | undefined;
|
|
169
169
|
readonly selectionEnd: [number, number] | undefined;
|
|
170
170
|
clear(): void;
|
|
171
|
-
update(terminal:
|
|
171
|
+
update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
|
|
172
172
|
isCellSelected(terminal: Terminal, x: number, y: number): boolean;
|
|
173
173
|
}
|
|
@@ -8,9 +8,9 @@ import { IRenderDebouncerWithCallback } from 'browser/Types';
|
|
|
8
8
|
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
|
9
9
|
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
|
10
10
|
import { EventEmitter } from 'common/EventEmitter';
|
|
11
|
-
import { Disposable, MutableDisposable } from 'common/Lifecycle';
|
|
11
|
+
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
|
12
12
|
import { DebouncedIdleTask } from 'common/TaskQueue';
|
|
13
|
-
import { IBufferService, IDecorationService,
|
|
13
|
+
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
14
14
|
|
|
15
15
|
interface ISelectionState {
|
|
16
16
|
start: [number, number] | undefined;
|
|
@@ -24,6 +24,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
24
24
|
private _renderer: MutableDisposable<IRenderer> = this.register(new MutableDisposable());
|
|
25
25
|
private _renderDebouncer: IRenderDebouncerWithCallback;
|
|
26
26
|
private _pausedResizeTask = new DebouncedIdleTask();
|
|
27
|
+
private _observerDisposable = this.register(new MutableDisposable());
|
|
27
28
|
|
|
28
29
|
private _isPaused: boolean = false;
|
|
29
30
|
private _needsFullRefresh: boolean = false;
|
|
@@ -38,7 +39,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
private readonly _onDimensionsChange = this.register(new EventEmitter<IRenderDimensions>());
|
|
41
|
-
public readonly onDimensionsChange =
|
|
42
|
+
public readonly onDimensionsChange = this._onDimensionsChange.event;
|
|
42
43
|
private readonly _onRenderedViewportChange = this.register(new EventEmitter<{ start: number, end: number }>());
|
|
43
44
|
public readonly onRenderedViewportChange = this._onRenderedViewportChange.event;
|
|
44
45
|
private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>());
|
|
@@ -56,12 +57,11 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
56
57
|
@IDecorationService decorationService: IDecorationService,
|
|
57
58
|
@IBufferService bufferService: IBufferService,
|
|
58
59
|
@ICoreBrowserService coreBrowserService: ICoreBrowserService,
|
|
59
|
-
@IInstantiationService instantiationService: IInstantiationService,
|
|
60
60
|
@IThemeService themeService: IThemeService
|
|
61
61
|
) {
|
|
62
62
|
super();
|
|
63
63
|
|
|
64
|
-
this._renderDebouncer = new RenderDebouncer(
|
|
64
|
+
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end), coreBrowserService);
|
|
65
65
|
this.register(this._renderDebouncer);
|
|
66
66
|
|
|
67
67
|
this.register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));
|
|
@@ -102,12 +102,17 @@ export class RenderService extends Disposable implements IRenderService {
|
|
|
102
102
|
|
|
103
103
|
this.register(themeService.onChangeColors(() => this._fullRefresh()));
|
|
104
104
|
|
|
105
|
+
this._registerIntersectionObserver(coreBrowserService.window, screenElement);
|
|
106
|
+
this.register(coreBrowserService.onWindowChange((w) => this._registerIntersectionObserver(w, screenElement)));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private _registerIntersectionObserver(w: Window & typeof globalThis, screenElement: HTMLElement): void {
|
|
105
110
|
// Detect whether IntersectionObserver is detected and enable renderer pause
|
|
106
111
|
// and resume based on terminal visibility if so
|
|
107
|
-
if ('IntersectionObserver' in
|
|
108
|
-
const observer = new
|
|
112
|
+
if ('IntersectionObserver' in w) {
|
|
113
|
+
const observer = new w.IntersectionObserver(e => this._handleIntersectionChange(e[e.length - 1]), { threshold: 0 });
|
|
109
114
|
observer.observe(screenElement);
|
|
110
|
-
this.
|
|
115
|
+
this._observerDisposable.value = toDisposable(() => observer.disconnect());
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
package/src/common/Color.ts
CHANGED
|
@@ -245,6 +245,23 @@ export namespace rgb {
|
|
|
245
245
|
* Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
|
|
246
246
|
*/
|
|
247
247
|
export namespace rgba {
|
|
248
|
+
export function blend(bg: number, fg: number): number {
|
|
249
|
+
$a = (fg & 0xFF) / 0xFF;
|
|
250
|
+
if ($a === 1) {
|
|
251
|
+
return fg;
|
|
252
|
+
}
|
|
253
|
+
const fgR = (fg >> 24) & 0xFF;
|
|
254
|
+
const fgG = (fg >> 16) & 0xFF;
|
|
255
|
+
const fgB = (fg >> 8) & 0xFF;
|
|
256
|
+
const bgR = (bg >> 24) & 0xFF;
|
|
257
|
+
const bgG = (bg >> 16) & 0xFF;
|
|
258
|
+
const bgB = (bg >> 8) & 0xFF;
|
|
259
|
+
$r = bgR + Math.round((fgR - bgR) * $a);
|
|
260
|
+
$g = bgG + Math.round((fgG - bgG) * $a);
|
|
261
|
+
$b = bgB + Math.round((fgB - bgB) * $a);
|
|
262
|
+
return channels.toRgba($r, $g, $b);
|
|
263
|
+
}
|
|
264
|
+
|
|
248
265
|
/**
|
|
249
266
|
* Given a foreground color and a background color, either increase or reduce the luminance of the
|
|
250
267
|
* foreground color until the specified contrast ratio is met. If pure white or black is hit
|
|
@@ -519,7 +519,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
519
519
|
|
|
520
520
|
// handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
|
|
521
521
|
if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {
|
|
522
|
-
bufferRow.
|
|
522
|
+
bufferRow.setCellFromCodepoint(this._activeBuffer.x - 1, 0, 1, curAttr);
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
let precedingJoinState = this._parser.precedingJoinState;
|
|
@@ -581,7 +581,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
581
581
|
}
|
|
582
582
|
// clear left over cells to the right
|
|
583
583
|
while (oldCol < cols) {
|
|
584
|
-
oldRow.
|
|
584
|
+
oldRow.setCellFromCodepoint(oldCol++, 0, 1, curAttr);
|
|
585
585
|
}
|
|
586
586
|
} else {
|
|
587
587
|
this._activeBuffer.x = cols - 1;
|
|
@@ -605,7 +605,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
605
605
|
bufferRow.addCodepointToCell(this._activeBuffer.x - offset,
|
|
606
606
|
code, chWidth);
|
|
607
607
|
for (let delta = chWidth - oldWidth; --delta >= 0; ) {
|
|
608
|
-
bufferRow.
|
|
608
|
+
bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);
|
|
609
609
|
}
|
|
610
610
|
continue;
|
|
611
611
|
}
|
|
@@ -613,17 +613,17 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
613
613
|
// insert mode: move characters to right
|
|
614
614
|
if (insertMode) {
|
|
615
615
|
// right shift cells according to the width
|
|
616
|
-
bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr)
|
|
616
|
+
bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr));
|
|
617
617
|
// test last cell - since the last cell has only room for
|
|
618
618
|
// a halfwidth char any fullwidth shifted there is lost
|
|
619
619
|
// and will be set to empty cell
|
|
620
620
|
if (bufferRow.getWidth(cols - 1) === 2) {
|
|
621
|
-
bufferRow.
|
|
621
|
+
bufferRow.setCellFromCodepoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr);
|
|
622
622
|
}
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
// write current char to buffer and advance cursor
|
|
626
|
-
bufferRow.
|
|
626
|
+
bufferRow.setCellFromCodepoint(this._activeBuffer.x++, code, chWidth, curAttr);
|
|
627
627
|
|
|
628
628
|
// fullwidth char - also set next cell to placeholder stub and advance cursor
|
|
629
629
|
// for graphemes bigger than fullwidth we can simply loop to zero
|
|
@@ -631,7 +631,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
631
631
|
if (chWidth > 0) {
|
|
632
632
|
while (--chWidth) {
|
|
633
633
|
// other than a regular empty cell a cell following a wide char has no width
|
|
634
|
-
bufferRow.
|
|
634
|
+
bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
637
|
}
|
|
@@ -640,7 +640,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
640
640
|
|
|
641
641
|
// handle wide chars: reset cell to the right if it is second cell of a wide char
|
|
642
642
|
if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
|
|
643
|
-
bufferRow.
|
|
643
|
+
bufferRow.setCellFromCodepoint(this._activeBuffer.x, 0, 1, curAttr);
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
|
|
@@ -1145,7 +1145,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1145
1145
|
start,
|
|
1146
1146
|
end,
|
|
1147
1147
|
this._activeBuffer.getNullCell(this._eraseAttrData()),
|
|
1148
|
-
this._eraseAttrData(),
|
|
1149
1148
|
respectProtect
|
|
1150
1149
|
);
|
|
1151
1150
|
if (clearWrap) {
|
|
@@ -1366,8 +1365,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1366
1365
|
line.insertCells(
|
|
1367
1366
|
this._activeBuffer.x,
|
|
1368
1367
|
params.params[0] || 1,
|
|
1369
|
-
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1370
|
-
this._eraseAttrData()
|
|
1368
|
+
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1371
1369
|
);
|
|
1372
1370
|
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
|
|
1373
1371
|
}
|
|
@@ -1393,8 +1391,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1393
1391
|
line.deleteCells(
|
|
1394
1392
|
this._activeBuffer.x,
|
|
1395
1393
|
params.params[0] || 1,
|
|
1396
|
-
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1397
|
-
this._eraseAttrData()
|
|
1394
|
+
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1398
1395
|
);
|
|
1399
1396
|
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
|
|
1400
1397
|
}
|
|
@@ -1461,7 +1458,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1461
1458
|
const param = params.params[0] || 1;
|
|
1462
1459
|
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
|
|
1463
1460
|
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
|
|
1464
|
-
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1461
|
+
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
|
|
1465
1462
|
line.isWrapped = false;
|
|
1466
1463
|
}
|
|
1467
1464
|
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
|
|
@@ -1494,7 +1491,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1494
1491
|
const param = params.params[0] || 1;
|
|
1495
1492
|
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
|
|
1496
1493
|
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
|
|
1497
|
-
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1494
|
+
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
|
|
1498
1495
|
line.isWrapped = false;
|
|
1499
1496
|
}
|
|
1500
1497
|
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
|
|
@@ -1517,7 +1514,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1517
1514
|
const param = params.params[0] || 1;
|
|
1518
1515
|
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
|
|
1519
1516
|
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
|
|
1520
|
-
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1517
|
+
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
|
|
1521
1518
|
line.isWrapped = false;
|
|
1522
1519
|
}
|
|
1523
1520
|
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
|
|
@@ -1540,7 +1537,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1540
1537
|
const param = params.params[0] || 1;
|
|
1541
1538
|
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
|
|
1542
1539
|
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
|
|
1543
|
-
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1540
|
+
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
|
|
1544
1541
|
line.isWrapped = false;
|
|
1545
1542
|
}
|
|
1546
1543
|
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
|
|
@@ -1562,8 +1559,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|
|
1562
1559
|
line.replaceCells(
|
|
1563
1560
|
this._activeBuffer.x,
|
|
1564
1561
|
this._activeBuffer.x + (params.params[0] || 1),
|
|
1565
|
-
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1566
|
-
this._eraseAttrData()
|
|
1562
|
+
this._activeBuffer.getNullCell(this._eraseAttrData())
|
|
1567
1563
|
);
|
|
1568
1564
|
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
|
|
1569
1565
|
}
|
package/src/common/Types.d.ts
CHANGED
|
@@ -119,6 +119,7 @@ export interface IExtendedAttrs {
|
|
|
119
119
|
ext: number;
|
|
120
120
|
underlineStyle: UnderlineStyle;
|
|
121
121
|
underlineColor: number;
|
|
122
|
+
underlineVariantOffset: number;
|
|
122
123
|
urlId: number;
|
|
123
124
|
clone(): IExtendedAttrs;
|
|
124
125
|
isEmpty(): boolean;
|
|
@@ -209,6 +210,7 @@ export interface IAttributeData {
|
|
|
209
210
|
isUnderlineColorPalette(): boolean;
|
|
210
211
|
isUnderlineColorDefault(): boolean;
|
|
211
212
|
getUnderlineStyle(): number;
|
|
213
|
+
getUnderlineVariantOffset(): number;
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
/** Cell data */
|
|
@@ -233,11 +235,11 @@ export interface IBufferLine {
|
|
|
233
235
|
set(index: number, value: CharData): void;
|
|
234
236
|
loadCell(index: number, cell: ICellData): ICellData;
|
|
235
237
|
setCell(index: number, cell: ICellData): void;
|
|
236
|
-
|
|
238
|
+
setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void;
|
|
237
239
|
addCodepointToCell(index: number, codePoint: number, width: number): void;
|
|
238
|
-
insertCells(pos: number, n: number, ch: ICellData
|
|
239
|
-
deleteCells(pos: number, n: number, fill: ICellData
|
|
240
|
-
replaceCells(start: number, end: number, fill: ICellData,
|
|
240
|
+
insertCells(pos: number, n: number, ch: ICellData): void;
|
|
241
|
+
deleteCells(pos: number, n: number, fill: ICellData): void;
|
|
242
|
+
replaceCells(start: number, end: number, fill: ICellData, respectProtect?: boolean): void;
|
|
241
243
|
resize(cols: number, fill: ICellData): boolean;
|
|
242
244
|
cleanupMemory(): number;
|
|
243
245
|
fill(fillCellData: ICellData, respectProtect?: boolean): void;
|
|
@@ -245,7 +247,7 @@ export interface IBufferLine {
|
|
|
245
247
|
clone(): IBufferLine;
|
|
246
248
|
getTrimmedLength(): number;
|
|
247
249
|
getNoBgTrimmedLength(): number;
|
|
248
|
-
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
|
|
250
|
+
translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
|
|
249
251
|
|
|
250
252
|
/* direct access to cell attrs */
|
|
251
253
|
getWidth(index: number): number;
|
|
@@ -126,6 +126,9 @@ export class AttributeData implements IAttributeData {
|
|
|
126
126
|
? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
|
|
127
127
|
: UnderlineStyle.NONE;
|
|
128
128
|
}
|
|
129
|
+
public getUnderlineVariantOffset(): number {
|
|
130
|
+
return this.extended.underlineVariantOffset;
|
|
131
|
+
}
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
|
|
@@ -174,6 +177,18 @@ export class ExtendedAttrs implements IExtendedAttrs {
|
|
|
174
177
|
this._urlId = value;
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
public get underlineVariantOffset(): number {
|
|
181
|
+
const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
|
|
182
|
+
if (val < 0) {
|
|
183
|
+
return val ^ 0xFFFFFFF8;
|
|
184
|
+
}
|
|
185
|
+
return val;
|
|
186
|
+
}
|
|
187
|
+
public set underlineVariantOffset(value: number) {
|
|
188
|
+
this._ext &= ~ExtFlags.VARIANT_OFFSET;
|
|
189
|
+
this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
constructor(
|
|
178
193
|
ext: number = 0,
|
|
179
194
|
urlId: number = 0
|