feat: the SFTP browser opens in the shell's current directory (OSC 7)

When the remote shell reports its working directory (OSC 7: fish, or
the bash/zsh hook in the manual), opening the SFTP browser from that
terminal goes there, including when it was already open elsewhere.
Without it the browser still falls back to the exec'd 'pwd' (the login
directory) and then the SFTP home. The File menu entry now goes through
the same request as the toolbar and palette.
This commit is contained in:
Andy Dixon
2026-10-12 18:40:11 +01:00
parent 58cb531e32
commit 1fcb5fa575
3 changed files with 21 additions and 4 deletions
+7 -1
View File
@@ -614,7 +614,13 @@ impl App {
} }
Request::ExportEncrypted(ids) => self.transfer.export_encrypted(ids), Request::ExportEncrypted(ids) => self.transfer.export_encrypted(ids),
Request::ImportEncrypted => self.transfer.import_encrypted(), Request::ImportEncrypted => self.transfer.import_encrypted(),
Request::Sftp(id) => self.sftp.toggle(&id), Request::Sftp(id) => {
let at = match self.s.pane(&id).map(|p| &p.view) {
Some(View::Terminal(t)) => t.reported_cwd.as_ref().map(|c| c.1.clone()),
_ => None,
};
self.sftp.toggle(&id, at);
}
Request::SendToActive(text) => match self.s.focused_session() { Request::SendToActive(text) => match self.s.focused_session() {
Some(id) if self.s.session_state(&id) == Some(SessionState::Connected) => self.send_input(&id, text.as_bytes()), Some(id) if self.s.session_state(&id) == Some(SessionState::Connected) => self.send_input(&id, text.as_bytes()),
_ => self.s.error("No active connected session"), _ => self.s.error("No active connected session"),
+1 -1
View File
@@ -132,7 +132,7 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) {
if item(ui, icon::FOLDER_OPEN, "SFTP browser", "") if item(ui, icon::FOLDER_OPEN, "SFTP browser", "")
&& let Some(f) = focused.clone() && let Some(f) = focused.clone()
{ {
app.sftp.toggle(&f); app.s.request(crate::state::Request::Sftp(f));
} }
if item(ui, icon::ARROWS_LEFT_RIGHT, "Port forwards", "") if item(ui, icon::ARROWS_LEFT_RIGHT, "Port forwards", "")
&& let Some(f) = focused.clone() && let Some(f) = focused.clone()
+13 -2
View File
@@ -109,6 +109,8 @@ pub struct Sftp {
/// Profile id (or host:port for quick connects) → bookmarked paths. Loaded on first use. /// Profile id (or host:port for quick connects) → bookmarked paths. Loaded on first use.
bookmarks: Option<BTreeMap<String, Vec<String>>>, bookmarks: Option<BTreeMap<String, Vec<String>>>,
inbox: Arc<Mutex<Vec<Done>>>, inbox: Arc<Mutex<Vec<Done>>>,
/// Where a browser opened from a terminal should start: the directory its shell reported.
start_at: HashMap<String, String>,
} }
// --- path helpers --------------------------------------------------------------------------- // --- path helpers ---------------------------------------------------------------------------
@@ -215,11 +217,16 @@ fn state_line(ui: &mut Ui, loading: bool, error: Option<&str>) -> bool {
} }
impl Sftp { impl Sftp {
pub fn toggle(&mut self, session: &str) { /// Opens (or closes) the browser for `session`; `at` is the shell's reported directory, which
/// the browser goes to when it opens.
pub fn toggle(&mut self, session: &str, at: Option<String>) {
if self.open_for.as_deref() == Some(session) { if self.open_for.as_deref() == Some(session) {
self.close(session); self.close(session);
} else { } else {
self.open_for = Some(session.into()); self.open_for = Some(session.into());
if let Some(dir) = at.filter(|d| d.starts_with('/')) {
self.start_at.insert(session.into(), dir);
}
} }
} }
@@ -488,7 +495,11 @@ impl Sftp {
let Some(session) = self.open_for.clone() else { let Some(session) = self.open_for.clone() else {
return; return;
}; };
if !self.panels.contains_key(&session) { if let Some(dir) = self.start_at.remove(&session) {
if self.panels.get(&session).is_none_or(|p| p.cwd != dir) {
self.navigate(s, &session, Some(dir));
}
} else if !self.panels.contains_key(&session) {
self.navigate(s, &session, None); self.navigate(s, &session, None);
} }
let t = Tokens::get(ui.ctx()); let t = Tokens::get(ui.ctx());