From d474e4ade3064405d4cc27b65ade1118139977c3 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:03:04 +0800 Subject: [PATCH] feat(sftp): open the Files panel at the shell's current directory Reopening the SFTP panel went back to the last browsed folder; a fresh open now prefers the pane shell's cwd, falling back to the last browsed folder, then the login directory. Following a pane switch with the panel still open keeps each pane's last browsed folder. Also pin the test config dir in the sftp gpui harness so filtered runs do not trip the real-config guard, and fix docs/remote/sftp.mdx, which said the panel opens on the remote home directory. Closes #826 --- docs/remote/sftp.mdx | 9 ++++-- src/ui/right_panel.rs | 1 + src/ui/sftp.rs | 69 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 69 insertions(+), 10 deletions(-) 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);