@xterm/addon-webgl 0.20.0-beta.28 → 0.20.0-beta.281

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.
@@ -10,26 +10,32 @@ import { CursorBlinkStateManager } from './CursorBlinkStateManager';
10
10
  import { observeDevicePixelDimensions } from './DevicePixelObserver';
11
11
  import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
12
12
  import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
13
- import { CharData, IBufferLine, ICellData } from 'common/Types';
13
+ import { CharData, IBufferLine, ICellData } from 'common/buffer/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';
17
- import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
16
+ import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
17
+ import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
18
+ import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
18
19
  import { Terminal } from '@xterm/xterm';
19
20
  import { GlyphRenderer } from './GlyphRenderer';
20
21
  import { RectangleRenderer } from './RectangleRenderer';
21
- import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel';
22
+ import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './RenderModel';
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
 
31
+ const enum Constants {
32
+ MERGE_RETRY_LIMIT = 32
33
+ }
34
+
30
35
  export class WebglRenderer extends Disposable implements IRenderer {
31
36
  private _renderLayers: IRenderLayer[];
32
- private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
37
+ private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this._register(new MutableDisposable());
38
+ private _textBlinkStateManager: TextBlinkStateManager;
33
39
  private _charAtlasDisposable = this._register(new MutableDisposable());
34
40
  private _charAtlas: ITextureAtlas | undefined;
35
41
  private _devicePixelRatio: number;
@@ -37,8 +43,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
37
43
  private _observerDisposable = this._register(new MutableDisposable());
38
44
 
39
45
  private _model: RenderModel = new RenderModel();
46
+ private _rowHasBlinkingCells: boolean[] = [];
47
+ private _rowHasBlinkingCellsCount: number = 0;
40
48
  private _workCell: ICellData = new CellData();
41
- private _workCell2: ICellData = new CellData();
42
49
  private _cellColorResolver: CellColorResolver;
43
50
 
44
51
  private _canvas: HTMLCanvasElement;
@@ -70,6 +77,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
70
77
  private readonly _coreBrowserService: ICoreBrowserService,
71
78
  private readonly _coreService: ICoreService,
72
79
  private readonly _decorationService: IDecorationService,
80
+ private readonly _logService: ILogService,
73
81
  private readonly _optionsService: IOptionsService,
74
82
  private readonly _themeService: IThemeService,
75
83
  private readonly _customGlyphs: boolean = true,
@@ -105,23 +113,29 @@ export class WebglRenderer extends Disposable implements IRenderer {
105
113
  this._updateDimensions();
106
114
  this._updateCursorBlink();
107
115
  this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));
116
+ this._textBlinkStateManager = this._register(new TextBlinkStateManager(
117
+ () => this._requestRedrawViewport(),
118
+ this._coreBrowserService,
119
+ this._optionsService
120
+ ));
121
+ this._resetBlinkingRowState();
108
122
 
109
123
  this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
110
124
 
111
125
  this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
112
- console.log('webglcontextlost event received');
126
+ this._logService.debug('webglcontextlost event received');
113
127
  // Prevent the default behavior in order to enable WebGL context restoration.
114
128
  e.preventDefault();
115
129
  // Wait a few seconds to see if the 'webglcontextrestored' event is fired.
116
130
  // If not, dispatch the onContextLoss notification to observers.
117
131
  this._contextRestorationTimeout = setTimeout(() => {
118
132
  this._contextRestorationTimeout = undefined;
119
- console.warn('webgl context not restored; firing onContextLoss');
133
+ this._logService.warn('webgl context not restored; firing onContextLoss');
120
134
  this._onContextLoss.fire(e);
121
135
  }, 3000 /* ms */);
122
136
  }));
123
137
  this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
124
- console.warn('webglcontextrestored event received');
138
+ this._logService.warn('webglcontextrestored event received');
125
139
  clearTimeout(this._contextRestorationTimeout);
126
140
  this._contextRestorationTimeout = undefined;
127
141
  // The texture atlas and glyph renderer must be fully reinitialized
