remove wildcard behavior on keybindings (#21315)

This commit is contained in:
Tommaso Sciortino
2026-03-05 22:11:53 +00:00
committed by GitHub
parent e8bc7bea44
commit 19c9508fd1
10 changed files with 135 additions and 415 deletions

View File

@@ -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?

View File

@@ -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
"

View File

@@ -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

View File

@@ -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,
}),
);

View File

@@ -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;

View File

@@ -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', () => {

View File

@@ -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
);
}