@@ -111,24 +111,74 @@ pub async fn handle_native_pick_folder(AppHandle:AppHandle, _Args:Vec<Value>) ->
111111 Ok ( Value :: Null )
112112}
113113
114+ /// Electron-style filter passed through `showOpenDialog({ filters: [...] })`.
115+ /// Shape: `{ name: "VSIX Extensions", extensions: ["vsix"] }`. The tauri
116+ /// dialog plugin's `add_filter(name, &[&str])` expects the same pair.
117+ #[ derive( Debug , Clone ) ]
118+ struct DialogFilter {
119+ Name : String ,
120+ Extensions : Vec < String > ,
121+ }
122+
123+ /// Parse `options.filters` into a vector of `DialogFilter`. Unknown / malformed
124+ /// entries are silently skipped rather than failing the whole dialog open -
125+ /// the user still gets the picker, just without the filter hint.
126+ fn ParseDialogFilters ( Options : & Value ) -> Vec < DialogFilter > {
127+ Options
128+ . get ( "filters" )
129+ . and_then ( Value :: as_array)
130+ . map ( |Array | {
131+ Array
132+ . iter ( )
133+ . filter_map ( |Entry | {
134+ let Name =
135+ Entry . get ( "name" ) . and_then ( Value :: as_str) . unwrap_or ( "Files" ) . to_string ( ) ;
136+ let Extensions : Vec < String > = Entry
137+ . get ( "extensions" )
138+ . and_then ( Value :: as_array)
139+ . map ( |List | {
140+ List . iter ( ) . filter_map ( |V | V . as_str ( ) . map ( str:: to_string) ) . collect ( )
141+ } )
142+ . unwrap_or_default ( ) ;
143+ if Extensions . is_empty ( ) { None } else { Some ( DialogFilter { Name , Extensions } ) }
144+ } )
145+ . collect ( )
146+ } )
147+ . unwrap_or_default ( )
148+ }
149+
114150/// Show open dialog with file/folder picker.
115151///
116- /// VS Code calls this via `nativeHostService.showOpenDialog(options)` with
117- /// Electron-style `properties: ["openDirectory" | "openFile" |
118- /// "multiSelections" | "createDirectory"]`. The expected return shape is
119- /// `{ canceled: bool, filePaths: string[] }`.
152+ /// VS Code calls this via `nativeHostService.showOpenDialog(options)` and
153+ /// expects the Electron contract:
154+ ///
155+ /// - `properties: ["openDirectory" | "openFile" | "multiSelections" |
156+ /// "createDirectory" | "showHiddenFiles"]`
157+ /// - `filters: [{ name, extensions: ["vsix", …] }, …]`
158+ /// - `title`, `buttonLabel`, `defaultPath`
159+ /// - returns `{ canceled: bool, filePaths: string[] }`.
120160///
121- /// This handler was previously a stub returning "canceled: true" - that's
122- /// why clicking the Explorer's "Open Folder" button (which goes through
123- /// this method, not through `pickFolderAndOpen`) silently did nothing. We
124- /// now drive the Tauri dialog plugin directly, honouring the `properties`
125- /// flags so folder-mode is picked when requested.
161+ /// The VSIX install flow (`Install from VSIX…`) relies on `filters` to narrow
162+ /// the picker to `.vsix` and on `openFile + multiSelections` so the user can
163+ /// pick several archives at once. Without either, the dialog either never
164+ /// opens (old stub) or opens unfiltered - both produced the "nothing happens"
165+ /// symptom in the field. This handler drives the Tauri dialog plugin
166+ /// end-to-end: every option in the VS Code contract maps to a builder call.
126167pub async fn handle_native_show_open_dialog ( AppHandle : AppHandle , Args : Vec < Value > ) -> Result < Value , String > {
127168 use tauri_plugin_dialog:: DialogExt ;
128169
129170 dev_log ! ( "folder" , "showOpenDialog: {:?}" , Args ) ;
130171
131- let Options = Args . first ( ) . cloned ( ) . unwrap_or ( Value :: Null ) ;
172+ // Electron passes `(windowId, options)`; `options` is always the last
173+ // element regardless of how the renderer was invoked. Searching by shape
174+ // (`first object with a "properties" or "filters" field`) keeps us robust
175+ // against VS Code versions that pass an extra prefix arg.
176+ let Options = Args
177+ . iter ( )
178+ . rev ( )
179+ . find ( |V | V . is_object ( ) )
180+ . cloned ( )
181+ . unwrap_or ( Value :: Null ) ;
132182 let Properties : Vec < String > = Options
133183 . get ( "properties" )
134184 . and_then ( Value :: as_array)
@@ -142,13 +192,26 @@ pub async fn handle_native_show_open_dialog(AppHandle:AppHandle, Args:Vec<Value>
142192 . unwrap_or ( if IsFolder { "Open Folder" } else { "Open File" } )
143193 . to_string ( ) ;
144194 let DefaultPath = Options . get ( "defaultPath" ) . and_then ( Value :: as_str) . map ( str:: to_string) ;
195+ // `filters` only affects file pickers; Tauri's folder picker ignores them.
196+ // Parsing unconditionally keeps the code branchless - the unused vector
197+ // costs nothing and we avoid an extra branch in the hot path.
198+ let Filters = ParseDialogFilters ( & Options ) ;
145199
146200 let Handle = AppHandle . clone ( ) ;
201+ let FiltersForThread = Filters . clone ( ) ;
147202 let Selected = tokio:: task:: spawn_blocking ( move || -> Vec < String > {
148203 let mut Builder = Handle . dialog ( ) . file ( ) . set_title ( & Title ) ;
149204 if let Some ( Path ) = DefaultPath . as_deref ( ) {
150205 Builder = Builder . set_directory ( Path ) ;
151206 }
207+ // Apply filters only for file pickers - Tauri returns an error on
208+ // folder pickers if filters are set on some platforms.
209+ if !IsFolder {
210+ for Filter in & FiltersForThread {
211+ let ExtRefs : Vec < & str > = Filter . Extensions . iter ( ) . map ( String :: as_str) . collect ( ) ;
212+ Builder = Builder . add_filter ( & Filter . Name , & ExtRefs ) ;
213+ }
214+ }
152215 if IsFolder {
153216 if IsMultiple {
154217 Builder
@@ -178,7 +241,14 @@ pub async fn handle_native_show_open_dialog(AppHandle:AppHandle, Args:Vec<Value>
178241 dev_log ! ( "folder" , "showOpenDialog cancelled by user" ) ;
179242 Ok ( json ! ( { "canceled" : true , "filePaths" : [ ] } ) )
180243 } else {
181- dev_log ! ( "folder" , "showOpenDialog selected {} path(s)" , Selected . len( ) ) ;
244+ dev_log ! (
245+ "folder" ,
246+ "showOpenDialog selected {} path(s) (folder={}, multi={}, filters={})" ,
247+ Selected . len( ) ,
248+ IsFolder ,
249+ IsMultiple ,
250+ Filters . len( )
251+ ) ;
182252 Ok ( json ! ( { "canceled" : false , "filePaths" : Selected } ) )
183253 }
184254}
0 commit comments