aboutsummaryrefslogtreecommitdiff
path: root/src/app/api
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-02 02:20:40 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-02 02:20:40 +0300
commit48d58648aefcc59964a99be9c23778fe69346663 (patch)
treecd982894e474bfd8b8ce7fbcb81d6487d7a7a064 /src/app/api
parenta9111cbab2a7550c7e84ec94c8b388de9a44daa3 (diff)
fix: follow cached cwd for new tabs
refs #912
Diffstat (limited to 'src/app/api')
-rw-r--r--src/app/api/layouts.rs81
-rw-r--r--src/app/api/panes.rs9
-rw-r--r--src/app/api/tabs.rs72
3 files changed, 132 insertions, 30 deletions
diff --git a/src/app/api/layouts.rs b/src/app/api/layouts.rs
index c7c02f5f..a2705acc 100644
--- a/src/app/api/layouts.rs
+++ b/src/app/api/layouts.rs
@@ -345,11 +345,9 @@ impl App {
&self.terminal_runtimes,
)
});
- self.resolve_new_terminal_cwd(follow_cwd.or_else(|| {
- self.state
- .focused_runtime_in_workspace(&self.terminal_runtimes, ws_idx)
- .and_then(|runtime| runtime.cwd())
- }))
+ self.resolve_new_terminal_cwd(
+ follow_cwd.or_else(|| self.focused_pane_cwd_in_workspace(ws_idx)),
+ )
}
fn apply_layout_node_to_pane(
@@ -395,16 +393,11 @@ impl App {
let default_shell = self.state.default_shell.clone();
let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes;
let host_terminal_theme = self.state.host_terminal_theme;
- let cwd = pane.cwd.as_ref().map(PathBuf::from).or_else(|| {
- self.state.workspaces.get(ws_idx).and_then(|ws| {
- let tab_idx = ws.find_tab_index_for_pane(target_pane_id)?;
- ws.tabs.get(tab_idx)?.cwd_for_pane(
- target_pane_id,
- &self.state.terminals,
- &self.terminal_runtimes,
- )
- })
- });
+ let cwd = pane
+ .cwd
+ .as_ref()
+ .map(PathBuf::from)
+ .or_else(|| self.cwd_for_pane_in_workspace(ws_idx, target_pane_id));
let extra_env = super::env::normalize_launch_env(pane.env.clone())
.map_err(|(_, message)| message.to_string())?;
let direction = match direction {
@@ -596,10 +589,20 @@ mod tests {
use super::*;
use crate::{
api::schema::{ErrorResponse, ResponseResult, SuccessResponse},
- config::Config,
+ config::{Config, ShellModeConfig},
workspace::Workspace,
};
+ #[cfg(windows)]
+ fn exiting_test_command() -> &'static str {
+ "C:\\Windows\\System32\\whoami.exe"
+ }
+
+ #[cfg(not(windows))]
+ fn exiting_test_command() -> &'static str {
+ "/usr/bin/true"
+ }
+
fn app_with_workspace() -> App {
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
let mut app = App::new(
@@ -609,6 +612,8 @@ mod tests {
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("layout")];
app.state.active = Some(0);
app.state.selected = 0;
@@ -616,6 +621,13 @@ mod tests {
app
}
+ fn shutdown_test_runtimes(app: &mut App) {
+ let runtimes: Vec<_> = app.terminal_runtimes.drain().collect();
+ for (_terminal_id, runtime) in runtimes {
+ runtime.shutdown();
+ }
+ }
+
#[test]
fn layout_export_returns_portable_tree() {
let mut app = app_with_workspace();
@@ -779,6 +791,43 @@ mod tests {
second_pane.command,
Some(vec!["sh".into(), "-c".into(), "true".into()])
);
+ shutdown_test_runtimes(&mut app);
+ }
+
+ #[tokio::test]
+ async fn layout_apply_new_tab_follows_cached_focused_pane_cwd_without_runtime() {
+ let mut app = app_with_workspace();
+ let focused_pane = app.state.workspaces[0].tabs[0].root_pane;
+ let cached_cwd = std::env::temp_dir();
+ let terminal_id = app.state.workspaces[0]
+ .terminal_id(focused_pane)
+ .cloned()
+ .unwrap();
+ app.state.terminals.get_mut(&terminal_id).unwrap().cwd = cached_cwd.clone();
+
+ let response = app.handle_layout_apply(
+ "req".into(),
+ LayoutApplyParams {
+ workspace_id: None,
+ tab_id: None,
+ tab_label: Some("cached".into()),
+ focus: false,
+ root: LayoutNode::Pane {
+ pane: LayoutPane::default(),
+ },
+ },
+ );
+
+ let success: SuccessResponse = serde_json::from_str(&response).unwrap();
+ assert!(matches!(success.result, ResponseResult::LayoutApply { .. }));
+ let created = &app.state.workspaces[0].tabs[1];
+ let created_terminal_id = created.terminal_id(created.root_pane).unwrap();
+ let created_cwd = &app.state.terminals.get(created_terminal_id).unwrap().cwd;
+ assert_eq!(
+ crate::worktree::canonical_or_original(created_cwd),
+ crate::worktree::canonical_or_original(&cached_cwd)
+ );
+ shutdown_test_runtimes(&mut app);
}
#[tokio::test]
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs
index 5723be1e..a0428b27 100644
--- a/src/app/api/panes.rs
+++ b/src/app/api/panes.rs
@@ -51,14 +51,7 @@ impl App {
};
let (rows, cols) = self.state.estimate_pane_size();
let split_cwd = params.cwd.map(std::path::PathBuf::from).or_else(|| {
- let follow_cwd = self.state.workspaces.get(ws_idx).and_then(|ws| {
- let tab_idx = ws.find_tab_index_for_pane(target_pane_id)?;
- ws.tabs.get(tab_idx)?.cwd_for_pane(
- target_pane_id,
- &self.state.terminals,
- &self.terminal_runtimes,
- )
- });
+ let follow_cwd = self.cwd_for_pane_in_workspace(ws_idx, target_pane_id);
Some(self.resolve_new_terminal_cwd(follow_cwd))
});
let default_shell = self.state.default_shell.clone();
diff --git a/src/app/api/tabs.rs b/src/app/api/tabs.rs
index abd05b29..ce126ca7 100644
--- a/src/app/api/tabs.rs
+++ b/src/app/api/tabs.rs
@@ -63,11 +63,7 @@ impl App {
return encode_error(id, "workspace_not_found", "no active workspace");
};
let cwd = cwd.map(PathBuf::from).unwrap_or_else(|| {
- let follow_cwd = self
- .state
- .focused_runtime_in_workspace(&self.terminal_runtimes, ws_idx)
- .and_then(|rt| rt.cwd());
- self.resolve_new_terminal_cwd(follow_cwd)
+ self.resolve_new_terminal_cwd(self.focused_pane_cwd_in_workspace(ws_idx))
});
let (rows, cols) = self.state.estimate_pane_size();
let default_shell = self.state.default_shell.clone();
@@ -297,7 +293,28 @@ fn tab_not_found(id: String, tab_id: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{api::schema::SuccessResponse, config::Config, workspace::Workspace};
+ use crate::{
+ api::schema::SuccessResponse,
+ config::{Config, ShellModeConfig},
+ workspace::Workspace,
+ };
+
+ #[cfg(windows)]
+ fn exiting_test_command() -> &'static str {
+ "C:\\Windows\\System32\\whoami.exe"
+ }
+
+ #[cfg(not(windows))]
+ fn exiting_test_command() -> &'static str {
+ "/usr/bin/true"
+ }
+
+ fn shutdown_test_runtimes(app: &mut App) {
+ let runtimes: Vec<_> = app.terminal_runtimes.drain().collect();
+ for (_terminal_id, runtime) in runtimes {
+ runtime.shutdown();
+ }
+ }
#[test]
fn api_tab_move_reorders_tabs_in_target_workspace() {
@@ -342,4 +359,47 @@ mod tests {
)
}));
}
+
+ #[tokio::test]
+ async fn tab_create_follows_cached_focused_pane_cwd_without_runtime() {
+ let event_hub = crate::api::EventHub::default();
+ let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
+ let mut app = App::new(&Config::default(), true, None, api_rx, event_hub);
+ app.state.default_shell = exiting_test_command().into();
+ app.state.shell_mode = ShellModeConfig::NonLogin;
+ let workspace = Workspace::test_new("tabs");
+ let focused_pane = workspace.tabs[0].root_pane;
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.ensure_test_terminals();
+ let cached_cwd = std::env::temp_dir();
+ let terminal_id = app.state.workspaces[0]
+ .terminal_id(focused_pane)
+ .cloned()
+ .unwrap();
+ app.state.terminals.get_mut(&terminal_id).unwrap().cwd = cached_cwd.clone();
+
+ let response = app.handle_tab_create(
+ "req".into(),
+ TabCreateParams {
+ workspace_id: None,
+ cwd: None,
+ focus: false,
+ label: None,
+ env: Default::default(),
+ },
+ );
+
+ let success: SuccessResponse = serde_json::from_str(&response).unwrap();
+ assert!(matches!(success.result, ResponseResult::TabCreated { .. }));
+ let created = &app.state.workspaces[0].tabs[1];
+ let created_terminal_id = created.terminal_id(created.root_pane).unwrap();
+ let created_cwd = &app.state.terminals.get(created_terminal_id).unwrap().cwd;
+ assert_eq!(
+ crate::worktree::canonical_or_original(created_cwd),
+ crate::worktree::canonical_or_original(&cached_cwd)
+ );
+ shutdown_test_runtimes(&mut app);
+ }
}