@@ -136,6 +150,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
136
150
  this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
137
151
  }));
138
152
 
153
+ this._register(addDisposableListener(this._coreBrowserService.mainDocument, 'mousedown', () => this._cursorBlinkStateManager.value?.restartBlinkAnimation()));
154
+
139
155
  this._core.screenElement!.appendChild(this._canvas);
140
156
 
141
157
  [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState();
@@ -143,6 +159,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
143
159
  this._isAttached = this._core.screenElement!.isConnected;
144
160
 
145
161
  this._register(toDisposable(() => {
162
+ clearTimeout(this._contextRestorationTimeout);
163
+ this._contextRestorationTimeout = undefined;
146
164
  for (const l of this._renderLayers) {
147
165
  l.dispose();
148
166
  }
@@ -176,6 +194,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
176
194
  this._updateDimensions();
177
195
 
178
196
  this._model.resize(this._terminal.cols, this._terminal.rows);
197
+ this._resetBlinkingRowState();
179
198
 
180
199
  // Resize all render layers
181
200
  for (const l of this._renderLayers) {
@@ -202,6 +221,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
202
221
  // Force a full refresh. Resizing `_glyphRenderer` should clear it already,
203
222
  // so there is no need to clear it again here.
204
223
  this._clearModel(false);
224
+
225
+ // Render synchronously to avoid flicker when the canvas is cleared
226
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
205
227
  }
206
228
 
207
229
  public handleCharSizeChanged(): void {
@@ -226,6 +248,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
226
248
  this._requestRedrawViewport();
227
249
  }
228
250
 
251
+ public handleViewportVisibilityChange(isVisible: boolean): void {
252
+ this._textBlinkStateManager.setViewportVisible(isVisible);
253
+ }
254
+
229
255
  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
230
256
  for (const l of this._renderLayers) {
231
257
  l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
@@ -251,8 +277,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
251
277
  * Initializes members dependent on WebGL context state.
252
278
  */
253
279
  private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
254
- this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
255
- this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);
280
+ this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService, this._logService);
281
+ this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService, this._logService);
256
282
 
257
283
  // Update dimensions and acquire char atlas
258
284
  this.handleCharSizeChanged();
@@ -285,8 +311,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
285
311
  if (this._charAtlas !== atlas) {
286
312
  this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
287
313
  this._charAtlasDisposable.value = combinedDisposable(
288
- Event.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
289
- Event.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
314
+ EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
315
+ EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
290
316
  );
291
317
  }
292
318
  this._charAtlas = atlas;
@@ -318,6 +344,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
318
344
  l.reset(this._terminal);
319
345
  }
320
346
 
347
+ this._resetBlinkingRowState();
348
+ this._textBlinkStateManager.setNeedsBlinkInViewport(false);
349
+
321
350
  this._cursorBlinkStateManager.value?.restartBlinkAnimation();
322
351
  this._updateCursorBlink();
323
352
  }
@@ -353,6 +382,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
353
382
  this._updateModel(start, end);
354
383
  }
355
384
 
385
+ // A mid-update atlas page merge invalidates vertex data and may not bump the host
386
+ // page's version, so re-run the update and force a full texture rebind.
387
+ let merged = false;
388
+ let mergeRetries = 0;
389
+ while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
390
+ merged = true;
391
+ this._clearModel(true);
392
+ this._updateModel(0, this._terminal.rows - 1);
393
+ }
394
+ if (merged) {
395
+ this._glyphRenderer.value.invalidateAtlasTextures();
396
+ }
397
+
356
398
  // Render
357
399
  this._rectangleRenderer.value.renderBackgrounds();
358
400
  this._glyphRenderer.value.render(this._model);
@@ -385,8 +427,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
385
427
  let line: IBufferLine;
386
428
  let joinedRanges: [number, number][];
387
429
  let isJoined: boolean;
388
- let skipJoinedCheckUntilX: number = 0;
389
- let isValidJoinRange: boolean = true;
430
+ let skipJoinedCheckUntilX: number;
431
+ let isValidJoinRange: boolean;
390
432
  let lastCharX: number;
