Skip to content

Commit 589b8c4

Browse files
committed
fix: restore terminal state on Ctrl+C during interactive task selection
1 parent 2295d41 commit 589b8c4

File tree

6 files changed

+64
-9
lines changed

6 files changed

+64
-9
lines changed

crates/vite_select/src/interactive.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ pub fn run(
489489
header: Option<&str>,
490490
page_size: usize,
491491
mut after_render: impl FnMut(&RenderState<'_>),
492-
) -> anyhow::Result<()> {
492+
) -> anyhow::Result<super::SelectResult> {
493493
if items.is_empty() {
494494
anyhow::bail!("No tasks available");
495495
}
@@ -516,15 +516,15 @@ pub fn run(
516516
}
517517
KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
518518
cleanup(&mut out, &state)?;
519-
std::process::exit(130);
519+
return Ok(super::SelectResult::Cancelled);
520520
}
521521
KeyCode::Enter => {
522522
let Some(idx) = state.selected_item_index() else {
523523
continue;
524524
};
525525
*selected_index = idx;
526526
cleanup(&mut out, &state)?;
527-
return Ok(());
527+
return Ok(super::SelectResult::Selected);
528528
}
529529
KeyCode::Up => {
530530
state.move_up();

crates/vite_select/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,26 @@ pub struct SelectParams<'a> {
5050
pub page_size: usize,
5151
}
5252

53+
/// Result of an interactive selection.
54+
pub enum SelectResult {
55+
/// The user selected an item.
56+
Selected,
57+
/// The user cancelled the selection (e.g. Ctrl+C).
58+
Cancelled,
59+
}
60+
5361
/// Show a task selection list.
5462
///
5563
/// In [`Mode::Interactive`], enters a terminal UI with fuzzy search and
5664
/// keyboard navigation. `after_render` is called after every render with the
5765
/// current visible state (useful for emitting test milestones). On Enter,
5866
/// `*selected_index` is set to the chosen item's index in the original
59-
/// `items` slice.
67+
/// `items` slice. Returns [`SelectResult::Cancelled`] if the user presses
68+
/// Ctrl+C.
6069
///
6170
/// In [`Mode::NonInteractive`], renders the list once to `writer` and
62-
/// returns. `page_size` and `after_render` are ignored.
71+
/// returns [`SelectResult::Selected`]. `page_size` and `after_render` are
72+
/// ignored.
6373
///
6474
/// # Errors
6575
///
@@ -69,7 +79,7 @@ pub fn select_list(
6979
params: &SelectParams<'_>,
7080
mode: Mode<'_>,
7181
after_render: impl FnMut(&RenderState<'_>),
72-
) -> anyhow::Result<()> {
82+
) -> anyhow::Result<SelectResult> {
7383
match mode {
7484
Mode::Interactive { selected_index } => interactive::run(
7585
params.items,
@@ -79,7 +89,10 @@ pub fn select_list(
7989
params.page_size,
8090
after_render,
8191
),
82-
Mode::NonInteractive => non_interactive(writer, params.items, params.query, params.header),
92+
Mode::NonInteractive => {
93+
non_interactive(writer, params.items, params.query, params.header)?;
94+
Ok(SelectResult::Selected)
95+
}
8396
}
8497
}
8598

crates/vite_task/src/session/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ impl<'a> Session<'a> {
449449
page_size: 12,
450450
};
451451

452-
vite_select::select_list(&mut stdout, &params, mode, |state| {
452+
let select_result = vite_select::select_list(&mut stdout, &params, mode, |state| {
453453
use std::io::Write;
454454
let milestone_name =
455455
vite_str::format!("task-select:{}:{}", state.query, state.selected_index);
@@ -459,6 +459,10 @@ impl<'a> Session<'a> {
459459
let _ = out.flush();
460460
})?;
461461

462+
if matches!(select_result, vite_select::SelectResult::Cancelled) {
463+
return Err(SessionError::EarlyExit(ExitStatus(130)));
464+
}
465+
462466
let Some(selected_index) = selected_index else {
463467
// Non-interactive, the list was printed.
464468
return Err(SessionError::EarlyExit(if not_found_name.is_some() {

crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-select/snapshots.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,17 @@ steps = [
203203
name = "typo in task script fails without list"
204204
steps = ["vt run run-typo-task"]
205205

206+
# Interactive: Ctrl+C cancels selection and exits with code 130
207+
[[e2e]]
208+
name = "interactive ctrl-c cancels"
209+
cwd = "packages/app"
210+
steps = [
211+
{ command = "vt run", interactions = [
212+
{ "expect-milestone" = "task-select::0" },
213+
{ "write-key" = "ctrl-c" },
214+
] },
215+
]
216+
206217
# --verbose without task: not bare, errors with "no task specifier provided"
207218
[[e2e]]
208219
name = "verbose without task errors"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
3+
expression: e2e_outputs
4+
info:
5+
cwd: packages/app
6+
---
7+
[130]> vt run
8+
@ expect-milestone: task-select::0
9+
Select a task (↑/↓, Enter to run, type to search):
10+
11+
build echo build app
12+
lint echo lint app
13+
test echo test app
14+
lib (packages/lib)
15+
build echo build lib
16+
lint echo lint lib
17+
test echo test lib
18+
typecheck echo typecheck lib
19+
task-select-test (workspace root)
20+
check echo check root
21+
clean echo clean root
22+
deploy echo deploy root
23+
(…5 more)
24+
@ write-key: ctrl-c

crates/vite_task_bin/tests/e2e_snapshots/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,14 @@ struct WriteKeyInteraction {
122122
}
123123

124124
#[derive(serde::Deserialize, Debug, Clone, Copy)]
125-
#[serde(rename_all = "lowercase")]
125+
#[serde(rename_all = "kebab-case")]
126126
enum WriteKey {
127127
Up,
128128
Down,
129129
Enter,
130130
Escape,
131131
Backspace,
132+
CtrlC,
132133
}
133134

134135
impl WriteKey {
@@ -139,6 +140,7 @@ impl WriteKey {
139140
Self::Enter => "enter",
140141
Self::Escape => "escape",
141142
Self::Backspace => "backspace",
143+
Self::CtrlC => "ctrl-c",
142144
}
143145
}
144146

@@ -149,6 +151,7 @@ impl WriteKey {
149151
Self::Enter => b"\r",
150152
Self::Escape => b"\x1b",
151153
Self::Backspace => b"\x7f",
154+
Self::CtrlC => b"\x03",
152155
}
153156
}
154157
}

0 commit comments

Comments
 (0)