From e2c3611c637f6d18fd5f6354e496b89cdb6bd173 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Tue, 13 May 2025 11:24:04 -0700 Subject: Multiline editor (#302) Co-authored-by: Taylor Mullen --- packages/cli/src/ui/components/InputPrompt.tsx | 90 ++++++++++++++++---------- 1 file changed, 56 insertions(+), 34 deletions(-) (limited to 'packages/cli/src/ui/components/InputPrompt.tsx') 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>; - inputKey: number; - setInputKey: React.Dispatch>; + 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 = ({ 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 = ({ 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 = ({ } 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 = ({ 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 ( > - { // 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). }} /> -- cgit v1.2.3