@xterm/xterm 5.4.0-beta.2 → 5.4.0-beta.21
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 → Linkifier.ts} +36 -61
- package/src/browser/OscLinkProvider.ts +2 -1
- package/src/browser/RenderDebouncer.ts +6 -5
- package/src/browser/Terminal.ts +36 -28
- package/src/browser/Types.d.ts +2 -9
- 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/LinkProviderService.ts +28 -0
- package/src/browser/services/RenderService.ts +13 -8
- package/src/browser/services/Services.ts +12 -1
- package/src/common/Color.ts +17 -0
- package/src/common/CoreTerminal.ts +1 -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
package/src/browser/Types.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { IEvent } from 'common/EventEmitter';
|
|
|
7
7
|
import { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types';
|
|
8
8
|
import { IBuffer } from 'common/buffer/Types';
|
|
9
9
|
import { IDisposable, Terminal as ITerminalApi } from '@xterm/xterm';
|
|
10
|
-
import { IMouseService, IRenderService } from './services/Services';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* A portion of the public API that are implemented identially internally and simply passed through.
|
|
@@ -18,9 +17,9 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
|
|
|
18
17
|
screenElement: HTMLElement | undefined;
|
|
19
18
|
browser: IBrowser;
|
|
20
19
|
buffer: IBuffer;
|
|
20
|
+
linkifier: ILinkifier2 | undefined;
|
|
21
21
|
viewport: IViewport | undefined;
|
|
22
22
|
options: Required<ITerminalOptions>;
|
|
23
|
-
linkifier2: ILinkifier2;
|
|
24
23
|
|
|
25
24
|
onBlur: IEvent<void>;
|
|
26
25
|
onFocus: IEvent<void>;
|
|
@@ -32,6 +31,7 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
|
34
|
+
export type CustomWheelEventHandler = (event: WheelEvent) => boolean;
|
|
35
35
|
|
|
36
36
|
export type LineData = CharData[];
|
|
37
37
|
|
|
@@ -127,13 +127,6 @@ export interface ILinkifier2 extends IDisposable {
|
|
|
127
127
|
onShowLinkUnderline: IEvent<ILinkifierEvent>;
|
|
128
128
|
onHideLinkUnderline: IEvent<ILinkifierEvent>;
|
|
129
129
|
readonly currentLink: ILinkWithState | undefined;
|
|
130
|
-
|
|
131
|
-
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
|
|
132
|
-
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
interface ILinkProvider {
|
|
136
|
-
provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
interface ILink {
|
|
@@ -148,6 +148,9 @@ export class Terminal extends Disposable implements ITerminalApi {
|
|
|
148
148
|
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
|
149
149
|
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
|
150
150
|
}
|
|
151
|
+
public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void {
|
|
152
|
+
this._core.attachCustomWheelEventHandler(customWheelEventHandler);
|
|
153
|
+
}
|
|
151
154
|
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
152
155
|
return this._core.registerLinkProvider(linkProvider);
|
|
153
156
|
}
|
|
@@ -7,9 +7,10 @@ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererR
|
|
|
7
7
|
import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
|
8
8
|
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
|
9
9
|
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
|
10
|
-
import {
|
|
10
|
+
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
|
11
|
+
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
|
11
12
|
import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
12
|
-
import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
|
|
13
|
+
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
|
|
13
14
|
import { color } from 'common/Color';
|
|
14
15
|
import { EventEmitter } from 'common/EventEmitter';
|
|
15
16
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
@@ -25,7 +26,6 @@ const SELECTION_CLASS = 'xterm-selection';
|
|
|
25
26
|
|
|
26
27
|
let nextTerminalId = 1;
|
|
27
28
|
|
|
28
|
-
|
|
29
29
|
/**
|
|
30
30
|
* A fallback renderer for when canvas is slow. This is not meant to be
|
|
31
31
|
* particularly fast or feature complete, more just stable and usable for when
|
|
@@ -41,12 +41,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
41
41
|
private _rowElements: HTMLElement[] = [];
|
|
42
42
|
private _selectionContainer: HTMLElement;
|
|
43
43
|
private _widthCache: WidthCache;
|
|
44
|
+
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
|
|
44
45
|
|
|
45
46
|
public dimensions: IRenderDimensions;
|
|
46
47
|
|
|
47
48
|
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
|
|
48
49
|
|
|
49
50
|
constructor(
|
|
51
|
+
private readonly _terminal: ITerminal,
|
|
50
52
|
private readonly _document: Document,
|
|
51
53
|
private readonly _element: HTMLElement,
|
|
52
54
|
private readonly _screenElement: HTMLElement,
|
|
@@ -291,6 +293,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
291
293
|
public handleResize(cols: number, rows: number): void {
|
|
292
294
|
this._refreshRowElements(cols, rows);
|
|
293
295
|
this._updateDimensions();
|
|
296
|
+
this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode);
|
|
294
297
|
}
|
|
295
298
|
|
|
296
299
|
public handleCharSizeChanged(): void {
|
|
@@ -320,11 +323,13 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
320
323
|
return;
|
|
321
324
|
}
|
|
322
325
|
|
|
326
|
+
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
|
|
327
|
+
|
|
323
328
|
// Translate from buffer position to viewport position
|
|
324
|
-
const viewportStartRow =
|
|
325
|
-
const viewportEndRow =
|
|
326
|
-
const viewportCappedStartRow =
|
|
327
|
-
const viewportCappedEndRow =
|
|
329
|
+
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
|
|
330
|
+
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
|
|
331
|
+
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
|
|
332
|
+
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
|
|
328
333
|
|
|
329
334
|
// No need to draw the selection
|
|
330
335
|
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
|
@@ -365,10 +370,16 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
365
370
|
*/
|
|
366
371
|
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
|
367
372
|
const element = this._document.createElement('div');
|
|
373
|
+
const left = colStart * this.dimensions.css.cell.width;
|
|
374
|
+
let width = this.dimensions.css.cell.width * (colEnd - colStart);
|
|
375
|
+
if (left + width > this.dimensions.css.canvas.width) {
|
|
376
|
+
width = this.dimensions.css.canvas.width - left;
|
|
377
|
+
}
|
|
378
|
+
|
|
368
379
|
element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;
|
|
369
380
|
element.style.top = `${row * this.dimensions.css.cell.height}px`;
|
|
370
|
-
element.style.left = `${
|
|
371
|
-
element.style.width = `${
|
|
381
|
+
element.style.left = `${left}px`;
|
|
382
|
+
element.style.width = `${width}px`;
|
|
372
383
|
return element;
|
|
373
384
|
}
|
|
374
385
|
|
|
@@ -11,7 +11,7 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/servic
|
|
|
11
11
|
import { color, rgba } from 'common/Color';
|
|
12
12
|
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
13
13
|
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
|
14
|
-
import {
|
|
14
|
+
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
|
15
15
|
import { AttributeData } from 'common/buffer/AttributeData';
|
|
16
16
|
import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
|
17
17
|
import { IColorContrastCache } from 'browser/Types';
|
|
@@ -458,7 +458,7 @@ export class DomRendererRowFactory {
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
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 ||
|
|
461
|
+
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || treatGlyphAsBackgroundColor(cell.getCode())) {
|
|
462
462
|
return false;
|
|
463
463
|
}
|
|
464
464
|
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ILinkProvider, ILinkProviderService } from 'browser/services/Services';
|
|
2
|
+
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
3
|
+
import { IDisposable } from 'common/Types';
|
|
4
|
+
|
|
5
|
+
export class LinkProviderService extends Disposable implements ILinkProviderService {
|
|
6
|
+
declare public serviceBrand: undefined;
|
|
7
|
+
|
|
8
|
+
public readonly linkProviders: ILinkProvider[] = [];
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.register(toDisposable(() => this.linkProviders.length = 0));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
16
|
+
this.linkProviders.push(linkProvider);
|
|
17
|
+
return {
|
|
18
|
+
dispose: () => {
|
|
19
|
+
// Remove the link provider from the list
|
|
20
|
+
const providerIndex = this.linkProviders.indexOf(linkProvider);
|
|
21
|
+
|
|
22
|
+
if (providerIndex !== -1) {
|
|
23
|
+
this.linkProviders.splice(providerIndex, 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -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
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { IEvent } from 'common/EventEmitter';
|
|
7
7
|
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
|
8
|
-
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
|
8
|
+
import { IColorSet, ILink, ReadonlyColorSet } from 'browser/Types';
|
|
9
9
|
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
|
10
10
|
import { createDecorator } from 'common/services/ServiceRegistry';
|
|
11
11
|
import { AllColorIndex, IDisposable } from 'common/Types';
|
|
@@ -145,3 +145,14 @@ export interface IThemeService {
|
|
|
145
145
|
*/
|
|
146
146
|
modifyColors(callback: (colors: IColorSet) => void): void;
|
|
147
147
|
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
export const ILinkProviderService = createDecorator<ILinkProviderService>('LinkProviderService');
|
|
151
|
+
export interface ILinkProviderService extends IDisposable {
|
|
152
|
+
serviceBrand: undefined;
|
|
153
|
+
readonly linkProviders: ReadonlyArray<ILinkProvider>;
|
|
154
|
+
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
|
|
155
|
+
}
|
|
156
|
+
export interface ILinkProvider {
|
|
157
|
+
provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;
|
|
158
|
+
}
|
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
|
|
@@ -120,6 +120,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
|
|
120
120
|
this._oscLinkService = this._instantiationService.createInstance(OscLinkService);
|
|
121
121
|
this._instantiationService.setService(IOscLinkService, this._oscLinkService);
|
|
122
122
|
|
|
123
|
+
|
|
123
124
|
// Register input handler and handle/forward events
|
|
124
125
|
this._inputHandler = this.register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService));
|
|
125
126
|
this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
|