diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-07 01:19:39 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-07 01:20:13 +0300 |
| commit | 17e2b2b878ca9b8cad60fd26baef549479c13081 (patch) | |
| tree | afc5947934ee3dd382805952605224de4c77ce13 /src/app/api | |
| parent | 4f22e9a73ba4d9029468e078235c5f5ce629d179 (diff) | |
fix: honor prefix in copy mode
refs #681
Based on PR #1092 by @reobin.
Co-authored-by: Robin Gagnon <me@reobin.dev>
Diffstat (limited to 'src/app/api')
| -rw-r--r-- | src/app/api/panes.rs | 99 | ||||
| -rw-r--r-- | src/app/api/plugins/mod.rs | 2 | ||||
| -rw-r--r-- | src/app/api/tabs.rs | 4 | ||||
| -rw-r--r-- | src/app/api/workspaces.rs | 4 |
4 files changed, 95 insertions, 14 deletions
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs index 60b2e93b..14c62942 100644 --- a/src/app/api/panes.rs +++ b/src/app/api/panes.rs @@ -14,7 +14,9 @@ use crate::api::schema::{ PaneZoomResult, ReadFormat, ReadSource, ResponseResult, }; use crate::app::actions::{PaneZoomCommand, PaneZoomNoopReason}; -use crate::app::{App, Mode}; +use crate::app::App; +#[cfg(test)] +use crate::app::Mode; use crate::layout::{find_in_direction, NavDirection, PaneId}; use super::super::api_helpers::{ @@ -102,7 +104,7 @@ impl App { self.state.switch_workspace_tab(ws_idx, target_tab_idx); self.state .record_pane_focus_change(previous_focus, ws_idx, new_pane.pane_id); - self.state.mode = Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); } self.terminal_runtimes .insert(new_pane.terminal.id.clone(), new_pane.runtime); @@ -164,7 +166,7 @@ impl App { }; self.state.focus_pane_in_workspace(ws_idx, pane_id); - self.state.mode = Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); let Some(pane) = self.pane_info(ws_idx, pane_id) else { return pane_not_found(id, &target.pane_id); @@ -356,7 +358,7 @@ impl App { if let Some(target_pane_id) = target { self.state.focus_pane_in_workspace(ws_idx, target_pane_id); self.state.switch_workspace_tab(ws_idx, tab_idx); - self.state.mode = Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); } let focused_pane_id = self .state @@ -947,7 +949,7 @@ impl App { .switch_workspace_tab(target_ws_idx, target_tab_idx); self.state .record_pane_focus_change(previous_focus, target_ws_idx, moved_pane_id); - self.state.mode = Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); } let created_workspace = created_workspace.then(|| self.workspace_info(target_ws_idx)); let created_tab = if created_tab { @@ -1104,7 +1106,7 @@ impl App { if outcome.changed || outcome.focus_changed { self.schedule_session_save(); } - self.state.mode = Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); let Some(layout) = self.pane_layout_snapshot(ws_idx, tab_idx) else { return encode_error(id, "pane_layout_unavailable", "pane layout unavailable"); }; @@ -1485,7 +1487,7 @@ impl App { }; ws.close_pane(pane_id) }; - self.state.plugin_panes.remove(&pane_id); + self.state.remove_plugin_pane_records([pane_id]); if should_close_workspace { self.state.selected = ws_idx; self.state.close_selected_workspace(); @@ -2456,6 +2458,48 @@ mod tests { } #[test] + fn api_pane_move_focuses_copy_mode_pane_back_into_copy_mode() { + let mut app = app_with_linked_worktree(); + let source = app.state.workspaces[0].tabs[0].root_pane; + let target_tab = app.state.workspaces[0].test_add_tab(Some("target")); + let target = app.state.workspaces[0].tabs[target_tab].root_pane; + seed_terminal_states(&mut app); + app.state.copy_mode = Some(crate::app::state::CopyModeState { + pane_id: source, + cursor_row: 0, + cursor_col: 0, + entry_offset_from_bottom: 0, + selection: None, + }); + let source_public = app.public_pane_id(0, source).unwrap(); + let target_public = app.public_pane_id(0, target).unwrap(); + let target_tab_public = app.public_tab_id(0, target_tab).unwrap(); + + let response = app.handle_pane_move( + "req".into(), + PaneMoveParams { + pane_id: source_public, + destination: PaneMoveDestination::Tab { + tab_id: target_tab_public, + target_pane_id: Some(target_public), + split: SplitDirection::Right, + ratio: None, + }, + focus: true, + }, + ); + + let success: SuccessResponse = serde_json::from_str(&response).unwrap(); + let ResponseResult::PaneMove { move_result } = success.result else { + panic!("expected pane move response"); + }; + assert!(move_result.changed); + assert_eq!(app.state.mode, Mode::Copy); + assert_eq!(app.state.copy_mode.expect("copy mode").pane_id, source); + assert_eq!(app.state.workspaces[0].tabs[0].layout.focused(), source); + } + + #[test] fn api_pane_move_to_existing_tab_across_workspace_reassigns_public_pane_id() { let mut app = app_with_linked_worktree(); app.state.workspaces.push(Workspace::test_new("other")); @@ -3044,6 +3088,47 @@ mod tests { } #[test] + fn api_pane_zoom_focuses_copy_mode_pane_back_into_copy_mode() { + let mut app = app_with_linked_worktree(); + app.state.workspaces.push(Workspace::test_new("other")); + let source = app.state.workspaces[0].tabs[0].root_pane; + let target = app.state.workspaces[1].tabs[0].root_pane; + let _other = app.state.workspaces[0].test_split(ratatui::layout::Direction::Horizontal); + let _target_other = + app.state.workspaces[1].test_split(ratatui::layout::Direction::Horizontal); + app.state.workspaces[1].tabs[0].layout.focus_pane(target); + app.state.active = Some(1); + app.state.selected = 1; + app.state.mode = Mode::Terminal; + app.state.copy_mode = Some(crate::app::state::CopyModeState { + pane_id: source, + cursor_row: 0, + cursor_col: 0, + entry_offset_from_bottom: 0, + selection: None, + }); + let source_public = app.public_pane_id(0, source).unwrap(); + + let response = app.handle_pane_zoom( + "req".into(), + PaneZoomParams { + pane_id: Some(source_public), + mode: PaneZoomMode::On, + }, + ); + + let success: SuccessResponse = serde_json::from_str(&response).unwrap(); + let ResponseResult::PaneZoom { zoom } = success.result else { + panic!("expected pane zoom response"); + }; + assert!(zoom.focus_changed); + assert_eq!(app.state.active, Some(0)); + assert_eq!(app.state.mode, Mode::Copy); + assert_eq!(app.state.workspaces[0].focused_pane_id(), Some(source)); + assert_eq!(app.state.workspaces[1].focused_pane_id(), Some(target)); + } + + #[test] fn api_pane_zoom_single_pane_returns_noop() { let mut app = app_with_linked_worktree(); app.state.active = Some(0); diff --git a/src/app/api/plugins/mod.rs b/src/app/api/plugins/mod.rs index ec0a5d64..b77b4715 100644 --- a/src/app/api/plugins/mod.rs +++ b/src/app/api/plugins/mod.rs @@ -405,7 +405,7 @@ impl App { return encode_error(id, "plugin_pane_not_found", "plugin pane not found"); } self.state.focus_pane_in_workspace(ws_idx, pane_id); - self.state.mode = crate::app::Mode::Terminal; + self.state.settle_terminal_mode_after_focus(); let Some(record) = self.state.plugin_panes.get(&pane_id).cloned() else { return encode_error(id, "plugin_pane_not_found", "plugin pane not found"); }; diff --git a/src/app/api/tabs.rs b/src/app/api/tabs.rs index 1f29f54b..46487cfa 100644 --- a/src/app/api/tabs.rs +++ b/src/app/api/tabs.rs @@ -248,9 +248,7 @@ impl App { format!("tab {} could not be closed", target.tab_id), ); } - for pane_id in pane_ids { - self.state.plugin_panes.remove(&pane_id); - } + self.state.remove_plugin_pane_records(pane_ids); self.state.remove_unattached_terminal_ids(terminal_ids); self.shutdown_detached_terminal_runtimes(); self.schedule_session_save(); diff --git a/src/app/api/workspaces.rs b/src/app/api/workspaces.rs index 19e4f6ae..8899ec44 100644 --- a/src/app/api/workspaces.rs +++ b/src/app/api/workspaces.rs @@ -175,9 +175,7 @@ impl App { .unwrap_or_default(); self.state.selected = index; self.state.close_selected_workspace(); - for pane_id in pane_ids { - self.state.plugin_panes.remove(&pane_id); - } + self.state.remove_plugin_pane_records(pane_ids); self.shutdown_detached_terminal_runtimes(); self.emit_event(EventEnvelope { event: EventKind::WorkspaceClosed, |
