From 1e1d06328cc9aefa354b7ce463f9f2d254395662 Mon Sep 17 00:00:00 2001 From: Bryan Lee <38807139+liby@users.noreply.github.com> Date: Wed, 8 Jul 2026 01:54:42 +0800 Subject: fix: preserve navigator search commands (#1140) Navigator search treated Escape as either modal close or query reset, which left no way to return to command mode while keeping the filtered row set. `/` also rebuilt search mode by clearing the query, so a user could not resume editing the same filter after backing out. Make Escape leave search focus only, reserve command-mode Escape for closing the picker, and let `/` refocus the existing query. The footer now lists the shortcuts for the active mode so command keys are not advertised while text input owns them. refs #1115 --- src/app/input/modal.rs | 120 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 17 deletions(-) (limited to 'src/app/input') diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index 82d393d6..84aed8da 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -165,15 +165,7 @@ pub(crate) fn handle_navigator_key( if state.navigator.search_focused { match key.code { KeyCode::Esc => { - if state.navigator.query.is_empty() { - state.navigator.search_focused = false; - leave_modal(state); - } else { - state.navigator.query.clear(); - state.navigator.state_filter = None; - state.navigator.search_focused = false; - state.clamp_navigator_selection_from(terminal_runtimes); - } + state.navigator.search_focused = false; } KeyCode::Enter => { state.accept_navigator_selection_from(terminal_runtimes); @@ -208,19 +200,12 @@ pub(crate) fn handle_navigator_key( match key.code { KeyCode::Esc => { - if state.navigator.query.is_empty() && state.navigator.state_filter.is_none() { - leave_modal(state); - } else { - state.navigator.query.clear(); - state.navigator.state_filter = None; - state.clamp_navigator_selection_from(terminal_runtimes); - } + leave_modal(state); } KeyCode::Enter => { state.accept_navigator_selection_from(terminal_runtimes); } KeyCode::Char('/') => { - state.navigator.query.clear(); state.navigator.state_filter = None; state.navigator.search_focused = true; state.clamp_navigator_selection_from(terminal_runtimes); @@ -1643,6 +1628,107 @@ mod tests { assert!(state.navigator.query.is_empty()); } + #[test] + fn navigator_empty_search_escape_returns_to_commands() { + let mut state = state_with_workspaces(&["alpha", "beta"]); + let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); + state.mode = Mode::Navigator; + state.navigator.search_focused = true; + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Navigator); + assert!(!state.navigator.search_focused); + assert!(state.navigator.query.is_empty()); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Char('w'), KeyModifiers::empty()), + ); + + assert_eq!( + state.navigator.state_filter, + Some(NavigatorStateFilter::Working) + ); + assert!(state.navigator.query.is_empty()); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Terminal); + } + + #[test] + fn navigator_search_escape_blurs_then_next_escape_closes() { + let mut state = state_with_workspaces(&["alpha", "beta"]); + let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); + state.mode = Mode::Navigator; + state.navigator.search_focused = true; + state.navigator.query = "a".into(); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Navigator); + assert!(!state.navigator.search_focused); + assert_eq!(state.navigator.query, "a"); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty()), + ); + + assert_eq!(state.navigator.selected, 1); + assert_eq!(state.navigator.query, "a"); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Char('/'), KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Navigator); + assert!(state.navigator.search_focused); + assert_eq!(state.navigator.query, "a"); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty()), + ); + + assert_eq!(state.navigator.query, "al"); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Navigator); + assert!(!state.navigator.search_focused); + + handle_navigator_key( + &mut state, + &terminal_runtimes, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Terminal); + } + #[test] fn open_rename_active_tab_can_prefill_default_new_tab_name() { let mut state = state_with_workspaces(&["test"]); -- cgit v1.2.3