From c2e46608a0049d30d27dd760f43003d31485790c Mon Sep 17 00:00:00 2001 From: Liang Date: Mon, 17 Aug 2026 11:15:39 +0800 Subject: [PATCH 1/2] fix(select): handle Ctrl+U in task picker Crossterm decodes Ctrl+U as Char('u') with CONTROL, which fell through modifier-agnostic text handling. Clear the query for Ctrl+U and restrict text insertion to unmodified or Shift keys so control sequences cannot enter search text. Co-authored-by: GPT-5 Codex --- CHANGELOG.md | 1 + .../fixtures/task_select/snapshots.toml | 27 +++++ .../interactive_ctrl_u_clears_query.md | 110 ++++++++++++++++++ crates/vt_bin/tests/e2e_snapshots/main.rs | 6 + crates/vt_select/src/interactive.rs | 6 +- 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_u_clears_query.md 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..9f3d3a1df 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.contains(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 modifiers.is_empty() || modifiers == KeyModifiers::SHIFT => { state.query.push(c); state.refilter(); } From 7673df375f93f89f3dc7164a37a11600ca1bb9c7 Mon Sep 17 00:00:00 2001 From: Liang Date: Mon, 17 Aug 2026 12:43:02 +0800 Subject: [PATCH 2/2] fix(select): preserve AltGr search input Allow printable Ctrl+Alt character events used by AltGr while keeping Ctrl+U limited to the exact control shortcut. Co-authored-by: GPT-5.6 Codex --- crates/vt_select/src/interactive.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/vt_select/src/interactive.rs b/crates/vt_select/src/interactive.rs index 9f3d3a1df..4b8183e4a 100644 --- a/crates/vt_select/src/interactive.rs +++ b/crates/vt_select/src/interactive.rs @@ -518,7 +518,7 @@ pub fn run( cleanup(&mut out, &state)?; return Ok(super::SelectResult::Cancelled); } - KeyCode::Char('u') if modifiers.contains(KeyModifiers::CONTROL) => { + KeyCode::Char('u') if modifiers == KeyModifiers::CONTROL => { state.query.clear(); state.refilter(); } @@ -536,7 +536,7 @@ pub fn run( KeyCode::Down => { state.move_down(); } - KeyCode::Char(c) if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT => { + KeyCode::Char(c) if accepts_search_character(modifiers) => { state.query.push(c); state.refilter(); } @@ -554,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 { @@ -676,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")]);