summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useInputHistory.ts
diff options
context:
space:
mode:
authorEvan Senter <[email protected]>2025-04-19 14:31:59 +0100
committerGitHub <[email protected]>2025-04-19 14:31:59 +0100
commit75ecb4a81fa76aa00374601b2c0bbe9d657b4aa7 (patch)
tree5793e545a45073801a1c817530ff095769e1399c /packages/cli/src/ui/hooks/useInputHistory.ts
parent2f5f6baf0f4c9c1133b0271fcb3b9e89402b97a1 (diff)
Adding in a history buffer (#38)
Up and down arrows traverse the command history.
Diffstat (limited to 'packages/cli/src/ui/hooks/useInputHistory.ts')
-rw-r--r--packages/cli/src/ui/hooks/useInputHistory.ts125
1 files changed, 125 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/useInputHistory.ts b/packages/cli/src/ui/hooks/useInputHistory.ts
new file mode 100644
index 00000000..9a6aaacb
--- /dev/null
+++ b/packages/cli/src/ui/hooks/useInputHistory.ts
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { useState, useCallback } from 'react';
+import { useInput } from 'ink';
+
+// Props for the hook
+interface UseInputHistoryProps {
+ userMessages: readonly string[]; // History of user messages
+ onSubmit: (value: string) => void; // Original submit function from App
+ isActive: boolean; // To enable/disable the useInput hook
+}
+
+// Return type of the hook
+interface UseInputHistoryReturn {
+ query: string; // The current input query managed by the hook
+ setQuery: React.Dispatch<React.SetStateAction<string>>; // Setter for the query
+ handleSubmit: (value: string) => void; // Wrapped submit handler
+ inputKey: number; // Key to force input reset
+}
+
+export function useInputHistory({
+ userMessages,
+ onSubmit,
+ isActive,
+}: UseInputHistoryProps): UseInputHistoryReturn {
+ const [query, setQuery] = useState(''); // Hook manages its own query state
+ const [historyIndex, setHistoryIndex] = useState<number>(-1); // -1 means current query
+ const [originalQueryBeforeNav, setOriginalQueryBeforeNav] =
+ useState<string>('');
+ const [inputKey, setInputKey] = useState<number>(0); // Key for forcing input reset
+
+ // Function to reset navigation state, called on submit or manual reset
+ const resetHistoryNav = useCallback(() => {
+ setHistoryIndex(-1);
+ setOriginalQueryBeforeNav('');
+ }, []);
+
+ // Wrapper for the onSubmit prop to include resetting history navigation
+ const handleSubmit = useCallback(
+ (value: string) => {
+ const trimmedValue = value.trim();
+ if (trimmedValue) {
+ // Only submit non-empty values
+ onSubmit(trimmedValue); // Call the original submit function
+ }
+ setQuery(''); // Clear the input field managed by this hook
+ resetHistoryNav(); // Reset history state
+ // Don't increment inputKey here, only on nav changes
+ },
+ [onSubmit, resetHistoryNav],
+ );
+
+ useInput(
+ (input, key) => {
+ // Do nothing if the hook is not active
+ if (!isActive) {
+ return;
+ }
+
+ let didNavigate = false;
+
+ if (key.upArrow) {
+ if (userMessages.length === 0) return;
+
+ let nextIndex = historyIndex;
+ if (historyIndex === -1) {
+ // Starting navigation UP, save current input
+ setOriginalQueryBeforeNav(query);
+ nextIndex = 0; // Go to the most recent item (index 0 in reversed view)
+ } else if (historyIndex < userMessages.length - 1) {
+ // Continue navigating UP (towards older items)
+ nextIndex = historyIndex + 1;
+ } else {
+ return; // Already at the oldest item
+ }
+
+ if (nextIndex !== historyIndex) {
+ setHistoryIndex(nextIndex);
+ // History is ordered newest to oldest, so access from the end
+ const newValue = userMessages[userMessages.length - 1 - nextIndex];
+ setQuery(newValue);
+ setInputKey((k) => k + 1); // Increment key on navigation change
+ didNavigate = true;
+ }
+ } else if (key.downArrow) {
+ if (historyIndex === -1) return; // Already at the bottom (current input)
+
+ const nextIndex = historyIndex - 1; // Move towards more recent items / current input
+ setHistoryIndex(nextIndex);
+
+ if (nextIndex === -1) {
+ // Restore original query
+ setQuery(originalQueryBeforeNav);
+ } else {
+ // Set query based on reversed index
+ const newValue = userMessages[userMessages.length - 1 - nextIndex];
+ setQuery(newValue);
+ }
+ setInputKey((k) => k + 1); // Increment key on navigation change
+ didNavigate = true;
+ } else {
+ // If user types anything other than arrows while navigating, reset history navigation state
+ if (historyIndex !== -1 && !didNavigate) {
+ // Check if it's a key that modifies input content
+ if (input || key.backspace || key.delete) {
+ resetHistoryNav();
+ // The actual query state update for typing is handled by the component's onChange calling setQuery
+ }
+ }
+ }
+ },
+ { isActive }, // Pass isActive to useInput
+ );
+
+ return {
+ query,
+ setQuery, // Return the hook's setQuery
+ handleSubmit, // Return the wrapped submit handler
+ inputKey, // Return the key
+ };
+}