diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 76431368f30..7890bc0dc8e 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -10,6 +10,7 @@ import { type ProjectId, type ProviderApprovalDecision, type ServerProvider, + type ResolvedKeybindingsConfig, type ScopedThreadRef, type ThreadId, type TurnId, @@ -415,6 +416,7 @@ interface PersistentThreadTerminalDrawerProps { splitShortcutLabel: string | undefined; newShortcutLabel: string | undefined; closeShortcutLabel: string | undefined; + keybindings: ResolvedKeybindingsConfig; onAddTerminalContext: (selection: TerminalContextSelection) => void; } @@ -427,6 +429,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra splitShortcutLabel, newShortcutLabel, closeShortcutLabel, + keybindings, onAddTerminalContext, }: PersistentThreadTerminalDrawerProps) { const serverThread = useStore(useMemo(() => createThreadSelectorByRef(threadRef), [threadRef])); @@ -570,6 +573,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra splitShortcutLabel={visible ? splitShortcutLabel : undefined} newShortcutLabel={visible ? newShortcutLabel : undefined} closeShortcutLabel={visible ? closeShortcutLabel : undefined} + keybindings={keybindings} onActiveTerminalChange={activateTerminal} onCloseTerminal={closeTerminal} onHeightChange={setTerminalHeight} @@ -3443,6 +3447,7 @@ export default function ChatView(props: ChatViewProps) { splitShortcutLabel={splitTerminalShortcutLabel ?? undefined} newShortcutLabel={newTerminalShortcutLabel ?? undefined} closeShortcutLabel={closeTerminalShortcutLabel ?? undefined} + keybindings={keybindings} onAddTerminalContext={addTerminalContextToDraft} /> ))} diff --git a/apps/web/src/components/ThreadTerminalDrawer.browser.tsx b/apps/web/src/components/ThreadTerminalDrawer.browser.tsx index 37e0df1cc4b..2df2e04f5c4 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.browser.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.browser.tsx @@ -177,6 +177,7 @@ async function mountTerminalViewport(props: { autoFocus={false} resizeEpoch={0} drawerHeight={320} + keybindings={[]} />, { container: host }, ); @@ -196,6 +197,7 @@ async function mountTerminalViewport(props: { autoFocus={false} resizeEpoch={0} drawerHeight={320} + keybindings={[]} />, ); }, diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 14f4f640501..6c71e5eb334 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1,6 +1,7 @@ import { FitAddon } from "@xterm/addon-fit"; import { Plus, SquareSplitHorizontal, TerminalSquare, Trash2, XIcon } from "lucide-react"; import { + type ResolvedKeybindingsConfig, type ScopedThreadRef, type TerminalEvent, type TerminalSessionSnapshot, @@ -29,7 +30,12 @@ import { wrappedTerminalLinkRangeIntersectsBufferLine, } from "../terminal-links"; import { + isDiffToggleShortcut, isTerminalClearShortcut, + isTerminalCloseShortcut, + isTerminalNewShortcut, + isTerminalSplitShortcut, + isTerminalToggleShortcut, terminalDeleteShortcutData, terminalNavigationShortcutData, } from "../keybindings"; @@ -255,6 +261,7 @@ interface TerminalViewportProps { autoFocus: boolean; resizeEpoch: number; drawerHeight: number; + keybindings: ResolvedKeybindingsConfig; } export function TerminalViewport({ @@ -271,6 +278,7 @@ export function TerminalViewport({ autoFocus, resizeEpoch, drawerHeight, + keybindings, }: TerminalViewportProps) { const containerRef = useRef(null); const terminalRef = useRef(null); @@ -282,6 +290,7 @@ export function TerminalViewport({ const selectionActionRequestIdRef = useRef(0); const selectionActionOpenRef = useRef(false); const selectionActionTimerRef = useRef(null); + const keybindingsRef = useRef(keybindings); const lastAppliedTerminalEventIdRef = useRef(0); const terminalHydratedRef = useRef(false); const handleSessionExited = useEffectEvent(() => { @@ -292,6 +301,10 @@ export function TerminalViewport({ }); const readTerminalLabel = useEffectEvent(() => terminalLabel); + useEffect(() => { + keybindingsRef.current = keybindings; + }, [keybindings]); + useEffect(() => { const mount = containerRef.current; if (!mount) return; @@ -403,6 +416,18 @@ export function TerminalViewport({ }; terminal.attachCustomKeyEventHandler((event) => { + const currentKeybindings = keybindingsRef.current; + const options = { context: { terminalFocus: true, terminalOpen: true } }; + if ( + isTerminalToggleShortcut(event, currentKeybindings, options) || + isTerminalSplitShortcut(event, currentKeybindings, options) || + isTerminalNewShortcut(event, currentKeybindings, options) || + isTerminalCloseShortcut(event, currentKeybindings, options) || + isDiffToggleShortcut(event, currentKeybindings, options) + ) { + return false; + } + const navigationData = terminalNavigationShortcutData(event); if (navigationData !== null) { event.preventDefault(); @@ -795,6 +820,7 @@ interface ThreadTerminalDrawerProps { onCloseTerminal: (terminalId: string) => void; onHeightChange: (height: number) => void; onAddTerminalContext: (selection: TerminalContextSelection) => void; + keybindings: ResolvedKeybindingsConfig; } interface TerminalActionButtonProps { @@ -848,6 +874,7 @@ export default function ThreadTerminalDrawer({ onCloseTerminal, onHeightChange, onAddTerminalContext, + keybindings, }: ThreadTerminalDrawerProps) { const [drawerHeight, setDrawerHeight] = useState(() => clampDrawerHeight(height)); const [resizeEpoch, setResizeEpoch] = useState(0); @@ -1166,6 +1193,7 @@ export default function ThreadTerminalDrawer({ autoFocus={terminalId === resolvedActiveTerminalId} resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} + keybindings={keybindings} /> @@ -1188,6 +1216,7 @@ export default function ThreadTerminalDrawer({ autoFocus resizeEpoch={resizeEpoch} drawerHeight={drawerHeight} + keybindings={keybindings} /> )}