feat(tui): single ctrl+c clears input, double ctrl+c exits
This commit is contained in:
@@ -38,15 +38,18 @@ function minimalTuiPrivates(value: MinimalTui): {
|
||||
handleToolEvent: (event: unknown) => void;
|
||||
handleCommand: (command: unknown) => Promise<void>;
|
||||
handleEscapeAction: () => boolean;
|
||||
handleCtrlCPress: (nowMs?: number) => boolean;
|
||||
prompt: (text: string) => Promise<string>;
|
||||
rl: {
|
||||
once: (event: string, cb: () => void) => void;
|
||||
removeListener: (event: string, cb: () => void) => void;
|
||||
question: (text: string, cb: (answer: string) => void) => void;
|
||||
write: (data: string | null, key?: { ctrl?: boolean; name?: string }) => void;
|
||||
prompt: () => void;
|
||||
};
|
||||
activePromptCancel: (() => void) | null;
|
||||
activeOperationCancel: (() => void) | null;
|
||||
running: boolean;
|
||||
} {
|
||||
return value as unknown as {
|
||||
handleBackendCommand: (provider: string) => Promise<void>;
|
||||
@@ -56,15 +59,18 @@ function minimalTuiPrivates(value: MinimalTui): {
|
||||
handleToolEvent: (event: unknown) => void;
|
||||
handleCommand: (command: unknown) => Promise<void>;
|
||||
handleEscapeAction: () => boolean;
|
||||
handleCtrlCPress: (nowMs?: number) => boolean;
|
||||
prompt: (text: string) => Promise<string>;
|
||||
rl: {
|
||||
once: (event: string, cb: () => void) => void;
|
||||
removeListener: (event: string, cb: () => void) => void;
|
||||
question: (text: string, cb: (answer: string) => void) => void;
|
||||
write: (data: string | null, key?: { ctrl?: boolean; name?: string }) => void;
|
||||
prompt: () => void;
|
||||
};
|
||||
activePromptCancel: (() => void) | null;
|
||||
activeOperationCancel: (() => void) | null;
|
||||
running: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -423,6 +429,7 @@ describe('MinimalTui prompt cancellation', () => {
|
||||
onAnswer = cb;
|
||||
}),
|
||||
write,
|
||||
prompt: vi.fn(),
|
||||
};
|
||||
|
||||
const promptPromise = minimalTuiPrivates(tui).prompt('Confirm? ');
|
||||
@@ -457,4 +464,44 @@ describe('MinimalTui prompt cancellation', () => {
|
||||
expect(minimalTuiPrivates(tui).handleEscapeAction()).toBe(true);
|
||||
expect(cancelRunningOperation).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('treats first Ctrl+C as clear-input and second as exit intent', () => {
|
||||
const mockSession = {
|
||||
id: 'test',
|
||||
getHistory: () => [],
|
||||
addMessage: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
replaceHistory: vi.fn(),
|
||||
};
|
||||
|
||||
const write = vi.fn();
|
||||
const prompt = vi.fn();
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
try {
|
||||
const tui = new MinimalTui({
|
||||
session: asSession(mockSession),
|
||||
modelClient: asRouter({}),
|
||||
systemPrompt: 'test',
|
||||
});
|
||||
minimalTuiPrivates(tui).rl = {
|
||||
once: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
question: vi.fn(),
|
||||
write,
|
||||
prompt,
|
||||
};
|
||||
minimalTuiPrivates(tui).running = true;
|
||||
|
||||
const first = minimalTuiPrivates(tui).handleCtrlCPress(1000);
|
||||
const second = minimalTuiPrivates(tui).handleCtrlCPress(2000);
|
||||
|
||||
expect(first).toBe(false);
|
||||
expect(second).toBe(true);
|
||||
expect(write).toHaveBeenCalledWith(null, { ctrl: true, name: 'u' });
|
||||
expect(prompt).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Press Ctrl+C again to quit'));
|
||||
} finally {
|
||||
logSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user