diff --git a/docs/remote/sftp.mdx b/docs/remote/sftp.mdx index b983b3a1..1e16f91e 100644 --- a/docs/remote/sftp.mdx +++ b/docs/remote/sftp.mdx @@ -13,9 +13,12 @@ second login, no second password. ## Browsing -The panel opens on the remote home directory. **Go to Shell Directory** in the -overflow menu jumps it to wherever the pane's shell currently is, which is -usually where you actually want to be. +The panel opens wherever the pane's shell currently is. If tty7 cannot tell +(the remote shell does not report its directory), it opens on the last folder +you browsed in that pane, or on the remote home directory the first time. +Switching between panes with the panel open brings each pane back to the folder +you left it on. **Go to Shell Directory** in the overflow menu jumps back to the +shell's directory at any time. Right-click a row for **Edit** (**Open** on a directory), **Download**, **Follow Symlink**, **Rename**, **chmod…**, and delete. The overflow menu adds diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index 477b87b6..ad88772e 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -429,6 +429,7 @@ impl Tty7App { self.sftp_close_browser(cx); } if !panel_open { + self.sftp_panel.panel_was_closed = true; return None; } let width = self.right_panel_px(window, cx); diff --git a/src/ui/sftp.rs b/src/ui/sftp.rs index 660811d1..b33a2895 100644 --- a/src/ui/sftp.rs +++ b/src/ui/sftp.rs @@ -150,6 +150,10 @@ pub(crate) struct SftpPanelState { pub(crate) open_workspace: Option, pub(crate) cwd: String, pub(crate) cwds: std::collections::HashMap, + /// The side panel has been closed since the browser last opened, so the + /// next open is the user asking to look at files again rather than the + /// browser following a pane switch. See [`sftp_start_dir`]. + pub(crate) panel_was_closed: bool, pub(crate) entries: Vec, pub(crate) filter_input: gpui::Entity, pub(crate) error: Option, @@ -201,6 +205,7 @@ impl SftpPanelState { open_workspace: None, cwd: "/".to_string(), cwds: std::collections::HashMap::new(), + panel_was_closed: true, entries: Vec::new(), filter_input, error: None, @@ -444,13 +449,10 @@ impl Tty7App { self.sftp_poll_jobs(cx); self.sftp_start_polling(cx); - if let Some(start) = self - .sftp_panel - .cwds - .get(&pane_id) - .cloned() - .or_else(|| self.pane_shell_cwd(pane_id, window, cx)) - { + let fresh_open = std::mem::take(&mut self.sftp_panel.panel_was_closed); + let remembered = self.sftp_panel.cwds.get(&pane_id).cloned(); + let shell = self.pane_shell_cwd(pane_id, window, cx); + if let Some(start) = sftp_start_dir(fresh_open, shell, remembered) { self.sftp_navigate(start, cx); return; } @@ -1141,6 +1143,7 @@ impl Tty7App { return None; } if !this.right_panel_open(cx) { + this.sftp_panel.panel_was_closed = true; this.sftp_close_browser(cx); return None; } @@ -1976,10 +1979,61 @@ impl Tty7App { } } +/// Where the browser starts when it opens on a pane. +/// +/// Opening the panel afresh is a request to see where the shell is now, so the +/// shell's directory wins over wherever the browser was left last time. When +/// the browser instead follows a pane switch with the panel still open, the +/// user never left it, so the directory they were browsing there comes back. +/// Either falls back to the other; `None` means neither is known and the +/// caller goes to the login directory. +fn sftp_start_dir( + fresh_open: bool, + shell_cwd: Option, + remembered: Option, +) -> Option { + if fresh_open { + shell_cwd.or(remembered) + } else { + remembered.or(shell_cwd) + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn a_fresh_open_starts_at_the_shell_directory() { + let shell = Some("/srv/app".to_string()); + let last = Some("/var/log".to_string()); + assert_eq!( + sftp_start_dir(true, shell.clone(), last.clone()).as_deref(), + Some("/srv/app") + ); + assert_eq!( + sftp_start_dir(true, None, last.clone()).as_deref(), + Some("/var/log"), + "no shell cwd (no shell integration) falls back to the last browsed" + ); + assert_eq!(sftp_start_dir(true, None, None), None); + } + + #[test] + fn a_pane_switch_returns_to_the_directory_browsed_there() { + let shell = Some("/srv/app".to_string()); + let last = Some("/var/log".to_string()); + assert_eq!( + sftp_start_dir(false, shell.clone(), last).as_deref(), + Some("/var/log") + ); + assert_eq!( + sftp_start_dir(false, shell, None).as_deref(), + Some("/srv/app"), + "a pane never browsed starts at its shell directory" + ); + } + fn upload(job_id: u64, state: SftpJobState) -> SftpJobProgress { SftpJobProgress { job_id, @@ -2243,6 +2297,7 @@ mod gpui_tests { use gpui_component::input::InputState; fn harness(cx: &mut TestAppContext) -> (Entity, VisualTestContext) { + crate::core::config::pin_test_config_dir(); cx.executor().allow_parking(); cx.update(|cx| { gpui_component::init(cx);