diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index 6d6b40e..c983653 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -55,6 +55,11 @@ export function Input({ const [cursorPos, setCursorPos] = useState(undefined); const [currentCursorPosition, setCurrentCursorPosition] = useState(0); + // Command history + const [history, setHistory] = useState([]); + const [historyIndex, setHistoryIndex] = useState(-1); + const [temporaryInput, setTemporaryInput] = useState(""); + // Sync with external mode changes (from plan approval dialog) useEffect(() => { if (externalMode !== undefined) { @@ -140,6 +145,44 @@ export function Input({ } }); + // Handle up/down arrow keys for command history + useInput((_input, key) => { + // Don't interfere with autocomplete navigation + if (isAutocompleteActive) { + return; + } + + if (key.upArrow) { + // Navigate backwards in history + if (history.length === 0) return; + + if (historyIndex === -1) { + // Starting to navigate history - save current input + setTemporaryInput(value); + // Go to most recent command + setHistoryIndex(history.length - 1); + setValue(history[history.length - 1] ?? ""); + } else if (historyIndex > 0) { + // Go to older command + setHistoryIndex(historyIndex - 1); + setValue(history[historyIndex - 1] ?? ""); + } + } else if (key.downArrow) { + // Navigate forwards in history + if (historyIndex === -1) return; // Not in history mode + + if (historyIndex < history.length - 1) { + // Go to newer command + setHistoryIndex(historyIndex + 1); + setValue(history[historyIndex + 1] ?? ""); + } else { + // At the end of history - restore temporary input + setHistoryIndex(-1); + setValue(temporaryInput); + } + } + }); + // Reset escape and ctrl-c state when user types (value changes) useEffect(() => { if (value !== previousValueRef.current && value !== "") { @@ -151,6 +194,16 @@ export function Input({ previousValueRef.current = value; }, [value]); + // Exit history mode when user starts typing + useEffect(() => { + // If user is in history mode and the value changes (they're typing) + // Exit history mode but keep the modified text + if (historyIndex !== -1 && value !== history[historyIndex]) { + setHistoryIndex(-1); + setTemporaryInput(""); + } + }, [value, historyIndex, history]); + // Clean up timers on unmount useEffect(() => { return () => { @@ -184,6 +237,16 @@ export function Input({ return; } const previousValue = value; + + // Add to history if not empty and not a duplicate of the last entry + if (previousValue.trim() && previousValue !== history[history.length - 1]) { + setHistory([...history, previousValue]); + } + + // Reset history navigation + setHistoryIndex(-1); + setTemporaryInput(""); + setValue(""); // Clear immediately for responsiveness const result = await onSubmit(previousValue); // If message was NOT submitted (e.g. pending approval), restore it