@xterm/addon-webgl 0.20.0-beta.21 → 0.20.0-beta.210

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.
@@ -13,7 +13,8 @@ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeS
13
13
  import { CharData, IBufferLine, ICellData } from 'common/Types';
14
14
  import { AttributeData } from 'common/buffer/AttributeData';
15
15
  import { CellData } from 'common/buffer/CellData';
16
- import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
16
+ import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
17
+ import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
17
18
  import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
18
19
  import { Terminal } from '@xterm/xterm';
19
20
  import { GlyphRenderer } from './GlyphRenderer';
@@ -22,14 +23,15 @@ import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET
22
23
  import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
23
24
  import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
24
25
  import { IRenderLayer } from './renderLayer/Types';
25
- import { Emitter, Event } from 'vs/base/common/event';
26
- import { addDisposableListener } from 'vs/base/browser/dom';
27
- import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
26
+ import { Emitter, EventUtils } from 'common/Event';
27
+ import { addDisposableListener } from 'browser/Dom';
28
+ import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
28
29
  import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
29
30
 
30
31
  export class WebglRenderer extends Disposable implements IRenderer {
31
32
  private _renderLayers: IRenderLayer[];
32
- private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
33
+ private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this._register(new MutableDisposable());
34
+ private _textBlinkStateManager: TextBlinkStateManager;
33
35
  private _charAtlasDisposable = this._register(new MutableDisposable());
34
36
  private _charAtlas: ITextureAtlas | undefined;
35
37
  private _devicePixelRatio: number;
@@ -37,8 +39,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
37
39
  private _observerDisposable = this._register(new MutableDisposable());
38
40
 
39
41
  private _model: RenderModel = new RenderModel();
42
+ private _rowHasBlinkingCells: boolean[] = [];
43
+ private _rowHasBlinkingCellsCount: number = 0;
40
44
  private _workCell: ICellData = new CellData();
41
- private _workCell2: ICellData = new CellData();
42
45
  private _cellColorResolver: CellColorResolver;
43
46
 
44
47
  private _canvas: HTMLCanvasElement;
@@ -105,6 +108,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
105
108
  this._updateDimensions();
106
109
  this._updateCursorBlink();
107
110
  this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));
111
+ this._textBlinkStateManager = this._register(new TextBlinkStateManager(
112
+ () => this._requestRedrawViewport(),
113
+ this._coreBrowserService,
114
+ this._optionsService
115
+ ));
116
+ this._resetBlinkingRowState();
108
117
 
109
118
  this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
110
119
 
@@ -136,6 +145,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
136
145
  this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
137
146
  }));
138
147
 
148
+ this._register(addDisposableListener(this._coreBrowserService.mainDocument, 'mousedown', () => this._cursorBlinkStateManager.value?.restartBlinkAnimation()));
149
+
139
150
  this._core.screenElement!.appendChild(this._canvas);
140
151
 
141
152
  [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState();
@@ -176,6 +187,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
176
187
  this._updateDimensions();
177
188
 
178
189
  this._model.resize(this._terminal.cols, this._terminal.rows);
190
+ this._resetBlinkingRowState();
179
191
 
180
192
  // Resize all render layers
181
193
  for (const l of this._renderLayers) {
@@ -202,6 +214,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
202
214
  // Force a full refresh. Resizing `_glyphRenderer` should clear it already,
203
215
  // so there is no need to clear it again here.
204
216
  this._clearModel(false);
217
+
218
+ // Render synchronously to avoid flicker when the canvas is cleared
219
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
205
220
  }
206
221
 
207
222
  public handleCharSizeChanged(): void {
@@ -226,6 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
226
241
  this._requestRedrawViewport();
227
242
  }
228
243
 
244
+ public handleViewportVisibilityChange(isVisible: boolean): void {
245
+ this._textBlinkStateManager.setViewportVisible(isVisible);
246
+ }
247
+
229
248
  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
230
249
  for (const l of this._renderLayers) {
231
250
  l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
@@ -285,8 +304,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
285
304
  if (this._charAtlas !== atlas) {
286
305
  this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
287
306
  this._charAtlasDisposable.value = combinedDisposable(
288
- Event.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
289
- Event.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
307
+ EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
308
+ EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
290
309
  );
291
310
  }
292
311
  this._charAtlas = atlas;
@@ -318,6 +337,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
318
337
  l.reset(this._terminal);
319
338
  }
320
339
 
340
+ this._resetBlinkingRowState();
341
+ this._textBlinkStateManager.setNeedsBlinkInViewport(false);
342
+
321
343
  this._cursorBlinkStateManager.value?.restartBlinkAnimation();
322
344
  this._updateCursorBlink();
323
345
  }
