@@ -10,7 +10,7 @@ use std::path::Path;
1010///
1111/// - **Android / iOS:** Unsupported.
1212pub fn reveal_item_in_dir < P : AsRef < Path > > ( path : P ) -> crate :: Result < ( ) > {
13- let path = path. as_ref ( ) . canonicalize ( ) ?;
13+ let path = dunce :: canonicalize ( path. as_ref ( ) ) ?;
1414
1515 #[ cfg( any(
1616 windows,
@@ -21,7 +21,47 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
2121 target_os = "netbsd" ,
2222 target_os = "openbsd"
2323 ) ) ]
24- return imp:: reveal_item_in_dir ( & path) ;
24+ return imp:: reveal_items_in_dir ( & [ path] ) ;
25+
26+ #[ cfg( not( any(
27+ windows,
28+ target_os = "macos" ,
29+ target_os = "linux" ,
30+ target_os = "dragonfly" ,
31+ target_os = "freebsd" ,
32+ target_os = "netbsd" ,
33+ target_os = "openbsd"
34+ ) ) ) ]
35+ Err ( crate :: Error :: UnsupportedPlatform )
36+ }
37+
38+ /// Reveal the paths the system's default explorer.
39+ ///
40+ /// ## Platform-specific:
41+ ///
42+ /// - **Android / iOS:** Unsupported.
43+ pub fn reveal_items_in_dir < I , P > ( paths : I ) -> crate :: Result < ( ) >
44+ where
45+ I : IntoIterator < Item = P > ,
46+ P : AsRef < Path > ,
47+ {
48+ let mut canonicalized = vec ! [ ] ;
49+
50+ for path in paths {
51+ let path = dunce:: canonicalize ( path. as_ref ( ) ) ?;
52+ canonicalized. push ( path) ;
53+ }
54+
55+ #[ cfg( any(
56+ windows,
57+ target_os = "macos" ,
58+ target_os = "linux" ,
59+ target_os = "dragonfly" ,
60+ target_os = "freebsd" ,
61+ target_os = "netbsd" ,
62+ target_os = "openbsd"
63+ ) ) ]
64+ return imp:: reveal_items_in_dir ( & canonicalized) ;
2565
2666 #[ cfg( not( any(
2767 windows,
@@ -37,8 +77,10 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
3777
3878#[ cfg( windows) ]
3979mod imp {
40- use super :: * ;
80+ use std:: collections:: HashMap ;
81+ use std:: path:: { Path , PathBuf } ;
4182
83+ use windows:: Win32 :: UI :: Shell :: Common :: ITEMIDLIST ;
4284 use windows:: {
4385 core:: { w, HSTRING , PCWSTR } ,
4486 Win32 :: {
@@ -54,56 +96,95 @@ mod imp {
5496 } ,
5597 } ;
5698
57- pub fn reveal_item_in_dir ( path : & Path ) -> crate :: Result < ( ) > {
58- let file = dunce:: simplified ( path) ;
59-
60- let _ = unsafe { CoInitialize ( None ) } ;
61-
62- let dir = file
63- . parent ( )
64- . ok_or_else ( || crate :: Error :: NoParent ( file. to_path_buf ( ) ) ) ?;
99+ pub fn reveal_items_in_dir ( paths : & [ PathBuf ] ) -> crate :: Result < ( ) > {
100+ if paths. is_empty ( ) {
101+ return Ok ( ( ) ) ;
102+ }
65103
66- let dir = HSTRING :: from ( dir) ;
67- let dir_item = unsafe { ILCreateFromPathW ( & dir) } ;
104+ let mut grouped_paths: HashMap < & Path , Vec < & Path > > = HashMap :: new ( ) ;
105+ for path in paths {
106+ let parent = path
107+ . parent ( )
108+ . ok_or_else ( || crate :: Error :: NoParent ( path. to_path_buf ( ) ) ) ?;
109+ grouped_paths. entry ( parent) . or_default ( ) . push ( path) ;
110+ }
68111
69- let file_h = HSTRING :: from ( file) ;
70- let file_item = unsafe { ILCreateFromPathW ( & file_h) } ;
112+ let _ = unsafe { CoInitialize ( None ) } ;
71113
72- unsafe {
73- if let Err ( e) = SHOpenFolderAndSelectItems ( dir_item, Some ( & [ file_item] ) , 0 ) {
114+ for ( parent, to_reveals) in grouped_paths {
115+ let parent_item_id_list = OwnedItemIdList :: new ( parent) ?;
116+ let to_reveals_item_id_list = to_reveals
117+ . iter ( )
118+ . map ( |to_reveal| OwnedItemIdList :: new ( * to_reveal) )
119+ . collect :: < crate :: Result < Vec < _ > > > ( ) ?;
120+ if let Err ( e) = unsafe {
121+ SHOpenFolderAndSelectItems (
122+ parent_item_id_list. item ,
123+ Some (
124+ & to_reveals_item_id_list
125+ . iter ( )
126+ . map ( |item| item. item )
127+ . collect :: < Vec < _ > > ( ) ,
128+ ) ,
129+ 0 ,
130+ )
131+ } {
74132 // from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
75133 // On some systems, the above call mysteriously fails with "file not
76134 // found" even though the file is there. In these cases, ShellExecute()
77135 // seems to work as a fallback (although it won't select the file).
136+ //
137+ // Note: we only handle the first file here if multiple of are present
78138 if e. code ( ) . 0 == ERROR_FILE_NOT_FOUND . 0 as i32 {
79- let is_dir = file. is_dir ( ) ;
139+ let first_path = to_reveals[ 0 ] ;
140+ let is_dir = first_path. is_dir ( ) ;
80141 let mut info = SHELLEXECUTEINFOW {
81142 cbSize : std:: mem:: size_of :: < SHELLEXECUTEINFOW > ( ) as _ ,
82143 nShow : SW_SHOWNORMAL . 0 ,
83- lpFile : PCWSTR ( dir . as_ptr ( ) ) ,
144+ lpFile : PCWSTR ( parent_item_id_list . hstring . as_ptr ( ) ) ,
84145 lpClass : if is_dir { w ! ( "folder" ) } else { PCWSTR :: null ( ) } ,
85146 lpVerb : if is_dir {
86147 w ! ( "explore" )
87148 } else {
88149 PCWSTR :: null ( )
89150 } ,
90- ..std :: mem :: zeroed ( )
151+ ..Default :: default ( )
91152 } ;
92153
93- ShellExecuteExW ( & mut info) . inspect_err ( |_| {
94- ILFree ( Some ( dir_item) ) ;
95- ILFree ( Some ( file_item) ) ;
96- } ) ?;
154+ unsafe { ShellExecuteExW ( & mut info) } ?;
97155 }
98156 }
99157 }
100158
101- unsafe {
102- ILFree ( Some ( dir_item) ) ;
103- ILFree ( Some ( file_item) ) ;
159+ Ok ( ( ) )
160+ }
161+
162+ struct OwnedItemIdList {
163+ hstring : HSTRING ,
164+ item : * const ITEMIDLIST ,
165+ }
166+
167+ impl OwnedItemIdList {
168+ fn new ( path : & Path ) -> crate :: Result < Self > {
169+ let path_hstring = HSTRING :: from ( path) ;
170+ let item_id_list = unsafe { ILCreateFromPathW ( & path_hstring) } ;
171+ if item_id_list. is_null ( ) {
172+ Err ( crate :: Error :: InvalidPath ( path. to_owned ( ) ) )
173+ } else {
174+ Ok ( Self {
175+ hstring : path_hstring,
176+ item : item_id_list,
177+ } )
178+ }
104179 }
180+ }
105181
106- Ok ( ( ) )
182+ impl Drop for OwnedItemIdList {
183+ fn drop ( & mut self ) {
184+ if !self . item . is_null ( ) {
185+ unsafe { ILFree ( Some ( self . item ) ) } ;
186+ }
187+ }
107188 }
108189}
109190
@@ -115,24 +196,36 @@ mod imp {
115196 target_os = "openbsd"
116197) ) ]
117198mod imp {
118-
119- use std:: collections:: HashMap ;
120-
121199 use super :: * ;
200+ use std:: collections:: HashMap ;
201+ use std:: path:: PathBuf ;
122202
123- pub fn reveal_item_in_dir ( path : & Path ) -> crate :: Result < ( ) > {
203+ pub fn reveal_items_in_dir ( paths : & [ PathBuf ] ) -> crate :: Result < ( ) > {
124204 let connection = zbus:: blocking:: Connection :: session ( ) ?;
125205
126- reveal_with_filemanager1 ( path, & connection)
127- . or_else ( |_| reveal_with_open_uri_portal ( path, & connection) )
206+ reveal_with_filemanager1 ( paths, & connection) . or_else ( |e| {
207+ // Fallback to opening the directory of the first item if revealing multiple items fails.
208+ if let Some ( first_path) = paths. first ( ) {
209+ reveal_with_open_uri_portal ( first_path, & connection)
210+ } else {
211+ Err ( e)
212+ }
213+ } )
128214 }
129215
130216 fn reveal_with_filemanager1 (
131- path : & Path ,
217+ paths : & [ PathBuf ] ,
132218 connection : & zbus:: blocking:: Connection ,
133219 ) -> crate :: Result < ( ) > {
134- let uri = url:: Url :: from_file_path ( path)
135- . map_err ( |_| crate :: Error :: FailedToConvertPathToFileUrl ) ?;
220+ let uris: Result < Vec < _ > , _ > = paths
221+ . iter ( )
222+ . map ( |path| {
223+ url:: Url :: from_file_path ( path)
224+ . map_err ( |_| crate :: Error :: FailedToConvertPathToFileUrl )
225+ } )
226+ . collect ( ) ;
227+ let uris = uris?;
228+ let uri_strs: Vec < & str > = uris. iter ( ) . map ( |uri| uri. as_str ( ) ) . collect ( ) ;
136229
137230 #[ zbus:: proxy(
138231 interface = "org.freedesktop.FileManager1" ,
@@ -145,7 +238,7 @@ mod imp {
145238
146239 let proxy = FileManager1ProxyBlocking :: new ( connection) ?;
147240
148- proxy. ShowItems ( vec ! [ uri . as_str ( ) ] , "" )
241+ proxy. ShowItems ( uri_strs , "" )
149242 }
150243
151244 fn reveal_with_open_uri_portal (
@@ -177,14 +270,22 @@ mod imp {
177270
178271#[ cfg( target_os = "macos" ) ]
179272mod imp {
180- use super :: * ;
181273 use objc2_app_kit:: NSWorkspace ;
182274 use objc2_foundation:: { NSArray , NSString , NSURL } ;
183- pub fn reveal_item_in_dir ( path : & Path ) -> crate :: Result < ( ) > {
275+ use std:: path:: PathBuf ;
276+
277+ pub fn reveal_items_in_dir ( paths : & [ PathBuf ] ) -> crate :: Result < ( ) > {
184278 unsafe {
185- let path = path. to_string_lossy ( ) ;
186- let path = NSString :: from_str ( & path) ;
187- let urls = vec ! [ NSURL :: fileURLWithPath( & path) ] ;
279+ let mut urls = Vec :: new ( ) ;
280+
281+ for path in paths {
282+ let path = path. to_string_lossy ( ) ;
283+ let path = NSString :: from_str ( & path) ;
284+ let url = NSURL :: fileURLWithPath ( & path) ;
285+
286+ urls. push ( url) ;
287+ }
288+
188289 let urls = NSArray :: from_retained_slice ( & urls) ;
189290
190291 let workspace = NSWorkspace :: new ( ) ;
0 commit comments