windows: Refactor `prompt_for_paths` and `prompt_for_new_path` (#15774)

张小白 created

Refactored `prompt_for_paths` and `prompt_for_new_path`, now errors can
propagate properly.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/platform.rs | 166 +++++++--------------
1 file changed, 60 insertions(+), 106 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -1,11 +1,6 @@
-// todo(windows): remove
-#![allow(unused_variables)]
-
 use std::{
-    cell::{Cell, RefCell},
-    ffi::{c_void, OsString},
+    cell::RefCell,
     mem::ManuallyDrop,
-    os::windows::ffi::{OsStrExt, OsStringExt},
     path::{Path, PathBuf},
     rc::Rc,
     sync::Arc,
@@ -288,7 +283,7 @@ impl Platform for WindowsPlatform {
     }
 
     // todo(windows)
-    fn activate(&self, ignoring_other_apps: bool) {}
+    fn activate(&self, _ignoring_other_apps: bool) {}
 
     // todo(windows)
     fn hide(&self) {
@@ -366,68 +361,9 @@ impl Platform for WindowsPlatform {
         options: PathPromptOptions,
     ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
         let (tx, rx) = oneshot::channel();
-
         self.foreground_executor()
             .spawn(async move {
-                let tx = Cell::new(Some(tx));
-
-                // create file open dialog
-                let folder_dialog: IFileOpenDialog = unsafe {
-                    CoCreateInstance::<std::option::Option<&IUnknown>, IFileOpenDialog>(
-                        &FileOpenDialog,
-                        None,
-                        CLSCTX_ALL,
-                    )
-                    .unwrap()
-                };
-
-                // dialog options
-                let mut dialog_options: FILEOPENDIALOGOPTIONS = FOS_FILEMUSTEXIST;
-                if options.multiple {
-                    dialog_options |= FOS_ALLOWMULTISELECT;
-                }
-                if options.directories {
-                    dialog_options |= FOS_PICKFOLDERS;
-                }
-
-                unsafe {
-                    folder_dialog.SetOptions(dialog_options).unwrap();
-                    folder_dialog
-                        .SetTitle(&HSTRING::from(OsString::from("Select a folder")))
-                        .unwrap();
-                }
-
-                let hr = unsafe { folder_dialog.Show(None) };
-
-                if hr.is_err() {
-                    if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
-                        // user canceled error
-                        if let Some(tx) = tx.take() {
-                            tx.send(Ok(None)).unwrap();
-                        }
-                        return;
-                    }
-                }
-
-                let mut results = unsafe { folder_dialog.GetResults().unwrap() };
-
-                let mut paths: Vec<PathBuf> = Vec::new();
-                for i in 0..unsafe { results.GetCount().unwrap() } {
-                    let mut item: IShellItem = unsafe { results.GetItemAt(i).unwrap() };
-                    let mut path: PWSTR =
-                        unsafe { item.GetDisplayName(SIGDN_FILESYSPATH).unwrap() };
-                    let mut path_os_string = OsString::from_wide(unsafe { path.as_wide() });
-
-                    paths.push(PathBuf::from(path_os_string));
-                }
-
-                if let Some(tx) = tx.take() {
-                    if paths.is_empty() {
-                        tx.send(Ok(None)).unwrap();
-                    } else {
-                        tx.send(Ok(Some(paths))).unwrap();
-                    }
-                }
+                let _ = tx.send(file_open_dialog(options));
             })
             .detach();
 
@@ -439,23 +375,7 @@ impl Platform for WindowsPlatform {
         let (tx, rx) = oneshot::channel();
         self.foreground_executor()
             .spawn(async move {
-                unsafe {
-                    let Ok(dialog) = show_savefile_dialog(directory) else {
-                        let _ = tx.send(Ok(None));
-                        return;
-                    };
-                    let Ok(_) = dialog.Show(None) else {
-                        let _ = tx.send(Ok(None)); // user cancel
-                        return;
-                    };
-                    if let Ok(shell_item) = dialog.GetResult() {
-                        if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
-                            let _ = tx.send(Ok(Some(PathBuf::from(file.to_string().unwrap()))));
-                            return;
-                        }
-                    }
-                    let _ = tx.send(Ok(None));
-                }
+                let _ = tx.send(file_save_dialog(directory));
             })
             .detach();
 
@@ -489,8 +409,8 @@ impl Platform for WindowsPlatform {
     }
 
     // todo(windows)
-    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
-    fn set_dock_menu(&self, menus: Vec<MenuItem>, keymap: &Keymap) {}
+    fn set_menus(&self, _menus: Vec<Menu>, _keymap: &Keymap) {}
+    fn set_dock_menu(&self, _menus: Vec<MenuItem>, _keymap: &Keymap) {}
 
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
         self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
@@ -509,7 +429,7 @@ impl Platform for WindowsPlatform {
     }
 
     // todo(windows)
-    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
         Err(anyhow!("not yet implemented"))
     }
 
@@ -589,7 +509,7 @@ impl Platform for WindowsPlatform {
                     )
                 };
                 let password = credential_blob.to_vec();
-                unsafe { CredFree(credentials as *const c_void) };
+                unsafe { CredFree(credentials as *const _ as _) };
                 Ok(Some((username, password)))
             }
         })
@@ -655,27 +575,61 @@ fn open_target_in_explorer(target: &str) {
     }
 }
 
-unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
-    let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
-    let bind_context = CreateBindCtx(0)?;
-    let Ok(full_path) = directory.canonicalize() else {
-        return Ok(dialog);
-    };
-    let dir_str = full_path.into_os_string();
-    if dir_str.is_empty() {
-        return Ok(dialog);
+fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>> {
+    let folder_dialog: IFileOpenDialog =
+        unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? };
+
+    let mut dialog_options = FOS_FILEMUSTEXIST;
+    if options.multiple {
+        dialog_options |= FOS_ALLOWMULTISELECT;
+    }
+    if options.directories {
+        dialog_options |= FOS_PICKFOLDERS;
+    }
+
+    unsafe {
+        folder_dialog.SetOptions(dialog_options)?;
+        if folder_dialog.Show(None).is_err() {
+            // User cancelled
+            return Ok(None);
+        }
     }
-    let dir_vec = dir_str.encode_wide().collect_vec();
-    let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
-        .inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
-    if ret.is_ok() {
-        let dir_shell_item: IShellItem = ret.unwrap();
-        let _ = dialog
-            .SetFolder(&dir_shell_item)
-            .inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
+
+    let results = unsafe { folder_dialog.GetResults()? };
+    let file_count = unsafe { results.GetCount()? };
+    if file_count == 0 {
+        return Ok(None);
+    }
+
+    let mut paths = Vec::new();
+    for i in 0..file_count {
+        let item = unsafe { results.GetItemAt(i)? };
+        let path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
+        paths.push(PathBuf::from(path));
     }
 
-    Ok(dialog)
+    Ok(Some(paths))
+}
+
+fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> {
+    let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
+    if let Some(full_path) = directory.canonicalize().log_err() {
+        let full_path = full_path.to_string_lossy().to_string();
+        if !full_path.is_empty() {
+            let path_item: IShellItem =
+                unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
+            unsafe { dialog.SetFolder(&path_item).log_err() };
+        }
+    }
+    unsafe {
+        if dialog.Show(None).is_err() {
+            // User cancelled
+            return Ok(None);
+        }
+    }
+    let shell_item = unsafe { dialog.GetResult()? };
+    let file_path_string = unsafe { shell_item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
+    Ok(Some(PathBuf::from(file_path_string)))
 }
 
 fn begin_vsync(vsync_event: HANDLE) {