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