feat(terminal): add word/line cursor shortcuts for shell line-editing
Intercept keyboard events via xterm's custom key handler and emit readline/zle escape sequences so bindings work regardless of terminfo: - Alt/Option + Left/Right: word-wise cursor move - Alt/Option + Backspace: delete previous word - macOS Cmd + Left/Right: jump to line start/end - macOS Cmd + Backspace: clear to line start Uses `e.code` to stay correct on dead-key layouts, skips IME composition, and excludes AltGr (ctrl+alt) on Windows/Linux.
This commit is contained in:
@@ -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 <html> class changes for theme switching
|
||||
const themeObserver = new MutationObserver(() => {
|
||||
term.options.theme = getTerminalTheme(containerRef.current)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user