aboutsummaryrefslogtreecommitdiff
path: root/src/app/api
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 01:19:39 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 01:20:13 +0300
commit17e2b2b878ca9b8cad60fd26baef549479c13081 (patch)
treeafc5947934ee3dd382805952605224de4c77ce13 /src/app/api
parent4f22e9a73ba4d9029468e078235c5f5ce629d179 (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.rs99
-rw-r--r--src/app/api/plugins/mod.rs2
-rw-r--r--src/app/api/tabs.rs4
-rw-r--r--src/app/api/workspaces.rs4
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,