@@ -415,6 +437,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
415
437
  for (y = start; y <= end; y++) {
416
438
  row = y + terminal.buffer.ydisp;
417
439
  line = terminal.buffer.lines.get(row)!;
440
+ let rowHasBlinkingCells = false;
418
441
  this._model.lineLengths[y] = 0;
419
442
  isCursorRow = cursorY === row;
420
443
  skipJoinedCheckUntilX = 0;
@@ -472,8 +495,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
472
495
  code = cell.getCode();
473
496
  i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
474
497
 
498
+ if (!rowHasBlinkingCells && cell.isBlink()) {
499
+ rowHasBlinkingCells = true;
500
+ }
501
+
475
502
  // Load colors/resolve overrides into work colors
476
- this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
503
+ this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
477
504
 
478
505
  // Override colors for cursor cell
479
506
  if (isCursorVisible && row === cursorY) {
@@ -501,6 +528,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
501
528
  }
502
529
  }
503
530
 
531
+ if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
532
+ this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
533
+ }
534
+
504
535
  if (code !== NULL_CELL_CODE) {
505
536
  this._model.lineLengths[y] = x + 1;
506
537
  }
@@ -547,11 +578,31 @@ export class WebglRenderer extends Disposable implements IRenderer {
547
578
  x--; // Go back to the previous update cell for next iteration
548
579
  }
549
580
  }
581
+ this._setRowBlinkState(y, rowHasBlinkingCells);
550
582
  }
551
583
  if (modelUpdated) {
552
584
  this._rectangleRenderer.value!.updateBackgrounds(this._model);
553
585
  }
554
586
  this._rectangleRenderer.value!.updateCursor(this._model);
587
+ this._updateTextBlinkState();
588
+ }
589
+
590
+ private _resetBlinkingRowState(): void {
591
+ this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
592
+ this._rowHasBlinkingCellsCount = 0;
593
+ }
594
+
595
+ private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
596
+ const previous = this._rowHasBlinkingCells[row];
597
+ if (previous === hasBlinkingCells) {
598
+ return;
599
+ }
600
+ this._rowHasBlinkingCells[row] = hasBlinkingCells;
601
+ this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
602
+ }
603
+
604
+ private _updateTextBlinkState(): void {
605
+ this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
555
606
  }
556
607
 
557
608
  /**
@@ -617,7 +668,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
617
668
  // the change as it's an exact multiple of the cell sizes.
618
669
  this._canvas.width = width;
619
670
  this._canvas.height = height;
620
- this._requestRedrawViewport();
671
+ // Render synchronously to avoid flicker when the canvas is cleared
672
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
621
673
  }
622
674
 
623
675
  private _requestRedrawViewport(): void {
@@ -7,6 +7,35 @@ import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType,
7
7
 
8
8
  /* eslint-disable max-len */
9
9
 
