Fix circular locking in prompts (#6456)

Conrad Irwin created

Sometimes Cocoa calls app delegate methods (notably the display link)
while we're calling Cocoa methods. This causes a deadlock unless we
are careful to run cocao methods while we're not holding our internal
locks

Release Notes:

- Fixed a crash when opening the MacOS Save As dialogue.

Change summary

crates/gpui/src/platform/mac/platform.rs | 116 ++++++++++++++-----------
1 file changed, 63 insertions(+), 53 deletions(-)

Detailed changes

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

@@ -534,67 +534,77 @@ impl Platform for MacPlatform {
         &self,
         options: PathPromptOptions,
     ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        unsafe {
-            let panel = NSOpenPanel::openPanel(nil);
-            panel.setCanChooseDirectories_(options.directories.to_objc());
-            panel.setCanChooseFiles_(options.files.to_objc());
-            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
-            panel.setResolvesAliases_(false.to_objc());
-            let (done_tx, done_rx) = oneshot::channel();
-            let done_tx = Cell::new(Some(done_tx));
-            let block = ConcreteBlock::new(move |response: NSModalResponse| {
-                let result = if response == NSModalResponse::NSModalResponseOk {
-                    let mut result = Vec::new();
-                    let urls = panel.URLs();
-                    for i in 0..urls.count() {
-                        let url = urls.objectAtIndex(i);
-                        if url.isFileURL() == YES {
-                            if let Ok(path) = ns_url_to_path(url) {
-                                result.push(path)
+        let (done_tx, done_rx) = oneshot::channel();
+        self.foreground_executor()
+            .spawn(async move {
+                unsafe {
+                    let panel = NSOpenPanel::openPanel(nil);
+                    panel.setCanChooseDirectories_(options.directories.to_objc());
+                    panel.setCanChooseFiles_(options.files.to_objc());
+                    panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+                    panel.setResolvesAliases_(false.to_objc());
+                    let done_tx = Cell::new(Some(done_tx));
+                    let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                        let result = if response == NSModalResponse::NSModalResponseOk {
+                            let mut result = Vec::new();
+                            let urls = panel.URLs();
+                            for i in 0..urls.count() {
+                                let url = urls.objectAtIndex(i);
+                                if url.isFileURL() == YES {
+                                    if let Ok(path) = ns_url_to_path(url) {
+                                        result.push(path)
+                                    }
+                                }
                             }
-                        }
-                    }
-                    Some(result)
-                } else {
-                    None
-                };
+                            Some(result)
+                        } else {
+                            None
+                        };
 
-                if let Some(done_tx) = done_tx.take() {
-                    let _ = done_tx.send(result);
+                        if let Some(done_tx) = done_tx.take() {
+                            let _ = done_tx.send(result);
+                        }
+                    });
+                    let block = block.copy();
+                    let _: () = msg_send![panel, beginWithCompletionHandler: block];
                 }
-            });
-            let block = block.copy();
-            let _: () = msg_send![panel, beginWithCompletionHandler: block];
-            done_rx
-        }
+            })
+            .detach();
+        done_rx
     }
 
     fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        unsafe {
-            let panel = NSSavePanel::savePanel(nil);
-            let path = ns_string(directory.to_string_lossy().as_ref());
-            let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
-            panel.setDirectoryURL(url);
-
-            let (done_tx, done_rx) = oneshot::channel();
-            let done_tx = Cell::new(Some(done_tx));
-            let block = ConcreteBlock::new(move |response: NSModalResponse| {
-                let mut result = None;
-                if response == NSModalResponse::NSModalResponseOk {
-                    let url = panel.URL();
-                    if url.isFileURL() == YES {
-                        result = ns_url_to_path(panel.URL()).ok()
-                    }
-                }
+        let directory = directory.to_owned();
+        let (done_tx, done_rx) = oneshot::channel();
+        self.foreground_executor()
+            .spawn(async move {
+                unsafe {
+                    let panel = NSSavePanel::savePanel(nil);
+                    let path = ns_string(directory.to_string_lossy().as_ref());
+                    let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+                    panel.setDirectoryURL(url);
+
+                    let done_tx = Cell::new(Some(done_tx));
+                    let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                        let mut result = None;
+                        if response == NSModalResponse::NSModalResponseOk {
+                            let url = panel.URL();
+                            if url.isFileURL() == YES {
+                                result = ns_url_to_path(panel.URL()).ok()
+                            }
+                        }
 
-                if let Some(done_tx) = done_tx.take() {
-                    let _ = done_tx.send(result);
+                        if let Some(done_tx) = done_tx.take() {
+                            let _ = done_tx.send(result);
+                        }
+                    });
+                    let block = block.copy();
+                    let _: () = msg_send![panel, beginWithCompletionHandler: block];
                 }
-            });
-            let block = block.copy();
-            let _: () = msg_send![panel, beginWithCompletionHandler: block];
-            done_rx
-        }
+            })
+            .detach();
+
+        done_rx
     }
 
     fn reveal_path(&self, path: &Path) {