@aurigax-ai/ghostty-web 0.1.0 → 0.2.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.
@@ -13,7 +13,7 @@
13
13
  import { EventEmitter } from './event-emitter';
14
14
  import type { GhosttyTerminal } from './ghostty';
15
15
  import type { IEvent } from './interfaces';
16
- import type { CanvasRenderer } from './renderer';
16
+ import type { TerminalRenderer } from './renderer';
17
17
  import type { Terminal } from './terminal';
18
18
  import type { GhosttyCell } from './types';
19
19
 
@@ -34,7 +34,7 @@ export interface SelectionCoordinates {
34
34
 
35
35
  export class SelectionManager {
36
36
  private terminal: Terminal;
37
- private renderer: CanvasRenderer;
37
+ private renderer: TerminalRenderer;
38
38
  private wasmTerm: GhosttyTerminal;
39
39
  private textarea: HTMLTextAreaElement;
40
40
 
@@ -101,7 +101,7 @@ export class SelectionManager {
101
101
 
102
102
  constructor(
103
103
  terminal: Terminal,
104
- renderer: CanvasRenderer,
104
+ renderer: TerminalRenderer,
105
105
  wasmTerm: GhosttyTerminal,
106
106
  textarea: HTMLTextAreaElement
107
107
  ) {
@@ -114,6 +114,11 @@ export class SelectionManager {
114
114
  this.attachEventListeners();
115
115
  }
116
116
 
117
+ /** Points the selection at a new renderer drawing the same canvas. */
118
+ setRenderer(renderer: TerminalRenderer): void {
119
+ this.renderer = renderer;
120
+ }
121
+
117
122
  // ==========================================================================
118
123
  // Public API
119
124
  // ==========================================================================
package/lib/terminal.ts CHANGED
@@ -44,9 +44,10 @@ import { LinkDetector } from './link-detector';
44
44
  import { type IMarker, Marker } from './marker';
45
45
  import { OSC8LinkProvider } from './providers/osc8-link-provider';
46
46
  import { UrlRegexProvider } from './providers/url-regex-provider';
47
- import { CanvasRenderer } from './renderer';
47
+ import { CanvasRenderer, type RendererOptions, type TerminalRenderer } from './renderer';
48
48
  import { SelectionManager } from './selection-manager';
49
49
  import type { ILink, ILinkProvider } from './types';
50
+ import { WebglRenderer } from './webgl-renderer';
50
51
 
51
52
  // ============================================================================
52
53
  // Terminal Class
@@ -75,7 +76,7 @@ export class Terminal implements ITerminalCore {
75
76
  // Components (created on open())
76
77
  private ghostty?: Ghostty;
77
78
  public wasmTerm?: GhosttyTerminal; // Made public for link providers
78
- public renderer?: CanvasRenderer; // Made public for FitAddon
79
+ public renderer?: TerminalRenderer; // Made public for FitAddon
79
80
  private inputHandler?: InputHandler;
80
81
  private selectionManager?: SelectionManager;
81
82
  private canvas?: HTMLCanvasElement;
@@ -187,6 +188,7 @@ export class Terminal implements ITerminalCore {
187
188
  fontSize: options.fontSize ?? 15,
188
189
  fontFamily: options.fontFamily ?? 'monospace',
189
190
  allowTransparency: options.allowTransparency ?? false,
191
+ renderer: options.renderer ?? 'webgl',
190
192
  convertEol: options.convertEol ?? false,
191
193
  disableStdin: options.disableStdin ?? false,
192
194
  smoothScrollDuration: options.smoothScrollDuration ?? 100, // Default: 100ms smooth scroll
@@ -283,13 +285,6 @@ export class Terminal implements ITerminalCore {
283
285
  // Resize canvas to match new font metrics
284
286
  this.renderer.resize(this.cols, this.rows);
285
287
 
286
- // Update canvas element dimensions to match renderer
287
- const metrics = this.renderer.getMetrics();
288
- this.canvas.width = metrics.width * this.cols;
289
- this.canvas.height = metrics.height * this.rows;
290
- this.canvas.style.width = `${metrics.width * this.cols}px`;
291
- this.canvas.style.height = `${metrics.height * this.rows}px`;
292
-
293
288
  // Force full re-render with new font
294
289
  this.renderer.render(this.wasmTerm, true, this.viewportY, this);
295
290
  }
@@ -457,29 +452,18 @@ export class Terminal implements ITerminalCore {
457
452
  textarea.focus();
458
453
  });
459
454
 
460
- // Create renderer
461
- this.renderer = new CanvasRenderer(this.canvas, {
462
- fontSize: this.options.fontSize,
463
- fontFamily: this.options.fontFamily,
464
- cursorStyle: this.options.cursorStyle,
465
- cursorBlink: this.options.cursorBlink,
466
- theme: this.options.theme,
467
- });
468
- this.renderer.onNeedsFrame = () => this.wake();
469
-
470
- // Size canvas to terminal dimensions (use renderer.resize for proper DPI scaling)
455
+ this.renderer = this.createRenderer();
471
456
  this.renderer.resize(this.cols, this.rows);
472
457
 
473
458
  // Create mouse tracking configuration
474
459
  const canvas = this.canvas;
475
- const renderer = this.renderer;
476
460
  const wasmTerm = this.wasmTerm;
477
461
  const mouseConfig: MouseTrackingConfig = {
478
462
  hasMouseTracking: () => wasmTerm?.hasMouseTracking() ?? false,
479
463
  hasSgrMouseMode: () => wasmTerm?.getMode(1006, false) ?? true, // SGR extended mode
480
464
  getCellDimensions: () => ({
481
- width: renderer.charWidth,
482
- height: renderer.charHeight,
465
+ width: this.renderer!.charWidth,
466
+ height: this.renderer!.charHeight,
483
467
  }),
484
468
  getCanvasOffset: () => {
485
469
  const rect = canvas.getBoundingClientRect();
@@ -578,6 +562,57 @@ export class Terminal implements ITerminalCore {
578
562
  }
579
563
  }
580
564
 
565
+ /** Which renderer draws the terminal now: 'webgl', or 'canvas' when asked for or fallen back to. */
566
+ get rendererType(): 'webgl' | 'canvas' {
567
+ return this.renderer instanceof WebglRenderer ? 'webgl' : 'canvas';
568
+ }
569
+
570
+ private rendererOptions(): RendererOptions {
571
+ return {
572
+ fontSize: this.options.fontSize,
573
+ fontFamily: this.options.fontFamily,
574
+ cursorStyle: this.options.cursorStyle,
575
+ cursorBlink: this.options.cursorBlink,
576
+ theme: this.options.theme,
577
+ };
578
+ }
579
+
580
+ private createRenderer(): TerminalRenderer {
581
+ let renderer: TerminalRenderer | null = null;
582
+ if (this.options.renderer === 'webgl') {
583
+ try {
584
+ const webgl = new WebglRenderer(this.canvas!, this.rendererOptions());
585
+ webgl.onContextLost = () => this.fallBackToCanvas();
586
+ renderer = webgl;
587
+ } catch {
588
+ renderer = null;
589
+ }
590
+ }
591
+ renderer ??= new CanvasRenderer(this.canvas!, this.rendererOptions());
592
+ renderer.onNeedsFrame = () => this.wake();
593
+ return renderer;
594
+ }
595
+
596
+ /** Replaces a WebGL renderer whose context was lost with a canvas renderer on the same canvas. */
597
+ private fallBackToCanvas(): void {
598
+ if (!this.renderer || !this.canvas || !(this.renderer instanceof WebglRenderer)) return;
599
+ const hoveredLink = this.renderer.getHoveredHyperlinkId();
600
+ this.renderer.dispose();
601
+ const renderer = new CanvasRenderer(this.canvas, this.rendererOptions());
602
+ renderer.onNeedsFrame = () => this.wake();
603
+ renderer.setHoveredHyperlinkId(hoveredLink);
604
+ if (this.selectionManager) {
605
+ renderer.setSelectionManager(this.selectionManager);
606
+ this.selectionManager.setRenderer(renderer);
607
+ }
608
+ this.renderer = renderer;
609
+ renderer.resize(this.cols, this.rows);
610
+ if (this.wasmTerm) {
611
+ renderer.render(this.wasmTerm, true, this.viewportY, this, this.scrollbarOpacity);
612
+ }
613
+ this.wake();
614
+ }
615
+
581
616
  /**
582
617
  * Write data to terminal
583
618
  */
@@ -723,13 +758,6 @@ export class Terminal implements ITerminalCore {
723
758
  // Resize renderer
724
759
  this.renderer!.resize(cols, rows);
725
760
 
726
- // Update canvas dimensions
727
- const metrics = this.renderer!.getMetrics();
728
- this.canvas!.width = metrics.width * cols;
729
- this.canvas!.height = metrics.height * rows;
730
- this.canvas!.style.width = `${metrics.width * cols}px`;
731
- this.canvas!.style.height = `${metrics.height * rows}px`;
732
-
733
761
  // Fire resize event
734
762
  this.resizeEmitter.fire({ cols, rows });
735
763