mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
Support command/ctrl/alt backspace correctly (#17175)
This commit is contained in:
committed by
GitHub
parent
e894871afc
commit
f190b87223
@@ -19,14 +19,14 @@ available combinations.
|
|||||||
|
|
||||||
| Action | Keys |
|
| Action | Keys |
|
||||||
| ------------------------------------------- | ------------------------------------------------------------ |
|
| ------------------------------------------- | ------------------------------------------------------------ |
|
||||||
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home (no Ctrl, no Shift)` |
|
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home (no Shift, Ctrl)` |
|
||||||
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End (no Ctrl, no Shift)` |
|
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End (no Shift, Ctrl)` |
|
||||||
| Move the cursor up one line. | `Up Arrow (no Ctrl, no Cmd)` |
|
| Move the cursor up one line. | `Up Arrow (no Shift, Alt, Ctrl, Cmd)` |
|
||||||
| Move the cursor down one line. | `Down Arrow (no Ctrl, no 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 Ctrl, no Cmd)`<br />`Ctrl + B` |
|
| Move the cursor one character to the left. | `Left Arrow (no Shift, Alt, Ctrl, 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 character to the right. | `Right Arrow (no Shift, Alt, Ctrl, 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 left. | `Ctrl + Left Arrow`<br />`Alt + Left Arrow`<br />`Alt + B` |
|
||||||
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Cmd + Right Arrow`<br />`Cmd + F` |
|
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Alt + Right Arrow`<br />`Alt + F` |
|
||||||
|
|
||||||
#### Editing
|
#### Editing
|
||||||
|
|
||||||
@@ -35,12 +35,12 @@ available combinations.
|
|||||||
| 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`<br />`Ctrl + W` |
|
| Delete the previous word. | `Ctrl + Backspace`<br />`Alt + Backspace`<br />`Ctrl + W` |
|
||||||
| Delete the next word. | `Ctrl + Delete`<br />`Cmd + Delete` |
|
| Delete the next word. | `Ctrl + Delete`<br />`Alt + Delete` |
|
||||||
| Delete the character to the left. | `Backspace`<br />`Ctrl + H` |
|
| Delete the character to the left. | `Backspace`<br />`Ctrl + H` |
|
||||||
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
|
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
|
||||||
| Undo the most recent text edit. | `Ctrl + Z (no Shift)` |
|
| Undo the most recent text edit. | `Ctrl + Z (no Shift)` |
|
||||||
| Redo the most recent undone text edit. | `Ctrl + Shift + Z` |
|
| Redo the most recent undone text edit. | `Shift + Ctrl + Z` |
|
||||||
|
|
||||||
#### Scrolling
|
#### Scrolling
|
||||||
|
|
||||||
@@ -84,12 +84,12 @@ available combinations.
|
|||||||
|
|
||||||
#### Text Input
|
#### Text Input
|
||||||
|
|
||||||
| Action | Keys |
|
| Action | Keys |
|
||||||
| ---------------------------------------------- | ---------------------------------------------------------------------- |
|
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
| Submit the current prompt. | `Enter (no Ctrl, no Shift, no Cmd)` |
|
| Submit the current prompt. | `Enter (no Shift, Alt, Ctrl, Cmd)` |
|
||||||
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + Enter`<br />`Shift + Enter`<br />`Ctrl + J` |
|
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + Enter`<br />`Alt + Enter`<br />`Shift + Enter`<br />`Ctrl + J` |
|
||||||
| Open the current prompt in an external editor. | `Ctrl + X` |
|
| Open the current prompt in an external editor. | `Ctrl + X` |
|
||||||
| Paste from the clipboard. | `Ctrl + V`<br />`Cmd + V` |
|
| Paste from the clipboard. | `Ctrl + V`<br />`Cmd + V`<br />`Alt + V` |
|
||||||
|
|
||||||
#### App Controls
|
#### App Controls
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ available combinations.
|
|||||||
| Toggle detailed error information. | `F12` |
|
| Toggle detailed error information. | `F12` |
|
||||||
| Toggle the full TODO list. | `Ctrl + T` |
|
| Toggle the full TODO list. | `Ctrl + T` |
|
||||||
| Show IDE context details. | `Ctrl + G` |
|
| Show IDE context details. | `Ctrl + G` |
|
||||||
| Toggle Markdown rendering. | `Cmd + M` |
|
| Toggle Markdown rendering. | `Alt + M` |
|
||||||
| Toggle copy mode when in alternate buffer mode. | `Ctrl + S` |
|
| Toggle copy mode when in alternate buffer mode. | `Ctrl + S` |
|
||||||
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
|
| 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). | `Shift + Tab` |
|
| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). | `Shift + Tab` |
|
||||||
|
|||||||
@@ -33,14 +33,17 @@ describe('keyBindings config', () => {
|
|||||||
expect(binding.key.length).toBeGreaterThan(0);
|
expect(binding.key.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Modifier properties should be boolean or undefined
|
// Modifier properties should be boolean or undefined
|
||||||
if (binding.ctrl !== undefined) {
|
|
||||||
expect(typeof binding.ctrl).toBe('boolean');
|
|
||||||
}
|
|
||||||
if (binding.shift !== undefined) {
|
if (binding.shift !== undefined) {
|
||||||
expect(typeof binding.shift).toBe('boolean');
|
expect(typeof binding.shift).toBe('boolean');
|
||||||
}
|
}
|
||||||
if (binding.command !== undefined) {
|
if (binding.alt !== undefined) {
|
||||||
expect(typeof binding.command).toBe('boolean');
|
expect(typeof binding.alt).toBe('boolean');
|
||||||
|
}
|
||||||
|
if (binding.ctrl !== undefined) {
|
||||||
|
expect(typeof binding.ctrl).toBe('boolean');
|
||||||
|
}
|
||||||
|
if (binding.cmd !== undefined) {
|
||||||
|
expect(typeof binding.cmd).toBe('boolean');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,12 +90,14 @@ export enum Command {
|
|||||||
export interface KeyBinding {
|
export interface KeyBinding {
|
||||||
/** The key name (e.g., 'a', 'return', 'tab', 'escape') */
|
/** The key name (e.g., 'a', 'return', 'tab', 'escape') */
|
||||||
key: string;
|
key: string;
|
||||||
/** Control key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
|
||||||
ctrl?: boolean;
|
|
||||||
/** Shift key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
/** Shift key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
||||||
shift?: boolean;
|
shift?: boolean;
|
||||||
/** Command/meta key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
/** Alt/Option key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
||||||
command?: boolean;
|
alt?: boolean;
|
||||||
|
/** Control key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
||||||
|
ctrl?: boolean;
|
||||||
|
/** Command/Windows/Super key requirement: true=must be pressed, false=must not be pressed, undefined=ignore */
|
||||||
|
cmd?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,51 +121,54 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
// Cursor Movement
|
// Cursor Movement
|
||||||
[Command.HOME]: [
|
[Command.HOME]: [
|
||||||
{ key: 'a', ctrl: true },
|
{ key: 'a', ctrl: true },
|
||||||
{ key: 'home', ctrl: false, shift: false },
|
{ key: 'home', shift: false, ctrl: false },
|
||||||
],
|
],
|
||||||
[Command.END]: [
|
[Command.END]: [
|
||||||
{ key: 'e', ctrl: true },
|
{ key: 'e', ctrl: true },
|
||||||
{ key: 'end', ctrl: false, shift: false },
|
{ 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_UP]: [{ key: 'up', ctrl: false, command: false }],
|
|
||||||
[Command.MOVE_DOWN]: [{ key: 'down', ctrl: false, command: false }],
|
|
||||||
[Command.MOVE_LEFT]: [
|
[Command.MOVE_LEFT]: [
|
||||||
{ key: 'left', ctrl: false, command: false },
|
{ key: 'left', shift: false, alt: false, ctrl: false, cmd: false },
|
||||||
{ key: 'b', ctrl: true },
|
{ key: 'b', ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.MOVE_RIGHT]: [
|
[Command.MOVE_RIGHT]: [
|
||||||
{ key: 'right', ctrl: false, command: false },
|
{ key: 'right', shift: false, alt: false, ctrl: false, cmd: false },
|
||||||
{ key: 'f', ctrl: true },
|
{ key: 'f', ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.MOVE_WORD_LEFT]: [
|
[Command.MOVE_WORD_LEFT]: [
|
||||||
{ key: 'left', ctrl: true },
|
{ key: 'left', ctrl: true },
|
||||||
{ key: 'left', command: true },
|
{ key: 'left', alt: true },
|
||||||
{ key: 'b', command: true },
|
{ key: 'b', alt: true },
|
||||||
],
|
],
|
||||||
[Command.MOVE_WORD_RIGHT]: [
|
[Command.MOVE_WORD_RIGHT]: [
|
||||||
{ key: 'right', ctrl: true },
|
{ key: 'right', ctrl: true },
|
||||||
{ key: 'right', command: true },
|
{ key: 'right', alt: true },
|
||||||
{ key: 'f', command: true },
|
{ key: 'f', alt: true },
|
||||||
],
|
],
|
||||||
|
|
||||||
// Editing
|
// Editing
|
||||||
[Command.KILL_LINE_RIGHT]: [{ key: 'k', ctrl: true }],
|
[Command.KILL_LINE_RIGHT]: [{ key: 'k', ctrl: true }],
|
||||||
[Command.KILL_LINE_LEFT]: [{ key: 'u', ctrl: true }],
|
[Command.KILL_LINE_LEFT]: [{ key: 'u', ctrl: true }],
|
||||||
[Command.CLEAR_INPUT]: [{ key: 'c', ctrl: true }],
|
[Command.CLEAR_INPUT]: [{ key: 'c', ctrl: true }],
|
||||||
// Added command (meta/alt/option) for mac compatibility
|
|
||||||
[Command.DELETE_WORD_BACKWARD]: [
|
[Command.DELETE_WORD_BACKWARD]: [
|
||||||
{ key: 'backspace', ctrl: true },
|
{ key: 'backspace', ctrl: true },
|
||||||
{ key: 'backspace', command: true },
|
{ key: 'backspace', alt: true },
|
||||||
{ key: 'w', ctrl: true },
|
{ key: 'w', ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.DELETE_WORD_FORWARD]: [
|
[Command.DELETE_WORD_FORWARD]: [
|
||||||
{ key: 'delete', ctrl: true },
|
{ key: 'delete', ctrl: true },
|
||||||
{ key: 'delete', command: true },
|
{ key: 'delete', alt: true },
|
||||||
],
|
],
|
||||||
[Command.DELETE_CHAR_LEFT]: [{ key: 'backspace' }, { key: 'h', ctrl: true }],
|
[Command.DELETE_CHAR_LEFT]: [{ key: 'backspace' }, { key: 'h', ctrl: true }],
|
||||||
[Command.DELETE_CHAR_RIGHT]: [{ key: 'delete' }, { key: 'd', ctrl: true }],
|
[Command.DELETE_CHAR_RIGHT]: [{ key: 'delete' }, { key: 'd', ctrl: true }],
|
||||||
[Command.UNDO]: [{ key: 'z', ctrl: true, shift: false }],
|
[Command.UNDO]: [{ key: 'z', shift: false, ctrl: true }],
|
||||||
[Command.REDO]: [{ key: 'z', ctrl: true, shift: true }],
|
[Command.REDO]: [{ key: 'z', shift: true, ctrl: true }],
|
||||||
|
|
||||||
// Scrolling
|
// Scrolling
|
||||||
[Command.SCROLL_UP]: [{ key: 'up', shift: true }],
|
[Command.SCROLL_UP]: [{ key: 'up', shift: true }],
|
||||||
@@ -180,10 +185,9 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
[Command.PAGE_DOWN]: [{ key: 'pagedown' }],
|
[Command.PAGE_DOWN]: [{ key: 'pagedown' }],
|
||||||
|
|
||||||
// History & Search
|
// History & Search
|
||||||
[Command.HISTORY_UP]: [{ key: 'p', ctrl: true, shift: false }],
|
[Command.HISTORY_UP]: [{ key: 'p', shift: false, ctrl: true }],
|
||||||
[Command.HISTORY_DOWN]: [{ key: 'n', ctrl: true, shift: false }],
|
[Command.HISTORY_DOWN]: [{ key: 'n', shift: false, ctrl: true }],
|
||||||
[Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }],
|
[Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }],
|
||||||
// Note: original logic ONLY checked ctrl=false, ignored meta/shift/paste
|
|
||||||
[Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }],
|
[Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }],
|
||||||
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab' }],
|
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab' }],
|
||||||
|
|
||||||
@@ -203,14 +207,13 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
|
|
||||||
// Suggestions & Completions
|
// Suggestions & Completions
|
||||||
[Command.ACCEPT_SUGGESTION]: [{ key: 'tab' }, { key: 'return', ctrl: false }],
|
[Command.ACCEPT_SUGGESTION]: [{ key: 'tab' }, { key: 'return', ctrl: false }],
|
||||||
// Completion navigation (arrow or Ctrl+P/N)
|
|
||||||
[Command.COMPLETION_UP]: [
|
[Command.COMPLETION_UP]: [
|
||||||
{ key: 'up', shift: false },
|
{ key: 'up', shift: false },
|
||||||
{ key: 'p', ctrl: true, shift: false },
|
{ key: 'p', shift: false, ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.COMPLETION_DOWN]: [
|
[Command.COMPLETION_DOWN]: [
|
||||||
{ key: 'down', shift: false },
|
{ key: 'down', shift: false },
|
||||||
{ key: 'n', ctrl: true, shift: false },
|
{ key: 'n', shift: false, ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.EXPAND_SUGGESTION]: [{ key: 'right' }],
|
[Command.EXPAND_SUGGESTION]: [{ key: 'right' }],
|
||||||
[Command.COLLAPSE_SUGGESTION]: [{ key: 'left' }],
|
[Command.COLLAPSE_SUGGESTION]: [{ key: 'left' }],
|
||||||
@@ -220,30 +223,31 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
[Command.SUBMIT]: [
|
[Command.SUBMIT]: [
|
||||||
{
|
{
|
||||||
key: 'return',
|
key: 'return',
|
||||||
ctrl: false,
|
|
||||||
command: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Split into multiple data-driven bindings
|
|
||||||
// Now also includes shift+enter for multi-line input
|
|
||||||
[Command.NEWLINE]: [
|
[Command.NEWLINE]: [
|
||||||
{ key: 'return', ctrl: true },
|
{ key: 'return', ctrl: true },
|
||||||
{ key: 'return', command: true },
|
{ key: 'return', cmd: true },
|
||||||
|
{ key: 'return', alt: true },
|
||||||
{ key: 'return', shift: true },
|
{ key: 'return', shift: true },
|
||||||
{ key: 'j', ctrl: true },
|
{ key: 'j', ctrl: true },
|
||||||
],
|
],
|
||||||
[Command.OPEN_EXTERNAL_EDITOR]: [{ key: 'x', ctrl: true }],
|
[Command.OPEN_EXTERNAL_EDITOR]: [{ key: 'x', ctrl: true }],
|
||||||
[Command.PASTE_CLIPBOARD]: [
|
[Command.PASTE_CLIPBOARD]: [
|
||||||
{ key: 'v', ctrl: true },
|
{ key: 'v', ctrl: true },
|
||||||
{ key: 'v', command: true },
|
{ key: 'v', cmd: true },
|
||||||
|
{ key: 'v', alt: true },
|
||||||
],
|
],
|
||||||
|
|
||||||
// App Controls
|
// App Controls
|
||||||
[Command.SHOW_ERROR_DETAILS]: [{ key: 'f12' }],
|
[Command.SHOW_ERROR_DETAILS]: [{ key: 'f12' }],
|
||||||
[Command.SHOW_FULL_TODOS]: [{ key: 't', ctrl: true }],
|
[Command.SHOW_FULL_TODOS]: [{ key: 't', ctrl: true }],
|
||||||
[Command.SHOW_IDE_CONTEXT_DETAIL]: [{ key: 'g', ctrl: true }],
|
[Command.SHOW_IDE_CONTEXT_DETAIL]: [{ key: 'g', ctrl: true }],
|
||||||
[Command.TOGGLE_MARKDOWN]: [{ key: 'm', command: true }],
|
[Command.TOGGLE_MARKDOWN]: [{ key: 'm', alt: true }],
|
||||||
[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.CYCLE_APPROVAL_MODE]: [{ key: 'tab', shift: true }],
|
[Command.CYCLE_APPROVAL_MODE]: [{ key: 'tab', shift: true }],
|
||||||
|
|||||||
@@ -1660,9 +1660,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 'c',
|
name: 'c',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
...key,
|
...key,
|
||||||
} as Key);
|
} as Key);
|
||||||
});
|
});
|
||||||
@@ -1870,9 +1871,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 's',
|
name: 's',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x13',
|
sequence: '\x13',
|
||||||
});
|
});
|
||||||
@@ -1896,9 +1898,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 's',
|
name: 's',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x13',
|
sequence: '\x13',
|
||||||
});
|
});
|
||||||
@@ -1910,9 +1913,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 'any', // Any key should exit copy mode
|
name: 'any', // Any key should exit copy mode
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: 'a',
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
@@ -1930,9 +1934,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 's',
|
name: 's',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x13',
|
sequence: '\x13',
|
||||||
});
|
});
|
||||||
@@ -1945,9 +1950,10 @@ describe('AppContainer State Management', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
handleGlobalKeypress({
|
handleGlobalKeypress({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: 'a',
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,10 +108,10 @@ describe('ApiAuthDialog', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: keyName,
|
name: keyName,
|
||||||
sequence,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(expectedCall).toHaveBeenCalledWith(...args);
|
expect(expectedCall).toHaveBeenCalledWith(...args);
|
||||||
@@ -137,9 +137,9 @@ describe('ApiAuthDialog', () => {
|
|||||||
|
|
||||||
await keypressHandler({
|
await keypressHandler({
|
||||||
name: 'c',
|
name: 'c',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(clearApiKey).toHaveBeenCalled();
|
expect(clearApiKey).toHaveBeenCalled();
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ describe('LoginWithGoogleRestartDialog', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
sequence: '\u001b',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '\u001b',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onDismiss).toHaveBeenCalledTimes(1);
|
expect(onDismiss).toHaveBeenCalledTimes(1);
|
||||||
@@ -67,10 +67,10 @@ describe('LoginWithGoogleRestartDialog', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: keyName,
|
name: keyName,
|
||||||
sequence: keyName,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: keyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Advance timers to trigger the setTimeout callback
|
// Advance timers to trigger the setTimeout callback
|
||||||
|
|||||||
@@ -851,8 +851,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
completion.promptCompletion.text &&
|
completion.promptCompletion.text &&
|
||||||
key.sequence &&
|
key.sequence &&
|
||||||
key.sequence.length === 1 &&
|
key.sequence.length === 1 &&
|
||||||
|
!key.alt &&
|
||||||
!key.ctrl &&
|
!key.ctrl &&
|
||||||
!key.meta
|
!key.cmd
|
||||||
) {
|
) {
|
||||||
completion.promptCompletion.clear();
|
completion.promptCompletion.clear();
|
||||||
setExpandedSuggestionIndex(-1);
|
setExpandedSuggestionIndex(-1);
|
||||||
|
|||||||
@@ -91,9 +91,10 @@ describe('MultiFolderTrustDialog', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
keypressCallback({
|
keypressCallback({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
sequence: '',
|
sequence: '',
|
||||||
insertable: false,
|
insertable: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
|||||||
const triggerKey = (
|
const triggerKey = (
|
||||||
partialKey: Partial<{
|
partialKey: Partial<{
|
||||||
name: string;
|
name: string;
|
||||||
ctrl: boolean;
|
|
||||||
meta: boolean;
|
|
||||||
shift: boolean;
|
shift: boolean;
|
||||||
paste: boolean;
|
alt: boolean;
|
||||||
|
ctrl: boolean;
|
||||||
|
cmd: boolean;
|
||||||
insertable: boolean;
|
insertable: boolean;
|
||||||
sequence: string;
|
sequence: string;
|
||||||
}>,
|
}>,
|
||||||
@@ -108,9 +108,10 @@ const triggerKey = (
|
|||||||
|
|
||||||
const key = {
|
const key = {
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '',
|
sequence: '',
|
||||||
...partialKey,
|
...partialKey,
|
||||||
@@ -263,7 +264,13 @@ describe('SessionBrowser component', () => {
|
|||||||
|
|
||||||
// Type the query "query".
|
// Type the query "query".
|
||||||
for (const ch of ['q', 'u', 'e', 'r', 'y']) {
|
for (const ch of ['q', 'u', 'e', 'r', 'y']) {
|
||||||
triggerKey({ sequence: ch, name: ch, ctrl: false, meta: false });
|
triggerKey({
|
||||||
|
sequence: ch,
|
||||||
|
name: ch,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -781,9 +781,10 @@ export const useSessionBrowserInput = (
|
|||||||
state.setScrollOffset(0);
|
state.setScrollOffset(0);
|
||||||
} else if (
|
} else if (
|
||||||
key.sequence &&
|
key.sequence &&
|
||||||
|
key.sequence.length === 1 &&
|
||||||
|
!key.alt &&
|
||||||
!key.ctrl &&
|
!key.ctrl &&
|
||||||
!key.meta &&
|
!key.cmd
|
||||||
key.sequence.length === 1
|
|
||||||
) {
|
) {
|
||||||
state.setSearchQuery((prev) => prev + key.sequence);
|
state.setSearchQuery((prev) => prev + key.sequence);
|
||||||
state.setActiveIndex(0);
|
state.setActiveIndex(0);
|
||||||
|
|||||||
@@ -53,7 +53,14 @@ describe('ShellInputPrompt', () => {
|
|||||||
const handler = mockUseKeypress.mock.calls[0][0];
|
const handler = mockUseKeypress.mock.calls[0][0];
|
||||||
|
|
||||||
// Simulate keypress
|
// Simulate keypress
|
||||||
handler({ name, sequence, ctrl: false, shift: false, meta: false });
|
handler({
|
||||||
|
name,
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence,
|
||||||
|
});
|
||||||
|
|
||||||
expect(mockWriteToPty).toHaveBeenCalledWith(1, sequence);
|
expect(mockWriteToPty).toHaveBeenCalledWith(1, sequence);
|
||||||
});
|
});
|
||||||
@@ -66,7 +73,7 @@ describe('ShellInputPrompt', () => {
|
|||||||
|
|
||||||
const handler = mockUseKeypress.mock.calls[0][0];
|
const handler = mockUseKeypress.mock.calls[0][0];
|
||||||
|
|
||||||
handler({ name: key, ctrl: true, shift: true, meta: false });
|
handler({ name: key, shift: true, alt: false, ctrl: true, cmd: false });
|
||||||
|
|
||||||
expect(mockScrollPty).toHaveBeenCalledWith(1, direction);
|
expect(mockScrollPty).toHaveBeenCalledWith(1, direction);
|
||||||
});
|
});
|
||||||
@@ -78,10 +85,11 @@ describe('ShellInputPrompt', () => {
|
|||||||
|
|
||||||
handler({
|
handler({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
sequence: 'a',
|
|
||||||
ctrl: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
meta: false,
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockWriteToPty).not.toHaveBeenCalled();
|
expect(mockWriteToPty).not.toHaveBeenCalled();
|
||||||
@@ -94,10 +102,11 @@ describe('ShellInputPrompt', () => {
|
|||||||
|
|
||||||
handler({
|
handler({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
sequence: 'a',
|
|
||||||
ctrl: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
meta: false,
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockWriteToPty).not.toHaveBeenCalled();
|
expect(mockWriteToPty).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -151,18 +151,20 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
sequence: 'a',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
sequence: 'a',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: 'a',
|
||||||
});
|
});
|
||||||
expect(mockBuffer.text).toBe('a');
|
expect(mockBuffer.text).toBe('a');
|
||||||
});
|
});
|
||||||
@@ -176,18 +178,20 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
expect(mockBuffer.text).toBe('tes');
|
expect(mockBuffer.text).toBe('tes');
|
||||||
});
|
});
|
||||||
@@ -201,10 +205,11 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'left',
|
name: 'left',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cursor moves from end to before 't'
|
// Cursor moves from end to before 't'
|
||||||
@@ -221,10 +226,11 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'right',
|
name: 'right',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockBuffer.visualCursor[1]).toBe(3);
|
expect(mockBuffer.visualCursor[1]).toBe(3);
|
||||||
@@ -239,10 +245,11 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onSubmit).toHaveBeenCalledWith('test');
|
expect(onSubmit).toHaveBeenCalledWith('test');
|
||||||
@@ -257,10 +264,11 @@ describe('TextInput', () => {
|
|||||||
|
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
sequence: '',
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
sequence: '',
|
||||||
});
|
});
|
||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -1059,9 +1059,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'h',
|
name: 'h',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: 'h',
|
sequence: 'h',
|
||||||
}),
|
}),
|
||||||
@@ -1069,9 +1070,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'i',
|
name: 'i',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: 'i',
|
sequence: 'i',
|
||||||
}),
|
}),
|
||||||
@@ -1086,9 +1088,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: '\r',
|
sequence: '\r',
|
||||||
}),
|
}),
|
||||||
@@ -1103,9 +1106,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'j',
|
name: 'j',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\n',
|
sequence: '\n',
|
||||||
}),
|
}),
|
||||||
@@ -1120,9 +1124,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'tab',
|
name: 'tab',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\t',
|
sequence: '\t',
|
||||||
}),
|
}),
|
||||||
@@ -1137,9 +1142,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'tab',
|
name: 'tab',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: true,
|
shift: true,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\u001b[9;2u',
|
sequence: '\u001b[9;2u',
|
||||||
}),
|
}),
|
||||||
@@ -1159,9 +1165,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
}),
|
}),
|
||||||
@@ -1183,25 +1190,28 @@ describe('useTextBuffer', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'backspace',
|
name: 'backspace',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
@@ -1258,24 +1268,26 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'left',
|
name: 'left',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x1b[D',
|
sequence: '\x1b[D',
|
||||||
}),
|
}),
|
||||||
); // cursor [0,1]
|
);
|
||||||
expect(getBufferState(result).cursor).toEqual([0, 1]);
|
expect(getBufferState(result).cursor).toEqual([0, 1]);
|
||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'right',
|
name: 'right',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\x1b[C',
|
sequence: '\x1b[C',
|
||||||
}),
|
}),
|
||||||
); // cursor [0,2]
|
);
|
||||||
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1288,9 +1300,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: textWithAnsi,
|
sequence: textWithAnsi,
|
||||||
}),
|
}),
|
||||||
@@ -1305,9 +1318,10 @@ describe('useTextBuffer', () => {
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: true,
|
shift: true,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: '\r',
|
sequence: '\r',
|
||||||
}),
|
}),
|
||||||
@@ -1509,13 +1523,13 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
describe('Input Sanitization', () => {
|
describe('Input Sanitization', () => {
|
||||||
const createInput = (sequence: string) => ({
|
const createInput = (sequence: string) => ({
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence,
|
sequence,
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
input: '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m',
|
input: '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m',
|
||||||
@@ -1567,9 +1581,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: largeTextWithUnsafe,
|
sequence: largeTextWithUnsafe,
|
||||||
}),
|
}),
|
||||||
@@ -1601,9 +1616,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: largeTextWithAnsi,
|
sequence: largeTextWithAnsi,
|
||||||
}),
|
}),
|
||||||
@@ -1625,9 +1641,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: '',
|
name: '',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: emojis,
|
sequence: emojis,
|
||||||
}),
|
}),
|
||||||
@@ -1816,9 +1833,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: '\r',
|
sequence: '\r',
|
||||||
}),
|
}),
|
||||||
@@ -1837,9 +1855,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
act(() =>
|
act(() =>
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
name: 'f1',
|
name: 'f1',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: '\u001bOP',
|
sequence: '\u001bOP',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const INFORMATIVE_TIPS = [
|
|||||||
'See full, untruncated responses with Ctrl+S…',
|
'See full, untruncated responses with Ctrl+S…',
|
||||||
'Toggle auto-approval (YOLO mode) for all tools with Ctrl+Y…',
|
'Toggle auto-approval (YOLO mode) for all tools with Ctrl+Y…',
|
||||||
'Cycle through approval modes (Default, Plan, Auto-Edit) with Shift+Tab…',
|
'Cycle through approval modes (Default, Plan, Auto-Edit) with Shift+Tab…',
|
||||||
'Toggle Markdown rendering (raw markdown mode) with Option+M…',
|
'Toggle Markdown rendering (raw markdown mode) with Alt+M…',
|
||||||
'Toggle shell mode by typing ! in an empty prompt…',
|
'Toggle shell mode by typing ! in an empty prompt…',
|
||||||
'Insert a newline with a backslash (\\) followed by Enter…',
|
'Insert a newline with a backslash (\\) followed by Enter…',
|
||||||
'Navigate your prompt history with the Up and Down arrows…',
|
'Navigate your prompt history with the Up and Down arrows…',
|
||||||
|
|||||||
@@ -101,9 +101,9 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -116,9 +116,9 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: true,
|
shift: true,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -127,17 +127,17 @@ describe('KeypressContext', () => {
|
|||||||
{
|
{
|
||||||
modifier: 'Shift',
|
modifier: 'Shift',
|
||||||
sequence: '\x1b[57414;2u',
|
sequence: '\x1b[57414;2u',
|
||||||
expected: { ctrl: false, meta: false, shift: true },
|
expected: { shift: true, ctrl: false, cmd: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
modifier: 'Ctrl',
|
modifier: 'Ctrl',
|
||||||
sequence: '\x1b[57414;5u',
|
sequence: '\x1b[57414;5u',
|
||||||
expected: { ctrl: true, meta: false, shift: false },
|
expected: { shift: false, ctrl: true, cmd: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
modifier: 'Alt',
|
modifier: 'Alt',
|
||||||
sequence: '\x1b[57414;3u',
|
sequence: '\x1b[57414;3u',
|
||||||
expected: { ctrl: false, meta: true, shift: false },
|
expected: { shift: false, alt: true, ctrl: false, cmd: false },
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
'should handle numpad enter with $modifier modifier',
|
'should handle numpad enter with $modifier modifier',
|
||||||
@@ -163,9 +163,9 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'j',
|
name: 'j',
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -178,9 +178,10 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
ctrl: false,
|
|
||||||
meta: true,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -202,7 +203,13 @@ describe('KeypressContext', () => {
|
|||||||
|
|
||||||
act(() => stdin.write('a'));
|
act(() => stdin.write('a'));
|
||||||
expect(keyHandler).toHaveBeenLastCalledWith(
|
expect(keyHandler).toHaveBeenLastCalledWith(
|
||||||
expect.objectContaining({ name: 'a' }),
|
expect.objectContaining({
|
||||||
|
name: 'a',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => stdin.write('\r'));
|
act(() => stdin.write('\r'));
|
||||||
@@ -212,6 +219,10 @@ describe('KeypressContext', () => {
|
|||||||
name: 'return',
|
name: 'return',
|
||||||
sequence: '\r',
|
sequence: '\r',
|
||||||
insertable: true,
|
insertable: true,
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -228,6 +239,10 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenLastCalledWith(
|
expect(keyHandler).toHaveBeenLastCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'return',
|
name: 'return',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -245,6 +260,10 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -266,11 +285,21 @@ describe('KeypressContext', () => {
|
|||||||
|
|
||||||
expect(keyHandler).toHaveBeenNthCalledWith(
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
expect.objectContaining({ name: 'escape', meta: true }),
|
expect.objectContaining({
|
||||||
|
name: 'escape',
|
||||||
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
expect(keyHandler).toHaveBeenNthCalledWith(
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
expect.objectContaining({ name: 'escape', meta: true }),
|
expect.objectContaining({
|
||||||
|
name: 'escape',
|
||||||
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -296,7 +325,9 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
meta: true,
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -318,17 +349,17 @@ describe('KeypressContext', () => {
|
|||||||
{
|
{
|
||||||
name: 'Backspace',
|
name: 'Backspace',
|
||||||
inputSequence: '\x1b[127u',
|
inputSequence: '\x1b[127u',
|
||||||
expected: { name: 'backspace', meta: false },
|
expected: { name: 'backspace', alt: false, cmd: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Option+Backspace',
|
name: 'Alt+Backspace',
|
||||||
inputSequence: '\x1b[127;3u',
|
inputSequence: '\x1b[127;3u',
|
||||||
expected: { name: 'backspace', meta: true },
|
expected: { name: 'backspace', alt: true, cmd: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Ctrl+Backspace',
|
name: 'Ctrl+Backspace',
|
||||||
inputSequence: '\x1b[127;5u',
|
inputSequence: '\x1b[127;5u',
|
||||||
expected: { name: 'backspace', ctrl: true },
|
expected: { name: 'backspace', alt: false, ctrl: true, cmd: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Shift+Space',
|
name: 'Shift+Space',
|
||||||
@@ -612,14 +643,17 @@ describe('KeypressContext', () => {
|
|||||||
{ sequence: `\x1b[27;5;9~`, expected: { name: 'tab', ctrl: true } },
|
{ sequence: `\x1b[27;5;9~`, expected: { name: 'tab', ctrl: true } },
|
||||||
{
|
{
|
||||||
sequence: `\x1b[27;6;9~`,
|
sequence: `\x1b[27;6;9~`,
|
||||||
expected: { name: 'tab', ctrl: true, shift: true },
|
expected: { name: 'tab', shift: true, ctrl: true },
|
||||||
},
|
},
|
||||||
// XTerm Function Key
|
// XTerm Function Key
|
||||||
{ sequence: `\x1b[1;129A`, expected: { name: 'up' } },
|
{ sequence: `\x1b[1;129A`, expected: { name: 'up' } },
|
||||||
{ sequence: `\x1b[1;2H`, expected: { name: 'home', shift: true } },
|
{ sequence: `\x1b[1;2H`, expected: { name: 'home', shift: true } },
|
||||||
{ sequence: `\x1b[1;5F`, expected: { name: 'end', ctrl: true } },
|
{ sequence: `\x1b[1;5F`, expected: { name: 'end', ctrl: true } },
|
||||||
{ sequence: `\x1b[1;1P`, expected: { name: 'f1' } },
|
{ sequence: `\x1b[1;1P`, expected: { name: 'f1' } },
|
||||||
{ sequence: `\x1b[1;3Q`, expected: { name: 'f2', meta: true } },
|
{
|
||||||
|
sequence: `\x1b[1;3Q`,
|
||||||
|
expected: { name: 'f2', alt: true, cmd: false },
|
||||||
|
},
|
||||||
// Tilde Function Keys
|
// Tilde Function Keys
|
||||||
{ sequence: `\x1b[3~`, expected: { name: 'delete' } },
|
{ sequence: `\x1b[3~`, expected: { name: 'delete' } },
|
||||||
{ sequence: `\x1b[5~`, expected: { name: 'pageup' } },
|
{ sequence: `\x1b[5~`, expected: { name: 'pageup' } },
|
||||||
@@ -637,33 +671,75 @@ describe('KeypressContext', () => {
|
|||||||
// Legacy Arrows
|
// Legacy Arrows
|
||||||
{
|
{
|
||||||
sequence: `\x1b[A`,
|
sequence: `\x1b[A`,
|
||||||
expected: { name: 'up', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'up',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: `\x1b[B`,
|
sequence: `\x1b[B`,
|
||||||
expected: { name: 'down', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'down',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: `\x1b[C`,
|
sequence: `\x1b[C`,
|
||||||
expected: { name: 'right', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'right',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: `\x1b[D`,
|
sequence: `\x1b[D`,
|
||||||
expected: { name: 'left', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'left',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Legacy Home/End
|
// Legacy Home/End
|
||||||
{
|
{
|
||||||
sequence: `\x1b[H`,
|
sequence: `\x1b[H`,
|
||||||
expected: { name: 'home', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'home',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: `\x1b[F`,
|
sequence: `\x1b[F`,
|
||||||
expected: { name: 'end', ctrl: false, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'end',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: `\x1b[5H`,
|
sequence: `\x1b[5H`,
|
||||||
expected: { name: 'home', ctrl: true, meta: false, shift: false },
|
expected: {
|
||||||
|
name: 'home',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: true,
|
||||||
|
cmd: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
'should recognize sequence "$sequence" as $expected.name',
|
'should recognize sequence "$sequence" as $expected.name',
|
||||||
@@ -690,11 +766,23 @@ describe('KeypressContext', () => {
|
|||||||
|
|
||||||
expect(keyHandler).toHaveBeenNthCalledWith(
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
expect.objectContaining({ name: 'delete' }),
|
expect.objectContaining({
|
||||||
|
name: 'delete',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
expect(keyHandler).toHaveBeenNthCalledWith(
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
expect.objectContaining({ name: 'delete' }),
|
expect.objectContaining({
|
||||||
|
name: 'delete',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -751,9 +839,10 @@ describe('KeypressContext', () => {
|
|||||||
chunk: `\x1b[${keycode};3u`,
|
chunk: `\x1b[${keycode};3u`,
|
||||||
expected: {
|
expected: {
|
||||||
name: key,
|
name: key,
|
||||||
ctrl: false,
|
|
||||||
meta: true,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (terminal === 'MacTerminal') {
|
} else if (terminal === 'MacTerminal') {
|
||||||
@@ -766,24 +855,26 @@ describe('KeypressContext', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
sequence: `\x1b${key}`,
|
sequence: `\x1b${key}`,
|
||||||
name: key,
|
name: key,
|
||||||
ctrl: false,
|
|
||||||
meta: true,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
||||||
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode but
|
// Note: µ (mu) is sent with alt:false on iTerm2/VSCode but
|
||||||
// gets converted to m with meta:true
|
// gets converted to m with alt:true
|
||||||
return {
|
return {
|
||||||
terminal,
|
terminal,
|
||||||
key,
|
key,
|
||||||
chunk: accentedChar,
|
chunk: accentedChar,
|
||||||
expected: {
|
expected: {
|
||||||
name: key,
|
name: key,
|
||||||
ctrl: false,
|
|
||||||
meta: true, // Always expect meta:true after conversion
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: true, // Always expect alt:true after conversion
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
sequence: accentedChar,
|
sequence: accentedChar,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -825,7 +916,10 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
sequence: '\\',
|
sequence: '\\',
|
||||||
meta: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -858,6 +952,10 @@ describe('KeypressContext', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'undefined',
|
name: 'undefined',
|
||||||
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -876,6 +974,10 @@ describe('KeypressContext', () => {
|
|||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
sequence: '\x1b[m',
|
sequence: '\x1b[m',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1048,6 +1150,10 @@ describe('KeypressContext', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
sequence: 'a',
|
sequence: 'a',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1162,7 +1268,14 @@ describe('KeypressContext', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ name: 'f12', sequence: '\u001b[24~' }),
|
expect.objectContaining({
|
||||||
|
name: 'f12',
|
||||||
|
sequence: '\u001b[24~',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -251,9 +251,10 @@ function bufferPaste(keypressHandler: KeypressHandler): KeypressHandler {
|
|||||||
if (buffer.length > 0) {
|
if (buffer.length > 0) {
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'paste',
|
name: 'paste',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: buffer,
|
sequence: buffer,
|
||||||
});
|
});
|
||||||
@@ -300,9 +301,10 @@ function* emitKeys(
|
|||||||
let escaped = false;
|
let escaped = false;
|
||||||
|
|
||||||
let name = undefined;
|
let name = undefined;
|
||||||
let ctrl = false;
|
|
||||||
let meta = false;
|
|
||||||
let shift = false;
|
let shift = false;
|
||||||
|
let alt = false;
|
||||||
|
let ctrl = false;
|
||||||
|
let cmd = false;
|
||||||
let code = undefined;
|
let code = undefined;
|
||||||
let insertable = false;
|
let insertable = false;
|
||||||
|
|
||||||
@@ -353,9 +355,10 @@ function* emitKeys(
|
|||||||
const decoded = Buffer.from(base64Data, 'base64').toString('utf-8');
|
const decoded = Buffer.from(base64Data, 'base64').toString('utf-8');
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'paste',
|
name: 'paste',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: decoded,
|
sequence: decoded,
|
||||||
});
|
});
|
||||||
@@ -490,9 +493,10 @@ function* emitKeys(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse the key modifier
|
// Parse the key modifier
|
||||||
ctrl = !!(modifier & 4);
|
|
||||||
meta = !!(modifier & 10); // use 10 to catch both alt (2) and meta (8).
|
|
||||||
shift = !!(modifier & 1);
|
shift = !!(modifier & 1);
|
||||||
|
alt = !!(modifier & 2);
|
||||||
|
ctrl = !!(modifier & 4);
|
||||||
|
cmd = !!(modifier & 8);
|
||||||
|
|
||||||
const keyInfo = KEY_INFO_MAP[code];
|
const keyInfo = KEY_INFO_MAP[code];
|
||||||
if (keyInfo) {
|
if (keyInfo) {
|
||||||
@@ -503,13 +507,16 @@ function* emitKeys(
|
|||||||
if (keyInfo.ctrl) {
|
if (keyInfo.ctrl) {
|
||||||
ctrl = true;
|
ctrl = true;
|
||||||
}
|
}
|
||||||
if (name === 'space' && !ctrl && !meta) {
|
if (name === 'space' && !ctrl && !cmd && !alt) {
|
||||||
sequence = ' ';
|
sequence = ' ';
|
||||||
insertable = true;
|
insertable = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
name = 'undefined';
|
name = 'undefined';
|
||||||
if ((ctrl || meta) && (code.endsWith('u') || code.endsWith('~'))) {
|
if (
|
||||||
|
(ctrl || cmd || alt) &&
|
||||||
|
(code.endsWith('u') || code.endsWith('~'))
|
||||||
|
) {
|
||||||
// CSI-u or tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
|
// CSI-u or tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
|
||||||
const codeNumber = parseInt(code.slice(1, -1), 10);
|
const codeNumber = parseInt(code.slice(1, -1), 10);
|
||||||
if (
|
if (
|
||||||
@@ -523,26 +530,26 @@ function* emitKeys(
|
|||||||
} else if (ch === '\r') {
|
} else if (ch === '\r') {
|
||||||
// carriage return
|
// carriage return
|
||||||
name = 'return';
|
name = 'return';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
} else if (escaped && ch === '\n') {
|
} else if (escaped && ch === '\n') {
|
||||||
// Alt+Enter (linefeed), should be consistent with carriage return
|
// Alt+Enter (linefeed), should be consistent with carriage return
|
||||||
name = 'return';
|
name = 'return';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
} else if (ch === '\t') {
|
} else if (ch === '\t') {
|
||||||
// tab
|
// tab
|
||||||
name = 'tab';
|
name = 'tab';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
} else if (ch === '\b' || ch === '\x7f') {
|
} else if (ch === '\b' || ch === '\x7f') {
|
||||||
// backspace or ctrl+h
|
// backspace or ctrl+h
|
||||||
name = 'backspace';
|
name = 'backspace';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
} else if (ch === ESC) {
|
} else if (ch === ESC) {
|
||||||
// escape key
|
// escape key
|
||||||
name = 'escape';
|
name = 'escape';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
} else if (ch === ' ') {
|
} else if (ch === ' ') {
|
||||||
name = 'space';
|
name = 'space';
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
insertable = true;
|
insertable = true;
|
||||||
} else if (!escaped && ch <= '\x1a') {
|
} else if (!escaped && ch <= '\x1a') {
|
||||||
// ctrl+letter
|
// ctrl+letter
|
||||||
@@ -552,29 +559,30 @@ function* emitKeys(
|
|||||||
// Letter, number, shift+letter
|
// Letter, number, shift+letter
|
||||||
name = ch.toLowerCase();
|
name = ch.toLowerCase();
|
||||||
shift = /^[A-Z]$/.exec(ch) !== null;
|
shift = /^[A-Z]$/.exec(ch) !== null;
|
||||||
meta = escaped;
|
alt = escaped;
|
||||||
insertable = true;
|
insertable = true;
|
||||||
} else if (MAC_ALT_KEY_CHARACTER_MAP[ch] && process.platform === 'darwin') {
|
} else if (MAC_ALT_KEY_CHARACTER_MAP[ch] && process.platform === 'darwin') {
|
||||||
name = MAC_ALT_KEY_CHARACTER_MAP[ch];
|
name = MAC_ALT_KEY_CHARACTER_MAP[ch];
|
||||||
meta = true;
|
alt = true;
|
||||||
} else if (sequence === `${ESC}${ESC}`) {
|
} else if (sequence === `${ESC}${ESC}`) {
|
||||||
// Double escape
|
// Double escape
|
||||||
name = 'escape';
|
name = 'escape';
|
||||||
meta = true;
|
alt = true;
|
||||||
|
|
||||||
// Emit first escape key here, then continue processing
|
// Emit first escape key here, then continue processing
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: 'escape',
|
name: 'escape',
|
||||||
ctrl,
|
|
||||||
meta,
|
|
||||||
shift,
|
shift,
|
||||||
|
alt,
|
||||||
|
ctrl,
|
||||||
|
cmd,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: ESC,
|
sequence: ESC,
|
||||||
});
|
});
|
||||||
} else if (escaped) {
|
} else if (escaped) {
|
||||||
// Escape sequence timeout
|
// Escape sequence timeout
|
||||||
name = ch.length ? undefined : 'escape';
|
name = ch.length ? undefined : 'escape';
|
||||||
meta = true;
|
alt = true;
|
||||||
} else {
|
} else {
|
||||||
// Any other character is considered printable.
|
// Any other character is considered printable.
|
||||||
insertable = true;
|
insertable = true;
|
||||||
@@ -586,9 +594,10 @@ function* emitKeys(
|
|||||||
) {
|
) {
|
||||||
keypressHandler({
|
keypressHandler({
|
||||||
name: name || '',
|
name: name || '',
|
||||||
ctrl,
|
|
||||||
meta,
|
|
||||||
shift,
|
shift,
|
||||||
|
alt,
|
||||||
|
ctrl,
|
||||||
|
cmd,
|
||||||
insertable,
|
insertable,
|
||||||
sequence,
|
sequence,
|
||||||
});
|
});
|
||||||
@@ -599,9 +608,10 @@ function* emitKeys(
|
|||||||
|
|
||||||
export interface Key {
|
export interface Key {
|
||||||
name: string;
|
name: string;
|
||||||
ctrl: boolean;
|
|
||||||
meta: boolean;
|
|
||||||
shift: boolean;
|
shift: boolean;
|
||||||
|
alt: boolean;
|
||||||
|
ctrl: boolean;
|
||||||
|
cmd: boolean; // Command/Windows/Super key
|
||||||
insertable: boolean;
|
insertable: boolean;
|
||||||
sequence: string;
|
sequence: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,63 +139,63 @@ describe('MouseContext', () => {
|
|||||||
sequence: '\x1b[<0;10;20M',
|
sequence: '\x1b[<0;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'left-press',
|
name: 'left-press',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<0;10;20m',
|
sequence: '\x1b[<0;10;20m',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'left-release',
|
name: 'left-release',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<2;10;20M',
|
sequence: '\x1b[<2;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'right-press',
|
name: 'right-press',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<1;10;20M',
|
sequence: '\x1b[<1;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'middle-press',
|
name: 'middle-press',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<64;10;20M',
|
sequence: '\x1b[<64;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'scroll-up',
|
name: 'scroll-up',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<65;10;20M',
|
sequence: '\x1b[<65;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'scroll-down',
|
name: 'scroll-down',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<32;10;20M',
|
sequence: '\x1b[<32;10;20M',
|
||||||
expected: {
|
expected: {
|
||||||
name: 'move',
|
name: 'move',
|
||||||
|
shift: false,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,7 +208,7 @@ describe('MouseContext', () => {
|
|||||||
}, // Alt + left press
|
}, // Alt + left press
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<20;10;20M',
|
sequence: '\x1b[<20;10;20M',
|
||||||
expected: { name: 'left-press', ctrl: true, shift: true },
|
expected: { name: 'left-press', shift: true, ctrl: true },
|
||||||
}, // Ctrl + Shift + left press
|
}, // Ctrl + Shift + left press
|
||||||
{
|
{
|
||||||
sequence: '\x1b[<68;10;20M',
|
sequence: '\x1b[<68;10;20M',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function keyToAnsi(key: Key): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it's a simple character, return it.
|
// If it's a simple character, return it.
|
||||||
if (!key.ctrl && !key.meta && key.sequence) {
|
if (!key.ctrl && !key.cmd && key.sequence) {
|
||||||
return key.sequence;
|
return key.sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,8 +314,8 @@ describe('useApprovalModeIndicator', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
capturedUseKeypressHandler({
|
capturedUseKeypressHandler({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
ctrl: true,
|
|
||||||
shift: true,
|
shift: true,
|
||||||
|
ctrl: true,
|
||||||
} as Key);
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -114,7 +114,13 @@ describe(`useKeypress`, () => {
|
|||||||
const key = { name: 'return', sequence: '\x1B\r' };
|
const key = { name: 'return', sequence: '\x1B\r' };
|
||||||
act(() => stdin.write(key.sequence));
|
act(() => stdin.write(key.sequence));
|
||||||
expect(onKeypress).toHaveBeenCalledWith(
|
expect(onKeypress).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ ...key, meta: true }),
|
expect.objectContaining({
|
||||||
|
...key,
|
||||||
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,9 +146,10 @@ describe(`useKeypress`, () => {
|
|||||||
expect(onKeypress).toHaveBeenCalledTimes(1);
|
expect(onKeypress).toHaveBeenCalledTimes(1);
|
||||||
expect(onKeypress).toHaveBeenCalledWith({
|
expect(onKeypress).toHaveBeenCalledWith({
|
||||||
name: 'paste',
|
name: 'paste',
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
sequence: pasteText,
|
sequence: pasteText,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ describe('useSelectionList', () => {
|
|||||||
name,
|
name,
|
||||||
sequence,
|
sequence,
|
||||||
ctrl: options.ctrl ?? false,
|
ctrl: options.ctrl ?? false,
|
||||||
meta: false,
|
cmd: false,
|
||||||
|
alt: false,
|
||||||
shift: options.shift ?? false,
|
shift: options.shift ?? false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
};
|
};
|
||||||
@@ -328,7 +329,8 @@ describe('useSelectionList', () => {
|
|||||||
name,
|
name,
|
||||||
sequence: name,
|
sequence: name,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
cmd: false,
|
||||||
|
alt: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
insertable: true,
|
insertable: true,
|
||||||
};
|
};
|
||||||
@@ -377,7 +379,8 @@ describe('useSelectionList', () => {
|
|||||||
name,
|
name,
|
||||||
sequence: name,
|
sequence: name,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
cmd: false,
|
||||||
|
alt: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ vi.mock('../contexts/VimModeContext.js', () => ({
|
|||||||
const createKey = (partial: Partial<Key>): Key => ({
|
const createKey = (partial: Partial<Key>): Key => ({
|
||||||
name: partial.name || '',
|
name: partial.name || '',
|
||||||
sequence: partial.sequence || '',
|
sequence: partial.sequence || '',
|
||||||
ctrl: partial.ctrl || false,
|
|
||||||
meta: partial.meta || false,
|
|
||||||
shift: partial.shift || false,
|
shift: partial.shift || false,
|
||||||
|
alt: partial.alt || false,
|
||||||
|
ctrl: partial.ctrl || false,
|
||||||
|
cmd: partial.cmd || false,
|
||||||
insertable: partial.insertable || false,
|
insertable: partial.insertable || false,
|
||||||
...partial,
|
...partial,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -280,8 +280,9 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
|||||||
// Special handling for Enter key to allow command submission (lower priority than completion)
|
// Special handling for Enter key to allow command submission (lower priority than completion)
|
||||||
if (
|
if (
|
||||||
normalizedKey.name === 'return' &&
|
normalizedKey.name === 'return' &&
|
||||||
|
!normalizedKey.alt &&
|
||||||
!normalizedKey.ctrl &&
|
!normalizedKey.ctrl &&
|
||||||
!normalizedKey.meta
|
!normalizedKey.cmd
|
||||||
) {
|
) {
|
||||||
if (buffer.text.trim() && onSubmit) {
|
if (buffer.text.trim() && onSubmit) {
|
||||||
// Handle command submission directly
|
// Handle command submission directly
|
||||||
@@ -309,9 +310,10 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
|||||||
(key: Key): Key => ({
|
(key: Key): Key => ({
|
||||||
name: key.name || '',
|
name: key.name || '',
|
||||||
sequence: key.sequence || '',
|
sequence: key.sequence || '',
|
||||||
ctrl: key.ctrl || false,
|
|
||||||
meta: key.meta || false,
|
|
||||||
shift: key.shift || false,
|
shift: key.shift || false,
|
||||||
|
alt: key.alt || false,
|
||||||
|
ctrl: key.ctrl || false,
|
||||||
|
cmd: key.cmd || false,
|
||||||
insertable: key.insertable || false,
|
insertable: key.insertable || false,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import type { Key } from './hooks/useKeypress.js';
|
|||||||
describe('keyMatchers', () => {
|
describe('keyMatchers', () => {
|
||||||
const createKey = (name: string, mods: Partial<Key> = {}): Key => ({
|
const createKey = (name: string, mods: Partial<Key> = {}): Key => ({
|
||||||
name,
|
name,
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
insertable: false,
|
insertable: false,
|
||||||
sequence: name,
|
sequence: name,
|
||||||
...mods,
|
...mods,
|
||||||
@@ -70,8 +71,8 @@ describe('keyMatchers', () => {
|
|||||||
command: Command.MOVE_WORD_LEFT,
|
command: Command.MOVE_WORD_LEFT,
|
||||||
positive: [
|
positive: [
|
||||||
createKey('left', { ctrl: true }),
|
createKey('left', { ctrl: true }),
|
||||||
createKey('left', { meta: true }),
|
createKey('left', { alt: true }),
|
||||||
createKey('b', { meta: true }),
|
createKey('b', { alt: true }),
|
||||||
],
|
],
|
||||||
negative: [createKey('left'), createKey('b', { ctrl: true })],
|
negative: [createKey('left'), createKey('b', { ctrl: true })],
|
||||||
},
|
},
|
||||||
@@ -79,8 +80,8 @@ describe('keyMatchers', () => {
|
|||||||
command: Command.MOVE_WORD_RIGHT,
|
command: Command.MOVE_WORD_RIGHT,
|
||||||
positive: [
|
positive: [
|
||||||
createKey('right', { ctrl: true }),
|
createKey('right', { ctrl: true }),
|
||||||
createKey('right', { meta: true }),
|
createKey('right', { alt: true }),
|
||||||
createKey('f', { meta: true }),
|
createKey('f', { alt: true }),
|
||||||
],
|
],
|
||||||
negative: [createKey('right'), createKey('f', { ctrl: true })],
|
negative: [createKey('right'), createKey('f', { ctrl: true })],
|
||||||
},
|
},
|
||||||
@@ -115,7 +116,7 @@ describe('keyMatchers', () => {
|
|||||||
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', { alt: true }),
|
||||||
createKey('w', { ctrl: true }),
|
createKey('w', { ctrl: true }),
|
||||||
],
|
],
|
||||||
negative: [createKey('backspace'), createKey('delete', { ctrl: true })],
|
negative: [createKey('backspace'), createKey('delete', { ctrl: true })],
|
||||||
@@ -124,19 +125,19 @@ describe('keyMatchers', () => {
|
|||||||
command: Command.DELETE_WORD_FORWARD,
|
command: Command.DELETE_WORD_FORWARD,
|
||||||
positive: [
|
positive: [
|
||||||
createKey('delete', { ctrl: true }),
|
createKey('delete', { ctrl: true }),
|
||||||
createKey('delete', { meta: true }),
|
createKey('delete', { alt: true }),
|
||||||
],
|
],
|
||||||
negative: [createKey('delete'), createKey('backspace', { ctrl: true })],
|
negative: [createKey('delete'), createKey('backspace', { ctrl: true })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.UNDO,
|
command: Command.UNDO,
|
||||||
positive: [createKey('z', { ctrl: true, shift: false })],
|
positive: [createKey('z', { shift: false, ctrl: true })],
|
||||||
negative: [createKey('z'), createKey('z', { ctrl: true, shift: true })],
|
negative: [createKey('z'), createKey('z', { shift: true, ctrl: true })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.REDO,
|
command: Command.REDO,
|
||||||
positive: [createKey('z', { ctrl: true, shift: true })],
|
positive: [createKey('z', { shift: true, ctrl: true })],
|
||||||
negative: [createKey('z'), createKey('z', { ctrl: true, shift: false })],
|
negative: [createKey('z'), createKey('z', { shift: false, ctrl: true })],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Screen control
|
// Screen control
|
||||||
@@ -243,14 +244,16 @@ describe('keyMatchers', () => {
|
|||||||
positive: [createKey('return')],
|
positive: [createKey('return')],
|
||||||
negative: [
|
negative: [
|
||||||
createKey('return', { ctrl: true }),
|
createKey('return', { ctrl: true }),
|
||||||
createKey('return', { meta: true }),
|
createKey('return', { cmd: true }),
|
||||||
|
createKey('return', { alt: true }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.NEWLINE,
|
command: Command.NEWLINE,
|
||||||
positive: [
|
positive: [
|
||||||
createKey('return', { ctrl: true }),
|
createKey('return', { ctrl: true }),
|
||||||
createKey('return', { meta: true }),
|
createKey('return', { cmd: true }),
|
||||||
|
createKey('return', { alt: true }),
|
||||||
],
|
],
|
||||||
negative: [createKey('return'), createKey('n')],
|
negative: [createKey('return'), createKey('n')],
|
||||||
},
|
},
|
||||||
@@ -285,13 +288,13 @@ describe('keyMatchers', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.TOGGLE_MARKDOWN,
|
command: Command.TOGGLE_MARKDOWN,
|
||||||
positive: [createKey('m', { meta: true })],
|
positive: [createKey('m', { alt: true })],
|
||||||
negative: [createKey('m'), createKey('m', { shift: true })],
|
negative: [createKey('m'), createKey('m', { shift: true })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.TOGGLE_COPY_MODE,
|
command: Command.TOGGLE_COPY_MODE,
|
||||||
positive: [createKey('s', { ctrl: true })],
|
positive: [createKey('s', { ctrl: true })],
|
||||||
negative: [createKey('s'), createKey('s', { meta: true })],
|
negative: [createKey('s'), createKey('s', { alt: true })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.QUIT,
|
command: Command.QUIT,
|
||||||
@@ -333,7 +336,7 @@ describe('keyMatchers', () => {
|
|||||||
{
|
{
|
||||||
command: Command.TOGGLE_YOLO,
|
command: Command.TOGGLE_YOLO,
|
||||||
positive: [createKey('y', { ctrl: true })],
|
positive: [createKey('y', { ctrl: true })],
|
||||||
negative: [createKey('y'), createKey('y', { meta: true })],
|
negative: [createKey('y'), createKey('y', { alt: true })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: Command.CYCLE_APPROVAL_MODE,
|
command: Command.CYCLE_APPROVAL_MODE,
|
||||||
@@ -401,13 +404,13 @@ describe('keyMatchers', () => {
|
|||||||
...defaultKeyBindings,
|
...defaultKeyBindings,
|
||||||
[Command.QUIT]: [
|
[Command.QUIT]: [
|
||||||
{ key: 'q', ctrl: true },
|
{ key: 'q', ctrl: true },
|
||||||
{ key: 'q', command: true },
|
{ key: 'q', alt: true },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchers = createKeyMatchers(config);
|
const matchers = createKeyMatchers(config);
|
||||||
expect(matchers[Command.QUIT](createKey('q', { ctrl: true }))).toBe(true);
|
expect(matchers[Command.QUIT](createKey('q', { ctrl: true }))).toBe(true);
|
||||||
expect(matchers[Command.QUIT](createKey('q', { meta: true }))).toBe(true);
|
expect(matchers[Command.QUIT](createKey('q', { alt: true }))).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,28 +13,17 @@ import { Command, defaultKeyBindings } from '../config/keyBindings.js';
|
|||||||
* Pure data-driven matching logic
|
* Pure data-driven matching logic
|
||||||
*/
|
*/
|
||||||
function matchKeyBinding(keyBinding: KeyBinding, key: Key): boolean {
|
function matchKeyBinding(keyBinding: KeyBinding, key: Key): boolean {
|
||||||
if (keyBinding.key !== key.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check modifiers - follow original logic:
|
// Check modifiers - follow original logic:
|
||||||
// undefined = ignore this modifier (original behavior)
|
// undefined = ignore this modifier (original behavior)
|
||||||
// true = modifier must be pressed
|
// true = modifier must be pressed
|
||||||
// false = modifier must NOT be pressed
|
// false = modifier must NOT be pressed
|
||||||
|
return (
|
||||||
if (keyBinding.ctrl !== undefined && key.ctrl !== keyBinding.ctrl) {
|
keyBinding.key === key.name &&
|
||||||
return false;
|
(keyBinding.shift === undefined || key.shift === keyBinding.shift) &&
|
||||||
}
|
(keyBinding.alt === undefined || key.alt === keyBinding.alt) &&
|
||||||
|
(keyBinding.ctrl === undefined || key.ctrl === keyBinding.ctrl) &&
|
||||||
if (keyBinding.shift !== undefined && key.shift !== keyBinding.shift) {
|
(keyBinding.cmd === undefined || key.cmd === keyBinding.cmd)
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (keyBinding.command !== undefined && key.meta !== keyBinding.command) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -154,9 +154,10 @@ function formatBindings(bindings: readonly KeyBinding[]): string[] {
|
|||||||
|
|
||||||
function formatBinding(binding: KeyBinding): string {
|
function formatBinding(binding: KeyBinding): string {
|
||||||
const modifiers: string[] = [];
|
const modifiers: string[] = [];
|
||||||
if (binding.ctrl) modifiers.push('Ctrl');
|
|
||||||
if (binding.command) modifiers.push('Cmd');
|
|
||||||
if (binding.shift) modifiers.push('Shift');
|
if (binding.shift) modifiers.push('Shift');
|
||||||
|
if (binding.alt) modifiers.push('Alt');
|
||||||
|
if (binding.ctrl) modifiers.push('Ctrl');
|
||||||
|
if (binding.cmd) modifiers.push('Cmd');
|
||||||
|
|
||||||
const keyName = formatKeyName(binding.key);
|
const keyName = formatKeyName(binding.key);
|
||||||
if (!keyName) {
|
if (!keyName) {
|
||||||
@@ -167,12 +168,13 @@ function formatBinding(binding: KeyBinding): string {
|
|||||||
let combo = segments.join(' + ');
|
let combo = segments.join(' + ');
|
||||||
|
|
||||||
const restrictions: string[] = [];
|
const restrictions: string[] = [];
|
||||||
if (binding.ctrl === false) restrictions.push('no Ctrl');
|
if (binding.shift === false) restrictions.push('Shift');
|
||||||
if (binding.shift === false) restrictions.push('no Shift');
|
if (binding.alt === false) restrictions.push('Alt');
|
||||||
if (binding.command === false) restrictions.push('no Cmd');
|
if (binding.ctrl === false) restrictions.push('Ctrl');
|
||||||
|
if (binding.cmd === false) restrictions.push('Cmd');
|
||||||
|
|
||||||
if (restrictions.length > 0) {
|
if (restrictions.length > 0) {
|
||||||
combo = `${combo} (${restrictions.join(', ')})`;
|
combo = `${combo} (no ${restrictions.join(', ')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return combo ? `\`${combo}\`` : '';
|
return combo ? `\`${combo}\`` : '';
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('generate-keybindings-doc', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'Submit with Enter if no modifiers are held.',
|
description: 'Submit with Enter if no modifiers are held.',
|
||||||
bindings: [{ key: 'return', ctrl: false, shift: false }],
|
bindings: [{ key: 'return', shift: false, ctrl: false }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -47,7 +47,7 @@ describe('generate-keybindings-doc', () => {
|
|||||||
description: 'Move up through results.',
|
description: 'Move up through results.',
|
||||||
bindings: [
|
bindings: [
|
||||||
{ key: 'up', shift: false },
|
{ key: 'up', shift: false },
|
||||||
{ key: 'p', ctrl: true, shift: false },
|
{ key: 'p', shift: false, ctrl: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -59,7 +59,7 @@ describe('generate-keybindings-doc', () => {
|
|||||||
expect(markdown).toContain('Trigger custom action.');
|
expect(markdown).toContain('Trigger custom action.');
|
||||||
expect(markdown).toContain('`Ctrl + X`');
|
expect(markdown).toContain('`Ctrl + X`');
|
||||||
expect(markdown).toContain('Submit with Enter if no modifiers are held.');
|
expect(markdown).toContain('Submit with Enter if no modifiers are held.');
|
||||||
expect(markdown).toContain('`Enter (no Ctrl, no Shift)`');
|
expect(markdown).toContain('`Enter (no Shift, Ctrl)`');
|
||||||
expect(markdown).toContain('#### Navigation');
|
expect(markdown).toContain('#### Navigation');
|
||||||
expect(markdown).toContain('Move up through results.');
|
expect(markdown).toContain('Move up through results.');
|
||||||
expect(markdown).toContain('`Up Arrow (no Shift)`');
|
expect(markdown).toContain('`Up Arrow (no Shift)`');
|
||||||
|
|||||||
Reference in New Issue
Block a user