@@ -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,
@@ -40,7 +40,6 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
40
40
/// ## Platform-specific:
41
41
///
42
42
/// - **Android / iOS:** Unsupported.
43
- /// - **Windows:** Only supports revealing items in the same directory.
44
43
pub fn reveal_items_in_dir < I , P > ( paths : I ) -> crate :: Result < ( ) >
45
44
where
46
45
I : IntoIterator < Item = P > ,
49
48
let mut canonicalized = vec ! [ ] ;
50
49
51
50
for path in paths {
52
- let path = path. as_ref ( ) . canonicalize ( ) ?;
51
+ let path = dunce :: canonicalize ( path. as_ref ( ) ) ?;
53
52
canonicalized. push ( path) ;
54
53
}
55
54
78
77
79
78
#[ cfg( windows) ]
80
79
mod imp {
81
- use std:: path:: PathBuf ;
80
+ use std:: collections:: HashMap ;
81
+ use std:: path:: { Path , PathBuf } ;
82
82
83
83
use windows:: Win32 :: UI :: Shell :: Common :: ITEMIDLIST ;
84
84
use windows:: {
@@ -101,74 +101,90 @@ mod imp {
101
101
return Ok ( ( ) ) ;
102
102
}
103
103
104
- let first_path = dunce:: simplified ( & paths[ 0 ] ) ;
105
- let parent_dir = first_path
106
- . parent ( )
107
- . ok_or_else ( || crate :: Error :: NoParent ( first_path. to_path_buf ( ) ) ) ?;
108
-
109
- // On Windows, SHOpenFolderAndSelectItems requires all items to be in the same directory.
110
- // We filter the paths to ensure they all share the same parent as the first path.
111
- let files_in_same_dir: Vec < _ > = paths
112
- . iter ( )
113
- . map ( |p| dunce:: simplified ( p) )
114
- . filter ( |p| p. parent ( ) == Some ( parent_dir) )
115
- . collect ( ) ;
116
-
117
- if files_in_same_dir. is_empty ( ) {
118
- // This case can happen if the original list had paths from different directories.
119
- // We can't open multiple directories, so we do nothing.
120
- return Ok ( ( ) ) ;
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) ;
121
110
}
122
111
123
112
let _ = unsafe { CoInitialize ( None ) } ;
124
113
125
- let dir_hstring = HSTRING :: from ( parent_dir) ;
126
- let dir_item = unsafe { ILCreateFromPathW ( & dir_hstring) } ;
127
-
128
- // Ensure dir_item is freed even if subsequent operations fail.
129
- let mut created_file_items = Vec :: new ( ) ;
130
-
131
- for path in & files_in_same_dir {
132
- let file_hstring = HSTRING :: from ( path. as_os_str ( ) ) ;
133
- let file_item = unsafe { ILCreateFromPathW ( & file_hstring) } ;
134
- if !file_item. is_null ( ) {
135
- created_file_items. push ( file_item) ;
136
- }
137
- }
138
-
139
- // The function expects a slice of *const ITEMIDLIST, so we must cast our *mut pointers.
140
- let item_id_lists_const: Vec < * const ITEMIDLIST > =
141
- created_file_items. iter ( ) . map ( |& p| p as * const _ ) . collect ( ) ;
142
-
143
- let result = unsafe {
144
- if let Err ( e) = SHOpenFolderAndSelectItems ( dir_item, Some ( & item_id_lists_const) , 0 ) {
145
- // Fallback logic from the original function.
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
+ } {
132
+ // from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
133
+ // On some systems, the above call mysteriously fails with "file not
134
+ // found" even though the file is there. In these cases, ShellExecute()
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
146
138
if e. code ( ) . 0 == ERROR_FILE_NOT_FOUND . 0 as i32 {
139
+ let first_path = to_reveals[ 0 ] ;
140
+ let is_dir = first_path. is_dir ( ) ;
147
141
let mut info = SHELLEXECUTEINFOW {
148
142
cbSize : std:: mem:: size_of :: < SHELLEXECUTEINFOW > ( ) as _ ,
149
143
nShow : SW_SHOWNORMAL . 0 ,
150
- lpFile : PCWSTR ( dir_hstring. as_ptr ( ) ) ,
151
- lpVerb : w ! ( "explore" ) ,
144
+ lpFile : PCWSTR ( parent_item_id_list. hstring . as_ptr ( ) ) ,
145
+ lpClass : if is_dir { w ! ( "folder" ) } else { PCWSTR :: null ( ) } ,
146
+ lpVerb : if is_dir {
147
+ w ! ( "explore" )
148
+ } else {
149
+ PCWSTR :: null ( )
150
+ } ,
152
151
..Default :: default ( )
153
152
} ;
154
- ShellExecuteExW ( & mut info) . map ( |_| ( ) ) . map_err ( Into :: into)
155
- } else {
156
- Err ( e. into ( ) )
153
+
154
+ unsafe { ShellExecuteExW ( & mut info) } ?;
157
155
}
158
- } else {
159
- Ok ( ( ) )
160
156
}
161
- } ;
157
+ }
162
158
163
- // Free all allocated ITEMIDLISTs
164
- unsafe {
165
- for item in created_file_items {
166
- ILFree ( Some ( 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
+ } )
167
178
}
168
- ILFree ( Some ( dir_item) ) ;
169
179
}
180
+ }
170
181
171
- result
182
+ impl Drop for OwnedItemIdList {
183
+ fn drop ( & mut self ) {
184
+ if !self . item . is_null ( ) {
185
+ unsafe { ILFree ( Some ( self . item ) ) } ;
186
+ }
187
+ }
172
188
}
173
189
}
174
190
0 commit comments