mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
ux(polish) autocomplete in the input prompt (#18181)
This commit is contained in:
@@ -160,7 +160,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
backgroundShells,
|
||||
backgroundShellHeight,
|
||||
} = useUIState();
|
||||
const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
|
||||
const [suppressCompletion, setSuppressCompletion] = useState(false);
|
||||
const escPressCount = useRef(0);
|
||||
const [showEscapePrompt, setShowEscapePrompt] = useState(false);
|
||||
const escapeTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -181,15 +181,16 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
const shellHistory = useShellHistory(config.getProjectRoot());
|
||||
const shellHistoryData = shellHistory.history;
|
||||
|
||||
const completion = useCommandCompletion(
|
||||
const completion = useCommandCompletion({
|
||||
buffer,
|
||||
config.getTargetDir(),
|
||||
cwd: config.getTargetDir(),
|
||||
slashCommands,
|
||||
commandContext,
|
||||
reverseSearchActive,
|
||||
shellModeActive,
|
||||
config,
|
||||
);
|
||||
active: !suppressCompletion,
|
||||
});
|
||||
|
||||
const reverseSearchCompletion = useReverseSearchCompletion(
|
||||
buffer,
|
||||
@@ -302,11 +303,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
);
|
||||
|
||||
const customSetTextAndResetCompletionSignal = useCallback(
|
||||
(newText: string) => {
|
||||
buffer.setText(newText);
|
||||
setJustNavigatedHistory(true);
|
||||
(newText: string, cursorPosition?: 'start' | 'end' | number) => {
|
||||
buffer.setText(newText, cursorPosition);
|
||||
setSuppressCompletion(true);
|
||||
},
|
||||
[buffer, setJustNavigatedHistory],
|
||||
[buffer, setSuppressCompletion],
|
||||
);
|
||||
|
||||
const inputHistory = useInputHistory({
|
||||
@@ -316,25 +317,26 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
(!completion.showSuggestions || completion.suggestions.length === 1) &&
|
||||
!shellModeActive,
|
||||
currentQuery: buffer.text,
|
||||
currentCursorOffset: buffer.getOffset(),
|
||||
onChange: customSetTextAndResetCompletionSignal,
|
||||
});
|
||||
|
||||
// Effect to reset completion if history navigation just occurred and set the text
|
||||
useEffect(() => {
|
||||
if (justNavigatedHistory) {
|
||||
if (suppressCompletion) {
|
||||
resetCompletionState();
|
||||
resetReverseSearchCompletionState();
|
||||
resetCommandSearchCompletionState();
|
||||
setExpandedSuggestionIndex(-1);
|
||||
setJustNavigatedHistory(false);
|
||||
}
|
||||
}, [
|
||||
justNavigatedHistory,
|
||||
suppressCompletion,
|
||||
buffer.text,
|
||||
resetCompletionState,
|
||||
setJustNavigatedHistory,
|
||||
setSuppressCompletion,
|
||||
resetReverseSearchCompletionState,
|
||||
resetCommandSearchCompletionState,
|
||||
setExpandedSuggestionIndex,
|
||||
]);
|
||||
|
||||
// Helper function to handle loading queued messages into input
|
||||
@@ -405,6 +407,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
useMouseClick(
|
||||
innerBoxRef,
|
||||
(_event, relX, relY) => {
|
||||
setSuppressCompletion(true);
|
||||
if (isEmbeddedShellFocused) {
|
||||
setEmbeddedShellFocused(false);
|
||||
}
|
||||
@@ -470,6 +473,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
useMouse(
|
||||
(event: MouseEvent) => {
|
||||
if (event.name === 'right-release') {
|
||||
setSuppressCompletion(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
handleClipboardPaste();
|
||||
}
|
||||
@@ -479,6 +483,50 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
|
||||
const handleInput = useCallback(
|
||||
(key: Key) => {
|
||||
// Determine if this keypress is a history navigation command
|
||||
const isHistoryUp =
|
||||
!shellModeActive &&
|
||||
(keyMatchers[Command.HISTORY_UP](key) ||
|
||||
(keyMatchers[Command.NAVIGATION_UP](key) &&
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
(buffer.visualCursor[0] === 0 && buffer.visualScrollRow === 0))));
|
||||
const isHistoryDown =
|
||||
!shellModeActive &&
|
||||
(keyMatchers[Command.HISTORY_DOWN](key) ||
|
||||
(keyMatchers[Command.NAVIGATION_DOWN](key) &&
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
buffer.visualCursor[0] === buffer.allVisualLines.length - 1)));
|
||||
|
||||
const isHistoryNav = isHistoryUp || isHistoryDown;
|
||||
const isCursorMovement =
|
||||
keyMatchers[Command.MOVE_LEFT](key) ||
|
||||
keyMatchers[Command.MOVE_RIGHT](key) ||
|
||||
keyMatchers[Command.MOVE_UP](key) ||
|
||||
keyMatchers[Command.MOVE_DOWN](key) ||
|
||||
keyMatchers[Command.MOVE_WORD_LEFT](key) ||
|
||||
keyMatchers[Command.MOVE_WORD_RIGHT](key) ||
|
||||
keyMatchers[Command.HOME](key) ||
|
||||
keyMatchers[Command.END](key);
|
||||
|
||||
const isSuggestionsNav =
|
||||
(completion.showSuggestions ||
|
||||
reverseSearchCompletion.showSuggestions ||
|
||||
commandSearchCompletion.showSuggestions) &&
|
||||
(keyMatchers[Command.COMPLETION_UP](key) ||
|
||||
keyMatchers[Command.COMPLETION_DOWN](key) ||
|
||||
keyMatchers[Command.EXPAND_SUGGESTION](key) ||
|
||||
keyMatchers[Command.COLLAPSE_SUGGESTION](key) ||
|
||||
keyMatchers[Command.ACCEPT_SUGGESTION](key));
|
||||
|
||||
// Reset completion suppression if the user performs any action other than
|
||||
// history navigation or cursor movement.
|
||||
// We explicitly skip this if we are currently navigating suggestions.
|
||||
if (!isSuggestionsNav) {
|
||||
setSuppressCompletion(
|
||||
isHistoryNav || isCursorMovement || keyMatchers[Command.ESCAPE](key),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(jacobr): this special case is likely not needed anymore.
|
||||
// We should probably stop supporting paste if the InputPrompt is not
|
||||
// focused.
|
||||
@@ -702,6 +750,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
// We prioritize execution unless the user is explicitly selecting a different suggestion.
|
||||
if (
|
||||
completion.isPerfectMatch &&
|
||||
completion.completionMode !== CompletionMode.AT &&
|
||||
keyMatchers[Command.RETURN](key) &&
|
||||
(!completion.showSuggestions || completion.activeSuggestionIndex <= 0)
|
||||
) {
|
||||
@@ -801,7 +850,14 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.HISTORY_UP](key)) {
|
||||
if (isHistoryUp) {
|
||||
if (
|
||||
keyMatchers[Command.NAVIGATION_UP](key) &&
|
||||
buffer.visualCursor[1] > 0
|
||||
) {
|
||||
buffer.move('home');
|
||||
return true;
|
||||
}
|
||||
// Check for queued messages first when input is empty
|
||||
// If no queued messages, inputHistory.navigateUp() is called inside tryLoadQueuedMessages
|
||||
if (tryLoadQueuedMessages()) {
|
||||
@@ -811,41 +867,43 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
inputHistory.navigateUp();
|
||||
return true;
|
||||
}
|
||||
if (keyMatchers[Command.HISTORY_DOWN](key)) {
|
||||
inputHistory.navigateDown();
|
||||
return true;
|
||||
}
|
||||
// Handle arrow-up/down for history on single-line or at edges
|
||||
if (
|
||||
keyMatchers[Command.NAVIGATION_UP](key) &&
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
(buffer.visualCursor[0] === 0 && buffer.visualScrollRow === 0))
|
||||
) {
|
||||
// Check for queued messages first when input is empty
|
||||
// If no queued messages, inputHistory.navigateUp() is called inside tryLoadQueuedMessages
|
||||
if (tryLoadQueuedMessages()) {
|
||||
if (isHistoryDown) {
|
||||
if (
|
||||
keyMatchers[Command.NAVIGATION_DOWN](key) &&
|
||||
buffer.visualCursor[1] <
|
||||
cpLen(buffer.allVisualLines[buffer.visualCursor[0]] || '')
|
||||
) {
|
||||
buffer.move('end');
|
||||
return true;
|
||||
}
|
||||
// Only navigate history if popAllMessages doesn't exist
|
||||
inputHistory.navigateUp();
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
keyMatchers[Command.NAVIGATION_DOWN](key) &&
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
buffer.visualCursor[0] === buffer.allVisualLines.length - 1)
|
||||
) {
|
||||
inputHistory.navigateDown();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Shell History Navigation
|
||||
if (keyMatchers[Command.NAVIGATION_UP](key)) {
|
||||
if (
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
(buffer.visualCursor[0] === 0 && buffer.visualScrollRow === 0)) &&
|
||||
buffer.visualCursor[1] > 0
|
||||
) {
|
||||
buffer.move('home');
|
||||
return true;
|
||||
}
|
||||
const prevCommand = shellHistory.getPreviousCommand();
|
||||
if (prevCommand !== null) buffer.setText(prevCommand);
|
||||
return true;
|
||||
}
|
||||
if (keyMatchers[Command.NAVIGATION_DOWN](key)) {
|
||||
if (
|
||||
(buffer.allVisualLines.length === 1 ||
|
||||
buffer.visualCursor[0] === buffer.allVisualLines.length - 1) &&
|
||||
buffer.visualCursor[1] <
|
||||
cpLen(buffer.allVisualLines[buffer.visualCursor[0]] || '')
|
||||
) {
|
||||
buffer.move('end');
|
||||
return true;
|
||||
}
|
||||
const nextCommand = shellHistory.getNextCommand();
|
||||
if (nextCommand !== null) buffer.setText(nextCommand);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user