summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/App.tsx
diff options
context:
space:
mode:
authorLee Won Jun <[email protected]>2025-08-09 16:03:17 +0900
committerGitHub <[email protected]>2025-08-09 07:03:17 +0000
commitb8084ba8158b89facd49fd78a51abb80b1db54da (patch)
tree5fd41e255b5118d53798c29d9fad95478a1ed582 /packages/cli/src/ui/App.tsx
parent6487cc16895976ef6c983f8beca08a64addb6688 (diff)
Centralize Key Binding Logic and Refactor (Reopen) (#5356)
Co-authored-by: Lee-WonJun <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/App.tsx')
-rw-r--r--packages/cli/src/ui/App.tsx107
1 files changed, 64 insertions, 43 deletions
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index e3c77ad0..7ee9405f 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -13,8 +13,6 @@ import {
Text,
useStdin,
useStdout,
- useInput,
- type Key as InkKeyType,
} from 'ink';
import { StreamingState, type HistoryItem, MessageType } from './types.js';
import { useTerminalSize } from './hooks/useTerminalSize.js';
@@ -81,6 +79,8 @@ import { useBracketedPaste } from './hooks/useBracketedPaste.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useVimMode, VimModeProvider } from './contexts/VimModeContext.js';
import { useVim } from './hooks/vim.js';
+import { useKeypress, Key } from './hooks/useKeypress.js';
+import { keyMatchers, Command } from './keyMatchers.js';
import * as fs from 'fs';
import { UpdateNotification } from './components/UpdateNotification.js';
import {
@@ -613,50 +613,71 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
[handleSlashCommand],
);
- useInput((input: string, key: InkKeyType) => {
- let enteringConstrainHeightMode = false;
- if (!constrainHeight) {
- // Automatically re-enter constrain height mode if the user types
- // anything. When constrainHeight==false, the user will experience
- // significant flickering so it is best to disable it immediately when
- // the user starts interacting with the app.
- enteringConstrainHeightMode = true;
- setConstrainHeight(true);
- }
+ const handleGlobalKeypress = useCallback(
+ (key: Key) => {
+ let enteringConstrainHeightMode = false;
+ if (!constrainHeight) {
+ enteringConstrainHeightMode = true;
+ setConstrainHeight(true);
+ }
- if (key.ctrl && input === 'o') {
- setShowErrorDetails((prev) => !prev);
- } else if (key.ctrl && input === 't') {
- const newValue = !showToolDescriptions;
- setShowToolDescriptions(newValue);
+ if (keyMatchers[Command.SHOW_ERROR_DETAILS](key)) {
+ setShowErrorDetails((prev) => !prev);
+ } else if (keyMatchers[Command.TOGGLE_TOOL_DESCRIPTIONS](key)) {
+ const newValue = !showToolDescriptions;
+ setShowToolDescriptions(newValue);
- const mcpServers = config.getMcpServers();
- if (Object.keys(mcpServers || {}).length > 0) {
- handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
- }
- } else if (
- key.ctrl &&
- input === 'e' &&
- config.getIdeMode() &&
- ideContextState
- ) {
- handleSlashCommand('/ide status');
- } else if (key.ctrl && (input === 'c' || input === 'C')) {
- if (isAuthenticating) {
- // Let AuthInProgress component handle the input.
- return;
- }
- handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
- } else if (key.ctrl && (input === 'd' || input === 'D')) {
- if (buffer.text.length > 0) {
- // Do nothing if there is text in the input.
- return;
+ const mcpServers = config.getMcpServers();
+ if (Object.keys(mcpServers || {}).length > 0) {
+ handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
+ }
+ } else if (
+ keyMatchers[Command.TOGGLE_IDE_CONTEXT_DETAIL](key) &&
+ config.getIdeMode() &&
+ ideContextState
+ ) {
+ // Show IDE status when in IDE mode and context is available.
+ handleSlashCommand('/ide status');
+ } else if (keyMatchers[Command.QUIT](key)) {
+ // When authenticating, let AuthInProgress component handle Ctrl+C.
+ if (isAuthenticating) {
+ return;
+ }
+ handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
+ } else if (keyMatchers[Command.EXIT](key)) {
+ if (buffer.text.length > 0) {
+ return;
+ }
+ handleExit(ctrlDPressedOnce, setCtrlDPressedOnce, ctrlDTimerRef);
+ } else if (
+ keyMatchers[Command.SHOW_MORE_LINES](key) &&
+ !enteringConstrainHeightMode
+ ) {
+ setConstrainHeight(false);
}
- handleExit(ctrlDPressedOnce, setCtrlDPressedOnce, ctrlDTimerRef);
- } else if (key.ctrl && input === 's' && !enteringConstrainHeightMode) {
- setConstrainHeight(false);
- }
- });
+ },
+ [
+ constrainHeight,
+ setConstrainHeight,
+ setShowErrorDetails,
+ showToolDescriptions,
+ setShowToolDescriptions,
+ config,
+ ideContextState,
+ handleExit,
+ ctrlCPressedOnce,
+ setCtrlCPressedOnce,
+ ctrlCTimerRef,
+ buffer.text.length,
+ ctrlDPressedOnce,
+ setCtrlDPressedOnce,
+ ctrlDTimerRef,
+ handleSlashCommand,
+ isAuthenticating,
+ ],
+ );
+
+ useKeypress(handleGlobalKeypress, { isActive: true });
useEffect(() => {
if (config) {