summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/components/InputPrompt.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/components/InputPrompt.tsx')
-rw-r--r--packages/cli/src/ui/components/InputPrompt.tsx126
1 files changed, 118 insertions, 8 deletions
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 5a7b6353..db4eec1b 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -9,12 +9,13 @@ import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
import { useInputHistory } from '../hooks/useInputHistory.js';
-import { TextBuffer } from './shared/text-buffer.js';
+import { TextBuffer, logicalPosToOffset } from './shared/text-buffer.js';
import { cpSlice, cpLen } from '../utils/textUtils.js';
import chalk from 'chalk';
import stringWidth from 'string-width';
import { useShellHistory } from '../hooks/useShellHistory.js';
-import { useCompletion } from '../hooks/useCompletion.js';
+import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
+import { useSlashCompletion } from '../hooks/useSlashCompletion.js';
import { useKeypress, Key } from '../hooks/useKeypress.js';
import { CommandContext, SlashCommand } from '../commands/types.js';
import { Config } from '@google/gemini-cli-core';
@@ -69,18 +70,32 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setDirs(dirsChanged);
}
}, [dirs.length, dirsChanged]);
+ const [reverseSearchActive, setReverseSearchActive] = useState(false);
+ const [textBeforeReverseSearch, setTextBeforeReverseSearch] = useState('');
+ const [cursorPosition, setCursorPosition] = useState<[number, number]>([
+ 0, 0,
+ ]);
+ const shellHistory = useShellHistory(config.getProjectRoot());
+ const historyData = shellHistory.history;
- const completion = useCompletion(
+ const completion = useSlashCompletion(
buffer,
dirs,
config.getTargetDir(),
slashCommands,
commandContext,
+ reverseSearchActive,
config,
);
+ const reverseSearchCompletion = useReverseSearchCompletion(
+ buffer,
+ historyData,
+ reverseSearchActive,
+ );
const resetCompletionState = completion.resetCompletionState;
- const shellHistory = useShellHistory(config.getProjectRoot());
+ const resetReverseSearchCompletionState =
+ reverseSearchCompletion.resetCompletionState;
const handleSubmitAndClear = useCallback(
(submittedValue: string) => {
@@ -92,8 +107,16 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
buffer.setText('');
onSubmit(submittedValue);
resetCompletionState();
+ resetReverseSearchCompletionState();
},
- [onSubmit, buffer, resetCompletionState, shellModeActive, shellHistory],
+ [
+ onSubmit,
+ buffer,
+ resetCompletionState,
+ shellModeActive,
+ shellHistory,
+ resetReverseSearchCompletionState,
+ ],
);
const customSetTextAndResetCompletionSignal = useCallback(
@@ -118,6 +141,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
useEffect(() => {
if (justNavigatedHistory) {
resetCompletionState();
+ resetReverseSearchCompletionState();
setJustNavigatedHistory(false);
}
}, [
@@ -125,6 +149,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
buffer.text,
resetCompletionState,
setJustNavigatedHistory,
+ resetReverseSearchCompletionState,
]);
// Handle clipboard image pasting with Ctrl+V
@@ -197,6 +222,19 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
if (key.name === 'escape') {
+ if (reverseSearchActive) {
+ setReverseSearchActive(false);
+ reverseSearchCompletion.resetCompletionState();
+ buffer.setText(textBeforeReverseSearch);
+ const offset = logicalPosToOffset(
+ buffer.lines,
+ cursorPosition[0],
+ cursorPosition[1],
+ );
+ buffer.moveToOffset(offset);
+ return;
+ }
+
if (shellModeActive) {
setShellModeActive(false);
return;
@@ -208,11 +246,61 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
}
+ if (shellModeActive && key.ctrl && key.name === 'r') {
+ setReverseSearchActive(true);
+ setTextBeforeReverseSearch(buffer.text);
+ setCursorPosition(buffer.cursor);
+ return;
+ }
+
if (key.ctrl && key.name === 'l') {
onClearScreen();
return;
}
+ if (reverseSearchActive) {
+ const {
+ activeSuggestionIndex,
+ navigateUp,
+ navigateDown,
+ showSuggestions,
+ suggestions,
+ } = reverseSearchCompletion;
+
+ if (showSuggestions) {
+ if (key.name === 'up') {
+ navigateUp();
+ return;
+ }
+ if (key.name === 'down') {
+ navigateDown();
+ return;
+ }
+ if (key.name === 'tab') {
+ reverseSearchCompletion.handleAutocomplete(activeSuggestionIndex);
+ reverseSearchCompletion.resetCompletionState();
+ setReverseSearchActive(false);
+ return;
+ }
+ }
+
+ if (key.name === 'return' && !key.ctrl) {
+ const textToSubmit =
+ showSuggestions && activeSuggestionIndex > -1
+ ? suggestions[activeSuggestionIndex].value
+ : buffer.text;
+ handleSubmitAndClear(textToSubmit);
+ reverseSearchCompletion.resetCompletionState();
+ setReverseSearchActive(false);
+ return;
+ }
+
+ // Prevent up/down from falling through to regular history navigation
+ if (key.name === 'up' || key.name === 'down') {
+ return;
+ }
+ }
+
// If the command is a perfect match, pressing enter should execute it.
if (completion.isPerfectMatch && key.name === 'return') {
handleSubmitAndClear(buffer.text);
@@ -272,7 +360,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return;
}
} else {
- // Shell History Navigation
if (key.name === 'up') {
const prevCommand = shellHistory.getPreviousCommand();
if (prevCommand !== null) buffer.setText(prevCommand);
@@ -284,7 +371,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return;
}
}
-
if (key.name === 'return' && !key.ctrl && !key.meta && !key.paste) {
if (buffer.text.trim()) {
const [row, col] = buffer.cursor;
@@ -362,9 +448,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
inputHistory,
handleSubmitAndClear,
shellHistory,
+ reverseSearchCompletion,
handleClipboardImage,
resetCompletionState,
vimHandleInput,
+ reverseSearchActive,
+ textBeforeReverseSearch,
+ cursorPosition,
],
);
@@ -385,7 +475,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
<Text
color={shellModeActive ? Colors.AccentYellow : Colors.AccentPurple}
>
- {shellModeActive ? '! ' : '> '}
+ {shellModeActive ? (
+ reverseSearchActive ? (
+ <Text color={Colors.AccentCyan}>(r:) </Text>
+ ) : (
+ '! '
+ )
+ ) : (
+ '> '
+ )}
</Text>
<Box flexGrow={1} flexDirection="column">
{buffer.text.length === 0 && placeholder ? (
@@ -449,6 +547,18 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
/>
</Box>
)}
+ {reverseSearchActive && (
+ <Box>
+ <SuggestionsDisplay
+ suggestions={reverseSearchCompletion.suggestions}
+ activeIndex={reverseSearchCompletion.activeSuggestionIndex}
+ isLoading={reverseSearchCompletion.isLoadingSuggestions}
+ width={suggestionsWidth}
+ scrollOffset={reverseSearchCompletion.visibleStartIndex}
+ userInput={buffer.text}
+ />
+ </Box>
+ )}
</>
);
};