diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-08 15:37:01 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-08 15:37:01 +0300 |
| commit | bb0dad86f81768c8b7d5894acf7cd53bd05d302b (patch) | |
| tree | bf132301605fcfc40c992848b85da9d453647aaf /src/app | |
| parent | ac32c1a19bff6373a3b24618f8e553834e960bdc (diff) | |
fix: split panes in target tab without stealing focus
fixes #75
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/api.rs | 25 | ||||
| -rw-r--r-- | src/app/mod.rs | 121 |
2 files changed, 140 insertions, 6 deletions
diff --git a/src/app/api.rs b/src/app/api.rs index e689d89f..ab8058cf 100644 --- a/src/app/api.rs +++ b/src/app/api.rs @@ -819,7 +819,6 @@ impl App { }) .unwrap(); }; - ws.layout.focus_pane(target_pane_id); let direction = match params.direction { crate::api::schema::SplitDirection::Right => { ratatui::layout::Direction::Horizontal @@ -828,16 +827,18 @@ impl App { ratatui::layout::Direction::Vertical } }; - let new_pane_id = match ws.split_focused( + let (target_tab_idx, new_pane_id) = match ws.split_pane( + target_pane_id, direction, rows, cols, params.cwd.map(std::path::PathBuf::from), self.state.pane_scrollback_limit_bytes, self.state.host_terminal_theme, + params.focus, ) { - Ok(new_pane_id) => new_pane_id, - Err(err) => { + Some(Ok(result)) => result, + Some(Err(err)) => { return serde_json::to_string(&ErrorResponse { id: request.id, error: ErrorBody { @@ -847,9 +848,21 @@ impl App { }) .unwrap(); } + None => { + return serde_json::to_string(&ErrorResponse { + id: request.id, + error: ErrorBody { + code: "pane_not_found".into(), + message: format!("pane {} not found", params.target_pane_id), + }, + }) + .unwrap(); + } }; - if !params.focus { - ws.layout.focus_pane(target_pane_id); + if params.focus { + self.state.switch_workspace(ws_idx); + self.state.switch_tab(target_tab_idx); + self.state.mode = Mode::Terminal; } self.schedule_session_save(); let pane = self.pane_info(ws_idx, new_pane_id).unwrap(); diff --git a/src/app/mod.rs b/src/app/mod.rs index 32e89f0e..1d09230a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1436,6 +1436,127 @@ mod tests { assert!(app.state.should_quit); } + #[tokio::test] + async fn pane_split_request_targets_pane_in_background_tab() { + let _guard = config_env_lock().lock().unwrap(); + let original_shell = std::env::var_os("SHELL"); + std::env::set_var("SHELL", "/usr/bin/true"); + + let mut app = test_app(); + let mut workspace = Workspace::test_new("api-pane-split-background-tab"); + let active_pane = workspace.tabs[0].root_pane; + let background_tab = workspace.test_add_tab(Some("worker")); + let target_pane = workspace.tabs[background_tab].root_pane; + workspace.switch_tab(background_tab); + let background_previous_focus = + workspace.test_split(ratatui::layout::Direction::Horizontal); + workspace.switch_tab(0); + app.state.workspaces = vec![workspace]; + app.state.active = Some(0); + app.state.selected = 0; + + let target_pane_id = app.pane_info(0, target_pane).unwrap().pane_id; + let target_tab_id = app.public_tab_id(0, background_tab).unwrap(); + + let response = app.handle_api_request(crate::api::schema::Request { + id: "req_pane_split_background_tab".into(), + method: crate::api::schema::Method::PaneSplit(crate::api::schema::PaneSplitParams { + workspace_id: None, + target_pane_id, + direction: crate::api::schema::SplitDirection::Right, + cwd: None, + focus: false, + }), + }); + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + + assert_eq!(response["result"]["type"], "pane_info"); + assert_eq!(response["result"]["pane"]["tab_id"], target_tab_id); + assert_eq!(response["result"]["pane"]["focused"], false); + assert_eq!(app.state.active, Some(0)); + assert_eq!(app.state.workspaces[0].active_tab, 0); + assert_eq!( + app.state.workspaces[0].tabs[0].layout.focused(), + active_pane + ); + assert_eq!(app.state.workspaces[0].tabs[0].layout.pane_count(), 1); + assert_eq!( + app.state.workspaces[0].tabs[background_tab] + .layout + .focused(), + background_previous_focus + ); + assert_eq!( + app.state.workspaces[0].tabs[background_tab] + .layout + .pane_count(), + 3 + ); + + let runtimes: Vec<_> = app.state.workspaces[0] + .tabs + .iter_mut() + .flat_map(|tab| tab.runtimes.drain()) + .collect(); + for (pane_id, runtime) in runtimes { + runtime.shutdown(pane_id); + } + match original_shell { + Some(value) => std::env::set_var("SHELL", value), + None => std::env::remove_var("SHELL"), + } + } + + #[tokio::test] + async fn pane_split_request_focuses_new_pane_when_requested() { + let _guard = config_env_lock().lock().unwrap(); + let original_shell = std::env::var_os("SHELL"); + std::env::set_var("SHELL", "/usr/bin/true"); + + let mut app = test_app(); + let mut workspace = Workspace::test_new("api-pane-split-focus-background-tab"); + let background_tab = workspace.test_add_tab(Some("worker")); + workspace.switch_tab(0); + app.state.workspaces = vec![workspace]; + app.state.active = Some(0); + app.state.selected = 0; + + let target_pane = app.state.workspaces[0].tabs[background_tab].root_pane; + let target_pane_id = app.pane_info(0, target_pane).unwrap().pane_id; + let target_tab_id = app.public_tab_id(0, background_tab).unwrap(); + + let response = app.handle_api_request(crate::api::schema::Request { + id: "req_pane_split_focus_background_tab".into(), + method: crate::api::schema::Method::PaneSplit(crate::api::schema::PaneSplitParams { + workspace_id: None, + target_pane_id, + direction: crate::api::schema::SplitDirection::Right, + cwd: None, + focus: true, + }), + }); + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + + assert_eq!(response["result"]["type"], "pane_info"); + assert_eq!(response["result"]["pane"]["tab_id"], target_tab_id); + assert_eq!(response["result"]["pane"]["focused"], true); + assert_eq!(app.state.active, Some(0)); + assert_eq!(app.state.workspaces[0].active_tab, background_tab); + + let runtimes: Vec<_> = app.state.workspaces[0] + .tabs + .iter_mut() + .flat_map(|tab| tab.runtimes.drain()) + .collect(); + for (pane_id, runtime) in runtimes { + runtime.shutdown(pane_id); + } + match original_shell { + Some(value) => std::env::set_var("SHELL", value), + None => std::env::remove_var("SHELL"), + } + } + #[test] fn pane_close_request_closes_only_the_target_tab_when_other_tabs_exist() { let mut app = test_app(); |
