diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba0419ab..57cd866fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Fixed** Cmd+Backspace on macOS now clears the interactive task selector's search query instead of inserting `u`. - **Fixed** Vite+ diagnostics now display individual paths and working directories without Rust debug formatting such as quoted paths or escaped Windows backslashes ([#534](https://github.com/voidzero-dev/vite-task/pull/534)). - **Fixed** Automatic file-access tracking now works inside the default Codex CLI and Claude Code sandboxes ([#562](https://github.com/voidzero-dev/vite-task/issues/562), [#563](https://github.com/voidzero-dev/vite-task/issues/563), [#576](https://github.com/voidzero-dev/vite-task/pull/576), [#569](https://github.com/voidzero-dev/vite-task/pull/569)). - **Fixed** Broad workspace globs no longer discover and run package scripts inside `node_modules` ([#539](https://github.com/voidzero-dev/vite-task/pull/539)). diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml index eb046e7d8..ea7dba4fa 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml @@ -99,6 +99,33 @@ steps = [ ] }, ] +[[e2e]] +name = "interactive_ctrl_u_clears_query" +comment = """ +Ctrl+U should clear the current query and reset the selection, while other control characters are ignored and Shift characters remain searchable. +""" +cwd = "packages/app" +steps = [ + { argv = [ + "vt", + "run", + ], interactions = [ + { "expect-milestone" = "task-select::0" }, + { "write" = "lin" }, + { "expect-milestone" = "task-select:lin:0" }, + { "write-key" = "ctrl-w" }, + { "write-key" = "backspace" }, + { "expect-milestone" = "task-select:li:0" }, + { "write" = "N" }, + { "expect-milestone" = "task-select:liN:0" }, + { "write-key" = "down" }, + { "expect-milestone" = "task-select:liN:1" }, + { "write-key" = "ctrl-u" }, + { "expect-milestone" = "task-select::0" }, + { "write-key" = "enter" }, + ] }, +] + [[e2e]] name = "recursive_without_task_errors" comment = """ diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_u_clears_query.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_u_clears_query.md new file mode 100644 index 000000000..f783ac14f --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_u_clears_query.md @@ -0,0 +1,110 @@ +# interactive_ctrl_u_clears_query + +Ctrl+U should clear the current query and reset the selection, while other control characters are ignored and Shift characters remain searchable. + +## `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:** `lin` + +**→ expect-milestone:** `task-select:lin:0` + +``` +Select a task (↑/↓, Enter to run, type to search): lin + + › lint echo lint app + lib (packages/lib) + lint echo lint lib +``` + +**← write-key:** `ctrl-w` + +**← write-key:** `backspace` + +**→ expect-milestone:** `task-select:li:0` + +``` +Select a task (↑/↓, Enter to run, type to search): li + + › lint echo lint 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) + validate echo validate root +``` + +**← write:** `N` + +**→ expect-milestone:** `task-select:liN:0` + +``` +Select a task (↑/↓, Enter to run, type to search): liN + + › lint echo lint app + lib (packages/lib) + lint echo lint lib +``` + +**← write-key:** `down` + +**→ expect-milestone:** `task-select:liN:1` + +``` +Select a task (↑/↓, Enter to run, type to search): liN + + lint echo lint app + lib (packages/lib) + › lint echo lint lib +``` + +**← write-key:** `ctrl-u` + +**→ 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:** `enter` + +``` +Selected task: build +~/packages/app$ echo build app ⊘ cache disabled +build app +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/main.rs b/crates/vt_bin/tests/e2e_snapshots/main.rs index b72f599c5..e52a51ae9 100644 --- a/crates/vt_bin/tests/e2e_snapshots/main.rs +++ b/crates/vt_bin/tests/e2e_snapshots/main.rs @@ -195,6 +195,8 @@ enum WriteKey { Escape, Backspace, CtrlC, + CtrlU, + CtrlW, } impl WriteKey { @@ -206,6 +208,8 @@ impl WriteKey { Self::Escape => "escape", Self::Backspace => "backspace", Self::CtrlC => "ctrl-c", + Self::CtrlU => "ctrl-u", + Self::CtrlW => "ctrl-w", } } @@ -217,6 +221,8 @@ impl WriteKey { Self::Escape => b"\x1b", Self::Backspace => b"\x7f", Self::CtrlC => b"\x03", + Self::CtrlU => b"\x15", + Self::CtrlW => b"\x17", } } } diff --git a/crates/vt_select/src/interactive.rs b/crates/vt_select/src/interactive.rs index 12069099e..4b8183e4a 100644 --- a/crates/vt_select/src/interactive.rs +++ b/crates/vt_select/src/interactive.rs @@ -518,6 +518,10 @@ pub fn run( cleanup(&mut out, &state)?; return Ok(super::SelectResult::Cancelled); } + KeyCode::Char('u') if modifiers == KeyModifiers::CONTROL => { + state.query.clear(); + state.refilter(); + } KeyCode::Enter => { let Some(idx) = state.selected_item_index() else { continue; @@ -532,7 +536,7 @@ pub fn run( KeyCode::Down => { state.move_down(); } - KeyCode::Char(c) => { + KeyCode::Char(c) if accepts_search_character(modifiers) => { state.query.push(c); state.refilter(); } @@ -550,6 +554,12 @@ pub fn run( } } +fn accepts_search_character(modifiers: KeyModifiers) -> bool { + // Windows reports AltGr as Ctrl+Alt while preserving the printable character. + let modifiers = modifiers.difference(KeyModifiers::SHIFT); + modifiers.is_empty() || modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT) +} + /// Clear the widget output and restore cursor. fn cleanup(stdout: &mut impl Write, state: &State<'_>) -> anyhow::Result<()> { if state.rendered_lines > 0 { @@ -672,6 +682,14 @@ mod tests { strip_ansi(&String::from_utf8(buf).unwrap()) } + #[test] + fn search_character_modifiers_preserve_alt_gr_input() { + assert!(accepts_search_character(KeyModifiers::CONTROL | KeyModifiers::ALT)); + assert!(accepts_search_character( + KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT + )); + } + #[test] fn truncates_long_description() { let items = make_items(&[("build", "a really long command that exceeds the width limit")]);