@@ -10,7 +10,7 @@ use std::path::Path;
10
10
///
11
11
/// - **Android / iOS:** Unsupported.
12
12
pub 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 ( ) ) ?;
14
14
15
15
#[ cfg( any(
16
16
windows,
@@ -21,7 +21,47 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
21
21
target_os = "netbsd" ,
22
22
target_os = "openbsd"
23
23
) ) ]
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) ;
25
65
26
66
#[ cfg( not( any(
27
67
windows,
@@ -37,8 +77,10 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
37
77
38
78
#[ cfg( windows) ]
39
79
mod imp {
40
- use super :: * ;
80
+ use std:: collections:: HashMap ;
81
+ use std:: path:: { Path , PathBuf } ;
41
82
83
+ use windows:: Win32 :: UI :: Shell :: Common :: ITEMIDLIST ;
42
84
use windows:: {
43
85
core:: { w, HSTRING , PCWSTR } ,
44
86
Win32 :: {
@@ -54,56 +96,95 @@ mod imp {
54
96
} ,
55
97
} ;
56
98
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
+ }
65
103
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
+ }
68
111
69
- let file_h = HSTRING :: from ( file) ;
70
- let file_item = unsafe { ILCreateFromPathW ( & file_h) } ;
112
+ let _ = unsafe { CoInitialize ( None ) } ;
71
113
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
+ } {
74
132
// from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
75
133
// On some systems, the above call mysteriously fails with "file not
76
134
// found" even though the file is there. In these cases, ShellExecute()
77
135
// 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
78
138
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 ( ) ;
80
141
let mut info = SHELLEXECUTEINFOW {
81
142
cbSize : std:: mem:: size_of :: < SHELLEXECUTEINFOW > ( ) as _ ,
82
143
nShow : SW_SHOWNORMAL . 0 ,
83
- lpFile : PCWSTR ( dir . as_ptr ( ) ) ,
144
+ lpFile : PCWSTR ( parent_item_id_list . hstring . as_ptr ( ) ) ,
84
145
lpClass : if is_dir { w ! ( "folder" ) } else { PCWSTR :: null ( ) } ,
85
146
lpVerb : if is_dir {
86
147
w ! ( "explore" )
87
148
} else {
88
149
PCWSTR :: null ( )
89
150
} ,
90
- ..std :: mem :: zeroed ( )
151
+ ..Default :: default ( )
91
152
} ;
92
153
93
- ShellExecuteExW ( & mut info) . inspect_err ( |_| {
94
- ILFree ( Some ( dir_item) ) ;
95
- ILFree ( Some ( file_item) ) ;
96
- } ) ?;
154
+ unsafe { ShellExecuteExW ( & mut info) } ?;
97
155
}
98
156
}
99
157
}
100
158
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
+ }
104
179
}
180
+ }
105
181
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
+ }
107
188
}
108
189
}
109
190
@@ -115,24 +196,36 @@ mod imp {
115
196
target_os = "openbsd"
116
197
) ) ]
117
198
mod imp {
118
-
119
- use std:: collections:: HashMap ;
120
-
121
199
use super :: * ;
200
+ use std:: collections:: HashMap ;
201
+ use std:: path:: PathBuf ;
122
202
123
- pub fn reveal_item_in_dir ( path : & Path ) -> crate :: Result < ( ) > {
203
+ pub fn reveal_items_in_dir ( paths : & [ PathBuf ] ) -> crate :: Result < ( ) > {
124
204
let connection = zbus:: blocking:: Connection :: session ( ) ?;
125
205
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
+ } )
128
214
}
129
215
130
216
fn reveal_with_filemanager1 (
131
- path : & Path ,
217
+ paths : & [ PathBuf ] ,
132
218
connection : & zbus:: blocking:: Connection ,
133
219
) -> 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 ( ) ;
136
229
137
230
#[ zbus:: proxy(
138
231
interface = "org.freedesktop.FileManager1" ,
@@ -145,7 +238,7 @@ mod imp {
145
238
146
239
let proxy = FileManager1ProxyBlocking :: new ( connection) ?;
147
240
148
- proxy. ShowItems ( vec ! [ uri . as_str ( ) ] , "" )
241
+ proxy. ShowItems ( uri_strs , "" )
149
242
}
150
243
151
244
fn reveal_with_open_uri_portal (
@@ -177,14 +270,22 @@ mod imp {
177
270
178
271
#[ cfg( target_os = "macos" ) ]
179
272
mod imp {
180
- use super :: * ;
181
273
use objc2_app_kit:: NSWorkspace ;
182
274
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 < ( ) > {
184
278
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
+
188
289
let urls = NSArray :: from_retained_slice ( & urls) ;
189
290
190
291
let workspace = NSWorkspace :: new ( ) ;
0 commit comments