@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.
@@ -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 { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
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 = start[1] - this._bufferService.buffer.ydisp;
325
- const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
326
- const viewportCappedStartRow = Math.max(viewportStartRow, 0);
327
- const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
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 = `${colStart * this.dimensions.css.cell.width}px`;
371
- element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`;
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 { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils';
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 || excludeFromContrastRatioDemands(cell.getCode())) {
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 & 0xFFFFFF;
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 & 0xFFFFFF;
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
- $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF;
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 & 0xFFFFFF;
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 & 0xFFFFFF;
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 & 0xFFFFFF;
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 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
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 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
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 excludeFromContrastRatioDemands(codepoint: number): boolean {
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: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
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 viewportStartRow = start[1] - terminal.buffer.active.viewportY;
49
- const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
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 { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
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, excludeFromContrastRatioDemands(chars.charCodeAt(0)));
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
- this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
598
- this._tmpCtx.moveTo(xChLeft, yTop);
599
- this._tmpCtx.lineTo(xChRight, yTop);
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
- this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]);
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: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
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, IInstantiationService, IOptionsService } from 'common/services/Services';
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 = this._onDimensionsChange.event;
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(coreBrowserService.window, (start, end) => this._renderRows(start, end));
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 coreBrowserService.window) {
108
- const observer = new coreBrowserService.window.IntersectionObserver(e => this._handleIntersectionChange(e[e.length - 1]), { threshold: 0 });
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.register({ dispose: () => observer.disconnect() });
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
+ }
@@ -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));