Fix theme support and improve tooling
- Fix ITheme property: selection -> selectionBackground (ghostty-web compat) - Add dynamic body background color matching theme - Add THEME_BACKGROUNDS mapping in local_server.py - Add tsconfig.json for TypeScript type checking - Update Makefile to use bun run for all frontend commands - Add typecheck script to package.json (make build now typechecks) - Add detailed console tracing for theme debugging - Store fontFamily/fontSize in WebTerminal class for cell measurement v0.6.5
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: help install install-dev lint format test coverage check clean clean-all build build-all bundle bundle-watch bundle-clean bump-patch
|
.PHONY: help install install-dev lint format test coverage check clean clean-all build build-all bundle bundle-watch bundle-clean typecheck bump-patch
|
||||||
|
|
||||||
PYTHON ?= python3
|
PYTHON ?= python3
|
||||||
PIP ?= $(PYTHON) -m pip
|
PIP ?= $(PYTHON) -m pip
|
||||||
@@ -12,9 +12,11 @@ GHOSTTY_WASM = $(STATIC_JS_DIR)/ghostty-vt.wasm
|
|||||||
help:
|
help:
|
||||||
@echo "Build targets:"
|
@echo "Build targets:"
|
||||||
@echo " build-all - Full reproducible build (clean + deps + bundle + install)"
|
@echo " build-all - Full reproducible build (clean + deps + bundle + install)"
|
||||||
@echo " build - Build frontend only (bundle)"
|
@echo " build - Build frontend (typecheck + bundle)"
|
||||||
@echo " bundle - Build terminal.js and copy WASM"
|
@echo " build-fast - Build frontend without typecheck"
|
||||||
|
@echo " bundle - Alias for build"
|
||||||
@echo " bundle-watch - Watch mode for development"
|
@echo " bundle-watch - Watch mode for development"
|
||||||
|
@echo " typecheck - Run TypeScript type checking"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Python targets:"
|
@echo "Python targets:"
|
||||||
@echo " install - Install package in editable mode"
|
@echo " install - Install package in editable mode"
|
||||||
@@ -34,7 +36,7 @@ help:
|
|||||||
# Full reproducible build
|
# Full reproducible build
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
build-all: clean-all node_modules bundle install-dev check
|
build-all: clean-all node_modules build install-dev check
|
||||||
@echo "Build complete!"
|
@echo "Build complete!"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -64,6 +66,7 @@ check: lint coverage
|
|||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Frontend build targets (requires Bun: https://bun.sh)
|
# Frontend build targets (requires Bun: https://bun.sh)
|
||||||
|
# All frontend commands MUST go through bun run to ensure consistency
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Install node dependencies (creates bun.lock if missing)
|
# Install node dependencies (creates bun.lock if missing)
|
||||||
@@ -71,23 +74,26 @@ node_modules: package.json
|
|||||||
bun install
|
bun install
|
||||||
@touch node_modules
|
@touch node_modules
|
||||||
|
|
||||||
# Build terminal.js from TypeScript
|
# TypeScript type checking
|
||||||
$(TERMINAL_JS): $(TERMINAL_TS) node_modules
|
typecheck: node_modules
|
||||||
bun build $(TERMINAL_TS) --outfile=$(TERMINAL_JS) --minify --target=browser
|
bun run typecheck
|
||||||
|
|
||||||
# Copy WASM file from node_modules
|
# Main build target - typecheck + bundle + copy WASM
|
||||||
$(GHOSTTY_WASM): node_modules
|
build: node_modules
|
||||||
cp node_modules/ghostty-web/ghostty-vt.wasm $(GHOSTTY_WASM)
|
bun run build
|
||||||
|
|
||||||
# Main bundle target - builds JS and copies WASM
|
# Fast build without typecheck (for rapid iteration)
|
||||||
bundle: $(TERMINAL_JS) $(GHOSTTY_WASM)
|
build-fast: node_modules
|
||||||
|
bun run build:fast
|
||||||
|
@test -f $(GHOSTTY_WASM) || bun run copy-wasm
|
||||||
|
|
||||||
# Alias for bundle
|
# Alias for build
|
||||||
build: bundle
|
bundle: build
|
||||||
|
|
||||||
# Watch mode for development
|
# Watch mode for development
|
||||||
bundle-watch: $(GHOSTTY_WASM)
|
bundle-watch: node_modules
|
||||||
bun build $(TERMINAL_TS) --outfile=$(TERMINAL_JS) --watch --target=browser
|
@test -f $(GHOSTTY_WASM) || bun run copy-wasm
|
||||||
|
bun run watch
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Clean targets
|
# Clean targets
|
||||||
|
|||||||
+3
-1
@@ -9,8 +9,10 @@
|
|||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bun build src/textual_webterm/static/js/terminal.ts --outfile=src/textual_webterm/static/js/terminal.js --minify --target=browser && cp node_modules/ghostty-web/ghostty-vt.wasm src/textual_webterm/static/js/",
|
"build": "bun run typecheck && bun build src/textual_webterm/static/js/terminal.ts --outfile=src/textual_webterm/static/js/terminal.js --minify --target=browser && cp node_modules/ghostty-web/ghostty-vt.wasm src/textual_webterm/static/js/",
|
||||||
|
"build:fast": "bun build src/textual_webterm/static/js/terminal.ts --outfile=src/textual_webterm/static/js/terminal.js --minify --target=browser",
|
||||||
"watch": "bun build src/textual_webterm/static/js/terminal.ts --outfile=src/textual_webterm/static/js/terminal.js --watch --target=browser",
|
"watch": "bun build src/textual_webterm/static/js/terminal.ts --outfile=src/textual_webterm/static/js/terminal.js --watch --target=browser",
|
||||||
|
"typecheck": "bun x tsc --noEmit -p tsconfig.json",
|
||||||
"copy-wasm": "cp node_modules/ghostty-web/ghostty-vt.wasm src/textual_webterm/static/js/"
|
"copy-wasm": "cp node_modules/ghostty-web/ghostty-vt.wasm src/textual_webterm/static/js/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "textual-webterm"
|
name = "textual-webterm"
|
||||||
version = "0.6.4"
|
version = "0.6.5"
|
||||||
description = "Serve terminal sessions over the web"
|
description = "Serve terminal sessions over the web"
|
||||||
authors = ["Will McGugan <will@textualize.io>"]
|
authors = ["Will McGugan <will@textualize.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -38,6 +38,21 @@ CLEAR_AND_REDRAW_SEQ = "\x0c" # Ctrl+L: clear and redraw
|
|||||||
|
|
||||||
WEBTERM_STATIC_PATH = Path(__file__).parent / "static"
|
WEBTERM_STATIC_PATH = Path(__file__).parent / "static"
|
||||||
|
|
||||||
|
# Theme background colors - must match terminal.ts THEMES
|
||||||
|
THEME_BACKGROUNDS: dict[str, str] = {
|
||||||
|
"xterm": "#000000",
|
||||||
|
"monokai": "#2d2a2e",
|
||||||
|
"ristretto": "#2d2525",
|
||||||
|
"dark": "#1e1e1e",
|
||||||
|
"light": "#ffffff",
|
||||||
|
"dracula": "#282a36",
|
||||||
|
"catppuccin": "#1e1e2e",
|
||||||
|
"nord": "#2e3440",
|
||||||
|
"solarized-dark": "#002b36",
|
||||||
|
"solarized-light": "#fdf6e3",
|
||||||
|
"tokyo-night": "#1a1b26",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocalClientConnector(SessionConnector):
|
class LocalClientConnector(SessionConnector):
|
||||||
"""Local connector that handles communication between sessions and local server."""
|
"""Local connector that handles communication between sessions and local server."""
|
||||||
@@ -855,6 +870,9 @@ class LocalServer:
|
|||||||
escaped_font = self.font_family.replace('"', """)
|
escaped_font = self.font_family.replace('"', """)
|
||||||
data_attrs += f' data-font-family="{escaped_font}"'
|
data_attrs += f' data-font-family="{escaped_font}"'
|
||||||
|
|
||||||
|
# Get theme background color (fallback to black if unknown theme)
|
||||||
|
theme_bg = THEME_BACKGROUNDS.get(self.theme.lower(), "#000000")
|
||||||
|
|
||||||
html_content = f"""<!DOCTYPE html>
|
html_content = f"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -862,7 +880,7 @@ class LocalServer:
|
|||||||
<link rel=\"stylesheet\" href=\"/static/monospace.css\">
|
<link rel=\"stylesheet\" href=\"/static/monospace.css\">
|
||||||
<style>
|
<style>
|
||||||
html, body {{ width: 100%; height: 100%; }}
|
html, body {{ width: 100%; height: 100%; }}
|
||||||
body {{ background: #0c181f; margin: 0; padding: 0; overflow: hidden; }}
|
body {{ background: {theme_bg}; margin: 0; padding: 0; overflow: hidden; }}
|
||||||
.textual-terminal {{ width: 100%; height: 100%; display: block; overflow: hidden; }}
|
.textual-terminal {{ width: 100%; height: 100%; display: block; overflow: hidden; }}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -22,7 +22,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#e5e5e5",
|
foreground: "#e5e5e5",
|
||||||
cursor: "#e5e5e5",
|
cursor: "#e5e5e5",
|
||||||
cursorAccent: "#000000",
|
cursorAccent: "#000000",
|
||||||
selection: "#4d4d4d",
|
selectionBackground: "#4d4d4d",
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
red: "#cd0000",
|
red: "#cd0000",
|
||||||
green: "#00cd00",
|
green: "#00cd00",
|
||||||
@@ -46,7 +46,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#fcfcfa",
|
foreground: "#fcfcfa",
|
||||||
cursor: "#fcfcfa",
|
cursor: "#fcfcfa",
|
||||||
cursorAccent: "#2d2a2e",
|
cursorAccent: "#2d2a2e",
|
||||||
selection: "#5b595c",
|
selectionBackground: "#5b595c",
|
||||||
black: "#403e41",
|
black: "#403e41",
|
||||||
red: "#ff6188",
|
red: "#ff6188",
|
||||||
green: "#a9dc76",
|
green: "#a9dc76",
|
||||||
@@ -70,7 +70,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#fff1f3",
|
foreground: "#fff1f3",
|
||||||
cursor: "#fff1f3",
|
cursor: "#fff1f3",
|
||||||
cursorAccent: "#2d2525",
|
cursorAccent: "#2d2525",
|
||||||
selection: "#403838",
|
selectionBackground: "#403838",
|
||||||
black: "#2c2525",
|
black: "#2c2525",
|
||||||
red: "#fd6883",
|
red: "#fd6883",
|
||||||
green: "#adda78",
|
green: "#adda78",
|
||||||
@@ -94,7 +94,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#d4d4d4",
|
foreground: "#d4d4d4",
|
||||||
cursor: "#aeafad",
|
cursor: "#aeafad",
|
||||||
cursorAccent: "#1e1e1e",
|
cursorAccent: "#1e1e1e",
|
||||||
selection: "#264f78",
|
selectionBackground: "#264f78",
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
red: "#cd3131",
|
red: "#cd3131",
|
||||||
green: "#0dbc79",
|
green: "#0dbc79",
|
||||||
@@ -117,7 +117,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#383a42",
|
foreground: "#383a42",
|
||||||
cursor: "#526eff",
|
cursor: "#526eff",
|
||||||
cursorAccent: "#ffffff",
|
cursorAccent: "#ffffff",
|
||||||
selection: "#add6ff",
|
selectionBackground: "#add6ff",
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
red: "#e45649",
|
red: "#e45649",
|
||||||
green: "#50a14f",
|
green: "#50a14f",
|
||||||
@@ -140,7 +140,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#f8f8f2",
|
foreground: "#f8f8f2",
|
||||||
cursor: "#f8f8f2",
|
cursor: "#f8f8f2",
|
||||||
cursorAccent: "#282a36",
|
cursorAccent: "#282a36",
|
||||||
selection: "#44475a",
|
selectionBackground: "#44475a",
|
||||||
black: "#21222c",
|
black: "#21222c",
|
||||||
red: "#ff5555",
|
red: "#ff5555",
|
||||||
green: "#50fa7b",
|
green: "#50fa7b",
|
||||||
@@ -163,7 +163,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#cdd6f4",
|
foreground: "#cdd6f4",
|
||||||
cursor: "#f5e0dc",
|
cursor: "#f5e0dc",
|
||||||
cursorAccent: "#1e1e2e",
|
cursorAccent: "#1e1e2e",
|
||||||
selection: "#45475a",
|
selectionBackground: "#45475a",
|
||||||
black: "#45475a",
|
black: "#45475a",
|
||||||
red: "#f38ba8",
|
red: "#f38ba8",
|
||||||
green: "#a6e3a1",
|
green: "#a6e3a1",
|
||||||
@@ -186,7 +186,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#d8dee9",
|
foreground: "#d8dee9",
|
||||||
cursor: "#d8dee9",
|
cursor: "#d8dee9",
|
||||||
cursorAccent: "#2e3440",
|
cursorAccent: "#2e3440",
|
||||||
selection: "#434c5e",
|
selectionBackground: "#434c5e",
|
||||||
black: "#3b4252",
|
black: "#3b4252",
|
||||||
red: "#bf616a",
|
red: "#bf616a",
|
||||||
green: "#a3be8c",
|
green: "#a3be8c",
|
||||||
@@ -209,7 +209,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#ebdbb2",
|
foreground: "#ebdbb2",
|
||||||
cursor: "#ebdbb2",
|
cursor: "#ebdbb2",
|
||||||
cursorAccent: "#282828",
|
cursorAccent: "#282828",
|
||||||
selection: "#504945",
|
selectionBackground: "#504945",
|
||||||
black: "#282828",
|
black: "#282828",
|
||||||
red: "#cc241d",
|
red: "#cc241d",
|
||||||
green: "#98971a",
|
green: "#98971a",
|
||||||
@@ -232,7 +232,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#839496",
|
foreground: "#839496",
|
||||||
cursor: "#839496",
|
cursor: "#839496",
|
||||||
cursorAccent: "#002b36",
|
cursorAccent: "#002b36",
|
||||||
selection: "#073642",
|
selectionBackground: "#073642",
|
||||||
black: "#073642",
|
black: "#073642",
|
||||||
red: "#dc322f",
|
red: "#dc322f",
|
||||||
green: "#859900",
|
green: "#859900",
|
||||||
@@ -255,7 +255,7 @@ const THEMES: Record<string, ITheme> = {
|
|||||||
foreground: "#a9b1d6",
|
foreground: "#a9b1d6",
|
||||||
cursor: "#c0caf5",
|
cursor: "#c0caf5",
|
||||||
cursorAccent: "#1a1b26",
|
cursorAccent: "#1a1b26",
|
||||||
selection: "#33467c",
|
selectionBackground: "#33467c",
|
||||||
black: "#15161e",
|
black: "#15161e",
|
||||||
red: "#f7768e",
|
red: "#f7768e",
|
||||||
green: "#9ece6a",
|
green: "#9ece6a",
|
||||||
@@ -285,31 +285,45 @@ interface TerminalConfig {
|
|||||||
|
|
||||||
/** Parse configuration from element data attributes */
|
/** Parse configuration from element data attributes */
|
||||||
function parseConfig(element: HTMLElement): TerminalConfig {
|
function parseConfig(element: HTMLElement): TerminalConfig {
|
||||||
|
console.log("[webterm:parseConfig] Parsing config from element");
|
||||||
const config: TerminalConfig = {};
|
const config: TerminalConfig = {};
|
||||||
|
|
||||||
if (element.dataset.fontFamily) {
|
if (element.dataset.fontFamily) {
|
||||||
config.fontFamily = element.dataset.fontFamily;
|
config.fontFamily = element.dataset.fontFamily;
|
||||||
|
console.log(`[webterm:parseConfig] fontFamily: "${config.fontFamily}"`);
|
||||||
}
|
}
|
||||||
if (element.dataset.fontSize) {
|
if (element.dataset.fontSize) {
|
||||||
config.fontSize = parseInt(element.dataset.fontSize, 10);
|
config.fontSize = parseInt(element.dataset.fontSize, 10);
|
||||||
|
console.log(`[webterm:parseConfig] fontSize: ${config.fontSize}`);
|
||||||
}
|
}
|
||||||
if (element.dataset.scrollback) {
|
if (element.dataset.scrollback) {
|
||||||
config.scrollback = parseInt(element.dataset.scrollback, 10);
|
config.scrollback = parseInt(element.dataset.scrollback, 10);
|
||||||
|
console.log(`[webterm:parseConfig] scrollback: ${config.scrollback}`);
|
||||||
}
|
}
|
||||||
if (element.dataset.theme) {
|
if (element.dataset.theme) {
|
||||||
const themeName = element.dataset.theme.toLowerCase();
|
const themeName = element.dataset.theme.toLowerCase();
|
||||||
|
console.log(`[webterm:parseConfig] theme attribute: "${element.dataset.theme}" -> normalized: "${themeName}"`);
|
||||||
|
console.log(`[webterm:parseConfig] Available themes: ${Object.keys(THEMES).join(", ")}`);
|
||||||
|
console.log(`[webterm:parseConfig] Theme "${themeName}" in THEMES? ${themeName in THEMES}`);
|
||||||
|
|
||||||
if (themeName in THEMES) {
|
if (themeName in THEMES) {
|
||||||
config.theme = THEMES[themeName];
|
config.theme = THEMES[themeName];
|
||||||
|
console.log(`[webterm:parseConfig] Using built-in theme "${themeName}":`, JSON.stringify(config.theme, null, 2));
|
||||||
} else {
|
} else {
|
||||||
// Try parsing as JSON for custom themes
|
// Try parsing as JSON for custom themes
|
||||||
|
console.log(`[webterm:parseConfig] Theme not found in THEMES, trying JSON parse...`);
|
||||||
try {
|
try {
|
||||||
config.theme = JSON.parse(element.dataset.theme) as ITheme;
|
config.theme = JSON.parse(element.dataset.theme) as ITheme;
|
||||||
} catch {
|
console.log(`[webterm:parseConfig] Parsed custom JSON theme:`, config.theme);
|
||||||
console.warn(`Unknown theme "${element.dataset.theme}", using default`);
|
} catch (e) {
|
||||||
|
console.warn(`[webterm:parseConfig] Unknown theme "${element.dataset.theme}", JSON parse failed:`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`[webterm:parseConfig] No theme attribute found on element`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[webterm:parseConfig] Final config:`, config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,17 +368,23 @@ class WebTerminal {
|
|||||||
private mobileKeybar: HTMLElement | null = null;
|
private mobileKeybar: HTMLElement | null = null;
|
||||||
private ctrlActive = false;
|
private ctrlActive = false;
|
||||||
private shiftActive = false;
|
private shiftActive = false;
|
||||||
|
private fontFamily: string;
|
||||||
|
private fontSize: number;
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
wsUrl: string,
|
wsUrl: string,
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
fitAddon: FitAddon
|
fitAddon: FitAddon,
|
||||||
|
fontFamily: string,
|
||||||
|
fontSize: number
|
||||||
) {
|
) {
|
||||||
this.element = container;
|
this.element = container;
|
||||||
this.wsUrl = wsUrl;
|
this.wsUrl = wsUrl;
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
this.fitAddon = fitAddon;
|
this.fitAddon = fitAddon;
|
||||||
|
this.fontFamily = fontFamily;
|
||||||
|
this.fontSize = fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create and initialize a WebTerminal instance */
|
/** Create and initialize a WebTerminal instance */
|
||||||
@@ -373,29 +393,71 @@ class WebTerminal {
|
|||||||
wsUrl: string,
|
wsUrl: string,
|
||||||
config: TerminalConfig
|
config: TerminalConfig
|
||||||
): Promise<WebTerminal> {
|
): Promise<WebTerminal> {
|
||||||
|
console.log("[webterm:create] WebTerminal.create() called");
|
||||||
|
console.log("[webterm:create] Container:", container);
|
||||||
|
console.log("[webterm:create] wsUrl:", wsUrl);
|
||||||
|
console.log("[webterm:create] Config received:", JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
// Determine WASM path - try to find it relative to the script location
|
// Determine WASM path - try to find it relative to the script location
|
||||||
const wasmPath = getWasmPath();
|
const wasmPath = getWasmPath();
|
||||||
|
console.log("[webterm:create] WASM path:", wasmPath);
|
||||||
|
|
||||||
// Build terminal options
|
// Build terminal options
|
||||||
|
const themeToUse = config.theme ?? THEMES.xterm;
|
||||||
|
console.log("[webterm:create] Theme to use (config.theme ?? THEMES.xterm):", JSON.stringify(themeToUse, null, 2));
|
||||||
|
|
||||||
const options: ITerminalOptions = {
|
const options: ITerminalOptions = {
|
||||||
fontFamily: config.fontFamily ?? DEFAULT_FONT_FAMILY,
|
fontFamily: config.fontFamily ?? DEFAULT_FONT_FAMILY,
|
||||||
fontSize: config.fontSize ?? 16,
|
fontSize: config.fontSize ?? 16,
|
||||||
scrollback: config.scrollback ?? 1000,
|
scrollback: config.scrollback ?? 1000,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cursorStyle: "block",
|
cursorStyle: "block",
|
||||||
theme: config.theme ?? THEMES.xterm,
|
theme: themeToUse,
|
||||||
wasmPath,
|
wasmPath,
|
||||||
};
|
};
|
||||||
|
console.log("[webterm:create] Full ITerminalOptions:", JSON.stringify(options, null, 2));
|
||||||
|
|
||||||
|
console.log("[webterm:create] Creating ghostty-web Terminal instance...");
|
||||||
const terminal = new Terminal(options);
|
const terminal = new Terminal(options);
|
||||||
|
console.log("[webterm:create] Terminal created:", terminal);
|
||||||
|
console.log("[webterm:create] Terminal.options:", (terminal as unknown as { options?: unknown }).options);
|
||||||
|
|
||||||
|
console.log("[webterm:create] Creating FitAddon...");
|
||||||
const fitAddon = new FitAddon();
|
const fitAddon = new FitAddon();
|
||||||
|
console.log("[webterm:create] Loading FitAddon into terminal...");
|
||||||
terminal.loadAddon(fitAddon);
|
terminal.loadAddon(fitAddon);
|
||||||
|
|
||||||
// Open terminal (this loads WASM and initializes everything)
|
// Open terminal (this loads WASM and initializes everything)
|
||||||
|
console.log("[webterm:create] Calling terminal.open(container)...");
|
||||||
await terminal.open(container);
|
await terminal.open(container);
|
||||||
|
console.log("[webterm:create] terminal.open() completed");
|
||||||
|
|
||||||
const instance = new WebTerminal(container, wsUrl, terminal, fitAddon);
|
// Check internal state after open
|
||||||
|
const internalTerminal = terminal as unknown as Record<string, unknown>;
|
||||||
|
console.log("[webterm:create] Terminal internal keys:", Object.keys(internalTerminal));
|
||||||
|
if (internalTerminal.renderer) {
|
||||||
|
console.log("[webterm:create] Renderer exists:", internalTerminal.renderer);
|
||||||
|
const renderer = internalTerminal.renderer as Record<string, unknown>;
|
||||||
|
console.log("[webterm:create] Renderer keys:", Object.keys(renderer));
|
||||||
|
if (renderer.theme) {
|
||||||
|
console.log("[webterm:create] Renderer.theme:", renderer.theme);
|
||||||
|
}
|
||||||
|
if (renderer.palette) {
|
||||||
|
console.log("[webterm:create] Renderer.palette:", renderer.palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new WebTerminal(
|
||||||
|
container,
|
||||||
|
wsUrl,
|
||||||
|
terminal,
|
||||||
|
fitAddon,
|
||||||
|
options.fontFamily ?? DEFAULT_FONT_FAMILY,
|
||||||
|
options.fontSize ?? 16
|
||||||
|
);
|
||||||
|
console.log("[webterm:create] WebTerminal instance created");
|
||||||
instance.initialize();
|
instance.initialize();
|
||||||
|
console.log("[webterm:create] WebTerminal initialized");
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,8 +924,8 @@ class WebTerminal {
|
|||||||
const testElement = document.createElement('span');
|
const testElement = document.createElement('span');
|
||||||
testElement.style.visibility = 'hidden';
|
testElement.style.visibility = 'hidden';
|
||||||
testElement.style.position = 'absolute';
|
testElement.style.position = 'absolute';
|
||||||
testElement.style.fontFamily = this.options.fontFamily || 'monospace';
|
testElement.style.fontFamily = this.fontFamily;
|
||||||
testElement.style.fontSize = `${this.options.fontSize || 14}px`;
|
testElement.style.fontSize = `${this.fontSize}px`;
|
||||||
testElement.style.lineHeight = 'normal';
|
testElement.style.lineHeight = 'normal';
|
||||||
testElement.textContent = 'W';
|
testElement.textContent = 'W';
|
||||||
|
|
||||||
@@ -1052,9 +1114,14 @@ const instances: Map<HTMLElement, WebTerminal> = new Map();
|
|||||||
|
|
||||||
/** Initialize all terminal containers on page load */
|
/** Initialize all terminal containers on page load */
|
||||||
async function initTerminals(): Promise<void> {
|
async function initTerminals(): Promise<void> {
|
||||||
|
console.log("[webterm:init] initTerminals() called");
|
||||||
const containers = document.querySelectorAll<HTMLElement>(".textual-terminal");
|
const containers = document.querySelectorAll<HTMLElement>(".textual-terminal");
|
||||||
|
console.log(`[webterm:init] Found ${containers.length} .textual-terminal containers`);
|
||||||
|
|
||||||
for (const el of containers) {
|
for (const el of containers) {
|
||||||
|
console.log("[webterm:init] Processing container:", el);
|
||||||
|
console.log("[webterm:init] Dataset:", JSON.stringify(el.dataset));
|
||||||
|
|
||||||
const wsUrl = el.dataset.sessionWebsocketUrl;
|
const wsUrl = el.dataset.sessionWebsocketUrl;
|
||||||
if (!wsUrl) {
|
if (!wsUrl) {
|
||||||
console.error("Missing data-session-websocket-url on terminal container");
|
console.error("Missing data-session-websocket-url on terminal container");
|
||||||
@@ -1062,8 +1129,12 @@ async function initTerminals(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = parseConfig(el);
|
const config = parseConfig(el);
|
||||||
|
console.log("[webterm:init] Parsed config:", JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log("[webterm:init] Calling WebTerminal.create()...");
|
||||||
const terminal = await WebTerminal.create(el, wsUrl, config);
|
const terminal = await WebTerminal.create(el, wsUrl, config);
|
||||||
|
console.log("[webterm:init] WebTerminal created successfully");
|
||||||
instances.set(el, terminal);
|
instances.set(el, terminal);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to create terminal:", e);
|
console.error("Failed to create terminal:", e);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||||
|
},
|
||||||
|
"include": ["src/textual_webterm/static/js/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user