@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.
@@ -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
  }
@@ -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
 
@@ -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.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
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.setCellFromCodePoint(oldCol++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
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.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
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), 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.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg, curAttr.extended);
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.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
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.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
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.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
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()), 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()), 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()), 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()), 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
  }
@@ -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
- setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
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, eraseAttr?: IAttributeData): void;
239
- deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
240
- replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData, respectProtect?: boolean): void;
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