refactor(cli): keyboard handling and AskUserDialog (#17414)

This commit is contained in:
Jacob Richman
2026-01-27 14:26:00 -08:00
committed by GitHub
parent 3103697ea7
commit b51323b40c
46 changed files with 1220 additions and 385 deletions
+51 -51
View File
@@ -372,7 +372,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
// Insert at cursor position
buffer.replaceRangeByOffset(offset, offset, textToInsert);
return;
}
}
@@ -469,7 +468,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
// focused.
/// We want to handle paste even when not focused to support drag and drop.
if (!focus && key.name !== 'paste') {
return;
return false;
}
if (key.name === 'paste') {
@@ -498,11 +497,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
// Ensure we never accidentally interpret paste as regular input.
buffer.handleInput(key);
return;
return true;
}
if (vimHandleInput && vimHandleInput(key)) {
return;
return true;
}
// Reset ESC count and hide prompt on any non-ESC key
@@ -519,7 +518,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
) {
setShellModeActive(!shellModeActive);
buffer.setText(''); // Clear the '!' from input
return;
return true;
}
if (keyMatchers[Command.ESCAPE](key)) {
@@ -544,27 +543,27 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setReverseSearchActive,
reverseSearchCompletion.resetCompletionState,
);
return;
return true;
}
if (commandSearchActive) {
cancelSearch(
setCommandSearchActive,
commandSearchCompletion.resetCompletionState,
);
return;
return true;
}
if (shellModeActive) {
setShellModeActive(false);
resetEscapeState();
return;
return true;
}
if (completion.showSuggestions) {
completion.resetCompletionState();
setExpandedSuggestionIndex(-1);
resetEscapeState();
return;
return true;
}
// Handle double ESC
@@ -577,7 +576,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
escapeTimerRef.current = setTimeout(() => {
resetEscapeState();
}, 500);
return;
return true;
}
// Second ESC
@@ -585,26 +584,26 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (buffer.text.length > 0) {
buffer.setText('');
resetCompletionState();
return;
return true;
} else if (history.length > 0) {
onSubmit('/rewind');
return;
return true;
}
coreEvents.emitFeedback('info', 'Nothing to rewind to');
return;
return true;
}
if (shellModeActive && keyMatchers[Command.REVERSE_SEARCH](key)) {
setReverseSearchActive(true);
setTextBeforeReverseSearch(buffer.text);
setCursorPosition(buffer.cursor);
return;
return true;
}
if (keyMatchers[Command.CLEAR_SCREEN](key)) {
setBannerVisible(false);
onClearScreen();
return;
return true;
}
if (reverseSearchActive || commandSearchActive) {
@@ -629,29 +628,29 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (showSuggestions) {
if (keyMatchers[Command.NAVIGATION_UP](key)) {
navigateUp();
return;
return true;
}
if (keyMatchers[Command.NAVIGATION_DOWN](key)) {
navigateDown();
return;
return true;
}
if (keyMatchers[Command.COLLAPSE_SUGGESTION](key)) {
if (suggestions[activeSuggestionIndex].value.length >= MAX_WIDTH) {
setExpandedSuggestionIndex(-1);
return;
return true;
}
}
if (keyMatchers[Command.EXPAND_SUGGESTION](key)) {
if (suggestions[activeSuggestionIndex].value.length >= MAX_WIDTH) {
setExpandedSuggestionIndex(activeSuggestionIndex);
return;
return true;
}
}
if (keyMatchers[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH](key)) {
sc.handleAutocomplete(activeSuggestionIndex);
resetState();
setActive(false);
return;
return true;
}
}
@@ -663,7 +662,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
handleSubmitAndClear(textToSubmit);
resetState();
setActive(false);
return;
return true;
}
// Prevent up/down from falling through to regular history navigation
@@ -671,7 +670,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
keyMatchers[Command.NAVIGATION_UP](key) ||
keyMatchers[Command.NAVIGATION_DOWN](key)
) {
return;
return true;
}
}
@@ -683,7 +682,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
(!completion.showSuggestions || completion.activeSuggestionIndex <= 0)
) {
handleSubmit(buffer.text);
return;
return true;
}
if (completion.showSuggestions) {
@@ -691,12 +690,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (keyMatchers[Command.COMPLETION_UP](key)) {
completion.navigateUp();
setExpandedSuggestionIndex(-1); // Reset expansion when navigating
return;
return true;
}
if (keyMatchers[Command.COMPLETION_DOWN](key)) {
completion.navigateDown();
setExpandedSuggestionIndex(-1); // Reset expansion when navigating
return;
return true;
}
}
@@ -725,7 +724,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (completedText) {
setExpandedSuggestionIndex(-1);
handleSubmit(completedText.trim());
return;
return true;
}
} else if (!isArgumentCompletion) {
// Existing logic for command name completion
@@ -745,7 +744,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (completedText) {
setExpandedSuggestionIndex(-1);
handleSubmit(completedText.trim());
return;
return true;
}
}
}
@@ -756,7 +755,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setExpandedSuggestionIndex(-1); // Reset expansion after selection
}
}
return;
return true;
}
}
@@ -767,7 +766,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
completion.promptCompletion.text
) {
completion.promptCompletion.accept();
return;
return true;
}
if (!shellModeActive) {
@@ -775,22 +774,22 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setCommandSearchActive(true);
setTextBeforeReverseSearch(buffer.text);
setCursorPosition(buffer.cursor);
return;
return true;
}
if (keyMatchers[Command.HISTORY_UP](key)) {
// Check for queued messages first when input is empty
// If no queued messages, inputHistory.navigateUp() is called inside tryLoadQueuedMessages
if (tryLoadQueuedMessages()) {
return;
return true;
}
// Only navigate history if popAllMessages doesn't exist
inputHistory.navigateUp();
return;
return true;
}
if (keyMatchers[Command.HISTORY_DOWN](key)) {
inputHistory.navigateDown();
return;
return true;
}
// Handle arrow-up/down for history on single-line or at edges
if (
@@ -801,11 +800,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
// Check for queued messages first when input is empty
// If no queued messages, inputHistory.navigateUp() is called inside tryLoadQueuedMessages
if (tryLoadQueuedMessages()) {
return;
return true;
}
// Only navigate history if popAllMessages doesn't exist
inputHistory.navigateUp();
return;
return true;
}
if (
keyMatchers[Command.NAVIGATION_DOWN](key) &&
@@ -813,19 +812,19 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
buffer.visualCursor[0] === buffer.allVisualLines.length - 1)
) {
inputHistory.navigateDown();
return;
return true;
}
} else {
// Shell History Navigation
if (keyMatchers[Command.NAVIGATION_UP](key)) {
const prevCommand = shellHistory.getPreviousCommand();
if (prevCommand !== null) buffer.setText(prevCommand);
return;
return true;
}
if (keyMatchers[Command.NAVIGATION_DOWN](key)) {
const nextCommand = shellHistory.getNextCommand();
if (nextCommand !== null) buffer.setText(nextCommand);
return;
return true;
}
}
@@ -840,7 +839,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
// get some feedback that their keypress was handled rather than
// wondering why it was completely ignored.
buffer.newline();
return;
return true;
}
const [row, col] = buffer.cursor;
@@ -853,23 +852,23 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
handleSubmit(buffer.text);
}
}
return;
return true;
}
// Newline insertion
if (keyMatchers[Command.NEWLINE](key)) {
buffer.newline();
return;
return true;
}
// Ctrl+A (Home) / Ctrl+E (End)
if (keyMatchers[Command.HOME](key)) {
buffer.move('home');
return;
return true;
}
if (keyMatchers[Command.END](key)) {
buffer.move('end');
return;
return true;
}
// Ctrl+C (Clear input)
if (keyMatchers[Command.CLEAR_INPUT](key)) {
@@ -877,36 +876,36 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
buffer.setText('');
resetCompletionState();
}
return;
return false;
}
// Kill line commands
if (keyMatchers[Command.KILL_LINE_RIGHT](key)) {
buffer.killLineRight();
return;
return true;
}
if (keyMatchers[Command.KILL_LINE_LEFT](key)) {
buffer.killLineLeft();
return;
return true;
}
if (keyMatchers[Command.DELETE_WORD_BACKWARD](key)) {
buffer.deleteWordLeft();
return;
return true;
}
// External editor
if (keyMatchers[Command.OPEN_EXTERNAL_EDITOR](key)) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
buffer.openInExternalEditor();
return;
return true;
}
// Ctrl+V for clipboard paste
if (keyMatchers[Command.PASTE_CLIPBOARD](key)) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
handleClipboardPaste();
return;
return true;
}
if (keyMatchers[Command.FOCUS_SHELL_INPUT](key)) {
@@ -914,11 +913,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (activePtyId) {
setEmbeddedShellFocused(true);
}
return;
return true;
}
// Fall back to the text buffer's default input handling for all other keys
buffer.handleInput(key);
const handled = buffer.handleInput(key);
// Clear ghost text when user types regular characters (not navigation/control keys)
if (
@@ -932,6 +931,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
completion.promptCompletion.clear();
setExpandedSuggestionIndex(-1);
}
return handled;
},
[
focus,