diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-03 17:33:47 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-03 17:33:47 +0300 |
| commit | e82ae0b32981076ec7360e8d4dd5294a7abc05cf (patch) | |
| tree | f91cc8eeba8074315482aed84f5bcf6b4b31dbdb /src/app | |
| parent | 5ed262a43ac7f264a1a7523b6d4507ba4a976604 (diff) | |
feat: reuse rename modal for new tab naming
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/input.rs | 113 | ||||
| -rw-r--r-- | src/app/mod.rs | 20 | ||||
| -rw-r--r-- | src/app/state.rs | 2 |
3 files changed, 109 insertions, 26 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index 7191a8fa..73366576 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -825,8 +825,7 @@ fn execute_navigate_action(state: &mut AppState, action: NavigateAction) { } NavigateAction::RenameWorkspace => { if !state.workspaces.is_empty() { - state.name_input = state.workspaces[state.selected].display_name(); - state.mode = Mode::RenameWorkspace; + open_rename_workspace(state, state.selected); } } NavigateAction::CloseWorkspace => { @@ -851,14 +850,7 @@ fn execute_navigate_action(state: &mut AppState, action: NavigateAction) { state.request_new_tab = true; leave_navigate_mode(state); } - NavigateAction::RenameTab => { - if let Some(ws) = state.active.and_then(|i| state.workspaces.get(i)) { - if let Some(name) = ws.active_tab_display_name() { - state.name_input = name; - state.mode = Mode::RenameTab; - } - } - } + NavigateAction::RenameTab => open_rename_active_tab(state, false), NavigateAction::PreviousTab => { state.previous_tab(); leave_navigate_mode(state); @@ -905,6 +897,23 @@ fn leave_navigate_mode(state: &mut AppState) { } } +fn open_rename_workspace(state: &mut AppState, ws_idx: usize) { + state.selected = ws_idx; + state.name_input = state.workspaces[ws_idx].display_name(); + state.name_input_replace_on_type = false; + state.mode = Mode::RenameWorkspace; +} + +fn open_rename_active_tab(state: &mut AppState, replace_on_type: bool) { + if let Some(ws) = state.active.and_then(|i| state.workspaces.get(i)) { + if let Some(name) = ws.active_tab_display_name() { + state.name_input = name; + state.name_input_replace_on_type = replace_on_type; + state.mode = Mode::RenameTab; + } + } +} + /// Return to the appropriate mode after completing a modal action. /// Goes to Terminal if a workspace is active, otherwise Navigate. fn leave_modal(state: &mut AppState) { @@ -1008,12 +1017,17 @@ fn apply_rename_action(state: &mut AppState, action: ModalAction) { } } state.name_input.clear(); - state.mode = Mode::Navigate; + state.name_input_replace_on_type = false; + leave_modal(state); + } + ModalAction::Clear => { + state.name_input.clear(); + state.name_input_replace_on_type = false; } - ModalAction::Clear => state.name_input.clear(), ModalAction::Cancel => { state.name_input.clear(); - state.mode = Mode::Navigate; + state.name_input_replace_on_type = false; + leave_modal(state); } _ => {} } @@ -1027,9 +1041,18 @@ fn handle_rename_key(state: &mut AppState, key: KeyEvent) { match key.code { KeyCode::Backspace => { - state.name_input.pop(); + 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(c) => { + if state.name_input_replace_on_type { + state.name_input.clear(); + state.name_input_replace_on_type = false; + } state.name_input.push(c); } _ => {} @@ -1091,9 +1114,7 @@ fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuState, idx: let item = menu.items().get(idx).copied(); match (menu.kind, item) { (ContextMenuKind::Workspace { ws_idx }, Some("Rename")) => { - state.selected = ws_idx; - state.name_input = state.workspaces[ws_idx].display_name(); - state.mode = Mode::RenameWorkspace; + open_rename_workspace(state, ws_idx); } (ContextMenuKind::Workspace { ws_idx }, Some("Close")) => { state.selected = ws_idx; @@ -1115,12 +1136,7 @@ fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuState, idx: state.selected = ws_idx; state.active = Some(ws_idx); state.switch_tab(tab_idx); - if let Some(ws) = state.workspaces.get(ws_idx) { - if let Some(name) = ws.active_tab_display_name() { - state.name_input = name; - state.mode = Mode::RenameTab; - } - } + open_rename_active_tab(state, false); } (ContextMenuKind::Tab { ws_idx, tab_idx }, Some("Close")) => { state.selected = ws_idx; @@ -2724,7 +2740,7 @@ mod tests { &mut state, KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), ); - assert_eq!(state.mode, Mode::Navigate); + assert_eq!(state.mode, Mode::Terminal); assert_eq!(state.workspaces[0].display_name(), "renamed"); state.view.sidebar_rect = Rect::new(0, 0, 26, 20); @@ -2738,6 +2754,55 @@ mod tests { } #[test] + fn rename_cancel_returns_to_terminal_when_workspace_is_active() { + let mut state = state_with_workspaces(&["test"]); + state.mode = Mode::RenameTab; + state.name_input = "test".into(); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), + ); + + assert_eq!(state.mode, Mode::Terminal); + assert!(state.name_input.is_empty()); + } + + #[test] + fn rename_modal_replaces_prefilled_text_on_first_type() { + let mut state = state_with_workspaces(&["test"]); + state.mode = Mode::RenameTab; + state.name_input = "2".into(); + state.name_input_replace_on_type = true; + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('n'), KeyModifiers::empty()), + ); + assert_eq!(state.name_input, "n"); + assert!(!state.name_input_replace_on_type); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Char('e'), KeyModifiers::empty()), + ); + assert_eq!(state.name_input, "ne"); + } + + #[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); + state.workspaces[0].switch_tab(1); + + open_rename_active_tab(&mut state, true); + + assert_eq!(state.mode, Mode::RenameTab); + assert_eq!(state.name_input, "2"); + assert!(state.name_input_replace_on_type); + } + + #[test] fn confirm_close_keyboard_actions_are_direct_not_focused() { let mut state = state_with_workspaces(&["a", "b"]); state.mode = Mode::ConfirmClose; diff --git a/src/app/mod.rs b/src/app/mod.rs index 3b744d02..17fc1b5d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -193,6 +193,7 @@ impl App { request_new_tab: false, request_complete_onboarding: false, name_input: String::new(), + name_input_replace_on_type: false, onboarding_step: 0, onboarding_list: state::SelectionListState::new(1), release_notes: startup_release_notes.map(|notes| state::ReleaseNotesState { @@ -1822,8 +1823,23 @@ impl App { .and_then(|ws_idx| self.seed_cwd_from_workspace(ws_idx)) .or_else(|| std::env::current_dir().ok()) .unwrap_or_else(|| std::path::PathBuf::from("/")); - if let Err(e) = self.create_tab_with_options(initial_cwd, true) { - error!(err = %e, "failed to create tab"); + match self.create_tab_with_options(initial_cwd, true) { + Ok(_) => { + if let Some(ws) = self + .state + .active + .and_then(|ws_idx| self.state.workspaces.get(ws_idx)) + { + if let Some(name) = ws.active_tab_display_name() { + self.state.name_input = name; + self.state.name_input_replace_on_type = true; + self.state.mode = Mode::RenameTab; + } + } + } + Err(e) => { + error!(err = %e, "failed to create tab"); + } } } diff --git a/src/app/state.rs b/src/app/state.rs index dee805cd..43174081 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -539,6 +539,7 @@ pub struct AppState { pub request_new_tab: bool, pub request_complete_onboarding: bool, pub name_input: String, + pub name_input_replace_on_type: bool, pub onboarding_step: usize, pub onboarding_list: SelectionListState, pub release_notes: Option<ReleaseNotesState>, @@ -632,6 +633,7 @@ impl AppState { request_new_tab: false, request_complete_onboarding: false, name_input: String::new(), + name_input_replace_on_type: false, onboarding_step: 0, onboarding_list: SelectionListState::new(1), release_notes: None, |
