feat: support ctrl+c dismissal in ask flow
This commit is contained in:
@@ -163,6 +163,7 @@ Behavior details:
|
|||||||
- while editing a note or free-form answer, arrow keys and `Tab` stay inside the editor so the typing cursor can move naturally
|
- while editing a note or free-form answer, arrow keys and `Tab` stay inside the editor so the typing cursor can move naturally
|
||||||
- free-form answer editors support pi-style `@` file path autocomplete for quickly mentioning project files
|
- free-form answer editors support pi-style `@` file path autocomplete for quickly mentioning project files
|
||||||
- `Esc` closes the editor and returns to navigation mode
|
- `Esc` closes the editor and returns to navigation mode
|
||||||
|
- `Ctrl+C` dismisses the entire ask flow immediately, even from note/free-form editing, without saving the current draft
|
||||||
- `Space` toggles the active option on single-select questions too, but does not auto-advance
|
- `Space` toggles the active option on single-select questions too, but does not auto-advance
|
||||||
|
|
||||||
## Local development
|
## Local development
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ Main flow:
|
|||||||
- `Up`, `Down`: move between options
|
- `Up`, `Down`: move between options
|
||||||
- `Enter`: confirm or submit
|
- `Enter`: confirm or submit
|
||||||
- `Esc`: cancel the flow
|
- `Esc`: cancel the flow
|
||||||
|
- `Ctrl+C`: dismiss the entire flow immediately, even while editing a note or free-form answer
|
||||||
- `1..9`: select or toggle the matching option
|
- `1..9`: select or toggle the matching option
|
||||||
- `Space`: toggle the active option
|
- `Space`: toggle the active option
|
||||||
- `N`: edit the active option note
|
- `N`: edit the active option note
|
||||||
@@ -103,6 +104,7 @@ Editing flow:
|
|||||||
|
|
||||||
- `Enter`: save and submit the current input
|
- `Enter`: save and submit the current input
|
||||||
- `Esc`: save draft and close the editor
|
- `Esc`: save draft and close the editor
|
||||||
|
- `Ctrl+C`: dismiss the entire flow immediately without saving the current editor draft
|
||||||
- arrow keys and `Tab` stay in the editor so the cursor can move while typing
|
- arrow keys and `Tab` stay in the editor so the cursor can move while typing
|
||||||
- navigation resumes only after closing the editor with `Esc`
|
- navigation resumes only after closing the editor with `Esc`
|
||||||
|
|
||||||
|
|||||||
@@ -241,6 +241,11 @@ export function cancelFlow(state: AskState): AskState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dismissFlow(state: AskState): AskState {
|
||||||
|
const nextState = cancelFlow(state);
|
||||||
|
return nextState.completed ? nextState : cancelFlow(nextState);
|
||||||
|
}
|
||||||
|
|
||||||
function saveInputValue(
|
function saveInputValue(
|
||||||
state: AskState,
|
state: AskState,
|
||||||
rawValue: string,
|
rawValue: string,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
applyNumberShortcut,
|
applyNumberShortcut,
|
||||||
cancelFlow,
|
cancelFlow,
|
||||||
confirmCurrentSelection,
|
confirmCurrentSelection,
|
||||||
|
dismissFlow,
|
||||||
enterInputMode,
|
enterInputMode,
|
||||||
enterOptionNoteMode,
|
enterOptionNoteMode,
|
||||||
enterQuestionNoteMode,
|
enterQuestionNoteMode,
|
||||||
@@ -117,6 +118,11 @@ function handleEditingCommand(
|
|||||||
command: AskInputCommand,
|
command: AskInputCommand,
|
||||||
data: string
|
data: string
|
||||||
) {
|
) {
|
||||||
|
if (command.kind === "dismiss") {
|
||||||
|
commitNavigation(controller, dismissFlow(controller.state));
|
||||||
|
maybeFinish(controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (command.kind === "editMoveTab") {
|
if (command.kind === "editMoveTab") {
|
||||||
controller.state = saveEditorAndMoveTab(controller, command.delta);
|
controller.state = saveEditorAndMoveTab(controller, command.delta);
|
||||||
syncInputModeWithSelection(controller);
|
syncInputModeWithSelection(controller);
|
||||||
@@ -176,6 +182,10 @@ function handleNavigationCommand(
|
|||||||
applyNumberShortcut(controller.state, command.digit)
|
applyNumberShortcut(controller.state, command.digit)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
case "dismiss":
|
||||||
|
commitNavigation(controller, dismissFlow(controller.state));
|
||||||
|
maybeFinish(controller);
|
||||||
|
return;
|
||||||
case "ignore":
|
case "ignore":
|
||||||
case "editMoveTab":
|
case "editMoveTab":
|
||||||
case "editMoveOption":
|
case "editMoveOption":
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type AskInputCommand =
|
|||||||
| { kind: "openOptionNote" }
|
| { kind: "openOptionNote" }
|
||||||
| { kind: "confirm" }
|
| { kind: "confirm" }
|
||||||
| { kind: "cancel" }
|
| { kind: "cancel" }
|
||||||
|
| { kind: "dismiss" }
|
||||||
| { kind: "numberShortcut"; digit: number }
|
| { kind: "numberShortcut"; digit: number }
|
||||||
| { kind: "editMoveTab"; delta: 1 | -1 }
|
| { kind: "editMoveTab"; delta: 1 | -1 }
|
||||||
| { kind: "editMoveOption"; delta: 1 | -1 }
|
| { kind: "editMoveOption"; delta: 1 | -1 }
|
||||||
@@ -22,6 +23,10 @@ export function getInputCommand(
|
|||||||
state: AskState,
|
state: AskState,
|
||||||
data: string
|
data: string
|
||||||
): AskInputCommand {
|
): AskInputCommand {
|
||||||
|
if (matchesKey(data, Key.ctrl("c"))) {
|
||||||
|
return { kind: "dismiss" };
|
||||||
|
}
|
||||||
|
|
||||||
if (state.view.kind === "input" || state.view.kind === "note") {
|
if (state.view.kind === "input" || state.view.kind === "note") {
|
||||||
return getEditingInputCommand(data);
|
return getEditingInputCommand(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,27 @@ test("note editing mode delegates arrows and tab to the editor", () => {
|
|||||||
kind: "delegateToEditor",
|
kind: "delegateToEditor",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("ctrl+c dismisses the flow from both navigation and editing modes", () => {
|
||||||
|
const navigation = createInitialState({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "q1",
|
||||||
|
prompt: "Question?",
|
||||||
|
options: [{ value: "a", label: "A" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const input = inputState();
|
||||||
|
const note = enterQuestionNoteMode(navigation, "q1");
|
||||||
|
|
||||||
|
assert.deepEqual(getInputCommand(navigation, "\u0003"), {
|
||||||
|
kind: "dismiss",
|
||||||
|
});
|
||||||
|
assert.deepEqual(getInputCommand(input, "\u0003"), {
|
||||||
|
kind: "dismiss",
|
||||||
|
});
|
||||||
|
assert.deepEqual(getInputCommand(note, "\u0003"), {
|
||||||
|
kind: "dismiss",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
applyNumberShortcut,
|
applyNumberShortcut,
|
||||||
cancelFlow,
|
cancelFlow,
|
||||||
confirmCurrentSelection,
|
confirmCurrentSelection,
|
||||||
|
dismissFlow,
|
||||||
enterOptionNoteMode,
|
enterOptionNoteMode,
|
||||||
enterQuestionNoteMode,
|
enterQuestionNoteMode,
|
||||||
moveOption,
|
moveOption,
|
||||||
@@ -473,3 +474,27 @@ test("escape cancels from main flow but only exits input and note modes when edi
|
|||||||
assert.equal(state.view.kind, "navigate");
|
assert.equal(state.view.kind, "navigate");
|
||||||
assert.equal(state.cancelled, false);
|
assert.equal(state.cancelled, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dismissFlow cancels the tool even from editing modes", () => {
|
||||||
|
let state = createInitialState({
|
||||||
|
questions: [
|
||||||
|
{ id: "q", prompt: "Q?", options: [{ value: "a", label: "A" }] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
state = applyNumberShortcut(state, 2);
|
||||||
|
assert.equal(state.view.kind, "input");
|
||||||
|
state = dismissFlow(state);
|
||||||
|
assert.equal(state.cancelled, true);
|
||||||
|
assert.equal(state.completed, true);
|
||||||
|
|
||||||
|
state = createInitialState({
|
||||||
|
questions: [
|
||||||
|
{ id: "q", prompt: "Q?", options: [{ value: "a", label: "A" }] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
state = enterQuestionNoteMode(state, "q");
|
||||||
|
assert.equal(state.view.kind, "note");
|
||||||
|
state = dismissFlow(state);
|
||||||
|
assert.equal(state.cancelled, true);
|
||||||
|
assert.equal(state.completed, true);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user