10
+ const enum Shapes {
11
+ /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
12
+ /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
13
+
14
+ /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
15
+ /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
16
+ /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
17
+ /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
18
+
19
+ /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
20
+ /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
21
+ /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
22
+ /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
23
+
24
+ /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
25
+ /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
26
+ /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
27
+ /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
28
+
29
+ /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
30
+
31
+ /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
32
+ /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
33
+ /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
34
+ /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
35
+ /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
36
+ /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
37
+ }
38
+
10
39
  namespace GitBranchSymbolsParts {
11
40
  // Lines
12
41
  export const LINE_H: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 });
@@ -324,6 +353,29 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
324
353
 
325
354
  // #endregion
326
355
 
356
+ // #region Progress Indicators (EE00-EE0B)
357
+
358
+ // initially added in Fira Code and later Nerd Fonts
359
+ // https://github.com/ryanoasis/nerd-fonts/pull/1733
360
+
361
+ // Progress bars (EE00-EE05)
362
+ '\u{EE00}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY START
363
+ '\u{EE01}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY MIDDLE
364
+ '\u{EE02}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z' }, // PROGRESS BAR EMPTY END
365
+ '\u{EE03}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z' }, // PROGRESS BAR FILLED START
366
+ '\u{EE04}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z' }, // PROGRESS BAR FILLED MIDDLE
367
+ '\u{EE05}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z' }, // PROGRESS BAR FILLED END
368
+
369
+ // Progress spinners (EE06-EE0B) - 6-frame spinner animation using stroked arcs
370
+ '\u{EE06}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 1 (12 o'clock)
371
+ '\u{EE07}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 2 (2 o'clock)
372
+ '\u{EE08}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 3 (4 o'clock)
373
+ '\u{EE09}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 4 (6 o'clock)
374
+ '\u{EE0A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 5 (8 o'clock)
375
+ '\u{EE0B}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 6 (10 o'clock)
376
+
377
+ // #endregion
378
+
327
379
  // #region Git Branch Symbols (F5D0-F60D)
328
380
 
329
381
  // Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
@@ -621,8 +673,8 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
621
673
  ] },
622
674
 
623
675
  // Diagonal fill characters (1FB98-1FB99)
624
- '\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,1 M0,.25 L.75,1 M0,.5 L.5,1 M0,.75 L.25,1 M.25,0 L1,.75 M.5,0 L1,.5 M.75,0 L1,.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
625
- '\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.25 L.25,0 M0,.5 L.5,0 M0,.75 L.75,0 M0,1 L1,0 M.25,1 L1,.25 M.5,1 L1,.5 M.75,1 L1,.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
676
+ '\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
677
+ '\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
626
678
 
627
679
  // Smooth mosaic terminal graphic characters (1FB9A-1FB9B)
628
680
  '\u{1FB9A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0', type: CustomGlyphVectorType.FILL } }, // UPPER AND LOWER TRIANGULAR HALF BLOCK
@@ -797,6 +849,27 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
797
849
  // #endregion
798
850
  };
799
851
 
