Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/vite_select/src/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ pub fn run(
header: Option<&str>,
page_size: usize,
mut after_render: impl FnMut(&RenderState<'_>),
) -> anyhow::Result<()> {
) -> anyhow::Result<super::SelectResult> {
if items.is_empty() {
anyhow::bail!("No tasks available");
}
Expand All @@ -516,15 +516,15 @@ pub fn run(
}
KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
cleanup(&mut out, &state)?;
std::process::exit(130);
return Ok(super::SelectResult::Cancelled);
}
KeyCode::Enter => {
let Some(idx) = state.selected_item_index() else {
continue;
};
*selected_index = idx;
cleanup(&mut out, &state)?;
return Ok(());
return Ok(super::SelectResult::Selected);
}
KeyCode::Up => {
state.move_up();
Expand Down
21 changes: 17 additions & 4 deletions crates/vite_select/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,26 @@ pub struct SelectParams<'a> {
pub page_size: usize,
}

/// Result of an interactive selection.
pub enum SelectResult {
/// The user selected an item.
Selected,
/// The user cancelled the selection (e.g. Ctrl+C).
Cancelled,
}

/// Show a task selection list.
///
/// In [`Mode::Interactive`], enters a terminal UI with fuzzy search and
/// keyboard navigation. `after_render` is called after every render with the
/// current visible state (useful for emitting test milestones). On Enter,
/// `*selected_index` is set to the chosen item's index in the original
/// `items` slice.
/// `items` slice. Returns [`SelectResult::Cancelled`] if the user presses
/// Ctrl+C.
///
/// In [`Mode::NonInteractive`], renders the list once to `writer` and
/// returns. `page_size` and `after_render` are ignored.
/// returns [`SelectResult::Selected`]. `page_size` and `after_render` are
/// ignored.
///
/// # Errors
///
Expand All @@ -69,7 +79,7 @@ pub fn select_list(
params: &SelectParams<'_>,
mode: Mode<'_>,
after_render: impl FnMut(&RenderState<'_>),
) -> anyhow::Result<()> {
) -> anyhow::Result<SelectResult> {
match mode {
Mode::Interactive { selected_index } => interactive::run(
params.items,
Expand All @@ -79,7 +89,10 @@ pub fn select_list(
params.page_size,
after_render,
),
Mode::NonInteractive => non_interactive(writer, params.items, params.query, params.header),
Mode::NonInteractive => {
non_interactive(writer, params.items, params.query, params.header)?;
Ok(SelectResult::Selected)
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion crates/vite_task/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ impl<'a> Session<'a> {
page_size: 12,
};

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

if matches!(select_result, vite_select::SelectResult::Cancelled) {
return Err(SessionError::EarlyExit(ExitStatus(130)));
}

let Some(selected_index) = selected_index else {
// Non-interactive, the list was printed.
return Err(SessionError::EarlyExit(if not_found_name.is_some() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ steps = [
name = "typo in task script fails without list"
steps = ["vt run run-typo-task"]

# Interactive: Ctrl+C cancels selection and exits with code 130
[[e2e]]
name = "interactive ctrl-c cancels"
cwd = "packages/app"
steps = [
{ command = "vt run", interactions = [
{ "expect-milestone" = "task-select::0" },
{ "write-key" = "ctrl-c" },
] },
]

# --verbose without task: not bare, errors with "no task specifier provided"
[[e2e]]
name = "verbose without task errors"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
expression: e2e_outputs
info:
cwd: packages/app
---
[130]> vt run
@ expect-milestone: task-select::0
Select a task (↑/↓, Enter to run, type to search):

› build echo build app
lint echo lint app
test echo test app
lib (packages/lib)
build echo build lib
lint echo lint lib
test echo test lib
typecheck echo typecheck lib
task-select-test (workspace root)
check echo check root
clean echo clean root
deploy echo deploy root
(…5 more)
@ write-key: ctrl-c
5 changes: 4 additions & 1 deletion crates/vite_task_bin/tests/e2e_snapshots/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ struct WriteKeyInteraction {
}

#[derive(serde::Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "lowercase")]
#[serde(rename_all = "kebab-case")]
enum WriteKey {
Up,
Down,
Enter,
Escape,
Backspace,
CtrlC,
}

impl WriteKey {
Expand All @@ -139,6 +140,7 @@ impl WriteKey {
Self::Enter => "enter",
Self::Escape => "escape",
Self::Backspace => "backspace",
Self::CtrlC => "ctrl-c",
}
}

Expand All @@ -149,6 +151,7 @@ impl WriteKey {
Self::Enter => b"\r",
Self::Escape => b"\x1b",
Self::Backspace => b"\x7f",
Self::CtrlC => b"\x03",
}
}
}
Expand Down
Loading