diff --git a/src/components/terminal/terminal-view.tsx b/src/components/terminal/terminal-view.tsx index f77c261..a88e94c 100644 --- a/src/components/terminal/terminal-view.tsx +++ b/src/components/terminal/terminal-view.tsx @@ -9,6 +9,7 @@ import { terminalKill, } from "@/lib/api" import { useZoomLevel } from "@/hooks/use-appearance" +import { detectPlatform } from "@/hooks/use-platform" import type { TerminalEvent } from "@/lib/types" import type { ITheme, Terminal as XTermTerminal } from "@xterm/xterm" @@ -162,6 +163,43 @@ export function TerminalView({ fitAddonRef.current = fitAddon termRef.current = term + // Shell line-editing shortcuts. Sends readline/zle bindings so they + // work regardless of terminfo. + // Alt/Option + ←/→ / Backspace: word-level moves & delete + // macOS Cmd + ←/→ / Backspace : line-level moves & clear + // Uses `e.code` (physical key) to be robust against dead-key layouts on + // macOS where Option can turn some keys into `key: "Dead"`. + // AltGr on Windows/Linux is reported as ctrlKey+altKey and is excluded + // by the `!ctrlKey` guard below. + const isMac = detectPlatform() === "macos" + term.attachCustomKeyEventHandler((e) => { + if (e.type !== "keydown") return true + // Skip during IME composition to avoid corrupting candidate buffer. + if (e.isComposing) return true + + const { code, altKey, metaKey, ctrlKey, shiftKey } = e + + const writeSeq = (seq: string) => { + terminalWrite(terminalId, seq).catch(() => {}) + e.preventDefault() + return false + } + + if (altKey && !ctrlKey && !metaKey && !shiftKey) { + if (code === "ArrowLeft") return writeSeq("\x1bb") + if (code === "ArrowRight") return writeSeq("\x1bf") + if (code === "Backspace") return writeSeq("\x1b\x7f") + } + + if (isMac && metaKey && !altKey && !ctrlKey && !shiftKey) { + if (code === "ArrowLeft") return writeSeq("\x01") + if (code === "ArrowRight") return writeSeq("\x05") + if (code === "Backspace") return writeSeq("\x15") + } + + return true + }) + // Watch class changes for theme switching const themeObserver = new MutationObserver(() => { term.options.theme = getTerminalTheme(containerRef.current) diff --git a/src/hooks/use-platform.ts b/src/hooks/use-platform.ts index 86c8092..da2c830 100644 --- a/src/hooks/use-platform.ts +++ b/src/hooks/use-platform.ts @@ -4,7 +4,7 @@ import { useEffect, useState } from "react" export type PlatformType = "macos" | "windows" | "linux" | "unknown" -function detectPlatform(): PlatformType { +export function detectPlatform(): PlatformType { if (typeof navigator === "undefined") return "unknown" const platform = navigator.platform.toLowerCase()