From 8d66213ed9e42686a080b0d0fb3171cf104044a4 Mon Sep 17 00:00:00 2001 From: devkade Date: Mon, 30 Mar 2026 00:27:58 +0900 Subject: [PATCH 1/2] fix: cancel ask flow on ctrl-c --- src/ask-inline-ui.ts | 5 ++ src/ask-tabs-ui.ts | 5 ++ test/ask-ui-interaction.test.ts | 85 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/ask-inline-ui.ts b/src/ask-inline-ui.ts index 2e8bbe1..3bcf6bd 100644 --- a/src/ask-inline-ui.ts +++ b/src/ask-inline-ui.ts @@ -206,6 +206,11 @@ export async function askSingleQuestionWithInlineNote( }; const handleInput = (data: string) => { + if (matchesKey(data, Key.ctrl("c"))) { + done({ cancelled: true }); + return; + } + if (isNoteEditorOpen) { if (matchesKey(data, Key.tab) || matchesKey(data, Key.escape)) { isNoteEditorOpen = false; diff --git a/src/ask-tabs-ui.ts b/src/ask-tabs-ui.ts index f947fa8..c0d2336 100644 --- a/src/ask-tabs-ui.ts +++ b/src/ask-tabs-ui.ts @@ -438,6 +438,11 @@ export async function askQuestionsWithTabs( }; const handleInput = (data: string) => { + if (matchesKey(data, Key.ctrl("c"))) { + done(createTabsUiStateSnapshot(true, selectedOptionIndexesByQuestion, noteByQuestionByOption)); + return; + } + if (isNoteEditorOpen) { if (matchesKey(data, Key.tab) || matchesKey(data, Key.escape)) { isNoteEditorOpen = false; diff --git a/test/ask-ui-interaction.test.ts b/test/ask-ui-interaction.test.ts index 2253781..8a19038 100644 --- a/test/ask-ui-interaction.test.ts +++ b/test/ask-ui-interaction.test.ts @@ -65,6 +65,33 @@ describe("askSingleQuestionWithInlineNote interactive branches", () => { expect(result).toEqual({ selectedOptions: [], customInput: "custom-flow" }); }); + it("cancels single-question flow on Ctrl-C, including from note editor", async () => { + const ui = { + custom: async (factory: any) => { + const tui = { requestRender() {} }; + const theme = createFakeTheme(); + let result: any; + const done = (value: any) => { + result = value; + }; + + const component = await factory(tui, theme, {}, done); + component.render(40); + component.handleInput(" "); + component.handleInput("draft"); + component.handleInput(""); + return result; + }, + } as unknown as ExtensionUIContext; + + const result = await askSingleQuestionWithInlineNote(ui, { + question: "Choose one", + options: [{ label: "A" }, { label: "B" }], + }); + + expect(result).toEqual({ selectedOptions: [] }); + }); + it("handles navigation, inline edit exit, invalidate, and cancel", async () => { const ui = { custom: async (factory: any) => { @@ -250,6 +277,64 @@ describe("askQuestionsWithTabs interactive branches", () => { }); }); + it("cancels tab flow on Ctrl-C from question, note editor, and submit tab", async () => { + const ui = { + custom: async (factory: any) => { + const tui = { requestRender() {} }; + const theme = createFakeTheme(); + let result: any; + const done = (value: any) => { + result = value; + }; + + const component = await factory(tui, theme, {}, done); + component.render(40); + component.handleInput(" "); + component.handleInput("memo"); + component.handleInput(""); + return result; + }, + } as unknown as ExtensionUIContext; + + const result = await askQuestionsWithTabs(ui, [ + { id: "q1", question: "Question 1", options: [{ label: "A" }, { label: "B" }] }, + { id: "q2", question: "Question 2", options: [{ label: "C" }, { label: "D" }] }, + ]); + + expect(result).toEqual({ + cancelled: true, + selections: [{ selectedOptions: [] }, { selectedOptions: [] }], + }); + }); + + it("cancels tab flow on Ctrl-C from submit tab", async () => { + const ui = { + custom: async (factory: any) => { + const tui = { requestRender() {} }; + const theme = createFakeTheme(); + let result: any; + const done = (value: any) => { + result = value; + }; + + const component = await factory(tui, theme, {}, done); + component.handleInput("\r"); + component.handleInput("\u001b[C"); + component.handleInput("\u0003"); + return result; + }, + } as unknown as ExtensionUIContext; + + const result = await askQuestionsWithTabs(ui, [ + { id: "q1", question: "Question 1", options: [{ label: "A" }] }, + ]); + + expect(result).toEqual({ + cancelled: true, + selections: [{ selectedOptions: [] }], + }); + }); + it("covers submit-tab validation warning and cancel via Esc", async () => { const ui = { custom: async (factory: any) => { From 618f92cac18f54ee562f2d39f3b5f9d08d7a6739 Mon Sep 17 00:00:00 2001 From: devkade Date: Mon, 30 Mar 2026 00:36:31 +0900 Subject: [PATCH 2/2] test: clarify ctrl-c note editor case --- test/ask-ui-interaction.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ask-ui-interaction.test.ts b/test/ask-ui-interaction.test.ts index 8a19038..cff364d 100644 --- a/test/ask-ui-interaction.test.ts +++ b/test/ask-ui-interaction.test.ts @@ -277,7 +277,7 @@ describe("askQuestionsWithTabs interactive branches", () => { }); }); - it("cancels tab flow on Ctrl-C from question, note editor, and submit tab", async () => { + it("cancels tab flow on Ctrl-C from note editor", async () => { const ui = { custom: async (factory: any) => { const tui = { requestRender() {} };