Skip to content
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions plugins/opener/src/reveal_item_in_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ mod imp {
},
};

pub(super) fn normalize_path_for_shell(path: &Path) -> std::borrow::Cow<'_, Path> {
if let Some(s) = path.to_str() {
if let Some(unc) = s.strip_prefix(r"\\?\UNC\") {
return std::borrow::Cow::Owned(PathBuf::from(format!(r"\\{}", unc)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genuine question, is it safe to always convert \\?\UNC\ to \\?

Copy link
Copy Markdown
Author

@arelove arelove Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's safe specifically for ILCreateFromPathW since that Shell API doesn't support the \?\ prefix at all. The one edge case worth noting: if the UNC path exceeds MAX_PATH (260 chars), stripping \?\UNC\ could cause issues in other parts of the code that rely on extended-length path support. For the typical use case of revealing a file in Explorer this should be fine though.
But maybe I missed something, I'm not sure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable, we could go with either this or use absolute instead of canonicalize (#3343)

}
}
std::borrow::Cow::Borrowed(path)
}

pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
if paths.is_empty() {
return Ok(());
Expand Down Expand Up @@ -166,11 +175,12 @@ mod imp {

impl OwnedItemIdList {
fn new(path: &Path) -> crate::Result<Self> {
let path_hstring = HSTRING::from(path);
let path = normalize_path_for_shell(path);
let path_hstring = HSTRING::from(path.as_ref());
let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) };
if item_id_list.is_null() {
Err(crate::Error::FailedToConvertPathToItemIdList(
path.to_owned(),
path.to_path_buf(),
))
} else {
Ok(Self {
Expand Down Expand Up @@ -297,3 +307,47 @@ mod imp {
Ok(())
}
}

#[cfg(test)]
#[cfg(windows)]
mod tests {
use super::imp::normalize_path_for_shell;
use std::path::{Path, PathBuf};

fn simulate_canonicalize_unc() -> PathBuf {
PathBuf::from(r"\\?\UNC\server\share\file.mkv")
}

#[test]
fn unc_path_should_not_have_extended_prefix() {
let canonicalized = simulate_canonicalize_unc();
let result = normalize_path_for_shell(&canonicalized);
let s = result.as_ref().to_str().unwrap();
assert!(
!s.starts_with(r"\\?\UNC\"),
"Path still has \\?\\UNC\\ prefix: {}",
s
);
}

#[test]
fn unc_path_converts_correctly() {
let input = Path::new(r"\\?\UNC\server\share\file.mkv");
let result = normalize_path_for_shell(input);
assert_eq!(result.as_ref(), Path::new(r"\\server\share\file.mkv"));
}

#[test]
fn local_path_unchanged() {
let input = Path::new(r"C:\Users\valeri\file.txt");
let result = normalize_path_for_shell(input);
assert_eq!(result.as_ref(), input);
}

#[test]
fn plain_unc_unchanged() {
let input = Path::new(r"\\server\share\file.mkv");
let result = normalize_path_for_shell(input);
assert_eq!(result.as_ref(), input);
}
}
Loading