Skip to content

Commit 96a43f3

Browse files
authored
fix(install): filter KeyEventKind on Windows for arrow key navigation (#1362)
On Windows, crossterm emits both Press and Release events for each key press. Without filtering for Press-only, arrow keys in the `vp i` package manager prompt were processed twice per press (move then move back), making them appear non-functional. Closes #1361 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: small, localized change to interactive terminal input handling, primarily affecting Windows key navigation behavior. > > **Overview** > Fixes the interactive package manager selection menu to **ignore non-press keyboard events** by filtering `crossterm` `KeyEventKind` to `Press` before handling navigation/selection keys. > > This prevents double-handling of arrow keys on Windows (where both Press and Release events are emitted), making ↑/↓ navigation behave correctly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a94deea. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8718a3e commit 96a43f3

File tree

1 file changed

+61
-55
lines changed

1 file changed

+61
-55
lines changed

crates/vite_install/src/package_manager.rs

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88

99
use crossterm::{
1010
cursor,
11-
event::{self, Event, KeyCode, KeyEvent},
11+
event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
1212
execute,
1313
style::{Color, Print, ResetColor, SetForegroundColor},
1414
terminal,
@@ -826,63 +826,69 @@ fn interactive_package_manager_menu() -> Result<PackageManagerType, Error> {
826826
execute!(io::stdout(), cursor::MoveUp((options.len() - 1) as u16))?;
827827
}
828828

829-
// Read keyboard input
830-
if let Event::Key(KeyEvent { code, modifiers, .. }) = event::read()? {
831-
match code {
832-
// Handle Ctrl+C for exit
833-
KeyCode::Char('c') if modifiers.contains(event::KeyModifiers::CONTROL) => {
834-
// Clean up terminal before exiting
835-
terminal::disable_raw_mode()?;
836-
execute!(
837-
io::stdout(),
838-
cursor::Show,
839-
cursor::MoveDown(options.len() as u16),
840-
Print("\n\n"),
841-
SetForegroundColor(Color::Yellow),
842-
Print("⚠ Installation cancelled by user\n"),
843-
ResetColor
844-
)?;
845-
return Err(Error::UserCancelled);
829+
// Read keyboard input, skipping non-Press events (e.g. Release on Windows)
830+
let (code, modifiers) = loop {
831+
if let Event::Key(KeyEvent { code, modifiers, kind, .. }) = event::read()? {
832+
if kind == KeyEventKind::Press {
833+
break (code, modifiers);
846834
}
847-
KeyCode::Up => {
848-
selected_index = selected_index.saturating_sub(1);
849-
}
850-
KeyCode::Down => {
851-
if selected_index < options.len() - 1 {
852-
selected_index += 1;
853-
}
854-
}
855-
KeyCode::Enter | KeyCode::Char(' ') => {
856-
break Ok(options[selected_index].1);
857-
}
858-
KeyCode::Char('1') => {
859-
break Ok(options[0].1);
860-
}
861-
KeyCode::Char('2') if options.len() > 1 => {
862-
break Ok(options[1].1);
863-
}
864-
KeyCode::Char('3') if options.len() > 2 => {
865-
break Ok(options[2].1);
866-
}
867-
KeyCode::Char('4') if options.len() > 3 => {
868-
break Ok(options[3].1);
869-
}
870-
KeyCode::Esc | KeyCode::Char('q') => {
871-
// Exit on escape/quit
872-
terminal::disable_raw_mode()?;
873-
execute!(
874-
io::stdout(),
875-
cursor::Show,
876-
cursor::MoveDown(options.len() as u16),
877-
Print("\n\n"),
878-
SetForegroundColor(Color::Yellow),
879-
Print("⚠ Installation cancelled by user\n"),
880-
ResetColor
881-
)?;
882-
return Err(Error::UserCancelled);
835+
}
836+
};
837+
838+
match code {
839+
// Handle Ctrl+C for exit
840+
KeyCode::Char('c') if modifiers.contains(event::KeyModifiers::CONTROL) => {
841+
// Clean up terminal before exiting
842+
terminal::disable_raw_mode()?;
843+
execute!(
844+
io::stdout(),
845+
cursor::Show,
846+
cursor::MoveDown(options.len() as u16),
847+
Print("\n\n"),
848+
SetForegroundColor(Color::Yellow),
849+
Print("⚠ Installation cancelled by user\n"),
850+
ResetColor
851+
)?;
852+
return Err(Error::UserCancelled);
853+
}
854+
KeyCode::Up => {
855+
selected_index = selected_index.saturating_sub(1);
856+
}
857+
KeyCode::Down => {
858+
if selected_index < options.len() - 1 {
859+
selected_index += 1;
883860
}
884-
_ => {}
885861
}
862+
KeyCode::Enter | KeyCode::Char(' ') => {
863+
break Ok(options[selected_index].1);
864+
}
865+
KeyCode::Char('1') => {
866+
break Ok(options[0].1);
867+
}
868+
KeyCode::Char('2') if options.len() > 1 => {
869+
break Ok(options[1].1);
870+
}
871+
KeyCode::Char('3') if options.len() > 2 => {
872+
break Ok(options[2].1);
873+
}
874+
KeyCode::Char('4') if options.len() > 3 => {
875+
break Ok(options[3].1);
876+
}
877+
KeyCode::Esc | KeyCode::Char('q') => {
878+
// Exit on escape/quit
879+
terminal::disable_raw_mode()?;
880+
execute!(
881+
io::stdout(),
882+
cursor::Show,
883+
cursor::MoveDown(options.len() as u16),
884+
Print("\n\n"),
885+
SetForegroundColor(Color::Yellow),
886+
Print("⚠ Installation cancelled by user\n"),
887+
ResetColor
888+
)?;
889+
return Err(Error::UserCancelled);
890+
}
891+
_ => {}
886892
}
887893
};
888894

0 commit comments

Comments
 (0)