diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-19 22:50:27 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-19 22:51:24 +0300 |
| commit | 17f5ac10b090d2486e676d5e0c8c3077dd869deb (patch) | |
| tree | e6bbf7446b9d8ee2dc311ed3bf84b690031a780b /src | |
| parent | fede9b533c131b33fff6ebe3c931993807d14650 (diff) | |
fix: handle rename prompt editing shortcuts
refs #204
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/input/modal.rs | 162 |
1 files changed, 151 insertions, 11 deletions
diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index f40684f1..954f97de 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -1,4 +1,4 @@ -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Direction, Rect}; use crate::{ @@ -354,6 +354,67 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) { } } +fn clear_rename_input(state: &mut AppState) { + state.name_input.clear(); + state.name_input_replace_on_type = false; +} + +fn delete_rename_input_char(state: &mut AppState) { + if state.name_input_replace_on_type { + clear_rename_input(state); + } else { + state.name_input.pop(); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RenameWordDeleteClass { + Word, + Separator, +} + +fn rename_word_delete_class(ch: char) -> RenameWordDeleteClass { + if ch.is_alphanumeric() || ch == '_' { + RenameWordDeleteClass::Word + } else { + RenameWordDeleteClass::Separator + } +} + +fn delete_rename_input_word(state: &mut AppState) { + if state.name_input_replace_on_type { + clear_rename_input(state); + return; + } + + while state + .name_input + .chars() + .last() + .is_some_and(char::is_whitespace) + { + state.name_input.pop(); + } + + let Some(class) = state + .name_input + .chars() + .last() + .map(rename_word_delete_class) + else { + return; + }; + + while state + .name_input + .chars() + .last() + .is_some_and(|ch| !ch.is_whitespace() && rename_word_delete_class(ch) == class) + { + state.name_input.pop(); + } +} + pub(crate) fn handle_rename_key(state: &mut AppState, key: KeyEvent) { if let Some(action) = modal_action_from_key(&key, RENAME_ACTIONS) { apply_rename_action(state, action); @@ -361,18 +422,25 @@ pub(crate) fn handle_rename_key(state: &mut AppState, key: KeyEvent) { } match key.code { - KeyCode::Backspace => { - if state.name_input_replace_on_type { - state.name_input.clear(); - state.name_input_replace_on_type = false; - } else { - state.name_input.pop(); - } + KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => { + clear_rename_input(state); + } + KeyCode::Backspace if key.modifiers.contains(KeyModifiers::SUPER) => { + clear_rename_input(state); } - KeyCode::Char(c) => { + KeyCode::Backspace + if key.modifiers.contains(KeyModifiers::CONTROL) + || key.modifiers.contains(KeyModifiers::ALT) => + { + delete_rename_input_word(state); + } + KeyCode::Char('h' | 'w') if key.modifiers.contains(KeyModifiers::CONTROL) => { + delete_rename_input_word(state); + } + KeyCode::Backspace => delete_rename_input_char(state), + KeyCode::Char(c) if key.modifiers.difference(KeyModifiers::SHIFT).is_empty() => { if state.name_input_replace_on_type { - state.name_input.clear(); - state.name_input_replace_on_type = false; + clear_rename_input(state); } state.name_input.push(c); } @@ -664,6 +732,78 @@ mod tests { } #[test] + fn rename_modal_handles_line_editing_shortcuts() { + let mut state = state_with_workspaces(&["test"]); + state.mode = Mode::RenameWorkspace; + state.name_input = "website zero".into(); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()), + ); + assert_eq!(state.name_input, "website zer"); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Backspace, KeyModifiers::CONTROL), + ); + assert_eq!(state.name_input, "website "); + + state.name_input = "website-zero".into(); + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT), + ); + assert_eq!(state.name_input, "website-"); + + state.name_input = "website-zero".into(); + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL), + ); + assert_eq!(state.name_input, "website-"); + + state.name_input = "website-zero".into(); + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL), + ); + assert_eq!(state.name_input, "website-"); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Backspace, KeyModifiers::SUPER), + ); + assert!(state.name_input.is_empty()); + + state.name_input = "website zero".into(); + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL), + ); + assert!(state.name_input.is_empty()); + } + + #[test] + fn rename_modal_does_not_insert_modified_shortcut_chars() { + let mut state = state_with_workspaces(&["test"]); + state.mode = Mode::RenameWorkspace; + state.name_input = "website".into(); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL), + ); + assert_eq!(state.name_input, "website"); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('Z'), KeyModifiers::SHIFT), + ); + assert_eq!(state.name_input, "websiteZ"); + } + + #[test] fn open_rename_active_tab_can_prefill_default_new_tab_name() { let mut state = state_with_workspaces(&["test"]); state.workspaces[0].test_add_tab(None); |