852
+ export const blockPatternCodepoints = new Set<number>([
853
+ // Shade characters (2591-2593)
854
+ 0x2591,
855
+ 0x2592,
856
+ 0x2593,
857
+ // Rectangular shade characters (1FB8C-1FB94)
858
+ 0x1FB8C,
859
+ 0x1FB8D,
860
+ 0x1FB8E,
861
+ 0x1FB8F,
862
+ 0x1FB90,
863
+ 0x1FB91,
864
+ 0x1FB92,
865
+ 0x1FB94,
866
+ // Triangular shade characters (1FB9C-1FB9F)
867
+ 0x1FB9C,
868
+ 0x1FB9D,
869
+ 0x1FB9E,
870
+ 0x1FB9F
871
+ ]);
872
+
800
873
  /**
801
874
  * Generates a drawing function for sextant characters. Sextants are a 2x3 grid where each cell
802
875
  * can be on or off.
@@ -944,31 +1017,3 @@ function segmentedDigit(pattern: number): string {
944
1017
  return paths.join(' ');
945
1018
  }
946
1019
 
947
- const enum Shapes {
948
- /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
949
- /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
950
-
951
- /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
952
- /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
953
- /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
954
- /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
955
-
956
- /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
957
- /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
958
- /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
959
- /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
960
-
961
- /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
962
- /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
963
- /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
964
- /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
965
-
966
- /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
967
-
968
- /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
969
- /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
970
- /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
971
- /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
972
- /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
973
- /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
974
- }
@@ -22,14 +22,15 @@ export function tryDrawCustomGlyph(
22
22
  deviceCharHeight: number,
23
23
  fontSize: number,
24
24
  devicePixelRatio: number,
25
- backgroundColor?: string
25
+ backgroundColor?: string,
26
+ variantOffset: number = 0
26
27
  ): boolean {
27
28
  const unifiedCharDefinition = customGlyphDefinitions[c];
28
29
  if (unifiedCharDefinition) {
29
30
  // Normalize to array for uniform handling
30
31
  const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
31
32
  for (const part of parts) {
32
- drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor);
33
+ drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);
33
34
  }
34
35
  return true;
35
36
  }
@@ -48,7 +49,8 @@ function drawDefinitionPart(
48
49
  deviceCharHeight: number,
49
50
  fontSize: number,
50
51
  devicePixelRatio: number,
51
- backgroundColor?: string
52
+ backgroundColor?: string,
53
+ variantOffset: number = 0
52
54
  ): void {
53
55
  // Handle scaleType - adjust dimensions and offset when scaling to character area
54
56
  let drawWidth = deviceCellWidth;
@@ -74,7 +76,7 @@ function drawDefinitionPart(
74
76
  drawBlockVectorChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
75
77
  break;
76
78
  case CustomGlyphDefinitionType.BLOCK_PATTERN:
77
- drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
79
+ drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
78
80
  break;
79
81
  case CustomGlyphDefinitionType.PATH_FUNCTION:
80
82
  drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
@@ -437,7 +439,8 @@ function drawPatternChar(
437
439
  xOffset: number,
438
440
  yOffset: number,
439
441
  deviceCellWidth: number,
440
- deviceCellHeight: number
442
+ deviceCellHeight: number,
443
+ variantOffset: number = 0
441
444
  ): void {
442
445
  let patternSet = cachedPatterns.get(charDefinition);
443
446
  if (!patternSet) {
@@ -486,6 +489,15 @@ function drawPatternChar(
486
489
  pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
487
490
  patternSet.set(fillStyle, pattern);
488
491
  }
492
+ // Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
493
+ // variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
494
+ const dx = (variantOffset >> 1) & 1;
495
+ const dy = variantOffset & 1;
496
+ if (dx !== 0 || dy !== 0) {
497
+ pattern.setTransform(new DOMMatrix().translateSelf(-dx, -dy));
498
+ } else {
499
+ pattern.setTransform(new DOMMatrix());
500
+ }
489
501
  ctx.fillStyle = pattern;
490
502
  ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
491
503
  }
@@ -500,6 +512,11 @@ function drawPathFunctionCharacter(
500
512
  devicePixelRatio: number,
501
513
  strokeWidth?: number
502
514
  ): void {
515
+ ctx.save();
516
+ ctx.beginPath();
517
+ ctx.rect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
518
+ ctx.clip();
519
+
503
520
  ctx.beginPath();
504
521
  let actualInstructions: string;
505
522
  if (typeof charDefinition === 'function') {
@@ -526,7 +543,7 @@ function drawPathFunctionCharacter(
526
543
  if (!args[0] || !args[1]) {
527
544
  continue;
528
545
  }
529
- f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio), state);
546
+ f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio, 0, 0, false), state);
530
547
  state.lastCommand = type;
531
548
  }
532
549
  if (strokeWidth !== undefined) {
@@ -537,6 +554,7 @@ function drawPathFunctionCharacter(
537
554
  ctx.fill();
538
555
  }
539
556
  ctx.closePath();
557
+ ctx.restore();
540
558
  }
541
559
 
542
560
  /**
@@ -685,7 +703,7 @@ const svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext
685
703
  }
686
704
  };
687
705
 
688
- function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
706
+ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0, clampToCell: boolean = true): number[] {
689
707
  const result = args.map(e => parseFloat(e) || parseInt(e));
690
708
 
691
709
  if (result.length < 2) {
@@ -695,10 +713,11 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
695
713
  for (let x = 0; x < result.length; x += 2) {
696
714
  // Translate from 0-1 to 0-cellWidth
697
715
  result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);
698
- // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
699
- // line at 100% devicePixelRatio
716
+ // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
717
+ // clamp to the cell bounds.
700
718
  if (doClamp && result[x] !== 0) {
701
- result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
719
+ const rounded = Math.round(result[x] + 0.5) - 0.5;
720
+ result[x] = clampToCell ? clamp(rounded, cellWidth, 0) : rounded;
702
721
  }
703
722
  // Apply the cell's offset (ie. x*cellWidth)
704
723
  result[x] += xOffset + (leftPadding * devicePixelRatio);
@@ -707,10 +726,11 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
707
726
  for (let y = 1; y < result.length; y += 2) {
708
727
  // Translate from 0-1 to 0-cellHeight
709
728
  result[y] *= cellHeight;
710
- // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
711
- // line at 100% devicePixelRatio
729
+ // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
730
+ // clamp to the cell bounds.
712
731
  if (doClamp && result[y] !== 0) {
713
- result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
732
+ const rounded = Math.round(result[y] + 0.5) - 0.5;
733
+ result[y] = clampToCell ? clamp(rounded, cellHeight, 0) : rounded;
714
734
  }
715
735
  // Apply the cell's offset (ie. x*cellHeight)
716
736
  result[y] += yOffset;
@@ -7,7 +7,7 @@ import { ReadonlyColorSet } from 'browser/Types';
7
7
  import { acquireTextureAtlas } from '../CharAtlasCache';
8
8
  import { IRenderDimensions } from 'browser/renderer/shared/Types';
9
9
  import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
10
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
10
+ import { Disposable, toDisposable } from 'common/Lifecycle';
11
11
  import { CellData } from 'common/buffer/CellData';
12
12
  import { IOptionsService } from 'common/services/Services';
13
13
  import { Terminal } from '@xterm/xterm';
@@ -57,14 +57,17 @@ declare module '@xterm/addon-webgl' {
57
57
  * unicode ranges:
58
58
  *
59
59
  * - Box Drawing (U+2500-U+257F)
60
- * - Box Elements (U+2580-U+259F)
60
+ * - Block Elements (U+2580-U+259F)
61
61
  * - Braille Patterns (U+2800-U+28FF)
62
- * - Powerline Symbols (U+E0A0U+E0D4, Private Use Area with widespread
62
+ * - Powerline Symbols (U+E0A0-U+E0D4, Private Use Area with widespread
63
63
  * adoption)
64
+ * - Progress Indicators (U+EE00-U+EE0B, Private Use Area initially added in
65
+ * [Fira Code](https://github.com/tonsky/FiraCode) and later
66
+ * [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts/pull/1733)).
64
67
  * - Git Branch Symbols (U+F5D0-U+F60D, Private Use Area initially adopted
65
68
  * in [Kitty in 2024](https://github.com/kovidgoyal/kitty/pull/7681) by
66
69
  * author of [vim-flog](https://github.com/rbong/vim-flog))
67
- * - Symbols for Legacy Computing (U+1FB00U+1FBFF)
70
+ * - Symbols for Legacy Computing (U+1FB00-U+1FBFF)
68
71
  *
69
72
  * This will typically result in better rendering with continuous lines,
70
73
  * even when line height and letter spacing is used. The default is true.