diff options
Diffstat (limited to 'packages/cli/src/ui/components/InputPrompt.tsx')
| -rw-r--r-- | packages/cli/src/ui/components/InputPrompt.tsx | 90 |
1 files changed, 56 insertions, 34 deletions
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 20d4bcdf..072fe074 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -5,40 +5,47 @@ */ import React, { useCallback } from 'react'; -import { Text, Box, useInput, useFocus, Key } from 'ink'; -import TextInput from 'ink-text-input'; +import { Text, Box, Key } from 'ink'; import { Colors } from '../colors.js'; import { Suggestion } from './SuggestionsDisplay.js'; +import { MultilineTextEditor } from './shared/multiline-editor.js'; interface InputPromptProps { query: string; - setQuery: React.Dispatch<React.SetStateAction<string>>; - inputKey: number; - setInputKey: React.Dispatch<React.SetStateAction<number>>; + onChange: (value: string) => void; + onChangeAndMoveCursor: (value: string) => void; + editorState: EditorState; onSubmit: (value: string) => void; showSuggestions: boolean; suggestions: Suggestion[]; activeSuggestionIndex: number; - navigateUp: () => void; - navigateDown: () => void; resetCompletion: () => void; + navigateHistoryUp: () => void; + navigateHistoryDown: () => void; + navigateSuggestionUp: () => void; + navigateSuggestionDown: () => void; +} + +export interface EditorState { + key: number; + initialCursorOffset?: number; } export const InputPrompt: React.FC<InputPromptProps> = ({ query, - setQuery, - inputKey, - setInputKey, + onChange, + onChangeAndMoveCursor, + editorState, onSubmit, showSuggestions, suggestions, activeSuggestionIndex, - navigateUp, - navigateDown, + navigateHistoryUp, + navigateHistoryDown, + navigateSuggestionUp, + navigateSuggestionDown, resetCompletion, }) => { - const { isFocused } = useFocus({ autoFocus: true }); - const handleAutocomplete = useCallback( (indexToUse: number) => { if (indexToUse < 0 || indexToUse >= suggestions.length) { @@ -52,7 +59,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ const slashIndex = query.indexOf('/'); const base = query.substring(0, slashIndex + 1); const newValue = base + selectedSuggestion.value; - setQuery(newValue); + onChangeAndMoveCursor(newValue); } else { // Handle @ command completion const atIndex = query.lastIndexOf('@'); @@ -73,32 +80,30 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ } const newValue = base + selectedSuggestion.value; - setQuery(newValue); + onChangeAndMoveCursor(newValue); } resetCompletion(); // Hide suggestions after selection - setInputKey((k) => k + 1); // Increment key to force re-render and cursor reset }, - [query, setQuery, suggestions, resetCompletion, setInputKey], + [query, suggestions, resetCompletion, onChangeAndMoveCursor], ); - useInput( + const inputPreprocessor = useCallback( (input: string, key: Key) => { - if (!isFocused) { - return; - } - if (showSuggestions) { if (key.upArrow) { - navigateUp(); + navigateSuggestionUp(); + return true; } else if (key.downArrow) { - navigateDown(); + navigateSuggestionDown(); + return true; } else if (key.tab) { if (suggestions.length > 0) { const targetIndex = activeSuggestionIndex === -1 ? 0 : activeSuggestionIndex; if (targetIndex < suggestions.length) { handleAutocomplete(targetIndex); + return true; } } } else if (key.return) { @@ -109,34 +114,51 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ onSubmit(query); } } + return true; } else if (key.escape) { resetCompletion(); + return true; } } - // Enter key when suggestions are NOT showing is handled by TextInput's onSubmit prop below + return false; }, - { isActive: true }, + [ + handleAutocomplete, + navigateSuggestionDown, + navigateSuggestionUp, + query, + suggestions, + showSuggestions, + resetCompletion, + activeSuggestionIndex, + onSubmit, + ], ); return ( <Box borderStyle="round" borderColor={Colors.AccentBlue} paddingX={1}> <Text color={Colors.AccentPurple}>> </Text> <Box flexGrow={1}> - <TextInput - key={inputKey.toString()} - value={query} - onChange={setQuery} + <MultilineTextEditor + key={editorState.key.toString()} + initialCursorOffset={editorState.initialCursorOffset} + initialText={query} + onChange={onChange} placeholder="Enter your message or use tools (e.g., @src/file.txt)..." + /* Account for width used by the box and > */ + navigateUp={navigateHistoryUp} + navigateDown={navigateHistoryDown} + inputPreprocessor={inputPreprocessor} + widthUsedByParent={3} + widthFraction={0.9} onSubmit={() => { // This onSubmit is for the TextInput component itself. // It should only fire if suggestions are NOT showing, - // as useInput handles Enter when suggestions are visible. + // as inputPreprocessor handles Enter when suggestions are visible. const trimmedQuery = query.trim(); if (!showSuggestions && trimmedQuery) { onSubmit(trimmedQuery); } - // If suggestions ARE showing, useInput's Enter handler - // would have already dealt with it (either completing or submitting). }} /> </Box> |
