@xterm/xterm 5.4.0-beta.8 → 5.4.0

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.
@@ -23,10 +23,10 @@
23
23
 
24
24
  import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';
25
25
  import { addDisposableDomListener } from 'browser/Lifecycle';
26
- import { Linkifier2 } from 'browser/Linkifier2';
26
+ import { Linkifier } from './Linkifier';
27
27
  import * as Strings from 'browser/LocalizableStrings';
28
28
  import { OscLinkProvider } from 'browser/OscLinkProvider';
29
- import { CharacterJoinerHandler, CustomKeyEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';
29
+ import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';
30
30
  import { Viewport } from 'browser/Viewport';
31
31
  import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
32
32
  import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
@@ -39,9 +39,9 @@ import { CoreBrowserService } from 'browser/services/CoreBrowserService';
39
39
  import { MouseService } from 'browser/services/MouseService';
40
40
  import { RenderService } from 'browser/services/RenderService';
41
41
  import { SelectionService } from 'browser/services/SelectionService';
42
- import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
42
+ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
43
43
  import { ThemeService } from 'browser/services/ThemeService';
44
- import { color, rgba } from 'common/Color';
44
+ import { channels, color } from 'common/Color';
45
45
  import { CoreTerminal } from 'common/CoreTerminal';
46
46
  import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
47
47
  import { MutableDisposable, toDisposable } from 'common/Lifecycle';
@@ -57,6 +57,7 @@ import { IDecorationService } from 'common/services/Services';
57
57
  import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm';
58
58
  import { WindowsOptionsReportType } from '../common/InputHandler';
59
59
  import { AccessibilityManager } from './AccessibilityManager';
60
+ import { LinkProviderService } from 'browser/services/LinkProviderService';
60
61
 
61
62
  export class Terminal extends CoreTerminal implements ITerminal {
62
63
  public textarea: HTMLTextAreaElement | undefined;
@@ -69,14 +70,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
69
70
  private _helperContainer: HTMLElement | undefined;
70
71
  private _compositionView: HTMLElement | undefined;
71
72
 
73
+ public linkifier: ILinkifier2 | undefined;
72
74
  private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
73
75
 
74
76
  public browser: IBrowser = Browser as any;
75
77
 
76
78
  private _customKeyEventHandler: CustomKeyEventHandler | undefined;
79
+ private _customWheelEventHandler: CustomWheelEventHandler | undefined;
77
80
 
78
- // browser services
81
+ // Browser services
79
82
  private _decorationService: DecorationService;
83
+ private _linkProviderService: ILinkProviderService;
84
+
85
+ // Optional browser services
80
86
  private _charSizeService: ICharSizeService | undefined;
81
87
  private _coreBrowserService: ICoreBrowserService | undefined;
82
88
  private _mouseService: IMouseService | undefined;
@@ -112,7 +118,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
112
118
  */
113
119
  private _unprocessedDeadKey: boolean = false;
114
120
 
115
- public linkifier2: ILinkifier2;
116
121
  public viewport: IViewport | undefined;
117
122
  private _compositionHelper: ICompositionHelper | undefined;
118
123
  private _accessibilityManager: MutableDisposable<AccessibilityManager> = this.register(new MutableDisposable());
@@ -148,10 +153,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
148
153
 
149
154
  this._setup();
150
155
 
151
- this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
152
- this.linkifier2.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
153
156
  this._decorationService = this._instantiationService.createInstance(DecorationService);
154
157
  this._instantiationService.setService(IDecorationService, this._decorationService);
158
+ this._linkProviderService = this._instantiationService.createInstance(LinkProviderService);
159
+ this._instantiationService.setService(ILinkProviderService, this._linkProviderService);
160
+ this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
155
161
 
156
162
  // Setup InputHandler listeners
157
163
  this.register(this._inputHandler.onRequestBell(() => this._onBell.fire()));
@@ -205,17 +211,17 @@ export class Terminal extends CoreTerminal implements ITerminal {
205
211
  }
206
212
  switch (req.type) {
207
213
  case ColorRequestType.REPORT:
208
- const channels = color.toColorRGB(acc === 'ansi'
214
+ const colorRgb = color.toColorRGB(acc === 'ansi'
209
215
  ? this._themeService.colors.ansi[req.index]
210
216
  : this._themeService.colors[acc]);
211
- this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
217
+ this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(colorRgb)}${C1_ESCAPED.ST}`);
212
218
  break;
213
219
  case ColorRequestType.SET:
214
220
  if (acc === 'ansi') {
215
- this._themeService.modifyColors(colors => colors.ansi[req.index] = rgba.toColor(...req.color));
221
+ this._themeService.modifyColors(colors => colors.ansi[req.index] = channels.toColor(...req.color));
216
222
  } else {
217
223
  const narrowedAcc = acc;
218
- this._themeService.modifyColors(colors => colors[narrowedAcc] = rgba.toColor(...req.color));
224
+ this._themeService.modifyColors(colors => colors[narrowedAcc] = channels.toColor(...req.color));
219
225
  }
220
226
  break;
221
227
  case ColorRequestType.RESTORE:
@@ -260,11 +266,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
260
266
  /**
261
267
  * Binds the desired focus behavior on a given terminal object.
262
268
  */
263
- private _handleTextAreaFocus(ev: KeyboardEvent): void {
269
+ private _handleTextAreaFocus(ev: FocusEvent): void {
264
270
  if (this.coreService.decPrivateModes.sendFocus) {
265
271
  this.coreService.triggerDataEvent(C0.ESC + '[I');
266
272
  }
267
- this.updateCursorStyle(ev);
268
273
  this.element!.classList.add('focus');
269
274
  this._showCursor();
270
275
  this._onFocus.fire();
@@ -428,6 +433,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
428
433
 
429
434
  this.screenElement = this._document.createElement('div');
430
435
  this.screenElement.classList.add('xterm-screen');
436
+ this.register(addDisposableDomListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));
431
437
  // Create the container that will hold helpers like the textarea for
432
438
  // capturing DOM Events. Then produce the helpers.
433
439
  this._helperContainer = this._document.createElement('div');
@@ -458,11 +464,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
458
464
  ));
459
465
  this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
460
466
 
461
- this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev)));
467
+ this.register(addDisposableDomListener(this.textarea, 'focus', (ev: FocusEvent) => this._handleTextAreaFocus(ev)));
462
468
  this.register(addDisposableDomListener(this.textarea, 'blur', () => this._handleTextAreaBlur()));
463
469
  this._helperContainer.appendChild(this.textarea);
464
470
 
465
-
466
471
  this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
467
472
  this._instantiationService.setService(ICharSizeService, this._charSizeService);
468
473
 
@@ -482,6 +487,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
482
487
  this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
483
488
  this._helperContainer.appendChild(this._compositionView);
484
489
 
490
+ this._mouseService = this._instantiationService.createInstance(MouseService);
491
+ this._instantiationService.setService(IMouseService, this._mouseService);
492
+
493
+ this.linkifier = this.register(this._instantiationService.createInstance(Linkifier, this.screenElement));
494
+
485
495
  // Performance: Add viewport and helper elements from the fragment
486
496
  this.element.appendChild(fragment);
487
497
 
@@ -493,9 +503,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
493
503
  this._renderService.setRenderer(this._createRenderer());
494
504
  }
495
505
 
496
- this._mouseService = this._instantiationService.createInstance(MouseService);
497
- this._instantiationService.setService(IMouseService, this._mouseService);
498
-
499
506
  this.viewport = this._instantiationService.createInstance(Viewport, this._viewportElement, this._viewportScrollArea);
500
507
  this.viewport.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent, ScrollSource.VIEWPORT)),
501
508
  this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
@@ -513,7 +520,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
513
520
  this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
514
521
  this.element,
515
522
  this.screenElement,
516
- this.linkifier2
523
+ this.linkifier
517
524
  ));
518
525
  this._instantiationService.setService(ISelectionService, this._selectionService);
519
526
  this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
@@ -533,7 +540,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
533
540
  }));
534
541
  this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh()));
535
542
 
536
- this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
537
543
  this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
538
544
  this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
539
545
 
@@ -575,7 +581,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
575
581
  }
576
582
 
577
583
  private _createRenderer(): IRenderer {
578
- return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2);
584
+ return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier!);
579
585
  }
580
586
 
581
587
  /**
@@ -633,6 +639,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
633
639
  but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
634
640
  break;
635
641
  case 'wheel':
642
+ if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {
643
+ return false;
644
+ }
636
645
  const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
637
646
 
638
647
  if (amount === 0) {
@@ -792,6 +801,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
792
801
  // do nothing, if app side handles wheel itself
793
802
  if (requestedEvents.wheel) return;
794
803
 
804
+ if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {
805
+ return false;
806
+ }
807
+
795
808
  if (!this.buffer.hasScrollback) {
796
809
  // Convert wheel events into up/down events when the buffer does not have scrollback, this
797
810
  // enables scrolling in apps hosted in the alt buffer such as vim or tmux.
@@ -847,7 +860,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
847
860
  /**
848
861
  * Change the cursor style for different selection modes
849
862
  */
850
- public updateCursorStyle(ev: KeyboardEvent): void {
863
+ public updateCursorStyle(ev: KeyboardEvent | MouseEvent): void {
851
864
  if (this._selectionService?.shouldColumnSelect(ev)) {
852
865
  this.element!.classList.add('column-select');
853
866
  } else {
@@ -878,21 +891,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
878
891
  paste(data, this.textarea!, this.coreService, this.optionsService);
879
892
  }
880
893
 
881
- /**
882
- * Attaches a custom key event handler which is run before keys are processed,
883
- * giving consumers of xterm.js ultimate control as to what keys should be
884
- * processed by the terminal and what keys should not.
885
- * @param customKeyEventHandler The custom KeyboardEvent handler to attach.
886
- * This is a function that takes a KeyboardEvent, allowing consumers to stop
887
- * propagation and/or prevent the default action. The function returns whether
888
- * the event should be processed by xterm.js.
889
- */
890
894
  public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
891
895
  this._customKeyEventHandler = customKeyEventHandler;
892
896
  }
893
897
 
898
+ public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
899
+ this._customWheelEventHandler = customWheelEventHandler;
900
+ }
901
+
894
902
  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
895
- return this.linkifier2.registerLinkProvider(linkProvider);
903
+ return this._linkProviderService.registerLinkProvider(linkProvider);
896
904
  }
897
905
 
898
906
  public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
@@ -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 {
@@ -138,6 +138,9 @@ export class Terminal extends Disposable implements ITerminalApi {
138
138
  public focus(): void {
139
139
  this._core.focus();
140
140
  }
141
+ public input(data: string, wasUserInput: boolean = true): void {
142
+ this._core.input(data, wasUserInput);
143
+ }
141
144
  public resize(columns: number, rows: number): void {
142
145
  this._verifyIntegers(columns, rows);
143
146
  this._core.resize(columns, rows);
@@ -148,6 +151,9 @@ export class Terminal extends Disposable implements ITerminalApi {
148
151
  public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
149
152
  this._core.attachCustomKeyEventHandler(customKeyEventHandler);
150
153
  }
154
+ public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void {
155
+ this._core.attachCustomWheelEventHandler(customWheelEventHandler);
156
+ }
151
157
  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
152
158
  return this._core.registerLinkProvider(linkProvider);
153
159
  }
@@ -8,10 +8,10 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
8
8
  import { WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
9
9
  import { CellData } from 'common/buffer/CellData';
10
10
  import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
11
- import { color, rgba } from 'common/Color';
11
+ import { channels, color } 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';
@@ -376,7 +376,7 @@ export class DomRendererRowFactory {
376
376
  classes.push(`xterm-bg-${bg}`);
377
377
  break;
378
378
  case Attributes.CM_RGB:
379
- resolvedBg = rgba.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
379
+ resolvedBg = channels.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
380
380
  this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
381
381
  break;
382
382
  case Attributes.CM_DEFAULT:
@@ -408,7 +408,7 @@ export class DomRendererRowFactory {
408
408
  }
409
409
  break;
410
410
  case Attributes.CM_RGB:
411
- const color = rgba.toColor(
411
+ const color = channels.toColor(
412
412
  (fg >> 16) & 0xFF,
413
413
  (fg >> 8) & 0xFF,
414
414
  (fg ) & 0xFF
@@ -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
 
@@ -134,9 +134,14 @@ export class WidthCache implements IDisposable {
134
134
  public get(c: string, bold: boolean | number, italic: boolean | number): number {
135
135
  let cp = 0;
136
136
  if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) {
137
- return this._flat[cp] !== WidthCacheSettings.FLAT_UNSET
138
- ? this._flat[cp]
139
- : (this._flat[cp] = this._measure(c, 0));
137
+ if (this._flat[cp] !== WidthCacheSettings.FLAT_UNSET) {
138
+ return this._flat[cp];
139
+ }
140
+ const width = this._measure(c, 0);
141
+ if (width > 0) {
142
+ this._flat[cp] = width;
143
+ }
144
+ return width;
140
145
  }
141
146
  let key = c;
142
147
  if (bold) key += 'B';
@@ -147,7 +152,9 @@ export class WidthCache implements IDisposable {
147
152
  if (bold) variant |= FontVariant.BOLD;
148
153
  if (italic) variant |= FontVariant.ITALIC;
149
154
  width = this._measure(c, variant);
150
- this._holey!.set(key, width);
155
+ if (width > 0) {
156
+ this._holey!.set(key, width);
157
+ }
151
158
  }
152
159
  return width;
153
160
  }
@@ -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
+ }