Skip to content

Commit 692042a

Browse files
feat(Chat): use Ctrl+C to clear dirty Input
1 parent b4ffd45 commit 692042a

5 files changed

Lines changed: 55 additions & 1 deletion

File tree

src/components/Chat/Input.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ import { useRef, useState } from 'react';
55
import { COMMAND, KEY } from '../../constants';
66
import { time } from '../../utils';
77

8+
const { mockExit } = vi.hoisted(() => ({
9+
mockExit: vi.fn(),
10+
}));
11+
12+
vi.mock('ink', async () => ({
13+
...(await vi.importActual('ink')),
14+
useApp: vi.fn(() => ({
15+
exit: mockExit,
16+
})),
17+
}));
18+
819
vi.mock('@inkjs/ui', () => ({
920
TextInput: ({
1021
defaultValue,
@@ -40,6 +51,10 @@ vi.mock('@inkjs/ui', () => ({
4051
return;
4152
}
4253

54+
if (key.ctrl) {
55+
return;
56+
}
57+
4358
if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
4459
return;
4560
}
@@ -177,6 +192,10 @@ vi.mock('./FileSuggestions', () => ({
177192
import { Input } from './Input';
178193

179194
describe('Input', () => {
195+
beforeEach(() => {
196+
mockExit.mockReset();
197+
});
198+
180199
it('renders input prompt', () => {
181200
const { lastFrame } = render(<Input onSubmit={vi.fn()} />);
182201
expect(lastFrame()).toContain('>');
@@ -387,6 +406,26 @@ describe('Input', () => {
387406
expect(lastFrame()).not.toContain('src/components/Chat/Input.tsx');
388407
});
389408

409+
it('clears input on Ctrl+C when input is non-empty', async () => {
410+
const { lastFrame, stdin } = render(<Input onSubmit={vi.fn()} />);
411+
stdin.write('hi');
412+
await time.tick();
413+
expect(lastFrame()).toContain('[value:hi]');
414+
stdin.write(KEY.CTRL_C);
415+
await time.tick();
416+
expect(lastFrame()).not.toContain('[value:hi]');
417+
expect(lastFrame()).toContain(
418+
'[placeholder:Ask anything... (/ commands, @ files)]',
419+
);
420+
});
421+
422+
it('calls exit on Ctrl+C when input is empty', async () => {
423+
const { stdin } = render(<Input onSubmit={vi.fn()} />);
424+
stdin.write(KEY.CTRL_C);
425+
await time.tick();
426+
expect(mockExit).toHaveBeenCalledOnce();
427+
});
428+
390429
it('does not accept input when disabled', async () => {
391430
const onSubmit = vi.fn();
392431
const { lastFrame, stdin } = render(

src/components/Chat/Input.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TextInput } from '@inkjs/ui';
2-
import { Box, Text } from 'ink';
2+
import { Box, Text, useApp, useInput } from 'ink';
33
import { useCallback, useState } from 'react';
44

55
import { COMMAND, UI } from '../../constants';
@@ -17,6 +17,7 @@ function hasActiveMentionQuery(input: string): boolean {
1717
}
1818

1919
export function Input({ isDisabled = false, onSubmit }: Props) {
20+
const { exit } = useApp();
2021
const [input, setInput] = useState('');
2122
const [resetKey, setResetKey] = useState(0);
2223

@@ -58,6 +59,17 @@ export function Input({ isDisabled = false, onSubmit }: Props) {
5859
setResetKey((key) => key + 1);
5960
}, []);
6061

62+
useInput((_input, key) => {
63+
if (key.ctrl && _input === 'c') {
64+
if (input) {
65+
setInput('');
66+
setResetKey((key) => key + 1);
67+
} else {
68+
exit();
69+
}
70+
}
71+
});
72+
6173
const showCommandMenu = input.startsWith('/');
6274
const showFileSuggestions = !showCommandMenu && hasActiveMentionQuery(input);
6375

src/constants/key.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const BACKSPACE = '\x7f';
2+
export const CTRL_C = '\x03';
23
export const DOWN = '\x1B[B';
34
export const ENTER = '\r';
45
export const ESCAPE = '\x1B\x1B';

src/tui.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe('tui', () => {
3535
renderApp();
3636

3737
expect(render).toHaveBeenCalledWith(expect.anything(), {
38+
exitOnCtrlC: false,
3839
incrementalRendering: true,
3940
maxFps: 60,
4041
});

src/tui.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { screen } from './utils';
66
export function renderApp(): void {
77
const tree = <App />;
88
const app = render(tree, {
9+
exitOnCtrlC: false,
910
incrementalRendering: true,
1011
maxFps: 60,
1112
});

0 commit comments

Comments
 (0)