Migrate keybindings (#16460)

This commit is contained in:
Tommaso Sciortino
2026-01-12 16:28:10 -08:00
committed by GitHub
parent 548641c952
commit 8d3e93cdb0
6 changed files with 165 additions and 63 deletions
+14 -21
View File
@@ -16,18 +16,27 @@ available combinations.
#### Cursor Movement #### Cursor Movement
| Action | Keys | | Action | Keys |
| ----------------------------------------- | ---------------------- | | ------------------------------------------- | ------------------------------------------------------------ |
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` | | Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` | | Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
| Move the cursor one character to the left. | `Left Arrow (no Ctrl, no Cmd)`<br />`Ctrl + B` |
| Move the cursor one character to the right. | `Right Arrow (no Ctrl, no Cmd)`<br />`Ctrl + F` |
| Move the cursor one word to the left. | `Ctrl + Left Arrow`<br />`Cmd + Left Arrow`<br />`Cmd + B` |
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Cmd + Right Arrow`<br />`Cmd + F` |
#### Editing #### Editing
| Action | Keys | | Action | Keys |
| ------------------------------------------------ | ----------------------------------------- | | ------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| Delete from the cursor to the end of the line. | `Ctrl + K` | | Delete from the cursor to the end of the line. | `Ctrl + K` |
| Delete from the cursor to the start of the line. | `Ctrl + U` | | Delete from the cursor to the start of the line. | `Ctrl + U` |
| Clear all text in the input field. | `Ctrl + C` | | Clear all text in the input field. | `Ctrl + C` |
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace` | | Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace`<br />`Ctrl + ""`<br />`Cmd + ""`<br />`Ctrl + W` |
| Delete the next word. | `Ctrl + Delete`<br />`Cmd + Delete` |
| Delete the character to the left. | `Backspace`<br />`""`<br />`Ctrl + H` |
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
| Undo the most recent text edit. | `Ctrl + Z (no Shift)` |
| Redo the most recent undone text edit. | `Ctrl + Shift + Z` |
#### Screen Control #### Screen Control
@@ -115,27 +124,11 @@ available combinations.
## Additional context-specific shortcuts ## Additional context-specific shortcuts
- `Option+M` (macOS): Entering `µ` with Option+M also toggles Markdown - `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your
rendering, matching `Cmd+M`. terminal isn't configured to send Meta with Option.
- `!` on an empty prompt: Enter or exit shell mode. - `!` on an empty prompt: Enter or exit shell mode.
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line - `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
mode. mode.
- `Ctrl+Delete` / `Meta+Delete`: Delete the word to the right of the cursor.
- `Ctrl+B` or `Left Arrow`: Move the cursor one character to the left while
editing text.
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right.
- `Ctrl+D` or `Delete`: Remove the character immediately to the right of the
cursor.
- `Ctrl+H` or `Backspace`: Remove the character immediately to the left of the
cursor.
- `Ctrl+Left Arrow` / `Meta+Left Arrow` / `Meta+B`: Move one word to the left.
- `Ctrl+Right Arrow` / `Meta+Right Arrow` / `Meta+F`: Move one word to the
right.
- `Ctrl+W`: Delete the word to the left of the cursor (in addition to
`Ctrl+Backspace` / `Cmd+Backspace`).
- `Ctrl+Z` / `Ctrl+Shift+Z`: Undo or redo the most recent text edit.
- `Meta+Enter`: Open the current input in an external editor (alias for
`Ctrl+X`).
- `Esc` pressed twice quickly: Clear the current input buffer. - `Esc` pressed twice quickly: Clear the current input buffer.
- `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a - `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a
single-line input, navigate backward or forward through prompt history. single-line input, navigate backward or forward through prompt history.
+64 -1
View File
@@ -64,6 +64,15 @@ export enum Command {
TOGGLE_COPY_MODE = 'toggleCopyMode', TOGGLE_COPY_MODE = 'toggleCopyMode',
TOGGLE_YOLO = 'toggleYolo', TOGGLE_YOLO = 'toggleYolo',
TOGGLE_AUTO_EDIT = 'toggleAutoEdit', TOGGLE_AUTO_EDIT = 'toggleAutoEdit',
UNDO = 'undo',
REDO = 'redo',
MOVE_LEFT = 'moveLeft',
MOVE_RIGHT = 'moveRight',
MOVE_WORD_LEFT = 'moveWordLeft',
MOVE_WORD_RIGHT = 'moveWordRight',
DELETE_CHAR_LEFT = 'deleteCharLeft',
DELETE_CHAR_RIGHT = 'deleteCharRight',
DELETE_WORD_FORWARD = 'deleteWordForward',
QUIT = 'quit', QUIT = 'quit',
EXIT = 'exit', EXIT = 'exit',
SHOW_MORE_LINES = 'showMoreLines', SHOW_MORE_LINES = 'showMoreLines',
@@ -126,6 +135,37 @@ export const defaultKeyBindings: KeyBindingConfig = {
[Command.DELETE_WORD_BACKWARD]: [ [Command.DELETE_WORD_BACKWARD]: [
{ key: 'backspace', ctrl: true }, { key: 'backspace', ctrl: true },
{ key: 'backspace', command: true }, { key: 'backspace', command: true },
{ sequence: '\x7f', ctrl: true },
{ sequence: '\x7f', command: true },
{ key: 'w', ctrl: true },
],
[Command.MOVE_LEFT]: [
{ key: 'left', ctrl: false, command: false },
{ key: 'b', ctrl: true },
],
[Command.MOVE_RIGHT]: [
{ key: 'right', ctrl: false, command: false },
{ key: 'f', ctrl: true },
],
[Command.MOVE_WORD_LEFT]: [
{ key: 'left', ctrl: true },
{ key: 'left', command: true },
{ key: 'b', command: true },
],
[Command.MOVE_WORD_RIGHT]: [
{ key: 'right', ctrl: true },
{ key: 'right', command: true },
{ key: 'f', command: true },
],
[Command.DELETE_CHAR_LEFT]: [
{ key: 'backspace' },
{ sequence: '\x7f' },
{ key: 'h', ctrl: true },
],
[Command.DELETE_CHAR_RIGHT]: [{ key: 'delete' }, { key: 'd', ctrl: true }],
[Command.DELETE_WORD_FORWARD]: [
{ key: 'delete', ctrl: true },
{ key: 'delete', command: true },
], ],
// Screen control // Screen control
@@ -208,6 +248,8 @@ export const defaultKeyBindings: KeyBindingConfig = {
[Command.TOGGLE_COPY_MODE]: [{ key: 's', ctrl: true }], [Command.TOGGLE_COPY_MODE]: [{ key: 's', ctrl: true }],
[Command.TOGGLE_YOLO]: [{ key: 'y', ctrl: true }], [Command.TOGGLE_YOLO]: [{ key: 'y', ctrl: true }],
[Command.TOGGLE_AUTO_EDIT]: [{ key: 'tab', shift: true }], [Command.TOGGLE_AUTO_EDIT]: [{ key: 'tab', shift: true }],
[Command.UNDO]: [{ key: 'z', ctrl: true, shift: false }],
[Command.REDO]: [{ key: 'z', ctrl: true, shift: true }],
[Command.QUIT]: [{ key: 'c', ctrl: true }], [Command.QUIT]: [{ key: 'c', ctrl: true }],
[Command.EXIT]: [{ key: 'd', ctrl: true }], [Command.EXIT]: [{ key: 'd', ctrl: true }],
[Command.SHOW_MORE_LINES]: [{ key: 's', ctrl: true }], [Command.SHOW_MORE_LINES]: [{ key: 's', ctrl: true }],
@@ -242,7 +284,14 @@ export const commandCategories: readonly CommandCategory[] = [
}, },
{ {
title: 'Cursor Movement', title: 'Cursor Movement',
commands: [Command.HOME, Command.END], commands: [
Command.HOME,
Command.END,
Command.MOVE_LEFT,
Command.MOVE_RIGHT,
Command.MOVE_WORD_LEFT,
Command.MOVE_WORD_RIGHT,
],
}, },
{ {
title: 'Editing', title: 'Editing',
@@ -251,6 +300,11 @@ export const commandCategories: readonly CommandCategory[] = [
Command.KILL_LINE_LEFT, Command.KILL_LINE_LEFT,
Command.CLEAR_INPUT, Command.CLEAR_INPUT,
Command.DELETE_WORD_BACKWARD, Command.DELETE_WORD_BACKWARD,
Command.DELETE_WORD_FORWARD,
Command.DELETE_CHAR_LEFT,
Command.DELETE_CHAR_RIGHT,
Command.UNDO,
Command.REDO,
], ],
}, },
{ {
@@ -334,10 +388,19 @@ export const commandDescriptions: Readonly<Record<Command, string>> = {
[Command.ESCAPE]: 'Dismiss dialogs or cancel the current focus.', [Command.ESCAPE]: 'Dismiss dialogs or cancel the current focus.',
[Command.HOME]: 'Move the cursor to the start of the line.', [Command.HOME]: 'Move the cursor to the start of the line.',
[Command.END]: 'Move the cursor to the end of the line.', [Command.END]: 'Move the cursor to the end of the line.',
[Command.MOVE_LEFT]: 'Move the cursor one character to the left.',
[Command.MOVE_RIGHT]: 'Move the cursor one character to the right.',
[Command.MOVE_WORD_LEFT]: 'Move the cursor one word to the left.',
[Command.MOVE_WORD_RIGHT]: 'Move the cursor one word to the right.',
[Command.KILL_LINE_RIGHT]: 'Delete from the cursor to the end of the line.', [Command.KILL_LINE_RIGHT]: 'Delete from the cursor to the end of the line.',
[Command.KILL_LINE_LEFT]: 'Delete from the cursor to the start of the line.', [Command.KILL_LINE_LEFT]: 'Delete from the cursor to the start of the line.',
[Command.CLEAR_INPUT]: 'Clear all text in the input field.', [Command.CLEAR_INPUT]: 'Clear all text in the input field.',
[Command.DELETE_WORD_BACKWARD]: 'Delete the previous word.', [Command.DELETE_WORD_BACKWARD]: 'Delete the previous word.',
[Command.DELETE_WORD_FORWARD]: 'Delete the next word.',
[Command.DELETE_CHAR_LEFT]: 'Delete the character to the left.',
[Command.DELETE_CHAR_RIGHT]: 'Delete the character to the right.',
[Command.UNDO]: 'Undo the most recent text edit.',
[Command.REDO]: 'Redo the most recent undone text edit.',
[Command.CLEAR_SCREEN]: 'Clear the terminal screen and redraw the UI.', [Command.CLEAR_SCREEN]: 'Clear the terminal screen and redraw the UI.',
[Command.SCROLL_UP]: 'Scroll content up.', [Command.SCROLL_UP]: 'Scroll content up.',
[Command.SCROLL_DOWN]: 'Scroll content down.', [Command.SCROLL_DOWN]: 'Scroll content down.',
+1 -1
View File
@@ -144,7 +144,7 @@ export const Help: React.FC<Help> = ({ commands }) => (
</Text> </Text>
<Text color={theme.text.primary}> <Text color={theme.text.primary}>
<Text bold color={theme.text.accent}> <Text bold color={theme.text.accent}>
{process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'} Ctrl+X
</Text>{' '} </Text>{' '}
- Open input in external editor - Open input in external editor
</Text> </Text>
@@ -25,6 +25,7 @@ import {
} from '../../utils/textUtils.js'; } from '../../utils/textUtils.js';
import { parsePastedPaths } from '../../utils/clipboardUtils.js'; import { parsePastedPaths } from '../../utils/clipboardUtils.js';
import type { Key } from '../../contexts/KeypressContext.js'; import type { Key } from '../../contexts/KeypressContext.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import type { VimAction } from './vim-buffer-actions.js'; import type { VimAction } from './vim-buffer-actions.js';
import { handleVimAction } from './vim-buffer-actions.js'; import { handleVimAction } from './vim-buffer-actions.js';
@@ -2220,38 +2221,20 @@ export function useTextBuffer({
input === '\\r') // VSCode terminal represents shift + enter this way input === '\\r') // VSCode terminal represents shift + enter this way
) )
newline(); newline();
else if (key.name === 'left' && !key.meta && !key.ctrl) move('left'); else if (keyMatchers[Command.MOVE_LEFT](key)) move('left');
else if (key.ctrl && key.name === 'b') move('left'); else if (keyMatchers[Command.MOVE_RIGHT](key)) move('right');
else if (key.name === 'right' && !key.meta && !key.ctrl) move('right');
else if (key.ctrl && key.name === 'f') move('right');
else if (key.name === 'up') move('up'); else if (key.name === 'up') move('up');
else if (key.name === 'down') move('down'); else if (key.name === 'down') move('down');
else if ((key.ctrl || key.meta) && key.name === 'left') move('wordLeft'); else if (keyMatchers[Command.MOVE_WORD_LEFT](key)) move('wordLeft');
else if (key.meta && key.name === 'b') move('wordLeft'); else if (keyMatchers[Command.MOVE_WORD_RIGHT](key)) move('wordRight');
else if ((key.ctrl || key.meta) && key.name === 'right') else if (keyMatchers[Command.HOME](key)) move('home');
move('wordRight'); else if (keyMatchers[Command.END](key)) move('end');
else if (key.meta && key.name === 'f') move('wordRight'); else if (keyMatchers[Command.DELETE_WORD_BACKWARD](key)) deleteWordLeft();
else if (key.name === 'home') move('home'); else if (keyMatchers[Command.DELETE_WORD_FORWARD](key)) deleteWordRight();
else if (key.ctrl && key.name === 'a') move('home'); else if (keyMatchers[Command.DELETE_CHAR_LEFT](key)) backspace();
else if (key.name === 'end') move('end'); else if (keyMatchers[Command.DELETE_CHAR_RIGHT](key)) del();
else if (key.ctrl && key.name === 'e') move('end'); else if (keyMatchers[Command.UNDO](key)) undo();
else if (key.ctrl && key.name === 'w') deleteWordLeft(); else if (keyMatchers[Command.REDO](key)) redo();
else if (
(key.meta || key.ctrl) &&
(key.name === 'backspace' || input === '\x7f')
)
deleteWordLeft();
else if ((key.meta || key.ctrl) && key.name === 'delete')
deleteWordRight();
else if (
key.name === 'backspace' ||
input === '\x7f' ||
(key.ctrl && key.name === 'h')
)
backspace();
else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del();
else if (key.ctrl && !key.shift && key.name === 'z') undo();
else if (key.ctrl && key.shift && key.name === 'z') redo();
else if (key.insertable) { else if (key.insertable) {
insert(input, { paste: key.paste }); insert(input, { paste: key.paste });
} }
+1 -1
View File
@@ -112,7 +112,7 @@ export const INFORMATIVE_TIPS = [
'Paste from your clipboard with Ctrl+V...', 'Paste from your clipboard with Ctrl+V...',
'Undo text edits in the input with Ctrl+Z...', 'Undo text edits in the input with Ctrl+Z...',
'Redo undone text edits with Ctrl+Shift+Z...', 'Redo undone text edits with Ctrl+Shift+Z...',
'Open the current prompt in an external editor with Ctrl+X or Meta+Enter...', 'Open the current prompt in an external editor with Ctrl+X...',
'In menus, move up/down with k/j or the arrow keys...', 'In menus, move up/down with k/j or the arrow keys...',
'In menus, select an item by typing its number...', 'In menus, select an item by typing its number...',
"If you're using an IDE, see the context with Ctrl+G...", "If you're using an IDE, see the context with Ctrl+G...",
+65 -2
View File
@@ -39,7 +39,7 @@ describe('keyMatchers', () => {
// Cursor movement // Cursor movement
{ {
command: Command.HOME, command: Command.HOME,
positive: [createKey('a', { ctrl: true })], positive: [createKey('a', { ctrl: true }), createKey('home')],
negative: [ negative: [
createKey('a'), createKey('a'),
createKey('a', { shift: true }), createKey('a', { shift: true }),
@@ -48,13 +48,41 @@ describe('keyMatchers', () => {
}, },
{ {
command: Command.END, command: Command.END,
positive: [createKey('e', { ctrl: true })], positive: [createKey('e', { ctrl: true }), createKey('end')],
negative: [ negative: [
createKey('e'), createKey('e'),
createKey('e', { shift: true }), createKey('e', { shift: true }),
createKey('a', { ctrl: true }), createKey('a', { ctrl: true }),
], ],
}, },
{
command: Command.MOVE_LEFT,
positive: [createKey('left'), createKey('b', { ctrl: true })],
negative: [createKey('left', { ctrl: true }), createKey('b')],
},
{
command: Command.MOVE_RIGHT,
positive: [createKey('right'), createKey('f', { ctrl: true })],
negative: [createKey('right', { ctrl: true }), createKey('f')],
},
{
command: Command.MOVE_WORD_LEFT,
positive: [
createKey('left', { ctrl: true }),
createKey('left', { meta: true }),
createKey('b', { meta: true }),
],
negative: [createKey('left'), createKey('b', { ctrl: true })],
},
{
command: Command.MOVE_WORD_RIGHT,
positive: [
createKey('right', { ctrl: true }),
createKey('right', { meta: true }),
createKey('f', { meta: true }),
],
negative: [createKey('right'), createKey('f', { ctrl: true })],
},
// Text deletion // Text deletion
{ {
@@ -72,14 +100,49 @@ describe('keyMatchers', () => {
positive: [createKey('c', { ctrl: true })], positive: [createKey('c', { ctrl: true })],
negative: [createKey('c'), createKey('k', { ctrl: true })], negative: [createKey('c'), createKey('k', { ctrl: true })],
}, },
{
command: Command.DELETE_CHAR_LEFT,
positive: [
createKey('backspace'),
{ ...createKey('\x7f'), sequence: '\x7f' },
createKey('h', { ctrl: true }),
],
negative: [createKey('h'), createKey('x', { ctrl: true })],
},
{
command: Command.DELETE_CHAR_RIGHT,
positive: [createKey('delete'), createKey('d', { ctrl: true })],
negative: [createKey('d'), createKey('x', { ctrl: true })],
},
{ {
command: Command.DELETE_WORD_BACKWARD, command: Command.DELETE_WORD_BACKWARD,
positive: [ positive: [
createKey('backspace', { ctrl: true }), createKey('backspace', { ctrl: true }),
createKey('backspace', { meta: true }), createKey('backspace', { meta: true }),
{ ...createKey('\x7f', { ctrl: true }), sequence: '\x7f' },
{ ...createKey('\x7f', { meta: true }), sequence: '\x7f' },
createKey('w', { ctrl: true }),
], ],
negative: [createKey('backspace'), createKey('delete', { ctrl: true })], negative: [createKey('backspace'), createKey('delete', { ctrl: true })],
}, },
{
command: Command.DELETE_WORD_FORWARD,
positive: [
createKey('delete', { ctrl: true }),
createKey('delete', { meta: true }),
],
negative: [createKey('delete'), createKey('backspace', { ctrl: true })],
},
{
command: Command.UNDO,
positive: [createKey('z', { ctrl: true, shift: false })],
negative: [createKey('z'), createKey('z', { ctrl: true, shift: true })],
},
{
command: Command.REDO,
positive: [createKey('z', { ctrl: true, shift: true })],
negative: [createKey('z'), createKey('z', { ctrl: true, shift: false })],
},
// Screen control // Screen control
{ {