@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.
@@ -6,16 +6,15 @@
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
- import { NULL_COLOR, color, rgba } from 'common/Color';
11
+ import { NULL_COLOR, channels, color, rgba } from 'common/Color';
12
12
  import { EventEmitter } from 'common/EventEmitter';
13
13
  import { FourKeyMap } from 'common/MultiKeyMap';
14
14
  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
  /**
@@ -292,8 +291,7 @@ export class TextureAtlas implements ITextureAtlas {
292
291
  break;
293
292
  case Attributes.CM_RGB:
294
293
  const arr = AttributeData.toColorRGB(bgColor);
295
- // TODO: This object creation is slow
296
- result = rgba.toColor(arr[0], arr[1], arr[2]);
294
+ result = channels.toColor(arr[0], arr[1], arr[2]);
297
295
  break;
298
296
  case Attributes.CM_DEFAULT:
299
297
  default:
@@ -325,7 +323,7 @@ export class TextureAtlas implements ITextureAtlas {
325
323
  break;
326
324
  case Attributes.CM_RGB:
327
325
  const arr = AttributeData.toColorRGB(fgColor);
328
- result = rgba.toColor(arr[0], arr[1], arr[2]);
326
+ result = channels.toColor(arr[0], arr[1], arr[2]);
329
327
  break;
330
328
  case Attributes.CM_DEFAULT:
331
329
  default:
@@ -407,7 +405,7 @@ export class TextureAtlas implements ITextureAtlas {
407
405
  return undefined;
408
406
  }
409
407
 
410
- const color = rgba.toColor(
408
+ const color = channels.toColor(
411
409
  (result >> 24) & 0xFF,
412
410
  (result >> 16) & 0xFF,
413
411
  (result >> 8) & 0xFF
@@ -424,7 +422,6 @@ export class TextureAtlas implements ITextureAtlas {
424
422
  return this._config.colors.contrastCache;
425
423
  }
426
424
 
427
- @traceCall
428
425
  private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph {
429
426
  const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
430
427
 
@@ -492,7 +489,7 @@ export class TextureAtlas implements ITextureAtlas {
492
489
 
493
490
  const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
494
491
  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)));
492
+ const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, treatGlyphAsBackgroundColor(chars.charCodeAt(0)));
496
493
  this._tmpCtx.fillStyle = foregroundColor.css;
497
494
 
498
495
  // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
@@ -545,6 +542,7 @@ export class TextureAtlas implements ITextureAtlas {
545
542
  const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
546
543
  const yMid = yTop + lineWidth;
547
544
  const yBot = yTop + lineWidth * 2;
545
+ let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
548
546
 
549
547
  for (let i = 0; i < chWidth; i++) {
550
548
  this._tmpCtx.save();
@@ -594,12 +592,32 @@ export class TextureAtlas implements ITextureAtlas {
594
592
  );
595
593
  break;
596
594
  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);
595
+ const offsetWidth = nextOffset === 0 ? 0 :
596
+ (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset);
597
+ // a line and a gap.
598
+ const isLineStart = nextOffset >= lineWidth ? false : true;
599
+ if (isLineStart === false || offsetWidth === 0) {
600
+ this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
601
+ this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
602
+ this._tmpCtx.lineTo(xChRight, yTop);
603
+ } else {
604
+ this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
605
+ this._tmpCtx.moveTo(xChLeft, yTop);
606
+ this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop);
607
+ this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop);
608
+ this._tmpCtx.lineTo(xChRight, yTop);
609
+ }
610
+ nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
600
611
  break;
601
612
  case UnderlineStyle.DASHED:
602
- this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]);
613
+ const lineRatio = 0.6;
614
+ const gapRatio = 0.3;
615
+ // End line ratio is approximately equal to 0.1
616
+ const xChWidth = xChRight - xChLeft;
617
+ const line = Math.floor(lineRatio * xChWidth);
618
+ const gap = Math.floor(gapRatio * xChWidth);
619
+ const end = xChWidth - line - gap;
620
+ this._tmpCtx.setLineDash([line, gap, end]);
603
621
  this._tmpCtx.moveTo(xChLeft, yTop);
604
622
  this._tmpCtx.lineTo(xChRight, yTop);
605
623
  break;
@@ -8,12 +8,6 @@ import { EventEmitter } from 'common/EventEmitter';
8
8
  import { ICharSizeService } from 'browser/services/Services';
9
9
  import { Disposable } from 'common/Lifecycle';
10
10
 
11
-
12
- const enum MeasureSettings {
13
- REPEAT = 32
14
- }
15
-
16
-
17
11
  export class CharSizeService extends Disposable implements ICharSizeService {
18
12
  public serviceBrand: undefined;
19
13
 
@@ -32,7 +26,11 @@ export class CharSizeService extends Disposable implements ICharSizeService {
32
26
  @IOptionsService private readonly _optionsService: IOptionsService
33
27
  ) {
34
28
  super();
35
- this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
29
+ try {
30
+ this._measureStrategy = this.register(new TextMetricsMeasureStrategy(this._optionsService));
31
+ } catch {
32
+ this._measureStrategy = this.register(new DomMeasureStrategy(document, parentElement, this._optionsService));
33
+ }
36
34
  this.register(this._optionsService.onMultipleOptionChange(['fontFamily', 'fontSize'], () => this.measure()));
37
35
  }
38
36
 
@@ -47,12 +45,7 @@ export class CharSizeService extends Disposable implements ICharSizeService {
47
45
  }
48
46
 
49
47
  interface IMeasureStrategy {
50
- measure(): IReadonlyMeasureResult;
51
- }
52
-
53
- interface IReadonlyMeasureResult {
54
- readonly width: number;
55
- readonly height: number;
48
+ measure(): Readonly<IMeasureResult>;
56
49
  }
57
50
 
58
51
  interface IMeasureResult {
@@ -60,10 +53,26 @@ interface IMeasureResult {
60
53
  height: number;
61
54
  }
62
55
 
63
- // TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses
64
- // ctx.measureText
65
- class DomMeasureStrategy implements IMeasureStrategy {
66
- private _result: IMeasureResult = { width: 0, height: 0 };
56
+ const enum DomMeasureStrategyConstants {
57
+ REPEAT = 32
58
+ }
59
+
60
+ abstract class BaseMeasureStategy extends Disposable implements IMeasureStrategy {
61
+ protected _result: IMeasureResult = { width: 0, height: 0 };
62
+
63
+ protected _validateAndSet(width: number | undefined, height: number | undefined): void {
64
+ // If values are 0 then the element is likely currently display:none, in which case we should
65
+ // retain the previous value.
66
+ if (width !== undefined && width > 0 && height !== undefined && height > 0) {
67
+ this._result.width = width;
68
+ this._result.height = height;
69
+ }
70
+ }
71
+
72
+ public abstract measure(): Readonly<IMeasureResult>;
73
+ }
74
+
75
+ class DomMeasureStrategy extends BaseMeasureStategy {
67
76
  private _measureElement: HTMLElement;
68
77
 
69
78
  constructor(
@@ -71,32 +80,48 @@ class DomMeasureStrategy implements IMeasureStrategy {
71
80
  private _parentElement: HTMLElement,
72
81
  private _optionsService: IOptionsService
73
82
  ) {
83
+ super();
74
84
  this._measureElement = this._document.createElement('span');
75
85
  this._measureElement.classList.add('xterm-char-measure-element');
76
- this._measureElement.textContent = 'W'.repeat(MeasureSettings.REPEAT);
86
+ this._measureElement.textContent = 'W'.repeat(DomMeasureStrategyConstants.REPEAT);
77
87
  this._measureElement.setAttribute('aria-hidden', 'true');
78
88
  this._measureElement.style.whiteSpace = 'pre';
79
89
  this._measureElement.style.fontKerning = 'none';
80
90
  this._parentElement.appendChild(this._measureElement);
81
91
  }
82
92
 
83
- public measure(): IReadonlyMeasureResult {
93
+ public measure(): Readonly<IMeasureResult> {
84
94
  this._measureElement.style.fontFamily = this._optionsService.rawOptions.fontFamily;
85
95
  this._measureElement.style.fontSize = `${this._optionsService.rawOptions.fontSize}px`;
86
96
 
87
97
  // Note that this triggers a synchronous layout
88
- const geometry = {
89
- height: Number(this._measureElement.offsetHeight),
90
- width: Number(this._measureElement.offsetWidth)
91
- };
98
+ this._validateAndSet(Number(this._measureElement.offsetWidth) / DomMeasureStrategyConstants.REPEAT, Number(this._measureElement.offsetHeight));
92
99
 
93
- // If values are 0 then the element is likely currently display:none, in which case we should
94
- // retain the previous value.
95
- if (geometry.width !== 0 && geometry.height !== 0) {
96
- this._result.width = geometry.width / MeasureSettings.REPEAT;
97
- this._result.height = Math.ceil(geometry.height);
100
+ return this._result;
101
+ }
102
+ }
103
+
104
+ class TextMetricsMeasureStrategy extends BaseMeasureStategy {
105
+ private _canvas: OffscreenCanvas;
106
+ private _ctx: OffscreenCanvasRenderingContext2D;
107
+
108
+ constructor(
109
+ private _optionsService: IOptionsService
110
+ ) {
111
+ super();
112
+ // This will throw if any required API is not supported
113
+ this._canvas = new OffscreenCanvas(100, 100);
114
+ this._ctx = this._canvas.getContext('2d')!;
115
+ const a = this._ctx.measureText('W');
116
+ if (!('width' in a && 'fontBoundingBoxAscent' in a && 'fontBoundingBoxDescent' in a)) {
117
+ throw new Error('Required font metrics not supported');
98
118
  }
119
+ }
99
120
 
121
+ public measure(): Readonly<IMeasureResult> {
122
+ this._ctx.font = `${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;
123
+ const metrics = this._ctx.measureText('W');
124
+ this._validateAndSet(metrics.width, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);
100
125
  return this._result;
101
126
  }
102
127
  }
@@ -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
 
@@ -242,7 +247,7 @@ export class RenderService extends Disposable implements IRenderService {
242
247
  return;
243
248
  }
244
249
  if (this._isPaused) {
245
- this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows));
250
+ this._pausedResizeTask.set(() => this._renderer.value?.handleResize(cols, rows));
246
251
  } else {
247
252
  this._renderer.value.handleResize(cols, rows);
248
253
  }
@@ -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
+ }
@@ -33,6 +33,13 @@ export namespace channels {
33
33
  // >>> 0 forces an unsigned int
34
34
  return (r << 24 | g << 16 | b << 8 | a) >>> 0;
35
35
  }
36
+
37
+ export function toColor(r: number, g: number, b: number, a?: number): IColor {
38
+ return {
39
+ css: channels.toCss(r, g, b, a),
40
+ rgba: channels.toRgba(r, g, b, a)
41
+ };
42
+ }
36
43
  }
37
44
 
38
45
  /**
@@ -70,7 +77,7 @@ export namespace color {
70
77
  if (!result) {
71
78
  return undefined;
72
79
  }
73
- return rgba.toColor(
80
+ return channels.toColor(
74
81
  (result >> 24 & 0xFF),
75
82
  (result >> 16 & 0xFF),
76
83
  (result >> 8 & 0xFF)
@@ -142,14 +149,14 @@ export namespace css {
142
149
  $r = parseInt(css.slice(1, 2).repeat(2), 16);
143
150
  $g = parseInt(css.slice(2, 3).repeat(2), 16);
144
151
  $b = parseInt(css.slice(3, 4).repeat(2), 16);
145
- return rgba.toColor($r, $g, $b);
152
+ return channels.toColor($r, $g, $b);
146
153
  }
147
154
  case 5: { // #rgba
148
155
  $r = parseInt(css.slice(1, 2).repeat(2), 16);
149
156
  $g = parseInt(css.slice(2, 3).repeat(2), 16);
150
157
  $b = parseInt(css.slice(3, 4).repeat(2), 16);
151
158
  $a = parseInt(css.slice(4, 5).repeat(2), 16);
152
- return rgba.toColor($r, $g, $b, $a);
159
+ return channels.toColor($r, $g, $b, $a);
153
160
  }
154
161
  case 7: // #rrggbb
155
162
  return {
@@ -171,7 +178,7 @@ export namespace css {
171
178
  $g = parseInt(rgbaMatch[2]);
172
179
  $b = parseInt(rgbaMatch[3]);
173
180
  $a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
174
- return rgba.toColor($r, $g, $b, $a);
181
+ return channels.toColor($r, $g, $b, $a);
175
182
  }
176
183
 
177
184
  // Validate the context is available for canvas-based color parsing
@@ -245,6 +252,23 @@ export namespace rgb {
245
252
  * Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
246
253
  */
