diff --git a/.gitignore b/.gitignore index a382f99237..0b0f4d4ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ DerivedData/ **/*.db .pytest_cache .venv -.blob_store \ No newline at end of file +.blob_store +.vscode diff --git a/Cargo.toml b/Cargo.toml index f70b05ecbc..ae8d2c5247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -347,6 +347,7 @@ features = [ "Win32_System_Threading", "Win32_System_DataExchange", "Win32_System_Ole", + "Win32_System_Com", ] [patch.crates-io] diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 3f00aefe1e..5e7768b7e1 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -2,10 +2,10 @@ #![allow(unused_variables)] use std::{ - cell::RefCell, + cell::{Cell, RefCell}, collections::HashSet, - ffi::{c_uint, c_void}, - os::windows::ffi::OsStrExt, + ffi::{c_uint, c_void, OsString}, + os::windows::ffi::{OsStrExt, OsStringExt}, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -21,7 +21,7 @@ use parking_lot::Mutex; use time::UtcOffset; use util::{ResultExt, SemanticVersion}; use windows::{ - core::{HSTRING, PCWSTR}, + core::{IUnknown, HRESULT, HSTRING, PCWSTR, PWSTR}, Wdk::System::SystemServices::RtlGetVersion, Win32::{ Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, @@ -35,8 +35,9 @@ use windows::{ UI::{ Input::KeyboardAndMouse::GetDoubleClickTime, Shell::{ - FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName, - ShellExecuteW, SIGDN_FILESYSPATH, + FileOpenDialog, FileSaveDialog, IFileOpenDialog, IFileSaveDialog, IShellItem, + SHCreateItemFromParsingName, ShellExecuteW, FILEOPENDIALOGOPTIONS, + FOS_ALLOWMULTISELECT, FOS_FILEMUSTEXIST, FOS_PICKFOLDERS, SIGDN_FILESYSPATH, }, WindowsAndMessaging::{ DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, @@ -341,9 +342,74 @@ impl Platform for WindowsPlatform { self.inner.callbacks.lock().open_urls = Some(callback); } - // todo(windows) fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver>> { - unimplemented!() + 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::, 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(None).unwrap(); + } + return; + } + } + + let mut results = unsafe { folder_dialog.GetResults().unwrap() }; + + let mut paths: Vec = 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.len() == 0 { + tx.send(None).unwrap(); + } else { + tx.send(Some(paths)).unwrap(); + } + } + }) + .detach(); + + rx } fn prompt_for_new_path(&self, directory: &Path) -> Receiver> {