mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 22:48:03 +00:00
bug(ux) vim mode fixes. Start in insert mode. Fix bug blocking F12 and ctrl-X in vim mode. (#17938)
This commit is contained in:
@@ -19,7 +19,7 @@ import { SettingsContext } from '../contexts/SettingsContext.js';
|
||||
vi.mock('../contexts/VimModeContext.js', () => ({
|
||||
useVimMode: vi.fn(() => ({
|
||||
vimEnabled: false,
|
||||
vimMode: 'NORMAL',
|
||||
vimMode: 'INSERT',
|
||||
})),
|
||||
}));
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
@@ -54,7 +54,9 @@ vi.mock('./DetailedMessagesDisplay.js', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('./InputPrompt.js', () => ({
|
||||
InputPrompt: () => <Text>InputPrompt</Text>,
|
||||
InputPrompt: ({ placeholder }: { placeholder?: string }) => (
|
||||
<Text>InputPrompt: {placeholder}</Text>
|
||||
),
|
||||
calculatePromptWidths: vi.fn(() => ({
|
||||
inputWidth: 80,
|
||||
suggestionsWidth: 40,
|
||||
@@ -487,4 +489,40 @@ describe('Composer', () => {
|
||||
expect(lastFrame()).not.toContain('DetailedMessagesDisplay');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vim Mode Placeholders', () => {
|
||||
it('shows correct placeholder in INSERT mode', async () => {
|
||||
const uiState = createMockUIState({ isInputActive: true });
|
||||
const { useVimMode } = await import('../contexts/VimModeContext.js');
|
||||
vi.mocked(useVimMode).mockReturnValue({
|
||||
vimEnabled: true,
|
||||
vimMode: 'INSERT',
|
||||
toggleVimEnabled: vi.fn(),
|
||||
setVimMode: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toContain(
|
||||
"InputPrompt: Press 'Esc' for NORMAL mode.",
|
||||
);
|
||||
});
|
||||
|
||||
it('shows correct placeholder in NORMAL mode', async () => {
|
||||
const uiState = createMockUIState({ isInputActive: true });
|
||||
const { useVimMode } = await import('../contexts/VimModeContext.js');
|
||||
vi.mocked(useVimMode).mockReturnValue({
|
||||
vimEnabled: true,
|
||||
vimMode: 'NORMAL',
|
||||
toggleVimEnabled: vi.fn(),
|
||||
setVimMode: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toContain(
|
||||
"InputPrompt: Press 'i' for INSERT mode.",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
const uiState = useUIState();
|
||||
const uiActions = useUIActions();
|
||||
const { vimEnabled } = useVimMode();
|
||||
const { vimEnabled, vimMode } = useVimMode();
|
||||
const terminalWidth = process.stdout.columns;
|
||||
const isNarrow = isNarrowWidth(terminalWidth);
|
||||
const debugConsoleMaxHeight = Math.floor(Math.max(terminalWidth * 0.2, 5));
|
||||
@@ -143,7 +143,9 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
popAllMessages={uiActions.popAllMessages}
|
||||
placeholder={
|
||||
vimEnabled
|
||||
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
|
||||
? vimMode === 'INSERT'
|
||||
? " Press 'Esc' for NORMAL mode."
|
||||
: " Press 'i' for INSERT mode."
|
||||
: uiState.shellModeActive
|
||||
? ' Type your shell command'
|
||||
: ' Type your message or @path/to/file'
|
||||
|
||||
@@ -1418,7 +1418,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'h',
|
||||
shift: false,
|
||||
@@ -1427,9 +1427,9 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: 'h',
|
||||
}),
|
||||
);
|
||||
act(() =>
|
||||
});
|
||||
});
|
||||
void act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'i',
|
||||
shift: false,
|
||||
@@ -1447,7 +1447,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
shift: false,
|
||||
@@ -1456,8 +1456,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).lines).toEqual(['', '']);
|
||||
});
|
||||
|
||||
@@ -1465,7 +1465,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'j',
|
||||
shift: false,
|
||||
@@ -1474,8 +1474,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\n',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).lines).toEqual(['', '']);
|
||||
});
|
||||
|
||||
@@ -1483,7 +1483,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
shift: false,
|
||||
@@ -1492,8 +1492,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\t',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).text).toBe('');
|
||||
});
|
||||
|
||||
@@ -1501,7 +1501,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
shift: true,
|
||||
@@ -1510,8 +1510,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\u001b[9;2u',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).text).toBe('');
|
||||
});
|
||||
|
||||
@@ -1524,7 +1524,7 @@ describe('useTextBuffer', () => {
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end'));
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'backspace',
|
||||
shift: false,
|
||||
@@ -1533,8 +1533,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x7f',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).text).toBe('');
|
||||
});
|
||||
|
||||
@@ -1627,7 +1627,7 @@ describe('useTextBuffer', () => {
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor [0,2]
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'left',
|
||||
shift: false,
|
||||
@@ -1636,10 +1636,10 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x1b[D',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).cursor).toEqual([0, 1]);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'right',
|
||||
shift: false,
|
||||
@@ -1648,8 +1648,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x1b[C',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
@@ -1659,7 +1659,7 @@ describe('useTextBuffer', () => {
|
||||
);
|
||||
const textWithAnsi = '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m';
|
||||
// Simulate pasting by calling handleInput with a string longer than 1 char
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
shift: false,
|
||||
@@ -1668,8 +1668,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: textWithAnsi,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).text).toBe('Hello World');
|
||||
});
|
||||
|
||||
@@ -1677,7 +1677,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
shift: true,
|
||||
@@ -1686,8 +1686,8 @@ describe('useTextBuffer', () => {
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
); // Simulates Shift+Enter in VSCode terminal
|
||||
});
|
||||
}); // Simulates Shift+Enter in VSCode terminal
|
||||
expect(getBufferState(result).lines).toEqual(['', '']);
|
||||
});
|
||||
|
||||
@@ -1927,7 +1927,9 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
act(() => result.current.handleInput(createInput(input)));
|
||||
act(() => {
|
||||
result.current.handleInput(createInput(input));
|
||||
});
|
||||
expect(getBufferState(result).text).toBe(expected);
|
||||
});
|
||||
|
||||
@@ -1936,7 +1938,9 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const validText = 'Hello World\nThis is a test.';
|
||||
act(() => result.current.handleInput(createInput(validText)));
|
||||
act(() => {
|
||||
result.current.handleInput(createInput(validText));
|
||||
});
|
||||
expect(getBufferState(result).text).toBe(validText);
|
||||
});
|
||||
|
||||
@@ -1950,7 +1954,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
|
||||
expect(largeTextWithUnsafe.length).toBeGreaterThan(5000);
|
||||
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
shift: false,
|
||||
@@ -1959,8 +1963,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: largeTextWithUnsafe,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const resultText = getBufferState(result).text;
|
||||
expect(resultText).not.toContain('\x07');
|
||||
@@ -1985,7 +1989,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
|
||||
expect(largeTextWithAnsi.length).toBeGreaterThan(5000);
|
||||
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
shift: false,
|
||||
@@ -1994,8 +1998,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: largeTextWithAnsi,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const resultText = getBufferState(result).text;
|
||||
expect(resultText).not.toContain('\x1B[31m');
|
||||
@@ -2010,7 +2014,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const emojis = '🐍🐳🦀🦄';
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
shift: false,
|
||||
@@ -2019,8 +2023,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: emojis,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).text).toBe(emojis);
|
||||
});
|
||||
});
|
||||
@@ -2202,7 +2206,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
shift: false,
|
||||
@@ -2211,8 +2215,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).lines).toEqual(['']);
|
||||
});
|
||||
|
||||
@@ -2224,7 +2228,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
act(() =>
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'f1',
|
||||
shift: false,
|
||||
@@ -2233,8 +2237,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\u001bOP',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(getBufferState(result).lines).toEqual(['']);
|
||||
});
|
||||
|
||||
|
||||
@@ -3419,7 +3419,7 @@ export interface TextBuffer {
|
||||
/**
|
||||
* High level "handleInput" – receives what Ink gives us.
|
||||
*/
|
||||
handleInput: (key: Key) => void;
|
||||
handleInput: (key: Key) => boolean;
|
||||
/**
|
||||
* Opens the current buffer contents in the user's preferred terminal text
|
||||
* editor ($VISUAL or $EDITOR, falling back to "vi"). The method blocks
|
||||
|
||||
@@ -34,26 +34,24 @@ export const VimModeProvider = ({
|
||||
}) => {
|
||||
const initialVimEnabled = settings.merged.general.vimMode;
|
||||
const [vimEnabled, setVimEnabled] = useState(initialVimEnabled);
|
||||
const [vimMode, setVimMode] = useState<VimMode>(
|
||||
initialVimEnabled ? 'NORMAL' : 'INSERT',
|
||||
);
|
||||
const [vimMode, setVimMode] = useState<VimMode>('INSERT');
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize vimEnabled from settings on mount
|
||||
const enabled = settings.merged.general.vimMode;
|
||||
setVimEnabled(enabled);
|
||||
// When vim mode is enabled, always start in NORMAL mode
|
||||
// When vim mode is enabled, start in INSERT mode
|
||||
if (enabled) {
|
||||
setVimMode('NORMAL');
|
||||
setVimMode('INSERT');
|
||||
}
|
||||
}, [settings.merged.general.vimMode]);
|
||||
|
||||
const toggleVimEnabled = useCallback(async () => {
|
||||
const newValue = !vimEnabled;
|
||||
setVimEnabled(newValue);
|
||||
// When enabling vim mode, start in NORMAL mode
|
||||
// When enabling vim mode, start in INSERT mode
|
||||
if (newValue) {
|
||||
setVimMode('NORMAL');
|
||||
setVimMode('INSERT');
|
||||
}
|
||||
settings.setValue(SettingScope.User, 'general.vimMode', newValue);
|
||||
return newValue;
|
||||
|
||||
85
packages/cli/src/ui/hooks/vim-passthrough.test.tsx
Normal file
85
packages/cli/src/ui/hooks/vim-passthrough.test.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { act } from 'react';
|
||||
import { useVim } from './vim.js';
|
||||
import type { VimMode } from './vim.js';
|
||||
import type { TextBuffer } from '../components/shared/text-buffer.js';
|
||||
import type { Key } from './useKeypress.js';
|
||||
|
||||
// Mock the VimModeContext
|
||||
const mockVimContext = {
|
||||
vimEnabled: true,
|
||||
vimMode: 'INSERT' as VimMode,
|
||||
toggleVimEnabled: vi.fn(),
|
||||
setVimMode: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock('../contexts/VimModeContext.js', () => ({
|
||||
useVimMode: () => mockVimContext,
|
||||
VimModeProvider: ({ children }: { children: React.ReactNode }) => children,
|
||||
}));
|
||||
|
||||
const createKey = (partial: Partial<Key>): Key => ({
|
||||
name: partial.name || '',
|
||||
sequence: partial.sequence || '',
|
||||
shift: partial.shift || false,
|
||||
alt: partial.alt || false,
|
||||
ctrl: partial.ctrl || false,
|
||||
cmd: partial.cmd || false,
|
||||
insertable: partial.insertable || false,
|
||||
...partial,
|
||||
});
|
||||
|
||||
describe('useVim passthrough', () => {
|
||||
let mockBuffer: Partial<TextBuffer>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockBuffer = {
|
||||
text: 'hello',
|
||||
handleInput: vi.fn().mockReturnValue(false),
|
||||
vimEscapeInsertMode: vi.fn(),
|
||||
setText: vi.fn(),
|
||||
};
|
||||
mockVimContext.vimEnabled = true;
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
mode: 'INSERT' as VimMode,
|
||||
name: 'F12',
|
||||
key: createKey({ name: 'f12', sequence: '\u001b[24~' }),
|
||||
},
|
||||
{
|
||||
mode: 'INSERT' as VimMode,
|
||||
name: 'Ctrl-X',
|
||||
key: createKey({ name: 'x', ctrl: true, sequence: '\x18' }),
|
||||
},
|
||||
{
|
||||
mode: 'NORMAL' as VimMode,
|
||||
name: 'F12',
|
||||
key: createKey({ name: 'f12', sequence: '\u001b[24~' }),
|
||||
},
|
||||
{
|
||||
mode: 'NORMAL' as VimMode,
|
||||
name: 'Ctrl-X',
|
||||
key: createKey({ name: 'x', ctrl: true, sequence: '\x18' }),
|
||||
},
|
||||
])('should pass through $name in $mode mode', ({ mode, key }) => {
|
||||
mockVimContext.vimMode = mode;
|
||||
const { result } = renderHook(() => useVim(mockBuffer as TextBuffer));
|
||||
|
||||
let handled = true;
|
||||
act(() => {
|
||||
handled = result.current.handleInput(key);
|
||||
});
|
||||
|
||||
expect(handled).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ import { textBufferReducer } from '../components/shared/text-buffer.js';
|
||||
// Mock the VimModeContext
|
||||
const mockVimContext = {
|
||||
vimEnabled: true,
|
||||
vimMode: 'NORMAL' as VimMode,
|
||||
vimMode: 'INSERT' as VimMode,
|
||||
toggleVimEnabled: vi.fn(),
|
||||
setVimMode: vi.fn(),
|
||||
};
|
||||
@@ -91,6 +91,8 @@ const TEST_SEQUENCES = {
|
||||
LINE_END: createKey({ sequence: '$' }),
|
||||
REPEAT: createKey({ sequence: '.' }),
|
||||
CTRL_C: createKey({ sequence: '\x03', name: 'c', ctrl: true }),
|
||||
CTRL_X: createKey({ sequence: '\x18', name: 'x', ctrl: true }),
|
||||
F12: createKey({ sequence: '\u001b[24~', name: 'f12' }),
|
||||
} as const;
|
||||
|
||||
describe('useVim hook', () => {
|
||||
@@ -134,6 +136,7 @@ describe('useVim hook', () => {
|
||||
replaceRangeByOffset: vi.fn(),
|
||||
handleInput: vi.fn(),
|
||||
setText: vi.fn(),
|
||||
openInExternalEditor: vi.fn(),
|
||||
// Vim-specific methods
|
||||
vimDeleteWordForward: vi.fn(),
|
||||
vimDeleteWordBackward: vi.fn(),
|
||||
@@ -207,20 +210,23 @@ describe('useVim hook', () => {
|
||||
mockBuffer = createMockBuffer();
|
||||
// Reset mock context to default state
|
||||
mockVimContext.vimEnabled = true;
|
||||
mockVimContext.vimMode = 'NORMAL';
|
||||
mockVimContext.vimMode = 'INSERT';
|
||||
mockVimContext.toggleVimEnabled.mockClear();
|
||||
mockVimContext.setVimMode.mockClear();
|
||||
});
|
||||
|
||||
describe('Mode switching', () => {
|
||||
it('should start in NORMAL mode', () => {
|
||||
it('should start in INSERT mode', () => {
|
||||
const { result } = renderVimHook();
|
||||
expect(result.current.mode).toBe('NORMAL');
|
||||
expect(result.current.mode).toBe('INSERT');
|
||||
});
|
||||
|
||||
it('should switch to INSERT mode with i command', () => {
|
||||
const { result } = renderVimHook();
|
||||
|
||||
exitInsertMode(result);
|
||||
expect(result.current.mode).toBe('NORMAL');
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(TEST_SEQUENCES.INSERT);
|
||||
});
|
||||
@@ -266,6 +272,7 @@ describe('useVim hook', () => {
|
||||
describe('Navigation commands', () => {
|
||||
it('should handle h (left movement)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'h' }));
|
||||
@@ -276,6 +283,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle l (right movement)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'l' }));
|
||||
@@ -287,6 +295,7 @@ describe('useVim hook', () => {
|
||||
it('should handle j (down movement)', () => {
|
||||
const testBuffer = createMockBuffer('first line\nsecond line');
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'j' }));
|
||||
@@ -298,6 +307,7 @@ describe('useVim hook', () => {
|
||||
it('should handle k (up movement)', () => {
|
||||
const testBuffer = createMockBuffer('first line\nsecond line');
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'k' }));
|
||||
@@ -308,6 +318,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle 0 (move to start of line)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '0' }));
|
||||
@@ -318,6 +329,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle $ (move to end of line)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '$' }));
|
||||
@@ -330,6 +342,7 @@ describe('useVim hook', () => {
|
||||
describe('Mode switching commands', () => {
|
||||
it('should handle a (append after cursor)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'a' }));
|
||||
@@ -341,6 +354,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle A (append at end of line)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'A' }));
|
||||
@@ -352,6 +366,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle o (open line below)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'o' }));
|
||||
@@ -363,6 +378,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle O (open line above)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'O' }));
|
||||
@@ -376,6 +392,7 @@ describe('useVim hook', () => {
|
||||
describe('Edit commands', () => {
|
||||
it('should handle x (delete character)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
vi.clearAllMocks();
|
||||
|
||||
act(() => {
|
||||
@@ -388,6 +405,7 @@ describe('useVim hook', () => {
|
||||
it('should move cursor left when deleting last character on line (vim behavior)', () => {
|
||||
const testBuffer = createMockBuffer('hello', [0, 4]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'x' }));
|
||||
@@ -398,6 +416,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle first d key (sets pending state)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'd' }));
|
||||
@@ -410,6 +429,7 @@ describe('useVim hook', () => {
|
||||
describe('Count handling', () => {
|
||||
it('should handle count input and return to count 0 after command', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
const handled = result.current.handleInput(
|
||||
@@ -431,6 +451,7 @@ describe('useVim hook', () => {
|
||||
it('should only delete 1 character with x command when no count is specified', () => {
|
||||
const testBuffer = createMockBuffer();
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'x' }));
|
||||
@@ -446,7 +467,7 @@ describe('useVim hook', () => {
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
|
||||
expect(result.current.vimModeEnabled).toBe(true);
|
||||
expect(result.current.mode).toBe('NORMAL');
|
||||
expect(result.current.mode).toBe('INSERT');
|
||||
expect(result.current.handleInput).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -458,7 +479,7 @@ describe('useVim hook', () => {
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
|
||||
expect(result.current.vimModeEnabled).toBe(true);
|
||||
expect(result.current.mode).toBe('NORMAL');
|
||||
expect(result.current.mode).toBe('INSERT');
|
||||
expect(result.current.handleInput).toBeDefined();
|
||||
expect(testBuffer.replaceRangeByOffset).toBeDefined();
|
||||
expect(testBuffer.moveToOffset).toBeDefined();
|
||||
@@ -467,6 +488,7 @@ describe('useVim hook', () => {
|
||||
it('should handle w (next word)', () => {
|
||||
const testBuffer = createMockBuffer('hello world test');
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'w' }));
|
||||
@@ -478,6 +500,7 @@ describe('useVim hook', () => {
|
||||
it('should handle b (previous word)', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 6]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'b' }));
|
||||
@@ -489,6 +512,7 @@ describe('useVim hook', () => {
|
||||
it('should handle e (end of word)', () => {
|
||||
const testBuffer = createMockBuffer('hello world test');
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'e' }));
|
||||
@@ -500,6 +524,7 @@ describe('useVim hook', () => {
|
||||
it('should handle w when cursor is on the last word', () => {
|
||||
const testBuffer = createMockBuffer('hello world', [0, 8]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'w' }));
|
||||
@@ -510,6 +535,7 @@ describe('useVim hook', () => {
|
||||
|
||||
it('should handle first c key (sets pending change state)', () => {
|
||||
const { result } = renderVimHook();
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -563,6 +589,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat x command from current cursor position', () => {
|
||||
const testBuffer = createMockBuffer('abcd\nefgh\nijkl', [0, 1]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'x' }));
|
||||
@@ -580,6 +607,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat dd command from current position', () => {
|
||||
const testBuffer = createMockBuffer('line1\nline2\nline3', [1, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'd' }));
|
||||
@@ -601,6 +629,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat ce command from current position', () => {
|
||||
const testBuffer = createMockBuffer('word', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -625,6 +654,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat cc command from current position', () => {
|
||||
const testBuffer = createMockBuffer('line1\nline2\nline3', [1, 2]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -649,6 +679,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat cw command from current position', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 6]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -673,6 +704,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat D command from current position', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 6]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'D' }));
|
||||
@@ -692,6 +724,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat C command from current position', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 6]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'C' }));
|
||||
@@ -713,6 +746,7 @@ describe('useVim hook', () => {
|
||||
it('should repeat command after cursor movement', () => {
|
||||
const testBuffer = createMockBuffer('test text', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'x' }));
|
||||
@@ -728,8 +762,10 @@ describe('useVim hook', () => {
|
||||
});
|
||||
|
||||
it('should move cursor to the correct position after exiting INSERT mode with "a"', () => {
|
||||
const testBuffer = createMockBuffer('hello world', [0, 10]);
|
||||
const testBuffer = createMockBuffer('hello world', [0, 11]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
expect(testBuffer.cursor).toEqual([0, 10]);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'a' }));
|
||||
@@ -747,6 +783,7 @@ describe('useVim hook', () => {
|
||||
it('should handle ^ (move to first non-whitespace character)', () => {
|
||||
const testBuffer = createMockBuffer(' hello world', [0, 5]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '^' }));
|
||||
@@ -758,6 +795,7 @@ describe('useVim hook', () => {
|
||||
it('should handle G without count (go to last line)', () => {
|
||||
const testBuffer = createMockBuffer('line1\nline2\nline3', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'G' }));
|
||||
@@ -769,6 +807,7 @@ describe('useVim hook', () => {
|
||||
it('should handle gg (go to first line)', () => {
|
||||
const testBuffer = createMockBuffer('line1\nline2\nline3', [2, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// First 'g' sets pending state
|
||||
act(() => {
|
||||
@@ -786,6 +825,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with movement commands', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '3' }));
|
||||
@@ -804,6 +844,7 @@ describe('useVim hook', () => {
|
||||
it('should delete from cursor to start of next word', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'd' }));
|
||||
@@ -888,6 +929,7 @@ describe('useVim hook', () => {
|
||||
it('should delete multiple words with count', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '2' }));
|
||||
@@ -905,6 +947,7 @@ describe('useVim hook', () => {
|
||||
it('should record command for repeat with dot', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Execute dw
|
||||
act(() => {
|
||||
@@ -929,6 +972,7 @@ describe('useVim hook', () => {
|
||||
it('should delete from cursor to end of current word', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 1]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'd' }));
|
||||
@@ -943,6 +987,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with de', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '3' }));
|
||||
@@ -962,6 +1007,7 @@ describe('useVim hook', () => {
|
||||
it('should change from cursor to start of next word and enter INSERT mode', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -978,6 +1024,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with cw', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '2' }));
|
||||
@@ -996,6 +1043,7 @@ describe('useVim hook', () => {
|
||||
it('should be repeatable with dot', () => {
|
||||
const testBuffer = createMockBuffer('hello world test more', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Execute cw
|
||||
act(() => {
|
||||
@@ -1025,6 +1073,7 @@ describe('useVim hook', () => {
|
||||
it('should change from cursor to end of word and enter INSERT mode', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 1]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -1040,6 +1089,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with ce', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '2' }));
|
||||
@@ -1060,6 +1110,7 @@ describe('useVim hook', () => {
|
||||
it('should change entire line and enter INSERT mode', () => {
|
||||
const testBuffer = createMockBuffer('hello world\nsecond line', [0, 5]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -1078,6 +1129,7 @@ describe('useVim hook', () => {
|
||||
[1, 0],
|
||||
);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '3' }));
|
||||
@@ -1096,6 +1148,7 @@ describe('useVim hook', () => {
|
||||
it('should be repeatable with dot', () => {
|
||||
const testBuffer = createMockBuffer('line1\nline2\nline3', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Execute cc
|
||||
act(() => {
|
||||
@@ -1125,6 +1178,7 @@ describe('useVim hook', () => {
|
||||
it('should delete from cursor to start of previous word', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 11]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'd' }));
|
||||
@@ -1139,6 +1193,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with db', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 18]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '2' }));
|
||||
@@ -1158,6 +1213,7 @@ describe('useVim hook', () => {
|
||||
it('should change from cursor to start of previous word and enter INSERT mode', () => {
|
||||
const testBuffer = createMockBuffer('hello world test', [0, 11]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: 'c' }));
|
||||
@@ -1173,6 +1229,7 @@ describe('useVim hook', () => {
|
||||
it('should handle count with cb', () => {
|
||||
const testBuffer = createMockBuffer('one two three four', [0, 18]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInput(createKey({ sequence: '3' }));
|
||||
@@ -1193,6 +1250,7 @@ describe('useVim hook', () => {
|
||||
it('should clear pending delete state after dw', () => {
|
||||
const testBuffer = createMockBuffer('hello world', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Press 'd' to enter pending delete state
|
||||
act(() => {
|
||||
@@ -1220,6 +1278,7 @@ describe('useVim hook', () => {
|
||||
it('should clear pending change state after cw', () => {
|
||||
const testBuffer = createMockBuffer('hello world', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Execute cw
|
||||
act(() => {
|
||||
@@ -1246,6 +1305,7 @@ describe('useVim hook', () => {
|
||||
it('should clear pending state with escape', () => {
|
||||
const testBuffer = createMockBuffer('hello world', [0, 0]);
|
||||
const { result } = renderVimHook(testBuffer);
|
||||
exitInsertMode(result);
|
||||
|
||||
// Enter pending delete state
|
||||
act(() => {
|
||||
@@ -1621,7 +1681,7 @@ describe('useVim hook', () => {
|
||||
beforeEach(() => {
|
||||
mockBuffer = createMockBuffer('hello world');
|
||||
mockVimContext.vimEnabled = true;
|
||||
mockVimContext.vimMode = 'NORMAL';
|
||||
mockVimContext.vimMode = 'INSERT';
|
||||
mockHandleFinalSubmit = vi.fn();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
@@ -1634,6 +1694,11 @@ describe('useVim hook', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
exitInsertMode(result);
|
||||
// Wait to clear escape history
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
// First escape - should pass through (return false)
|
||||
let handled: boolean;
|
||||
@@ -1651,7 +1716,6 @@ describe('useVim hook', () => {
|
||||
});
|
||||
|
||||
it('should clear buffer on double-escape in INSERT mode', async () => {
|
||||
mockVimContext.vimMode = 'INSERT';
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
@@ -1676,6 +1740,11 @@ describe('useVim hook', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
exitInsertMode(result);
|
||||
// Wait to clear escape history
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
// First escape
|
||||
await act(async () => {
|
||||
@@ -1701,6 +1770,11 @@ describe('useVim hook', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
exitInsertMode(result);
|
||||
// Wait to clear escape history
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
// First escape
|
||||
await act(async () => {
|
||||
@@ -1730,6 +1804,7 @@ describe('useVim hook', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
exitInsertMode(result);
|
||||
|
||||
let handled: boolean;
|
||||
await act(async () => {
|
||||
@@ -1740,7 +1815,6 @@ describe('useVim hook', () => {
|
||||
});
|
||||
|
||||
it('should pass Ctrl+C through to InputPrompt in INSERT mode', async () => {
|
||||
mockVimContext.vimMode = 'INSERT';
|
||||
const { result } = renderHook(() =>
|
||||
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ type VimAction =
|
||||
| { type: 'ESCAPE_TO_NORMAL' };
|
||||
|
||||
const initialVimState: VimState = {
|
||||
mode: 'NORMAL',
|
||||
mode: 'INSERT',
|
||||
count: 0,
|
||||
pendingOperator: null,
|
||||
lastCommand: null,
|
||||
@@ -312,9 +312,7 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
return true; // Handled by vim (even if no onSubmit callback)
|
||||
}
|
||||
|
||||
// useKeypress already provides the correct format for TextBuffer
|
||||
buffer.handleInput(normalizedKey);
|
||||
return true; // Handled by vim
|
||||
return buffer.handleInput(normalizedKey);
|
||||
},
|
||||
[buffer, dispatch, updateMode, onSubmit, checkDoubleEscape],
|
||||
);
|
||||
@@ -784,7 +782,9 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
|
||||
// Unknown command, clear count and pending states
|
||||
dispatch({ type: 'CLEAR_PENDING_STATES' });
|
||||
return true; // Still handled by vim to prevent other handlers
|
||||
|
||||
// Not handled by vim so allow other handlers to process it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user