Skip to content

Commit 9fb7cd8

Browse files
committed
fix: restore terminal config support and fix formatting
- Add GhosttyTerminalConfig struct and ghostty_terminal_new_with_config to WASM API - Update GhosttyTerminal constructor to accept optional config parameter - Update Ghostty.createTerminal() to pass config through - Terminal class now passes theme/scrollback options to WASM config - Add parseColor() helper to convert CSS colors to 0xRRGGBB format - Fix double semicolon formatting issue in terminal.ts Restored config options: - scrollbackLimit: Max scrollback lines (0 = unlimited) - fgColor: Default foreground color (0xRRGGBB) - bgColor: Default background color (0xRRGGBB) - cursorColor: Cursor color (0xRRGGBB) - palette: 16-color ANSI palette
1 parent 96da9d4 commit 9fb7cd8

File tree

4 files changed

+393
-133
lines changed

4 files changed

+393
-133
lines changed

lib/ghostty.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
CellFlags,
1111
type Cursor,
1212
DirtyState,
13+
GHOSTTY_CONFIG_SIZE,
1314
type GhosttyCell,
15+
type GhosttyTerminalConfig,
1416
type GhosttyWasmExports,
1517
KeyEncoderOption,
1618
type KeyEvent,
@@ -27,6 +29,7 @@ export {
2729
type Cursor,
2830
DirtyState,
2931
type GhosttyCell,
32+
type GhosttyTerminalConfig,
3033
KeyEncoderOption,
3134
type RGB,
3235
type RenderStateColors,
@@ -49,8 +52,12 @@ export class Ghostty {
4952
return new KeyEncoder(this.exports);
5053
}
5154

52-
createTerminal(cols: number = 80, rows: number = 24): GhosttyTerminal {
53-
return new GhosttyTerminal(this.exports, this.memory, cols, rows);
55+
createTerminal(
56+
cols: number = 80,
57+
rows: number = 24,
58+
config?: GhosttyTerminalConfig
59+
): GhosttyTerminal {
60+
return new GhosttyTerminal(this.exports, this.memory, cols, rows, config);
5461
}
5562

5663
static async load(wasmPath?: string): Promise<Ghostty> {
@@ -261,13 +268,61 @@ export class GhosttyTerminal {
261268
/** Cell pool for zero-allocation rendering */
262269
private cellPool: GhosttyCell[] = [];
263270

264-
constructor(exports: GhosttyWasmExports, memory: WebAssembly.Memory, cols: number, rows: number) {
271+
constructor(
272+
exports: GhosttyWasmExports,
273+
memory: WebAssembly.Memory,
274+
cols: number = 80,
275+
rows: number = 24,
276+
config?: GhosttyTerminalConfig
277+
) {
265278
this.exports = exports;
266279
this.memory = memory;
267280
this._cols = cols;
268281
this._rows = rows;
269282

270-
this.handle = this.exports.ghostty_terminal_new(cols, rows);
283+
if (config) {
284+
// Allocate config struct in WASM memory
285+
const configPtr = this.exports.ghostty_wasm_alloc_u8_array(GHOSTTY_CONFIG_SIZE);
286+
if (configPtr === 0) {
287+
throw new Error('Failed to allocate config (out of memory)');
288+
}
289+
290+
try {
291+
// Write config to WASM memory
292+
const view = new DataView(this.memory.buffer);
293+
let offset = configPtr;
294+
295+
// scrollback_limit (u32)
296+
view.setUint32(offset, config.scrollbackLimit ?? 10000, true);
297+
offset += 4;
298+
299+
// fg_color (u32)
300+
view.setUint32(offset, config.fgColor ?? 0, true);
301+
offset += 4;
302+
303+
// bg_color (u32)
304+
view.setUint32(offset, config.bgColor ?? 0, true);
305+
offset += 4;
306+
307+
// cursor_color (u32)
308+
view.setUint32(offset, config.cursorColor ?? 0, true);
309+
offset += 4;
310+
311+
// palette[16] (u32 * 16)
312+
for (let i = 0; i < 16; i++) {
313+
view.setUint32(offset, config.palette?.[i] ?? 0, true);
314+
offset += 4;
315+
}
316+
317+
this.handle = this.exports.ghostty_terminal_new_with_config(cols, rows, configPtr);
318+
} finally {
319+
// Free the config memory
320+
this.exports.ghostty_wasm_free_u8_array(configPtr, GHOSTTY_CONFIG_SIZE);
321+
}
322+
} else {
323+
this.handle = this.exports.ghostty_terminal_new(cols, rows);
324+
}
325+
271326
if (!this.handle) throw new Error('Failed to create terminal');
272327

273328
this.initCellPool();

lib/terminal.ts

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import { BufferNamespace } from './buffer';
1919
import { EventEmitter } from './event-emitter';
20-
import type { Ghostty, GhosttyCell, GhosttyTerminal } from './ghostty';
20+
import type { Ghostty, GhosttyCell, GhosttyTerminal, GhosttyTerminalConfig } from './ghostty';
2121
import { getGhostty } from './index';
2222
import { InputHandler } from './input-handler';
2323
import type {
@@ -38,6 +38,101 @@ import { CanvasRenderer } from './renderer';
3838
import { SelectionManager } from './selection-manager';
3939
import type { ILink, ILinkProvider } from './types';
4040

41+
// ============================================================================
42+
// Helpers
43+
// ============================================================================
44+
45+
/**
46+
* Parse a CSS color string to 0xRRGGBB format.
47+
* Supports hex (#RGB, #RRGGBB), rgb(), and named colors.
48+
*/
49+
function parseColor(color: string | undefined): number {
50+
if (!color) return 0;
51+
52+
// Handle hex colors
53+
if (color.startsWith('#')) {
54+
const hex = color.slice(1);
55+
if (hex.length === 3) {
56+
// #RGB -> #RRGGBB
57+
const r = Number.parseInt(hex[0] + hex[0], 16);
58+
const g = Number.parseInt(hex[1] + hex[1], 16);
59+
const b = Number.parseInt(hex[2] + hex[2], 16);
60+
return (r << 16) | (g << 8) | b;
61+
} else if (hex.length === 6) {
62+
return Number.parseInt(hex, 16);
63+
}
64+
}
65+
66+
// Handle rgb(r, g, b)
67+
const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
68+
if (rgbMatch) {
69+
const r = Number.parseInt(rgbMatch[1], 10);
70+
const g = Number.parseInt(rgbMatch[2], 10);
71+
const b = Number.parseInt(rgbMatch[3], 10);
72+
return (r << 16) | (g << 8) | b;
73+
}
74+
75+
// For named colors, return 0 (use default)
76+
return 0;
77+
}
78+
79+
/**
80+
* Build GhosttyTerminalConfig from ITerminalOptions
81+
*/
82+
function buildTerminalConfig(options: ITerminalOptions): GhosttyTerminalConfig | undefined {
83+
const theme = options.theme;
84+
const scrollback = options.scrollback;
85+
86+
// If no customizations, return undefined to use defaults
87+
if (!theme && scrollback === undefined) {
88+
return undefined;
89+
}
90+
91+
const config: GhosttyTerminalConfig = {};
92+
93+
if (scrollback !== undefined) {
94+
config.scrollbackLimit = scrollback;
95+
}
96+
97+
if (theme) {
98+
config.fgColor = parseColor(theme.foreground);
99+
config.bgColor = parseColor(theme.background);
100+
config.cursorColor = parseColor(theme.cursor);
101+
102+
// Build palette from theme colors
103+
const palette: number[] = [];
104+
const paletteColors = [
105+
theme.black,
106+
theme.red,
107+
theme.green,
108+
theme.yellow,
109+
theme.blue,
110+
theme.magenta,
111+
theme.cyan,
112+
theme.white,
113+
theme.brightBlack,
114+
theme.brightRed,
115+
theme.brightGreen,
116+
theme.brightYellow,
117+
theme.brightBlue,
118+
theme.brightMagenta,
119+
theme.brightCyan,
120+
theme.brightWhite,
121+
];
122+
123+
for (const color of paletteColors) {
124+
palette.push(parseColor(color));
125+
}
126+
127+
// Only set palette if at least one color is defined
128+
if (palette.some((c) => c !== 0)) {
129+
config.palette = palette;
130+
}
131+
}
132+
133+
return config;
134+
}
135+
41136
// ============================================================================
42137
// Terminal Class
43138
// ============================================================================
@@ -259,8 +354,9 @@ export class Terminal implements ITerminalCore {
259354
parent.setAttribute('aria-label', 'Terminal input');
260355
parent.setAttribute('aria-multiline', 'true');
261356

262-
// Create WASM terminal with current dimensions
263-
this.wasmTerm = this.ghostty!.createTerminal(this.cols, this.rows);;
357+
// Create WASM terminal with current dimensions and config
358+
const config = buildTerminalConfig(this.options);
359+
this.wasmTerm = this.ghostty!.createTerminal(this.cols, this.rows, config);
264360

265361
// Create canvas element
266362
this.canvas = document.createElement('canvas');
@@ -560,7 +656,8 @@ export class Terminal implements ITerminalCore {
560656
if (this.wasmTerm) {
561657
this.wasmTerm.free();
562658
}
563-
this.wasmTerm = this.ghostty!.createTerminal(this.cols, this.rows);
659+
const config = buildTerminalConfig(this.options);
660+
this.wasmTerm = this.ghostty!.createTerminal(this.cols, this.rows, config);
564661

565662
// Clear renderer
566663
this.renderer!.clear();

lib/types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ export interface GhosttyWasmExports extends WebAssembly.Exports {
344344

345345
// Terminal lifecycle
346346
ghostty_terminal_new(cols: number, rows: number): TerminalHandle;
347+
ghostty_terminal_new_with_config(cols: number, rows: number, configPtr: number): TerminalHandle;
347348
ghostty_terminal_free(terminal: TerminalHandle): void;
348349
ghostty_terminal_resize(terminal: TerminalHandle, cols: number, rows: number): void;
349350
ghostty_terminal_write(terminal: TerminalHandle, dataPtr: number, dataLen: number): void;
@@ -427,14 +428,23 @@ export const CURSOR_STRUCT_SIZE = 8;
427428
*/
428429
export const COLORS_STRUCT_SIZE = 12;
429430

430-
// Legacy - kept for compatibility but not used with new API
431+
/**
432+
* Terminal configuration (passed to ghostty_terminal_new_with_config)
433+
* All color values use 0xRRGGBB format. A value of 0 means "use default".
434+
*/
431435
export interface GhosttyTerminalConfig {
432436
scrollbackLimit?: number;
433437
fgColor?: number;
434438
bgColor?: number;
435439
cursorColor?: number;
436440
palette?: number[];
437441
}
442+
443+
/**
444+
* Size of GhosttyTerminalConfig struct in WASM memory (bytes).
445+
* Layout: scrollback_limit(u32) + fg_color(u32) + bg_color(u32) + cursor_color(u32) + palette[16](u32*16)
446+
* Total: 4 + 4 + 4 + 4 + 64 = 80 bytes
447+
*/
438448
export const GHOSTTY_CONFIG_SIZE = 80;
439449

440450
/**

0 commit comments

Comments
 (0)