247
254
  export namespace rgba {
255
+ export function blend(bg: number, fg: number): number {
256
+ $a = (fg & 0xFF) / 0xFF;
257
+ if ($a === 1) {
258
+ return fg;
259
+ }
260
+ const fgR = (fg >> 24) & 0xFF;
261
+ const fgG = (fg >> 16) & 0xFF;
262
+ const fgB = (fg >> 8) & 0xFF;
263
+ const bgR = (bg >> 24) & 0xFF;
264
+ const bgG = (bg >> 16) & 0xFF;
265
+ const bgB = (bg >> 8) & 0xFF;
266
+ $r = bgR + Math.round((fgR - bgR) * $a);
267
+ $g = bgG + Math.round((fgG - bgG) * $a);
268
+ $b = bgB + Math.round((fgB - bgB) * $a);
269
+ return channels.toRgba($r, $g, $b);
270
+ }
271
+
248
272
  /**
249
273
  * Given a foreground color and a background color, either increase or reduce the luminance of the
250
274
  * foreground color until the specified contrast ratio is met. If pure white or black is hit
@@ -325,17 +349,9 @@ export namespace rgba {
325
349
  return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
326
350
  }
327
351
 
328
- // FIXME: Move this to channels NS?
329
352
  export function toChannels(value: number): [number, number, number, number] {
330
353
  return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
331
354
  }
332
-
333
- export function toColor(r: number, g: number, b: number, a?: number): IColor {
334
- return {
335
- css: channels.toCss(r, g, b, a),
336
- rgba: channels.toRgba(r, g, b, a)
337
- };
338
- }
339
355
  }
340
356
 
341
357
  export function toPaddedHex(c: number): string {
@@ -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));
@@ -167,6 +168,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
167
168
  this._writeBuffer.writeSync(data, maxSubsequentCalls);
168
169
  }
169
170
 
171
+ public input(data: string, wasUserInput: boolean = true): void {
172
+ this.coreService.triggerDataEvent(data, wasUserInput);
173
+ }
174
+
170
175
  public resize(x: number, y: number): void {
171
176
  if (isNaN(x) || isNaN(y)) {
172
177
  return;
@@ -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
  }
@@ -110,8 +110,8 @@ export interface ICharset {
110
110
  export type CharData = [number, string, number, number];
111
111
 
112
112
  export interface IColor {
113
- css: string;
114
- rgba: number; // 32-bit int with rgba in each byte
113
+ readonly css: string;
114
+ readonly rgba: number; // 32-bit int with rgba in each byte
115
115
  }
116
116
  export type IColorRGB = [number, number, number];
117
117
 
@@ -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;