mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-27 00:02:36 +00:00
fix(sidebar): group native SSH tabs by their remote folder (#891)
A native SSH pane is owned by this machine's daemon, so its paths are kept away from every Host call and it never gets a git_status_cwd. The sidebar only grouped from git_status_cwd, which left every native SSH tab in Scratch under repo-or-directory grouping, even though the remote shell reports its cwd over OSC 7. Fall back to that reported cwd for native SSH panes (absolute POSIX paths only) and resolve it as a settled 'no repo': repo-or-directory files the tab under the folder, repo grouping keeps it in Scratch. Typed 'ssh' and WSL panes are unchanged. Fixes #891
This commit is contained in:
@@ -263,6 +263,23 @@ fn cwd_is_on_host(pane_runs_remotely: bool, host_is_local: bool) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The cwd a native SSH pane's remote shell reported, for the few readers that
|
||||
/// only need a name for it and not a host to act on it.
|
||||
///
|
||||
/// Such a pane belongs to this machine's daemon, so [`cwd_is_on_host`] rightly
|
||||
/// turns its paths away from every `Host` call — there is no host to hand them
|
||||
/// to. But the shell on the far end states them itself (OSC 7), unlike a shell
|
||||
/// that ssh'd onward from a local prompt, whose directory is only ever a guess.
|
||||
/// Only an absolute POSIX path counts: that is what a remote sshd's shell
|
||||
/// reports, and anything else is not a directory worth naming.
|
||||
fn native_ssh_cwd(
|
||||
remote: Option<&RemoteContext>,
|
||||
cwd: Option<std::path::PathBuf>,
|
||||
) -> Option<std::path::PathBuf> {
|
||||
remote.filter(|r| r.kind == crate::daemon::protocol::RemoteKind::NativeSsh)?;
|
||||
cwd.filter(|c| c.to_string_lossy().starts_with('/'))
|
||||
}
|
||||
|
||||
/// Which path dialect a pane's output is written in.
|
||||
///
|
||||
/// A pane running on this machine spells paths the way this OS does, and that
|
||||
@@ -1950,6 +1967,12 @@ impl TerminalView {
|
||||
self.git_status_cwd.as_deref()
|
||||
}
|
||||
|
||||
/// See [`native_ssh_cwd`]. `None` for every pane that is not a native SSH
|
||||
/// one — those either have a `git_status_cwd` or have no cwd to name.
|
||||
pub fn native_ssh_cwd(&self) -> Option<std::path::PathBuf> {
|
||||
native_ssh_cwd(self.remote_context().as_ref(), self.cwd())
|
||||
}
|
||||
|
||||
/// Plant the cwd the git-status poll would have found. For tests that
|
||||
/// need a pane to look like it is sitting somewhere known — a real poll
|
||||
/// needs a live shell reporting a directory, which a quiet test pane has
|
||||
@@ -9754,6 +9777,37 @@ mod tests {
|
||||
assert!(!cwd_is_on_host(false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_native_ssh_pane_names_its_remote_cwd() {
|
||||
use super::{RemoteContext, native_ssh_cwd};
|
||||
use std::path::PathBuf;
|
||||
let native = RemoteContext {
|
||||
kind: RemoteKind::NativeSsh,
|
||||
argv: Vec::new(),
|
||||
target: "ubuntu@box".into(),
|
||||
};
|
||||
let home = || Some(PathBuf::from("/home/ubuntu"));
|
||||
assert_eq!(native_ssh_cwd(Some(&native), home()), home());
|
||||
|
||||
// `ssh` typed at a local prompt: the directory is a guess, not a
|
||||
// report from the far end.
|
||||
let typed = RemoteContext {
|
||||
kind: RemoteKind::Ssh,
|
||||
..native.clone()
|
||||
};
|
||||
assert_eq!(native_ssh_cwd(Some(&typed), home()), None);
|
||||
assert_eq!(native_ssh_cwd(Some(&wsl_context("Ubuntu")), home()), None);
|
||||
// A local pane has its own path through `git_status_cwd`.
|
||||
assert_eq!(native_ssh_cwd(None, home()), None);
|
||||
|
||||
assert_eq!(native_ssh_cwd(Some(&native), None), None);
|
||||
assert_eq!(
|
||||
native_ssh_cwd(Some(&native), Some(PathBuf::from("~"))),
|
||||
None,
|
||||
"only an absolute path names a directory"
|
||||
);
|
||||
}
|
||||
|
||||
/// Which machine's spelling a pane's paths are read in. Ungated on
|
||||
/// purpose: the bug this settles was a Windows-only one that hid behind a
|
||||
/// `#[cfg(unix)]` on the test that covered it.
|
||||
|
||||
+49
-7
@@ -1877,15 +1877,25 @@ impl Tty7App {
|
||||
if stated.as_ref().is_some_and(GroupKey::is_custom) {
|
||||
return stated;
|
||||
}
|
||||
let cwd = tab.pane.first_leaf().and_then(|leaf| {
|
||||
let resolved = tab.pane.first_leaf().and_then(|leaf| {
|
||||
let view = leaf.terminal()?.read(cx);
|
||||
Some((view.host_id(), view.git_status_cwd()?.to_path_buf()))
|
||||
});
|
||||
if let Some((id, cwd)) = cwd {
|
||||
let known = cx.global::<GitStatusCache>().known_repo_for(id, &cwd);
|
||||
if let Some(group) = resolved_group(grouping, known, &cwd) {
|
||||
*tab.sidebar_group.borrow_mut() = group;
|
||||
match view.git_status_cwd() {
|
||||
Some(cwd) => {
|
||||
let known = cx
|
||||
.global::<GitStatusCache>()
|
||||
.known_repo_for(view.host_id(), cwd);
|
||||
resolved_group(grouping, known, cwd)
|
||||
}
|
||||
// A native SSH pane never gets a `git_status_cwd`:
|
||||
// its paths are on a machine no `Host` reaches, so
|
||||
// nothing probes them (#891). Its shell still says
|
||||
// where it is, though, and that is enough for the
|
||||
// folder fallback.
|
||||
None => unprobed_group(grouping, &view.native_ssh_cwd()?),
|
||||
}
|
||||
});
|
||||
if let Some(group) = resolved {
|
||||
*tab.sidebar_group.borrow_mut() = group;
|
||||
}
|
||||
tab.sidebar_group.borrow().clone()
|
||||
})
|
||||
@@ -1977,6 +1987,20 @@ fn resolved_group(
|
||||
})
|
||||
}
|
||||
|
||||
/// The group for a cwd no repo probe can ever run in — a native SSH pane's.
|
||||
/// "Never probed" would leave such a tab in Scratch for good, so it is read
|
||||
/// as a settled "no repo": repo-or-directory grouping files it under the
|
||||
/// folder, the same as a local shell in a plain directory, and repo grouping
|
||||
/// leaves it in Scratch. A remote repo therefore groups by the folder the
|
||||
/// shell is in, not its root — there is nothing to ask for the root.
|
||||
///
|
||||
/// The key is the bare path, the same as every other derived group: a remote
|
||||
/// `/home/ubuntu` and a local one share a header, as two remote workspaces'
|
||||
/// identical paths already would.
|
||||
fn unprobed_group(grouping: SidebarGrouping, cwd: &Path) -> Option<Option<GroupKey>> {
|
||||
resolved_group(grouping, Some(None), cwd)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Section {
|
||||
key: Option<GroupKey>,
|
||||
@@ -2697,6 +2721,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_native_ssh_cwd_groups_by_its_remote_folder() {
|
||||
let home = p("/home/ubuntu");
|
||||
assert_eq!(
|
||||
unprobed_group(SidebarGrouping::RepoOrDirectory, &home),
|
||||
Some(Some(g("/home/ubuntu")))
|
||||
);
|
||||
assert_eq!(
|
||||
unprobed_group(SidebarGrouping::Repo, &home),
|
||||
Some(None),
|
||||
"under Repo there is no repo to find, so Scratch"
|
||||
);
|
||||
// Named by the last segment, like a local folder group.
|
||||
let keys = [Some(g("/home/ubuntu")), None];
|
||||
let sections = sidebar_sections(&keys);
|
||||
assert_eq!(sections[0].name.as_deref(), Some("ubuntu"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_known_repo_home_wins_over_the_folder_in_both_modes() {
|
||||
for mode in [SidebarGrouping::Repo, SidebarGrouping::RepoOrDirectory] {
|
||||
|
||||
Reference in New Issue
Block a user