Skip to content

Commit ca575fc

Browse files
jacob314yashodipmore
authored andcommitted
fix(cli): Polish shell autocomplete rendering to be a little more shell native feeling. (google-gemini#20931)
1 parent 0390e88 commit ca575fc

File tree

5 files changed

+327
-49
lines changed

5 files changed

+327
-49
lines changed

packages/cli/src/ui/components/InputPrompt.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ describe('InputPrompt', () => {
279279
},
280280
getCompletedText: vi.fn().mockReturnValue(null),
281281
completionMode: CompletionMode.IDLE,
282+
forceShowShellSuggestions: false,
283+
setForceShowShellSuggestions: vi.fn(),
284+
isShellSuggestionsVisible: true,
282285
};
283286
mockedUseCommandCompletion.mockReturnValue(mockCommandCompletion);
284287

packages/cli/src/ui/components/InputPrompt.tsx

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,27 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
301301
const resetCommandSearchCompletionState =
302302
commandSearchCompletion.resetCompletionState;
303303

304+
const getActiveCompletion = useCallback(() => {
305+
if (commandSearchActive) return commandSearchCompletion;
306+
if (reverseSearchActive) return reverseSearchCompletion;
307+
return completion;
308+
}, [
309+
commandSearchActive,
310+
commandSearchCompletion,
311+
reverseSearchActive,
312+
reverseSearchCompletion,
313+
completion,
314+
]);
315+
316+
const activeCompletion = getActiveCompletion();
317+
const shouldShowSuggestions = activeCompletion.showSuggestions;
318+
319+
const {
320+
forceShowShellSuggestions,
321+
setForceShowShellSuggestions,
322+
isShellSuggestionsVisible,
323+
} = completion;
324+
304325
const showCursor = focus && isShellFocused && !isEmbeddedShellFocused;
305326

306327
// Notify parent component about escape prompt state changes
@@ -363,7 +384,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
363384
userMessages,
364385
onSubmit: handleSubmitAndClear,
365386
isActive:
366-
(!completion.showSuggestions || completion.suggestions.length === 1) &&
387+
(!(completion.showSuggestions && isShellSuggestionsVisible) ||
388+
completion.suggestions.length === 1) &&
367389
!shellModeActive,
368390
currentQuery: buffer.text,
369391
currentCursorOffset: buffer.getOffset(),
@@ -595,9 +617,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
595617
keyMatchers[Command.END](key);
596618

597619
const isSuggestionsNav =
598-
(completion.showSuggestions ||
599-
reverseSearchCompletion.showSuggestions ||
600-
commandSearchCompletion.showSuggestions) &&
620+
shouldShowSuggestions &&
601621
(keyMatchers[Command.COMPLETION_UP](key) ||
602622
keyMatchers[Command.COMPLETION_DOWN](key) ||
603623
keyMatchers[Command.EXPAND_SUGGESTION](key) ||
@@ -612,6 +632,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
612632
isHistoryNav || isCursorMovement || keyMatchers[Command.ESCAPE](key),
613633
);
614634
hasUserNavigatedSuggestions.current = false;
635+
636+
if (key.name !== 'tab') {
637+
setForceShowShellSuggestions(false);
638+
}
615639
}
616640

617641
// TODO(jacobr): this special case is likely not needed anymore.
@@ -641,15 +665,25 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
641665
const isPlainTab =
642666
key.name === 'tab' && !key.shift && !key.alt && !key.ctrl && !key.cmd;
643667
const hasTabCompletionInteraction =
644-
completion.showSuggestions ||
668+
(completion.showSuggestions && isShellSuggestionsVisible) ||
645669
Boolean(completion.promptCompletion.text) ||
646670
reverseSearchActive ||
647671
commandSearchActive;
648672

