diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-02 13:58:26 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-02 13:58:26 +0300 |
| commit | cf3769feb9837c6040820c3e6096c5cc2907adc0 (patch) | |
| tree | 1bf055988874a2e1c871bf4830a920a240108bda /src/app/api/workspaces.rs | |
| parent | 2c422e60f1724ac5639c80abb73dc1f10a91b5c6 (diff) | |
feat: follow focused pane cwd for new workspaces
refs #919
Diffstat (limited to 'src/app/api/workspaces.rs')
| -rw-r--r-- | src/app/api/workspaces.rs | 89 |
1 files changed, 86 insertions, 3 deletions
diff --git a/src/app/api/workspaces.rs b/src/app/api/workspaces.rs index f4189a04..19e4f6ae 100644 --- a/src/app/api/workspaces.rs +++ b/src/app/api/workspaces.rs @@ -40,9 +40,10 @@ impl App { params: WorkspaceCreateParams, ) -> String { let cwd = params.cwd.map(PathBuf::from).unwrap_or_else(|| { - let follow_cwd = self - .workspace_creation_source() - .and_then(|ws_idx| self.seed_cwd_from_workspace(ws_idx)); + let follow_cwd = self.workspace_creation_source().and_then(|ws_idx| { + self.focused_pane_cwd_in_workspace(ws_idx) + .or_else(|| self.seed_cwd_from_workspace(ws_idx)) + }); self.resolve_new_terminal_cwd(follow_cwd) }); let extra_env = match super::env::normalize_launch_env(params.env) { @@ -212,6 +213,88 @@ mod tests { use super::*; use crate::{api::schema::SuccessResponse, config::Config, workspace::Workspace}; + // `new_cwd = follow` must anchor on the focused pane for every creation + // surface. Splits and tabs already do; a new workspace must follow the + // focused pane too, not the source workspace's first-tab root pane. + #[tokio::test] + async fn workspace_create_follows_focused_pane_cwd_not_first_tab_root() { + use super::super::test_support::{exiting_test_command, shutdown_test_runtimes}; + use crate::config::ShellModeConfig; + + let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut app = App::new( + &Config::default(), + true, + None, + api_rx, + crate::api::EventHub::default(), + ); + app.state.default_shell = exiting_test_command().into(); + app.state.shell_mode = ShellModeConfig::NonLogin; + app.state.workspaces = vec![Workspace::test_new("spaces")]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.ensure_test_terminals(); + + // Second tab becomes the focused pane, away from tab 1's root pane. + let response = app.handle_tab_create( + "tab".into(), + crate::api::schema::TabCreateParams { + workspace_id: None, + cwd: None, + focus: true, + label: None, + env: Default::default(), + }, + ); + let _: SuccessResponse = serde_json::from_str(&response).unwrap(); + // Drop runtimes so cwd resolution deterministically uses cached state. + shutdown_test_runtimes(&mut app); + + let focused_cwd = std::env::temp_dir().join(format!( + "herdr-ws-follow-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::create_dir_all(&focused_cwd).unwrap(); + let ws = &app.state.workspaces[0]; + let root_cwd = ws.identity_cwd.clone(); + let focused_pane = ws.focused_pane_id().unwrap(); + assert_ne!(focused_pane, ws.tabs[0].root_pane); + let terminal_id = ws.terminal_id(focused_pane).cloned().unwrap(); + app.state.terminals.get_mut(&terminal_id).unwrap().cwd = focused_cwd.clone(); + + let response = app.handle_workspace_create( + "req".into(), + WorkspaceCreateParams { + cwd: None, + focus: false, + label: None, + env: Default::default(), + }, + ); + + let success: SuccessResponse = serde_json::from_str(&response).unwrap(); + assert!(matches!( + success.result, + ResponseResult::WorkspaceCreated { .. } + )); + let created_cwd = &app.state.workspaces[1].identity_cwd; + assert_eq!( + crate::worktree::canonical_or_original(created_cwd), + crate::worktree::canonical_or_original(&focused_cwd) + ); + assert_ne!( + crate::worktree::canonical_or_original(created_cwd), + crate::worktree::canonical_or_original(&root_cwd) + ); + shutdown_test_runtimes(&mut app); + let _ = std::fs::remove_dir_all(&focused_cwd); + } + fn app_with_linked_worktree() -> App { let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); let mut app = App::new( |
