summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/cli/src/ui/hooks/vim.test.ts67
-rw-r--r--packages/cli/src/ui/hooks/vim.ts18
2 files changed, 80 insertions, 5 deletions
diff --git a/packages/cli/src/ui/hooks/vim.test.ts b/packages/cli/src/ui/hooks/vim.test.ts
index f939982f..0139119e 100644
--- a/packages/cli/src/ui/hooks/vim.test.ts
+++ b/packages/cli/src/ui/hooks/vim.test.ts
@@ -1203,7 +1203,9 @@ describe('useVim hook', () => {
});
// Press escape to clear pending state
- exitInsertMode(result);
+ act(() => {
+ result.current.handleInput({ name: 'escape' });
+ });
// Now 'w' should just move cursor, not delete
act(() => {
@@ -1215,6 +1217,69 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveWordForward).toHaveBeenCalledWith(1);
});
});
+
+ describe('NORMAL mode escape behavior', () => {
+ it('should pass escape through when no pending operator is active', () => {
+ mockVimContext.vimMode = 'NORMAL';
+ const { result } = renderVimHook();
+
+ const handled = result.current.handleInput({ name: 'escape' });
+
+ expect(handled).toBe(false);
+ });
+
+ it('should handle escape and clear pending operator', () => {
+ mockVimContext.vimMode = 'NORMAL';
+ const { result } = renderVimHook();
+
+ act(() => {
+ result.current.handleInput({ sequence: 'd' });
+ });
+
+ let handled: boolean | undefined;
+ act(() => {
+ handled = result.current.handleInput({ name: 'escape' });
+ });
+
+ expect(handled).toBe(true);
+ });
+ });
+ });
+
+ describe('Shell command pass-through', () => {
+ it('should pass through ctrl+r in INSERT mode', () => {
+ mockVimContext.vimMode = 'INSERT';
+ const { result } = renderVimHook();
+
+ const handled = result.current.handleInput({ name: 'r', ctrl: true });
+
+ expect(handled).toBe(false);
+ });
+
+ it('should pass through ! in INSERT mode when buffer is empty', () => {
+ mockVimContext.vimMode = 'INSERT';
+ const emptyBuffer = createMockBuffer('');
+ const { result } = renderVimHook(emptyBuffer);
+
+ const handled = result.current.handleInput({ sequence: '!' });
+
+ expect(handled).toBe(false);
+ });
+
+ it('should handle ! as input in INSERT mode when buffer is not empty', () => {
+ mockVimContext.vimMode = 'INSERT';
+ const nonEmptyBuffer = createMockBuffer('not empty');
+ const { result } = renderVimHook(nonEmptyBuffer);
+ const key = { sequence: '!', name: '!' };
+
+ act(() => {
+ result.current.handleInput(key);
+ });
+
+ expect(nonEmptyBuffer.handleInput).toHaveBeenCalledWith(
+ expect.objectContaining(key),
+ );
+ });
});
// Line operations (dd, cc) are tested in text-buffer.test.ts
diff --git a/packages/cli/src/ui/hooks/vim.ts b/packages/cli/src/ui/hooks/vim.ts
index cb65e1ee..97b73121 100644
--- a/packages/cli/src/ui/hooks/vim.ts
+++ b/packages/cli/src/ui/hooks/vim.ts
@@ -260,7 +260,8 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
normalizedKey.name === 'tab' ||
(normalizedKey.name === 'return' && !normalizedKey.ctrl) ||
normalizedKey.name === 'up' ||
- normalizedKey.name === 'down'
+ normalizedKey.name === 'down' ||
+ (normalizedKey.ctrl && normalizedKey.name === 'r')
) {
return false; // Let InputPrompt handle completion
}
@@ -270,6 +271,11 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
return false; // Let InputPrompt handle clipboard functionality
}
+ // Let InputPrompt handle shell commands
+ if (normalizedKey.sequence === '!' && buffer.text.length === 0) {
+ return false;
+ }
+
// Special handling for Enter key to allow command submission (lower priority than completion)
if (
normalizedKey.name === 'return' &&
@@ -399,10 +405,14 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
// Handle NORMAL mode
if (state.mode === 'NORMAL') {
- // Handle Escape key in NORMAL mode - clear all pending states
+ // If in NORMAL mode, allow escape to pass through to other handlers
+ // if there's no pending operation.
if (normalizedKey.name === 'escape') {
- dispatch({ type: 'CLEAR_PENDING_STATES' });
- return true; // Handled by vim
+ if (state.pendingOperator) {
+ dispatch({ type: 'CLEAR_PENDING_STATES' });
+ return true; // Handled by vim
+ }
+ return false; // Pass through to other handlers
}
// Handle count input (numbers 1-9, and 0 if count > 0)