391
433
  let range: [number, number];
392
434
  let isCursorRow: boolean;
@@ -414,7 +456,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
414
456
 
415
457
  for (y = start; y <= end; y++) {
416
458
  row = y + terminal.buffer.ydisp;
417
- line = terminal.buffer.lines.get(row)!;
459
+ const bufferLine = terminal.buffer.lines.get(row);
460
+ if (!bufferLine) {
461
+ this._model.lineLengths[y] = 0;
462
+ for (x = 0; x < terminal.cols; x++) {
463
+ j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
464
+ modelUpdated = true;
465
+ this._nullModelCell(x, y, j, 0, 0, 0);
466
+ }
467
+ this._setRowBlinkState(y, false);
468
+ continue;
469
+ }
470
+ line = bufferLine;
471
+ let rowHasBlinkingCells = false;
418
472
  this._model.lineLengths[y] = 0;
419
473
  isCursorRow = cursorY === row;
420
474
  skipJoinedCheckUntilX = 0;
@@ -470,10 +524,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
470
524
 
471
525
  chars = cell.getChars();
472
526
  code = cell.getCode();
473
- i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
527
+ i = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
528
+
529
+ if (!rowHasBlinkingCells && cell.isBlink()) {
530
+ rowHasBlinkingCells = true;
531
+ }
474
532
 
475
533
  // Load colors/resolve overrides into work colors
476
- this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
534
+ this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
477
535
 
478
536
  // Override colors for cursor cell
479
537
  if (isCursorVisible && row === cursorY) {
@@ -501,15 +559,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
501
559
  }
502
560
  }
503
561
 
562
+ if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
563
+ this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
564
+ }
565
+
504
566
  if (code !== NULL_CELL_CODE) {
505
567
  this._model.lineLengths[y] = x + 1;
506
568
  }
507
569
 
508
570
  // Nothing has changed, no updates needed
509
571
  if (this._model.cells[i] === code &&
510
- this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg &&
511
- this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg &&
512
- this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) {
572
+ this._model.cells[i + RenderModelConstants.BG_OFFSET] === this._cellColorResolver.result.bg &&
573
+ this._model.cells[i + RenderModelConstants.FG_OFFSET] === this._cellColorResolver.result.fg &&
574
+ this._model.cells[i + RenderModelConstants.EXT_OFFSET] === this._cellColorResolver.result.ext) {
513
575
  continue;
514
576
  }
515
577
 
@@ -522,9 +584,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
522
584
 
523
585
  // Cache the results in the model
524
586
  this._model.cells[i] = code;
525
- this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
526
- this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
527
- this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
587
+ this._model.cells[i + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
588
+ this._model.cells[i + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
589
+ this._model.cells[i + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
528
590
 
529
591
  width = cell.getWidth();
530
592
  this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg);
@@ -535,23 +597,47 @@ export class WebglRenderer extends Disposable implements IRenderer {
535
597
 
536
598
  // Null out non-first cells
537
599
  for (x++; x <= lastCharX; x++) {
538
- j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
539
- this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
540
- this._model.cells[j] = NULL_CELL_CODE;
600
+ j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
541
601
  // Don't re-resolve the cell color since multi-colored ligature backgrounds are not
542
602
  // supported
543
- this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
544
- this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
545
- this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
603
+ this._nullModelCell(x, y, j, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext);
546
604
  }
547
605
  x--; // Go back to the previous update cell for next iteration
548
606
  }
549
607
  }
608
+ this._setRowBlinkState(y, rowHasBlinkingCells);
550
609
  }
551
610
  if (modelUpdated) {
552
611
  this._rectangleRenderer.value!.updateBackgrounds(this._model);
553
612
  }
554
613
  this._rectangleRenderer.value!.updateCursor(this._model);
