Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions lib/terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2462,6 +2462,99 @@ describe('Options Proxy handleOptionChange', () => {

expect(term.options.cursorStyle).toBe('underline');
});

test('changing fontSize updates renderer and resizes canvas', async () => {
if (!container) return;

const term = await createIsolatedTerminal({ fontSize: 15, cols: 80, rows: 24 });
term.open(container);

// @ts-ignore - accessing private for test
const renderer = term.renderer;

// Verify initial font size
// @ts-ignore - accessing private for test
expect(renderer.fontSize).toBe(15);

// Change font size
term.options.fontSize = 20;

// Verify option was updated
expect(term.options.fontSize).toBe(20);

// Verify renderer's internal fontSize was updated
// @ts-ignore - accessing private for test
expect(renderer.fontSize).toBe(20);

// Verify metrics were recalculated (getMetrics returns a copy)
const metrics = renderer.getMetrics();
expect(metrics).toBeDefined();
expect(metrics.width).toBeGreaterThan(0);
expect(metrics.height).toBeGreaterThan(0);

term.dispose();
});

test('changing fontFamily updates renderer', async () => {
if (!container) return;

const term = await createIsolatedTerminal({ fontFamily: 'monospace', cols: 80, rows: 24 });
term.open(container);

// @ts-ignore - accessing private for test
const renderer = term.renderer;

// Change font family
term.options.fontFamily = 'Courier New, monospace';

// Verify option was updated
expect(term.options.fontFamily).toBe('Courier New, monospace');

// Verify renderer was updated
// @ts-ignore - accessing private for test
expect(renderer.fontFamily).toBe('Courier New, monospace');

term.dispose();
});

test('font change clears active selection', async () => {
if (!container) return;

const term = await createIsolatedTerminal({ fontSize: 15, cols: 80, rows: 24 });
term.open(container);

// Write some text and select it
term.write('Hello World');
term.select(0, 0, 5); // Select "Hello"
expect(term.hasSelection()).toBe(true);

// Change font size
term.options.fontSize = 20;

// Selection should be cleared (pixel positions changed)
expect(term.hasSelection()).toBe(false);

term.dispose();
});

test('font change maintains terminal dimensions (cols/rows)', async () => {
if (!container) return;

const term = await createIsolatedTerminal({ fontSize: 15, cols: 80, rows: 24 });
term.open(container);

const initialCols = term.cols;
const initialRows = term.rows;

// Change font size
term.options.fontSize = 20;

// Cols and rows should remain the same (canvas grows instead)
expect(term.cols).toBe(initialCols);
expect(term.rows).toBe(initialRows);

term.dispose();
});
});

// ==========================================================================
Expand Down
35 changes: 34 additions & 1 deletion lib/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,16 @@ export class Terminal implements ITerminalCore {
break;

case 'fontSize':
if (this.renderer) {
this.renderer.setFontSize(this.options.fontSize);
this.handleFontChange();
}
break;

case 'fontFamily':
if (this.renderer) {
console.warn('ghostty-web: font changes after open() are not yet fully supported');
this.renderer.setFontFamily(this.options.fontFamily);
this.handleFontChange();
}
break;

Expand All @@ -220,6 +227,32 @@ export class Terminal implements ITerminalCore {
}
}

/**
* Handle font changes (fontSize or fontFamily)
* Updates canvas size to match new font metrics and forces a full re-render
*/
private handleFontChange(): void {
if (!this.renderer || !this.wasmTerm || !this.canvas) return;

// Clear any active selection since pixel positions have changed
if (this.selectionManager) {
this.selectionManager.clearSelection();
}

// Resize canvas to match new font metrics
this.renderer.resize(this.cols, this.rows);

// Update canvas element dimensions to match renderer
const metrics = this.renderer.getMetrics();
this.canvas.width = metrics.width * this.cols;
this.canvas.height = metrics.height * this.rows;
this.canvas.style.width = `${metrics.width * this.cols}px`;
this.canvas.style.height = `${metrics.height * this.rows}px`;

// Force full re-render with new font
this.renderer.render(this.wasmTerm, true, this.viewportY, this);
}

/**
* Parse a CSS color string to 0xRRGGBB format.
* Returns 0 if the color is undefined or invalid.
Expand Down