fix(cli): Polish shell autocomplete rendering to be a little more shell native feeling. (#20931)

This commit is contained in:
Jacob Richman
2026-03-03 22:52:56 -08:00
committed by GitHub
parent 1017b78157
commit 12957ea16a
5 changed files with 327 additions and 49 deletions

View File

@@ -301,6 +301,27 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const resetCommandSearchCompletionState =
commandSearchCompletion.resetCompletionState;
const getActiveCompletion = useCallback(() => {
if (commandSearchActive) return commandSearchCompletion;
if (reverseSearchActive) return reverseSearchCompletion;
return completion;
}, [
commandSearchActive,
commandSearchCompletion,
reverseSearchActive,
reverseSearchCompletion,
completion,
]);
const activeCompletion = getActiveCompletion();
const shouldShowSuggestions = activeCompletion.showSuggestions;
const {
forceShowShellSuggestions,
setForceShowShellSuggestions,
isShellSuggestionsVisible,
} = completion;
const showCursor = focus && isShellFocused && !isEmbeddedShellFocused;
// Notify parent component about escape prompt state changes
@@ -363,7 +384,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
userMessages,
onSubmit: handleSubmitAndClear,
isActive:
(!completion.showSuggestions || completion.suggestions.length === 1) &&
(!(completion.showSuggestions && isShellSuggestionsVisible) ||
completion.suggestions.length === 1) &&
!shellModeActive,
currentQuery: buffer.text,
currentCursorOffset: buffer.getOffset(),
@@ -595,9 +617,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
keyMatchers[Command.END](key);
const isSuggestionsNav =
(completion.showSuggestions ||
reverseSearchCompletion.showSuggestions ||
commandSearchCompletion.showSuggestions) &&
shouldShowSuggestions &&
(keyMatchers[Command.COMPLETION_UP](key) ||
keyMatchers[Command.COMPLETION_DOWN](key) ||
keyMatchers[Command.EXPAND_SUGGESTION](key) ||
@@ -612,6 +632,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
isHistoryNav || isCursorMovement || keyMatchers[Command.ESCAPE](key),
);
hasUserNavigatedSuggestions.current = false;
if (key.name !== 'tab') {
setForceShowShellSuggestions(false);
}
}
// TODO(jacobr): this special case is likely not needed anymore.
@@ -641,15 +665,25 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const isPlainTab =
key.name === 'tab' && !key.shift && !key.alt && !key.ctrl && !key.cmd;
const hasTabCompletionInteraction =
completion.showSuggestions ||
(completion.showSuggestions && isShellSuggestionsVisible) ||
Boolean(completion.promptCompletion.text) ||
reverseSearchActive ||
commandSearchActive;
if (isPlainTab && shellModeActive) {
resetPlainTabPress();
if (!completion.showSuggestions) {
if (!shouldShowSuggestions) {
setSuppressCompletion(false);
if (completion.promptCompletion.text) {
completion.promptCompletion.accept();
return true;
} else if (
completion.suggestions.length > 0 &&
!forceShowShellSuggestions
) {
setForceShowShellSuggestions(true);
return true;
}
}
} else if (isPlainTab) {
if (!hasTabCompletionInteraction) {
@@ -752,7 +786,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (
key.sequence === '!' &&
buffer.text === '' &&
!completion.showSuggestions
!(completion.showSuggestions && isShellSuggestionsVisible)
) {
setShellModeActive(!shellModeActive);
buffer.setText(''); // Clear the '!' from input
@@ -791,15 +825,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return true;
}
if (shellModeActive) {
setShellModeActive(false);
if (completion.showSuggestions && isShellSuggestionsVisible) {
completion.resetCompletionState();
setExpandedSuggestionIndex(-1);
resetEscapeState();
return true;
}
if (completion.showSuggestions) {
completion.resetCompletionState();
setExpandedSuggestionIndex(-1);
if (shellModeActive) {
setShellModeActive(false);
resetEscapeState();
return true;
}
@@ -895,7 +929,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
completion.isPerfectMatch &&
keyMatchers[Command.SUBMIT](key) &&
recentUnsafePasteTime === null &&
(!completion.showSuggestions ||
(!(completion.showSuggestions && isShellSuggestionsVisible) ||
(completion.activeSuggestionIndex <= 0 &&
!hasUserNavigatedSuggestions.current))
) {
@@ -909,7 +943,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return true;
}
if (completion.showSuggestions) {
if (completion.showSuggestions && isShellSuggestionsVisible) {
if (completion.suggestions.length > 1) {
if (keyMatchers[Command.COMPLETION_UP](key)) {
completion.navigateUp();
@@ -1007,7 +1041,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (
key.name === 'tab' &&
!key.shift &&
!completion.showSuggestions &&
!(completion.showSuggestions && isShellSuggestionsVisible) &&
completion.promptCompletion.text
) {
completion.promptCompletion.accept();
@@ -1190,6 +1224,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
focus,
buffer,
completion,
setForceShowShellSuggestions,
shellModeActive,
setShellModeActive,
onClearScreen,
@@ -1221,6 +1256,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
registerPlainTabPress,
resetPlainTabPress,
toggleCleanUiDetailsVisible,
shouldShowSuggestions,
isShellSuggestionsVisible,
forceShowShellSuggestions,
],
);
@@ -1346,14 +1384,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
]);
const { inlineGhost, additionalLines } = getGhostTextLines();
const getActiveCompletion = () => {
if (commandSearchActive) return commandSearchCompletion;
if (reverseSearchActive) return reverseSearchCompletion;
return completion;
};
const activeCompletion = getActiveCompletion();
const shouldShowSuggestions = activeCompletion.showSuggestions;
const useBackgroundColor = config.getUseBackgroundColor();
const isLowColor = isLowColorDepth();