649673
if (isPlainTab && shellModeActive) {
650674
resetPlainTabPress();
651-
if (!completion.showSuggestions) {
675+
if (!shouldShowSuggestions) {
652676
setSuppressCompletion(false);
677+
if (completion.promptCompletion.text) {
678+
completion.promptCompletion.accept();
679+
return true;
680+
} else if (
681+
completion.suggestions.length > 0 &&
682+
!forceShowShellSuggestions
683+
) {
684+
setForceShowShellSuggestions(true);
685+
return true;
686+
}
653687
}
654688
} else if (isPlainTab) {
655689
if (!hasTabCompletionInteraction) {
@@ -752,7 +786,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
752786
if (
753787
key.sequence === '!' &&
754788
buffer.text === '' &&
755-
!completion.showSuggestions
789+
!(completion.showSuggestions && isShellSuggestionsVisible)
756790
) {
757791
setShellModeActive(!shellModeActive);
758792
buffer.setText(''); // Clear the '!' from input
@@ -791,15 +825,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
791825
return true;
792826
}
793827

794-
if (shellModeActive) {
795-
setShellModeActive(false);
828+
if (completion.showSuggestions && isShellSuggestionsVisible) {
829+
completion.resetCompletionState();
830+
setExpandedSuggestionIndex(-1);
796831
resetEscapeState();
797832
return true;
798833
}
799834

800-
if (completion.showSuggestions) {
801-
completion.resetCompletionState();
802-
setExpandedSuggestionIndex(-1);
835+
if (shellModeActive) {
836+
setShellModeActive(false);
803837
resetEscapeState();
804838
return true;
805839
}
@@ -895,7 +929,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
895929
completion.isPerfectMatch &&
896930
keyMatchers[Command.SUBMIT](key) &&
897931
recentUnsafePasteTime === null &&
898-
(!completion.showSuggestions ||
932+
(!(completion.showSuggestions && isShellSuggestionsVisible) ||
899933
(completion.activeSuggestionIndex <= 0 &&
900934
!hasUserNavigatedSuggestions.current))
901935
) {
@@ -909,7 +943,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
909943
return true;
910944
}
911945

912-
if (completion.showSuggestions) {
946+
if (completion.showSuggestions && isShellSuggestionsVisible) {
913947
if (completion.suggestions.length > 1) {
914948
if (keyMatchers[Command.COMPLETION_UP](key)) {
915949
completion.navigateUp();
@@ -1007,7 +1041,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
10071041
if (
10081042
key.name === 'tab' &&
10091043
!key.shift &&
1010-
!completion.showSuggestions &&
1044+
!(completion.showSuggestions && isShellSuggestionsVisible) &&
10111045
completion.promptCompletion.text
10121046
) {
10131047
completion.promptCompletion.accept();
@@ -1190,6 +1224,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
11901224
focus,
11911225
buffer,
11921226
completion,
1227+
setForceShowShellSuggestions,
11931228
shellModeActive,
11941229
setShellModeActive,
11951230
onClearScreen,
@@ -1221,6 +1256,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
12211256
registerPlainTabPress,
12221257
resetPlainTabPress,
12231258
toggleCleanUiDetailsVisible,
1259+
shouldShowSuggestions,
1260+
isShellSuggestionsVisible,
1261+
forceShowShellSuggestions,
12241262
],
12251263
);
12261264

@@ -1346,14 +1384,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
13461384
]);
13471385

13481386
const { inlineGhost, additionalLines } = getGhostTextLines();
1349-
const getActiveCompletion = () => {
1350-
if (commandSearchActive) return commandSearchCompletion;
1351-
if (reverseSearchActive) return reverseSearchCompletion;
1352-
return completion;
1353-
};
1354-
1355-
const activeCompletion = getActiveCompletion();
1356-
const shouldShowSuggestions = activeCompletion.showSuggestions;
13571387

13581388
const useBackgroundColor = config.getUseBackgroundColor();
13591389
const isLowColor = isLowColorDepth();

0 commit comments

Comments
 (0)