project_panel: Show Reveal in File Manager on wsl (#47288)

Assimilates https://github.com/zed-industries/zed/pull/46856 and adds
support for reveal file manager in the project panel on wsl. Closes
https://github.com/zed-industries/zed/pull/46856

Release Notes:

- Fixed "Reveal in File Manager" not working for WSL remote connections
on Windows.
- Show "Reveal in File Manager" in the project panel context menu on WSL

---------

Co-authored-by: Max Malkin <maxim_malkin@outlook.com>
This commit is contained in:
Lukas Wirth 2026-01-22 12:24:23 +01:00 committed by GitHub
parent 071b3d92e8
commit b082481a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 76 additions and 16 deletions

View file

@ -22248,8 +22248,12 @@ impl Editor {
_window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(target) = self.target_file(cx) {
cx.reveal_path(&target.abs_path(cx));
if let Some(path) = self.target_file_abs_path(cx) {
if let Some(project) = self.project() {
project.update(cx, |project, cx| project.reveal_path(&path, cx));
} else {
cx.reveal_path(&path);
}
}
}

View file

@ -265,6 +265,8 @@ pub fn deploy_context_menu(
!has_reveal_target,
if cfg!(target_os = "macos") {
"Reveal in Finder"
} else if cfg!(target_os = "windows") {
"Reveal in File Explorer"
} else {
"Reveal in File Manager"
},

View file

@ -1438,12 +1438,16 @@ impl OutlinePanel {
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
menu.context(self.focus_handle.clone())
.when(cfg!(target_os = "macos"), |menu| {
menu.action("Reveal in Finder", Box::new(RevealInFileManager))
})
.when(cfg!(not(target_os = "macos")), |menu| {
menu.action("Reveal in File Manager", Box::new(RevealInFileManager))
})
.action(
if cfg!(target_os = "macos") {
"Reveal in Finder"
} else if cfg!(target_os = "windows") {
"Reveal in File Explorer"
} else {
"Reveal in File Manager"
},
Box::new(RevealInFileManager),
)
.action("Open in Terminal", Box::new(OpenInTerminal))
.when(is_unfoldable, |menu| {
menu.action("Unfold Directory", Box::new(UnfoldDirectory))
@ -2012,7 +2016,8 @@ impl OutlinePanel {
.selected_entry()
.and_then(|entry| self.abs_path(entry, cx))
{
cx.reveal_path(&abs_path);
self.project
.update(cx, |project, cx| project.reveal_path(&abs_path, cx));
}
}

View file

@ -107,6 +107,8 @@ use node_runtime::NodeRuntime;
use parking_lot::Mutex;
pub use prettier_store::PrettierStore;
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
#[cfg(target_os = "windows")]
use remote::wsl_path_to_windows_path;
use remote::{RemoteClient, RemoteConnectionOptions};
use rpc::{
AnyProtoClient, ErrorCode,
@ -2136,6 +2138,27 @@ impl Project {
.map(|remote| remote.read(cx).connection_options())
}
/// Reveals the given path in the system file manager.
///
/// On Windows with a WSL remote connection, this converts the POSIX path
/// to a Windows UNC path before revealing.
pub fn reveal_path(&self, path: &Path, cx: &mut Context<Self>) {
#[cfg(target_os = "windows")]
if let Some(RemoteConnectionOptions::Wsl(wsl_options)) = self.remote_connection_options(cx)
{
let path = path.to_path_buf();
cx.spawn(async move |_, cx| {
wsl_path_to_windows_path(&wsl_options, &path)
.await
.map(|windows_path| cx.update(|cx| cx.reveal_path(&windows_path)))
})
.detach_and_log_err(cx);
return;
}
cx.reveal_path(path);
}
#[inline]
pub fn replica_id(&self) -> ReplicaId {
match self.client_state {

View file

@ -1088,7 +1088,7 @@ impl ProjectPanel {
let is_read_only = project.is_read_only(cx);
let is_remote = project.is_remote();
let is_collab = project.is_via_collab();
let is_local = project.is_local();
let is_local = project.is_local() || project.is_via_wsl_with_host_interop(cx);
let settings = ProjectPanelSettings::get_global(cx);
let visible_worktrees_count = project.visible_worktrees(cx).count();
@ -1119,11 +1119,17 @@ impl ProjectPanel {
menu.action("New File", Box::new(NewFile))
.action("New Folder", Box::new(NewDirectory))
.separator()
.when(is_local && cfg!(target_os = "macos"), |menu| {
menu.action("Reveal in Finder", Box::new(RevealInFileManager))
})
.when(is_local && cfg!(not(target_os = "macos")), |menu| {
menu.action("Reveal in File Manager", Box::new(RevealInFileManager))
.when(is_local, |menu| {
menu.action(
if cfg!(target_os = "macos") && !is_remote {
"Reveal in Finder"
} else if cfg!(target_os = "windows") && !is_remote {
"Reveal in File Explorer"
} else {
"Reveal in File Manager"
},
Box::new(RevealInFileManager),
)
})
.when(is_local, |menu| {
menu.action("Open in Default App", Box::new(OpenWithSystem))
@ -3077,7 +3083,9 @@ impl ProjectPanel {
cx: &mut Context<Self>,
) {
if let Some((worktree, entry)) = self.selected_sub_entry(cx) {
cx.reveal_path(&worktree.read(cx).absolutize(&entry.path));
let path = worktree.read(cx).absolutize(&entry.path);
self.project
.update(cx, |project, cx| project.reveal_path(&path, cx));
}
}

View file

@ -14,6 +14,8 @@ pub use remote_client::{
pub use transport::docker::DockerConnectionOptions;
pub use transport::ssh::{SshConnectionOptions, SshPortForwardOption};
pub use transport::wsl::WslConnectionOptions;
#[cfg(target_os = "windows")]
pub use transport::wsl::wsl_path_to_windows_path;
#[cfg(any(test, feature = "test-support"))]
pub use transport::mock::{

View file

@ -579,6 +579,22 @@ async fn windows_path_to_wsl_path_impl(
run_wsl_command_with_output_impl(options, "wslpath", &["-u", &source]).await
}
/// Converts a WSL/POSIX path to a Windows path using `wslpath -w`.
///
/// For example, `/home/user/project` becomes `\\wsl.localhost\Ubuntu\home\user\project`
#[cfg(target_os = "windows")]
pub fn wsl_path_to_windows_path(
options: &WslConnectionOptions,
wsl_path: &Path,
) -> impl Future<Output = Result<PathBuf>> + use<> {
let wsl_path_str = wsl_path.to_string_lossy().to_string();
let command = wsl_command_impl(options, "wslpath", &["-w", &wsl_path_str], true);
async move {
let windows_path = run_wsl_command_impl(command).await?;
Ok(PathBuf::from(windows_path))
}
}
fn run_wsl_command_impl(mut command: process::Command) -> impl Future<Output = Result<String>> {
async move {
let output = command