614
+ this._updateTextBlinkState();
615
+ }
616
+
617
+ private _nullModelCell(x: number, y: number, cellIndex: number, bg: number, fg: number, ext: number): void {
618
+ this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, bg, fg, ext, NULL_CELL_CHAR, 0, 0);
619
+ this._model.cells[cellIndex] = NULL_CELL_CODE;
620
+ this._model.cells[cellIndex + RenderModelConstants.BG_OFFSET] = bg;
621
+ this._model.cells[cellIndex + RenderModelConstants.FG_OFFSET] = fg;
622
+ this._model.cells[cellIndex + RenderModelConstants.EXT_OFFSET] = ext;
623
+ }
624
+
625
+ private _resetBlinkingRowState(): void {
626
+ this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
627
+ this._rowHasBlinkingCellsCount = 0;
628
+ }
629
+
630
+ private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
631
+ const previous = this._rowHasBlinkingCells[row];
632
+ if (previous === hasBlinkingCells) {
633
+ return;
634
+ }
635
+ this._rowHasBlinkingCells[row] = hasBlinkingCells;
636
+ this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
637
+ }
638
+
639
+ private _updateTextBlinkState(): void {
640
+ this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
555
641
  }
556
642
 
557
643
  /**
@@ -593,10 +679,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
593
679
  this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
594
680
  this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
595
681
 
596
- // The the size of the canvas on the page. It's important that this rounds to nearest integer
682
+ // The size of the canvas on the page. It's important that this rounds to nearest integer
597
683
  // and not ceils as browsers often have floating point precision issues where
598
684
  // `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
599
- // it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
685
+ // it's actually 1.1. Ceiling may cause blurriness as the backing canvas image is 1 pixel too
600
686
  // large for the canvas element size.
601
687
  this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
602
688
  this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);
@@ -617,7 +703,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
617
703
  // the change as it's an exact multiple of the cell sizes.
618
704
  this._canvas.width = width;
619
705
  this._canvas.height = height;
620
- this._requestRedrawViewport();
706
+ // Render synchronously to avoid flicker when the canvas is cleared
707
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
621
708
  }
622
709
 
623
710
  private _requestRedrawViewport(): void {
package/src/WebglUtils.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
7
+ import type { ILogService } from 'common/services/Services';
7
8
 
8
9
  /**
9
10
  * A matrix that when multiplies will translate 0-1 coordinates (left to right,
@@ -16,21 +17,21 @@ export const PROJECTION_MATRIX = new Float32Array([
16
17
  -1, 1, 0, 1
17
18
  ]);
18
19
 
19
- export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {
20
+ export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string, logService: ILogService): WebGLProgram | undefined {
20
21
  const program = throwIfFalsy(gl.createProgram());
21
- gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
22
- gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
22
+ gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, logService)));
23
+ gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, logService)));
23
24
  gl.linkProgram(program);
24
25
  const success = gl.getProgramParameter(program, gl.LINK_STATUS);
25
26
  if (success) {
26
27
  return program;
27
28
  }
28
29
 
29
- console.error(gl.getProgramInfoLog(program));
30
+ logService.error(gl.getProgramInfoLog(program));
30
31
  gl.deleteProgram(program);
31
32
  }
32
33
 
33
- export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
34
+ export function createShader(gl: WebGLRenderingContext, type: number, source: string, logService: ILogService): WebGLShader | undefined {
34
35
  const shader = throwIfFalsy(gl.createShader(type));
35
36
  gl.shaderSource(shader, source);
36
37
  gl.compileShader(shader);
@@ -39,7 +40,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
39
40
  return shader;
40
41
  }
41
42
 
42
- console.error(gl.getShaderInfoLog(shader));
43
+ logService.error(gl.getShaderInfoLog(shader));
43
44
  gl.deleteShader(shader);
44
45
  }
45
46
 
@@ -5,7 +5,36 @@
5
5
 
6
6
  import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';
7
7
 
8
- /* eslint-disable max-len */
8
+ /* oxlint-disable @stylistic/max-len -- generated glyph path data */
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
+ }
9
38
 
10
39
  namespace GitBranchSymbolsParts {
11
40
  // Lines
@@ -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
- }