diff --git a/docs/cli/keyboard-shortcuts.md b/docs/cli/keyboard-shortcuts.md
index c496b416c5..1c5faccffa 100644
--- a/docs/cli/keyboard-shortcuts.md
+++ b/docs/cli/keyboard-shortcuts.md
@@ -34,7 +34,7 @@ available combinations.
| ------------------------------------------------ | --------------------------------------------------------- |
| Delete from the cursor to the end of the line. | `Ctrl + K` |
| 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. | `Esc` |
| Delete the previous word. | `Ctrl + Backspace`
`Cmd + Backspace`
`Ctrl + W` |
| Delete the next word. | `Ctrl + Delete`
`Cmd + Delete` |
| Delete the character to the left. | `Backspace`
`Ctrl + H` |
@@ -117,7 +117,8 @@ available combinations.
- `!` on an empty prompt: Enter or exit shell mode.
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
mode.
-- `Esc` pressed twice quickly: Browse and rewind previous interactions.
+- `Esc` pressed twice quickly: Clear the input prompt if it is not empty,
+ otherwise browse and rewind previous interactions.
- `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a
single-line input, navigate backward or forward through prompt history.
- `Number keys (1-9, multi-digit)` inside selection dialogs: Jump directly to
diff --git a/packages/cli/src/config/keyBindings.ts b/packages/cli/src/config/keyBindings.ts
index 465225f3b4..36d25a6243 100644
--- a/packages/cli/src/config/keyBindings.ts
+++ b/packages/cli/src/config/keyBindings.ts
@@ -143,7 +143,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
// Editing
[Command.KILL_LINE_RIGHT]: [{ key: 'k', ctrl: true }],
[Command.KILL_LINE_LEFT]: [{ key: 'u', ctrl: true }],
- [Command.CLEAR_INPUT]: [{ key: 'c', ctrl: true }],
+ [Command.CLEAR_INPUT]: [{ key: 'escape' }],
// Added command (meta/alt/option) for mac compatibility
[Command.DELETE_WORD_BACKWARD]: [
{ key: 'backspace', ctrl: true },
diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index 0a801cba87..b38c49df42 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -1253,25 +1253,6 @@ describe('InputPrompt', () => {
unmount();
});
- it('should clear the buffer on Ctrl+C if it has text', async () => {
- await act(async () => {
- props.buffer.setText('some text to clear');
- });
- const { stdin, unmount } = renderWithProviders(, {
- uiActions,
- });
-
- await act(async () => {
- stdin.write('\x03'); // Ctrl+C character
- });
- await waitFor(() => {
- expect(props.buffer.setText).toHaveBeenCalledWith('');
- expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
- });
- expect(props.onSubmit).not.toHaveBeenCalled();
- unmount();
- });
-
it('should NOT clear the buffer on Ctrl+C if it is empty', async () => {
props.buffer.text = '';
const { stdin, unmount } = renderWithProviders(, {
@@ -1874,7 +1855,7 @@ describe('InputPrompt', () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
- it('should clear buffer on Ctrl-C', async () => {
+ it('should NOT clear buffer on Ctrl-C', async () => {
const onEscapePromptChange = vi.fn();
props.onEscapePromptChange = onEscapePromptChange;
props.buffer.setText('text to clear');
@@ -1887,16 +1868,16 @@ describe('InputPrompt', () => {
stdin.write('\x03');
vi.advanceTimersByTime(100);
- expect(props.buffer.setText).toHaveBeenCalledWith('');
- expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
+ expect(props.buffer.setText).not.toHaveBeenCalledWith('');
});
unmount();
});
- it('should submit /rewind on double ESC', async () => {
+ it('should submit /rewind on double ESC when buffer is empty', async () => {
const onEscapePromptChange = vi.fn();
props.onEscapePromptChange = onEscapePromptChange;
- props.buffer.setText('some text');
+ props.buffer.setText('');
+ vi.mocked(props.buffer.setText).mockClear();
const { stdin, unmount } = renderWithProviders(
,
@@ -1911,6 +1892,26 @@ describe('InputPrompt', () => {
unmount();
});
+ it('should clear the buffer on esc esc if it has text', async () => {
+ const onEscapePromptChange = vi.fn();
+ props.onEscapePromptChange = onEscapePromptChange;
+ props.buffer.setText('some text');
+ vi.mocked(props.buffer.setText).mockClear();
+
+ const { stdin, unmount } = renderWithProviders(
+ ,
+ );
+
+ await act(async () => {
+ stdin.write('\x1B\x1B');
+ vi.advanceTimersByTime(100);
+
+ expect(props.buffer.setText).toHaveBeenCalledWith('');
+ expect(props.onSubmit).not.toHaveBeenCalledWith('/rewind');
+ });
+ unmount();
+ });
+
it('should reset escape state on any non-ESC key', async () => {
const onEscapePromptChange = vi.fn();
props.onEscapePromptChange = onEscapePromptChange;
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index f2445d4061..6740302894 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -495,7 +495,7 @@ export const InputPrompt: React.FC = ({
return;
}
- // Handle double ESC for rewind
+ // Handle double ESC
if (escPressCount.current === 0) {
escPressCount.current = 1;
setShowEscapePrompt(true);
@@ -506,9 +506,14 @@ export const InputPrompt: React.FC = ({
resetEscapeState();
}, 500);
} else {
- // Second ESC triggers rewind
+ // Second ESC
resetEscapeState();
- onSubmit('/rewind');
+ if (keyMatchers[Command.CLEAR_INPUT](key) && buffer.text.length > 0) {
+ buffer.setText('');
+ resetCompletionState();
+ } else {
+ onSubmit('/rewind');
+ }
}
return;
}
@@ -790,15 +795,6 @@ export const InputPrompt: React.FC = ({
buffer.move('end');
return;
}
- // Ctrl+C (Clear input)
- if (keyMatchers[Command.CLEAR_INPUT](key)) {
- if (buffer.text.length > 0) {
- buffer.setText('');
- resetCompletionState();
- }
- return;
- }
-
// Kill line commands
if (keyMatchers[Command.KILL_LINE_RIGHT](key)) {
buffer.killLineRight();
diff --git a/packages/cli/src/ui/components/StatusDisplay.tsx b/packages/cli/src/ui/components/StatusDisplay.tsx
index c9e9e414e5..29c29275dd 100644
--- a/packages/cli/src/ui/components/StatusDisplay.tsx
+++ b/packages/cli/src/ui/components/StatusDisplay.tsx
@@ -45,7 +45,12 @@ export const StatusDisplay: React.FC = ({
}
if (uiState.showEscapePrompt) {
- return Press Esc again to rewind.;
+ const isPromptEmpty = uiState.buffer.text.trim().length === 0;
+ return (
+
+ Press Esc again to {isPromptEmpty ? 'rewind' : 'clear prompt'}.
+
+ );
}
if (uiState.queueErrorMessage) {
diff --git a/packages/cli/src/ui/keyMatchers.test.ts b/packages/cli/src/ui/keyMatchers.test.ts
index e8d9da4434..e7d7d4b523 100644
--- a/packages/cli/src/ui/keyMatchers.test.ts
+++ b/packages/cli/src/ui/keyMatchers.test.ts
@@ -96,8 +96,12 @@ describe('keyMatchers', () => {
},
{
command: Command.CLEAR_INPUT,
- positive: [createKey('c', { ctrl: true })],
- negative: [createKey('c'), createKey('k', { ctrl: true })],
+ positive: [createKey('escape')],
+ negative: [
+ createKey('c', { ctrl: true }),
+ createKey('c'),
+ createKey('k', { ctrl: true }),
+ ],
},
{
command: Command.DELETE_CHAR_LEFT,