From 81dbd0a13fad54e25c647022cbf3e0643d0a47a5 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Thu, 5 Mar 2026 12:49:17 -0800 Subject: [PATCH] remove wildcard behavior on keybindings --- docs/reference/keyboard-shortcuts.md | 114 +++++++++--------- packages/cli/src/config/keyBindings.test.ts | 40 ------ packages/cli/src/config/keyBindings.ts | 94 +++++---------- .../__snapshots__/AskUserDialog.test.tsx.snap | 75 ------------ .../ExitPlanModeDialog.test.tsx.snap | 108 ----------------- .../__snapshots__/InputPrompt.test.tsx.snap | 21 ---- .../src/ui/contexts/KeypressContext.test.tsx | 6 +- .../cli/src/ui/contexts/KeypressContext.tsx | 9 +- packages/cli/src/ui/keyMatchers.test.ts | 70 ++++++----- packages/cli/src/ui/keyMatchers.ts | 13 +- 10 files changed, 135 insertions(+), 415 deletions(-) diff --git a/docs/reference/keyboard-shortcuts.md b/docs/reference/keyboard-shortcuts.md index e5691c43ee2..5ad55a2c749 100644 --- a/docs/reference/keyboard-shortcuts.md +++ b/docs/reference/keyboard-shortcuts.md @@ -19,12 +19,12 @@ available combinations. | Action | Keys | | ------------------------------------------- | ------------------------------------------------------------ | -| Move the cursor to the start of the line. | `Ctrl + A`
`Home (no Shift, Ctrl)` | -| Move the cursor to the end of the line. | `Ctrl + E`
`End (no Shift, Ctrl)` | -| Move the cursor up one line. | `Up Arrow (no Shift, Alt, Ctrl, Cmd)` | -| Move the cursor down one line. | `Down Arrow (no Shift, Alt, Ctrl, Cmd)` | -| Move the cursor one character to the left. | `Left Arrow (no Shift, Alt, Ctrl, Cmd)` | -| Move the cursor one character to the right. | `Right Arrow (no Shift, Alt, Ctrl, Cmd)`
`Ctrl + F` | +| Move the cursor to the start of the line. | `Ctrl + A`
`Home` | +| Move the cursor to the end of the line. | `Ctrl + E`
`End` | +| Move the cursor up one line. | `Up Arrow` | +| Move the cursor down one line. | `Down Arrow` | +| Move the cursor one character to the left. | `Left Arrow` | +| Move the cursor one character to the right. | `Right Arrow`
`Ctrl + F` | | Move the cursor one word to the left. | `Ctrl + Left Arrow`
`Alt + Left Arrow`
`Alt + B` | | Move the cursor one word to the right. | `Ctrl + Right Arrow`
`Alt + Right Arrow`
`Alt + F` | @@ -39,7 +39,7 @@ available combinations. | Delete the next word. | `Ctrl + Delete`
`Alt + Delete`
`Alt + D` | | Delete the character to the left. | `Backspace`
`Ctrl + H` | | Delete the character to the right. | `Delete`
`Ctrl + D` | -| Undo the most recent text edit. | `Cmd + Z (no Shift)`
`Alt + Z (no Shift)` | +| Undo the most recent text edit. | `Cmd + Z`
`Alt + Z` | | Redo the most recent undone text edit. | `Shift + Ctrl + Z`
`Shift + Cmd + Z`
`Shift + Alt + Z` | #### Scrolling @@ -55,72 +55,72 @@ available combinations. #### History & Search -| Action | Keys | -| -------------------------------------------- | --------------------- | -| Show the previous entry in history. | `Ctrl + P (no Shift)` | -| Show the next entry in history. | `Ctrl + N (no Shift)` | -| Start reverse search through history. | `Ctrl + R` | -| Submit the selected reverse-search match. | `Enter (no Ctrl)` | -| Accept a suggestion while reverse searching. | `Tab (no Shift)` | -| Browse and rewind previous interactions. | `Double Esc` | +| Action | Keys | +| -------------------------------------------- | ------------ | +| Show the previous entry in history. | `Ctrl + P` | +| Show the next entry in history. | `Ctrl + N` | +| Start reverse search through history. | `Ctrl + R` | +| Submit the selected reverse-search match. | `Enter` | +| Accept a suggestion while reverse searching. | `Tab` | +| Browse and rewind previous interactions. | `Double Esc` | #### Navigation -| Action | Keys | -| -------------------------------------------------- | ------------------------------------------- | -| Move selection up in lists. | `Up Arrow (no Shift)` | -| Move selection down in lists. | `Down Arrow (no Shift)` | -| Move up within dialog options. | `Up Arrow (no Shift)`
`K (no Shift)` | -| Move down within dialog options. | `Down Arrow (no Shift)`
`J (no Shift)` | -| Move to the next item or question in a dialog. | `Tab (no Shift)` | -| Move to the previous item or question in a dialog. | `Shift + Tab` | +| Action | Keys | +| -------------------------------------------------- | --------------------- | +| Move selection up in lists. | `Up Arrow` | +| Move selection down in lists. | `Down Arrow` | +| Move up within dialog options. | `Up Arrow`
`K` | +| Move down within dialog options. | `Down Arrow`
`J` | +| Move to the next item or question in a dialog. | `Tab` | +| Move to the previous item or question in a dialog. | `Shift + Tab` | #### Suggestions & Completions -| Action | Keys | -| --------------------------------------- | -------------------------------------------------- | -| Accept the inline suggestion. | `Tab (no Shift)`
`Enter (no Ctrl)` | -| Move to the previous completion option. | `Up Arrow (no Shift)`
`Ctrl + P (no Shift)` | -| Move to the next completion option. | `Down Arrow (no Shift)`
`Ctrl + N (no Shift)` | -| Expand an inline suggestion. | `Right Arrow` | -| Collapse an inline suggestion. | `Left Arrow` | +| Action | Keys | +| --------------------------------------- | ---------------------------- | +| Accept the inline suggestion. | `Tab`
`Enter` | +| Move to the previous completion option. | `Up Arrow`
`Ctrl + P` | +| Move to the next completion option. | `Down Arrow`
`Ctrl + N` | +| Expand an inline suggestion. | `Right Arrow` | +| Collapse an inline suggestion. | `Left Arrow` | #### Text Input | Action | Keys | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| Submit the current prompt. | `Enter (no Shift, Alt, Ctrl, Cmd)` | +| Submit the current prompt. | `Enter` | | Insert a newline without submitting. | `Ctrl + Enter`
`Cmd + Enter`
`Alt + Enter`
`Shift + Enter`
`Ctrl + J` | | Open the current prompt or the plan in an external editor. | `Ctrl + X` | | Paste from the clipboard. | `Ctrl + V`
`Cmd + V`
`Alt + V` | #### App Controls -| Action | Keys | -| -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| Toggle detailed error information. | `F12` | -| Toggle the full TODO list. | `Ctrl + T` | -| Show IDE context details. | `Ctrl + G` | -| Toggle Markdown rendering. | `Alt + M` | -| Toggle copy mode when in alternate buffer mode. | `Ctrl + S` | -| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` | -| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift + Tab` | -| Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl + O` | -| Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl + O` | -| Toggle current background shell visibility. | `Ctrl + B` | -| Toggle background shell list. | `Ctrl + L` | -| Kill the active background shell. | `Ctrl + K` | -| Confirm selection in background shell list. | `Enter` | -| Dismiss background shell list. | `Esc` | -| Move focus from background shell to Gemini. | `Shift + Tab` | -| Move focus from background shell list to Gemini. | `Tab (no Shift)` | -| Show warning when trying to move focus away from background shell. | `Tab (no Shift)` | -| Show warning when trying to move focus away from shell input. | `Tab (no Shift)` | -| Move focus from Gemini to the active shell. | `Tab (no Shift)` | -| Move focus from the shell back to Gemini. | `Shift + Tab` | -| Clear the terminal screen and redraw the UI. | `Ctrl + L` | -| Restart the application. | `R` | -| Suspend the CLI and move it to the background. | `Ctrl + Z` | +| Action | Keys | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| Toggle detailed error information. | `F12` | +| Toggle the full TODO list. | `Ctrl + T` | +| Show IDE context details. | `Ctrl + G` | +| Toggle Markdown rendering. | `Alt + M` | +| Toggle copy mode when in alternate buffer mode. | `Ctrl + S` | +| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` | +| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift + Tab` | +| Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl + O` | +| Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl + O` | +| Toggle current background shell visibility. | `Ctrl + B` | +| Toggle background shell list. | `Ctrl + L` | +| Kill the active background shell. | `Ctrl + K` | +| Confirm selection in background shell list. | `Enter` | +| Dismiss background shell list. | `Esc` | +| Move focus from background shell to Gemini. | `Shift + Tab` | +| Move focus from background shell list to Gemini. | `Tab` | +| Show warning when trying to move focus away from background shell. | `Tab` | +| Show warning when trying to move focus away from shell input. | `Tab` | +| Move focus from Gemini to the active shell. | `Tab` | +| Move focus from the shell back to Gemini. | `Shift + Tab` | +| Clear the terminal screen and redraw the UI. | `Ctrl + L` | +| Restart the application. | `R`
`Shift + R` | +| Suspend the CLI and move it to the background. | `Ctrl + Z` | @@ -156,7 +156,7 @@ available combinations. ## Limitations - On [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal): - - `shift+enter` is not supported. + - `shift+enter` is only supported in version 1.25 and higher. - `shift+tab` [is not supported](https://github.com/google-gemini/gemini-cli/issues/20314) on Node 20 and earlier versions of Node 22. diff --git a/packages/cli/src/config/keyBindings.test.ts b/packages/cli/src/config/keyBindings.test.ts index c2abc32d276..e450e68b719 100644 --- a/packages/cli/src/config/keyBindings.test.ts +++ b/packages/cli/src/config/keyBindings.test.ts @@ -58,46 +58,6 @@ describe('keyBindings config', () => { const config: KeyBindingConfig = defaultKeyBindings; expect(config[Command.HOME]).toBeDefined(); }); - - it('should have correct specific bindings', () => { - // Verify navigation ignores shift - const navUp = defaultKeyBindings[Command.NAVIGATION_UP]; - expect(navUp).toContainEqual({ key: 'up', shift: false }); - - const navDown = defaultKeyBindings[Command.NAVIGATION_DOWN]; - expect(navDown).toContainEqual({ key: 'down', shift: false }); - - // Verify dialog navigation - const dialogNavUp = defaultKeyBindings[Command.DIALOG_NAVIGATION_UP]; - expect(dialogNavUp).toContainEqual({ key: 'up', shift: false }); - expect(dialogNavUp).toContainEqual({ key: 'k', shift: false }); - - const dialogNavDown = defaultKeyBindings[Command.DIALOG_NAVIGATION_DOWN]; - expect(dialogNavDown).toContainEqual({ key: 'down', shift: false }); - expect(dialogNavDown).toContainEqual({ key: 'j', shift: false }); - - // Verify physical home/end keys for cursor movement - expect(defaultKeyBindings[Command.HOME]).toContainEqual({ - key: 'home', - ctrl: false, - shift: false, - }); - expect(defaultKeyBindings[Command.END]).toContainEqual({ - key: 'end', - ctrl: false, - shift: false, - }); - - // Verify physical home/end keys for scrolling - expect(defaultKeyBindings[Command.SCROLL_HOME]).toContainEqual({ - key: 'home', - ctrl: true, - }); - expect(defaultKeyBindings[Command.SCROLL_END]).toContainEqual({ - key: 'end', - ctrl: true, - }); - }); }); describe('command metadata', () => { diff --git a/packages/cli/src/config/keyBindings.ts b/packages/cli/src/config/keyBindings.ts index 3122acef1d9..e2260d99d86 100644 --- a/packages/cli/src/config/keyBindings.ts +++ b/packages/cli/src/config/keyBindings.ts @@ -134,27 +134,12 @@ export const defaultKeyBindings: KeyBindingConfig = { [Command.EXIT]: [{ key: 'd', ctrl: true }], // Cursor Movement - [Command.HOME]: [ - { key: 'a', ctrl: true }, - { key: 'home', shift: false, ctrl: false }, - ], - [Command.END]: [ - { key: 'e', ctrl: true }, - { key: 'end', shift: false, ctrl: false }, - ], - [Command.MOVE_UP]: [ - { key: 'up', shift: false, alt: false, ctrl: false, cmd: false }, - ], - [Command.MOVE_DOWN]: [ - { key: 'down', shift: false, alt: false, ctrl: false, cmd: false }, - ], - [Command.MOVE_LEFT]: [ - { key: 'left', shift: false, alt: false, ctrl: false, cmd: false }, - ], - [Command.MOVE_RIGHT]: [ - { key: 'right', shift: false, alt: false, ctrl: false, cmd: false }, - { key: 'f', ctrl: true }, - ], + [Command.HOME]: [{ key: 'a', ctrl: true }, { key: 'home' }], + [Command.END]: [{ key: 'e', ctrl: true }, { key: 'end' }], + [Command.MOVE_UP]: [{ key: 'up' }], + [Command.MOVE_DOWN]: [{ key: 'down' }], + [Command.MOVE_LEFT]: [{ key: 'left' }], + [Command.MOVE_RIGHT]: [{ key: 'right' }, { key: 'f', ctrl: true }], [Command.MOVE_WORD_LEFT]: [ { key: 'left', ctrl: true }, { key: 'left', alt: true }, @@ -183,8 +168,8 @@ export const defaultKeyBindings: KeyBindingConfig = { [Command.DELETE_CHAR_LEFT]: [{ key: 'backspace' }, { key: 'h', ctrl: true }], [Command.DELETE_CHAR_RIGHT]: [{ key: 'delete' }, { key: 'd', ctrl: true }], [Command.UNDO]: [ - { key: 'z', cmd: true, shift: false }, - { key: 'z', alt: true, shift: false }, + { key: 'z', cmd: true }, + { key: 'z', alt: true }, ], [Command.REDO]: [ { key: 'z', ctrl: true, shift: true }, @@ -207,56 +192,33 @@ export const defaultKeyBindings: KeyBindingConfig = { [Command.PAGE_DOWN]: [{ key: 'pagedown' }], // History & Search - [Command.HISTORY_UP]: [{ key: 'p', shift: false, ctrl: true }], - [Command.HISTORY_DOWN]: [{ key: 'n', shift: false, ctrl: true }], + [Command.HISTORY_UP]: [{ key: 'p', ctrl: true }], + [Command.HISTORY_DOWN]: [{ key: 'n', ctrl: true }], [Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }], - [Command.REWIND]: [{ key: 'double escape' }], - [Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }], - [Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab', shift: false }], + [Command.REWIND]: [{ key: 'double escape' }], // for documentation only + [Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return' }], + [Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab' }], // Navigation - [Command.NAVIGATION_UP]: [{ key: 'up', shift: false }], - [Command.NAVIGATION_DOWN]: [{ key: 'down', shift: false }], + [Command.NAVIGATION_UP]: [{ key: 'up' }], + [Command.NAVIGATION_DOWN]: [{ key: 'down' }], // Navigation shortcuts appropriate for dialogs where we do not need to accept // text input. - [Command.DIALOG_NAVIGATION_UP]: [ - { key: 'up', shift: false }, - { key: 'k', shift: false }, - ], - [Command.DIALOG_NAVIGATION_DOWN]: [ - { key: 'down', shift: false }, - { key: 'j', shift: false }, - ], - [Command.DIALOG_NEXT]: [{ key: 'tab', shift: false }], + [Command.DIALOG_NAVIGATION_UP]: [{ key: 'up' }, { key: 'k' }], + [Command.DIALOG_NAVIGATION_DOWN]: [{ key: 'down' }, { key: 'j' }], + [Command.DIALOG_NEXT]: [{ key: 'tab' }], [Command.DIALOG_PREV]: [{ key: 'tab', shift: true }], // Suggestions & Completions - [Command.ACCEPT_SUGGESTION]: [ - { key: 'tab', shift: false }, - { key: 'return', ctrl: false }, - ], - [Command.COMPLETION_UP]: [ - { key: 'up', shift: false }, - { key: 'p', shift: false, ctrl: true }, - ], - [Command.COMPLETION_DOWN]: [ - { key: 'down', shift: false }, - { key: 'n', shift: false, ctrl: true }, - ], + [Command.ACCEPT_SUGGESTION]: [{ key: 'tab' }, { key: 'return' }], + [Command.COMPLETION_UP]: [{ key: 'up' }, { key: 'p', ctrl: true }], + [Command.COMPLETION_DOWN]: [{ key: 'down' }, { key: 'n', ctrl: true }], [Command.EXPAND_SUGGESTION]: [{ key: 'right' }], [Command.COLLAPSE_SUGGESTION]: [{ key: 'left' }], // Text Input // Must also exclude shift to allow shift+enter for newline - [Command.SUBMIT]: [ - { - key: 'return', - shift: false, - alt: false, - ctrl: false, - cmd: false, - }, - ], + [Command.SUBMIT]: [{ key: 'return' }], [Command.NEWLINE]: [ { key: 'return', ctrl: true }, { key: 'return', cmd: true }, @@ -283,19 +245,17 @@ export const defaultKeyBindings: KeyBindingConfig = { [Command.TOGGLE_BACKGROUND_SHELL_LIST]: [{ key: 'l', ctrl: true }], [Command.KILL_BACKGROUND_SHELL]: [{ key: 'k', ctrl: true }], [Command.UNFOCUS_BACKGROUND_SHELL]: [{ key: 'tab', shift: true }], - [Command.UNFOCUS_BACKGROUND_SHELL_LIST]: [{ key: 'tab', shift: false }], - [Command.SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING]: [ - { key: 'tab', shift: false }, - ], - [Command.SHOW_SHELL_INPUT_UNFOCUS_WARNING]: [{ key: 'tab', shift: false }], + [Command.UNFOCUS_BACKGROUND_SHELL_LIST]: [{ key: 'tab' }], + [Command.SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING]: [{ key: 'tab' }], + [Command.SHOW_SHELL_INPUT_UNFOCUS_WARNING]: [{ key: 'tab' }], [Command.BACKGROUND_SHELL_SELECT]: [{ key: 'return' }], [Command.BACKGROUND_SHELL_ESCAPE]: [{ key: 'escape' }], [Command.SHOW_MORE_LINES]: [{ key: 'o', ctrl: true }], [Command.EXPAND_PASTE]: [{ key: 'o', ctrl: true }], - [Command.FOCUS_SHELL_INPUT]: [{ key: 'tab', shift: false }], + [Command.FOCUS_SHELL_INPUT]: [{ key: 'tab' }], [Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab', shift: true }], [Command.CLEAR_SCREEN]: [{ key: 'l', ctrl: true }], - [Command.RESTART_APP]: [{ key: 'r' }], + [Command.RESTART_APP]: [{ key: 'r' }, { key: 'r', shift: true }], [Command.SUSPEND_APP]: [{ key: 'z', ctrl: true }], }; diff --git a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap index 96440266340..06f509f1f65 100644 --- a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap @@ -11,17 +11,6 @@ Enter to submit · Esc to cancel " `; -exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = ` -"Select your preferred language: - - 1. TypeScript - 2. JavaScript -● 3. Enter a custom value - -Enter to submit · Esc to cancel -" -`; - exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = ` "Select your preferred language: @@ -33,17 +22,6 @@ Enter to submit · Esc to cancel " `; -exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = ` -"Select your preferred language: - - 1. TypeScript - 2. JavaScript -● 3. Type another language... - -Enter to submit · Esc to cancel -" -`; - exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = ` "Choose an option @@ -58,20 +36,6 @@ Enter to select · ↑/↓ to navigate · Esc to cancel " `; -exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = ` -"Choose an option - -▲ -● 1. Option 1 - Description 1 - 2. Option 2 - Description 2 -▼ - -Enter to select · ↑/↓ to navigate · Esc to cancel -" -`; - exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = ` "Choose an option @@ -111,45 +75,6 @@ Enter to select · ↑/↓ to navigate · Esc to cancel " `; -exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 2`] = ` -"Choose an option - -● 1. Option 1 - Description 1 - 2. Option 2 - Description 2 - 3. Option 3 - Description 3 - 4. Option 4 - Description 4 - 5. Option 5 - Description 5 - 6. Option 6 - Description 6 - 7. Option 7 - Description 7 - 8. Option 8 - Description 8 - 9. Option 9 - Description 9 - 10. Option 10 - Description 10 - 11. Option 11 - Description 11 - 12. Option 12 - Description 12 - 13. Option 13 - Description 13 - 14. Option 14 - Description 14 - 15. Option 15 - Description 15 - 16. Enter a custom value - -Enter to select · ↑/↓ to navigate · Esc to cancel -" -`; - exports[`AskUserDialog > Text type questions > renders text input for type: "text" 1`] = ` "What should we name this component? diff --git a/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap index 9e210e34381..073c106ceb3 100644 --- a/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap @@ -27,33 +27,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel " `; -exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 2`] = ` -"Overview - -Add user authentication to the CLI application. - -Implementation Steps - - 1. Create src/auth/AuthService.ts with login/logout methods - 2. Add session storage in src/storage/SessionStore.ts - 3. Update src/commands/index.ts to check auth status - 4. Add tests in src/auth/__tests__/ - -Files to Modify - - - src/index.ts - Add auth middleware - - src/config.ts - Add auth configuration options - - 1. Yes, automatically accept edits - Approves plan and allows tools to run automatically - 2. Yes, manually accept edits - Approves plan but requires confirmation for each tool -● 3. Type your feedback... - -Enter to submit · Ctrl+X to edit plan · Esc to cancel -" -`; - exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = ` "Overview @@ -81,33 +54,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel " `; -exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 2`] = ` -"Overview - -Add user authentication to the CLI application. - -Implementation Steps - - 1. Create src/auth/AuthService.ts with login/logout methods - 2. Add session storage in src/storage/SessionStore.ts - 3. Update src/commands/index.ts to check auth status - 4. Add tests in src/auth/__tests__/ - -Files to Modify - - - src/index.ts - Add auth middleware - - src/config.ts - Add auth configuration options - - 1. Yes, automatically accept edits - Approves plan and allows tools to run automatically - 2. Yes, manually accept edits - Approves plan but requires confirmation for each tool -● 3. Add tests - -Enter to submit · Ctrl+X to edit plan · Esc to cancel -" -`; - exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = ` " Error reading plan: File not found " @@ -194,33 +140,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel " `; -exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = ` -"Overview - -Add user authentication to the CLI application. - -Implementation Steps - - 1. Create src/auth/AuthService.ts with login/logout methods - 2. Add session storage in src/storage/SessionStore.ts - 3. Update src/commands/index.ts to check auth status - 4. Add tests in src/auth/__tests__/ - -Files to Modify - - - src/index.ts - Add auth middleware - - src/config.ts - Add auth configuration options - - 1. Yes, automatically accept edits - Approves plan and allows tools to run automatically - 2. Yes, manually accept edits - Approves plan but requires confirmation for each tool -● 3. Type your feedback... - -Enter to submit · Ctrl+X to edit plan · Esc to cancel -" -`; - exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = ` "Overview @@ -248,33 +167,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel " `; -exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 2`] = ` -"Overview - -Add user authentication to the CLI application. - -Implementation Steps - - 1. Create src/auth/AuthService.ts with login/logout methods - 2. Add session storage in src/storage/SessionStore.ts - 3. Update src/commands/index.ts to check auth status - 4. Add tests in src/auth/__tests__/ - -Files to Modify - - - src/index.ts - Add auth middleware - - src/config.ts - Add auth configuration options - - 1. Yes, automatically accept edits - Approves plan and allows tools to run automatically - 2. Yes, manually accept edits - Approves plan but requires confirmation for each tool -● 3. Add tests - -Enter to submit · Ctrl+X to edit plan · Esc to cancel -" -`; - exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = ` " Error reading plan: File not found " diff --git a/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap index f40887b3b9c..5a2819702e0 100644 --- a/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap @@ -78,27 +78,6 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub " `; -exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = ` -"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - > [Pasted Text: 10 lines] -▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ -" -`; - -exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = ` -"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - > [Pasted Text: 10 lines] -▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ -" -`; - -exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = ` -"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - > [Pasted Text: 10 lines] -▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ -" -`; - exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = ` "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ > Type your message or @path/to/file diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index e25ff576428..bc8e1981683 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -288,7 +288,7 @@ describe('KeypressContext', () => { expect.objectContaining({ name: 'escape', shift: false, - alt: true, + alt: false, cmd: false, }), ); @@ -297,7 +297,7 @@ describe('KeypressContext', () => { expect.objectContaining({ name: 'escape', shift: false, - alt: true, + alt: false, cmd: false, }), ); @@ -326,7 +326,7 @@ describe('KeypressContext', () => { expect.objectContaining({ name: 'escape', shift: false, - alt: true, + alt: false, cmd: false, }), ); diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index 814404d84bd..d3f9031ffe6 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -178,8 +178,7 @@ function nonKeyboardEventFilter( } /** - * Converts return keys pressed quickly after other keys into plain - * insertable return characters. + * Converts return keys pressed quickly after insertable keys into a shift+return * * This is to accommodate older terminals that paste text without bracketing. */ @@ -201,7 +200,7 @@ function bufferFastReturn(keypressHandler: KeypressHandler): KeypressHandler { } else { keypressHandler(key); } - lastKeyTime = now; + lastKeyTime = key.insertable ? now : 0; }; } @@ -630,7 +629,7 @@ function* emitKeys( } else if (sequence === `${ESC}${ESC}`) { // Double escape name = 'escape'; - alt = true; + alt = false; // Emit first escape key here, then continue processing keypressHandler({ @@ -645,7 +644,7 @@ function* emitKeys( } else if (escaped) { // Escape sequence timeout name = ch.length ? undefined : 'escape'; - alt = true; + alt = ch.length > 0; } else { // Any other character is considered printable. insertable = true; diff --git a/packages/cli/src/ui/keyMatchers.test.ts b/packages/cli/src/ui/keyMatchers.test.ts index 763754ec951..888393be83a 100644 --- a/packages/cli/src/ui/keyMatchers.test.ts +++ b/packages/cli/src/ui/keyMatchers.test.ts @@ -32,8 +32,12 @@ describe('keyMatchers', () => { }, { command: Command.ESCAPE, - positive: [createKey('escape'), createKey('escape', { ctrl: true })], - negative: [createKey('e'), createKey('esc')], + positive: [createKey('escape')], + negative: [ + createKey('e'), + createKey('esc'), + createKey('escape', { ctrl: true }), + ], }, // Cursor movement @@ -192,13 +196,21 @@ describe('keyMatchers', () => { }, { command: Command.PAGE_UP, - positive: [createKey('pageup'), createKey('pageup', { shift: true })], - negative: [createKey('pagedown'), createKey('up')], + positive: [createKey('pageup')], + negative: [ + createKey('pagedown'), + createKey('up'), + createKey('pageup', { shift: true }), + ], }, { command: Command.PAGE_DOWN, - positive: [createKey('pagedown'), createKey('pagedown', { ctrl: true })], - negative: [createKey('pageup'), createKey('down')], + positive: [createKey('pagedown')], + negative: [ + createKey('pageup'), + createKey('down'), + createKey('pagedown', { ctrl: true }), + ], }, // History navigation @@ -214,13 +226,21 @@ describe('keyMatchers', () => { }, { command: Command.NAVIGATION_UP, - positive: [createKey('up'), createKey('up', { ctrl: true })], - negative: [createKey('p'), createKey('u')], + positive: [createKey('up')], + negative: [ + createKey('p'), + createKey('u'), + createKey('up', { ctrl: true }), + ], }, { command: Command.NAVIGATION_DOWN, - positive: [createKey('down'), createKey('down', { ctrl: true })], - negative: [createKey('n'), createKey('d')], + positive: [createKey('down')], + negative: [ + createKey('n'), + createKey('d'), + createKey('down', { ctrl: true }), + ], }, // Dialog navigation @@ -333,14 +353,12 @@ describe('keyMatchers', () => { }, { command: Command.SUSPEND_APP, - positive: [ - createKey('z', { ctrl: true }), - createKey('z', { ctrl: true, shift: true }), - ], + positive: [createKey('z', { ctrl: true })], negative: [ createKey('z'), createKey('y', { ctrl: true }), createKey('z', { alt: true }), + createKey('z', { ctrl: true, shift: true }), ], }, { @@ -365,8 +383,12 @@ describe('keyMatchers', () => { }, { command: Command.ACCEPT_SUGGESTION_REVERSE_SEARCH, - positive: [createKey('tab'), createKey('tab', { ctrl: true })], - negative: [createKey('return'), createKey('space')], + positive: [createKey('tab')], + negative: [ + createKey('return'), + createKey('space'), + createKey('tab', { ctrl: true }), + ], }, { command: Command.FOCUS_SHELL_INPUT, @@ -413,22 +435,6 @@ describe('keyMatchers', () => { }); }); }); - - it('should properly handle ACCEPT_SUGGESTION_REVERSE_SEARCH cases', () => { - expect( - keyMatchers[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]( - createKey('return', { ctrl: true }), - ), - ).toBe(false); // ctrl must be false - expect( - keyMatchers[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH](createKey('tab')), - ).toBe(true); - expect( - keyMatchers[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]( - createKey('tab', { ctrl: true }), - ), - ).toBe(true); // modifiers ignored - }); }); describe('Custom key bindings', () => { diff --git a/packages/cli/src/ui/keyMatchers.ts b/packages/cli/src/ui/keyMatchers.ts index 7c61db10163..f833e5ee09a 100644 --- a/packages/cli/src/ui/keyMatchers.ts +++ b/packages/cli/src/ui/keyMatchers.ts @@ -13,16 +13,15 @@ import { Command, defaultKeyBindings } from '../config/keyBindings.js'; * Pure data-driven matching logic */ function matchKeyBinding(keyBinding: KeyBinding, key: Key): boolean { - // Check modifiers - follow original logic: - // undefined = ignore this modifier (original behavior) + // Check modifiers: // true = modifier must be pressed - // false = modifier must NOT be pressed + // false or undefined = modifier must NOT be pressed return ( keyBinding.key === key.name && - (keyBinding.shift === undefined || key.shift === keyBinding.shift) && - (keyBinding.alt === undefined || key.alt === keyBinding.alt) && - (keyBinding.ctrl === undefined || key.ctrl === keyBinding.ctrl) && - (keyBinding.cmd === undefined || key.cmd === keyBinding.cmd) + !!key.shift === !!keyBinding.shift && + !!key.alt === !!keyBinding.alt && + !!key.ctrl === !!keyBinding.ctrl && + !!key.cmd === !!keyBinding.cmd ); }