gpui_macos: Migrate prompt_for_path to objc2 (#63994)

# Objective

Get rid of instances of the `block` crate, as it will not compile in a
future version of Rust.

## Solution

Migrate to the `objc2` crate family.

This gets rid of `block` inside of `platform.rs` 🎉 

Release Notes:

- N/A
This commit is contained in:
tidely authored and GitHub committed 2026-09-10 07:28:42 +00:00
1 parent b02587f47b
commit 2b56eae5a2
2 files changed
+77 -76

No files matched your search

+3
View File
@@ -700,7 +700,10 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [
"NSButton",
"NSControl",
"NSGraphics",
"NSOpenPanel",
"NSPanel",
"NSResponder",
"NSSavePanel",
"NSView",
"NSWindow",
"NSWorkspace",
+74 -76
View File
@@ -4,13 +4,13 @@ use crate::{
set_active_window_cursor_style,
};
use anyhow::{Context as _, anyhow};
use block::ConcreteBlock;
use block2::RcBlock;
use cocoa::{
appkit::{
NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSApplication,
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSControl as _,
NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSSavePanel,
NSVisualEffectState, NSVisualEffectView, NSWindow,
NSEventModifierFlags, NSMenu, NSMenuItem, NSVisualEffectState, NSVisualEffectView,
NSWindow,
},
base::{BOOL, NO, YES, id, nil, selector},
foundation::{
@@ -45,6 +45,7 @@ use objc::{
sel, sel_impl,
};
use objc2::MainThreadMarker;
use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel, NSSavePanel, NSWorkspace};
use objc2_foundation::NSActivityOptions;
use parking_lot::Mutex;
use ptr::null_mut;
@@ -727,8 +728,6 @@ impl Platform for MacPlatform {
}
fn register_url_scheme(&self, scheme: &str) -> Task<anyhow::Result<()>> {
use block2::RcBlock;
use objc2_app_kit::NSWorkspace;
use objc2_foundation::{NSBundle, NSError, NSString};
// API only available post Monterey
@@ -786,47 +785,45 @@ impl Platform for MacPlatform {
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
use objc2_foundation::NSString;
let marker = self.1;
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());
let panel = NSOpenPanel::openPanel(marker);
panel.setCanChooseDirectories(options.directories);
panel.setCanChooseFiles(options.files);
panel.setAllowsMultipleSelection(options.multiple);
panel.setCanCreateDirectories(true.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
&& let Ok(path) = ns_url_to_path(url)
{
result.push(path)
}
}
Some(result)
} else {
None
panel.setCanCreateDirectories(true);
panel.setResolvesAliases(false);
let done_tx = Cell::new(Some(done_tx));
let handler = RcBlock::new({
let panel = panel.clone();
move |response: NSModalResponse| {
let Some(done_tx) = done_tx.take() else {
return;
};
if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(Ok(result));
}
});
let block = block.copy();
if let Some(prompt) = options.prompt {
let _: () = msg_send![panel, setPrompt: ns_string(&prompt)];
let result = (response == NSModalResponseOK).then(|| {
panel
.URLs()
.iter()
.filter(|url| url.isFileURL())
.filter_map(|url| url.to_file_path())
.collect::<Vec<_>>()
});
_ = done_tx.send(Ok(result));
}
});
let _: () = msg_send![panel, beginWithCompletionHandler: block];
if let Some(prompt) = options.prompt {
panel.setPrompt(Some(&NSString::from_str(prompt.as_str())));
}
panel.beginWithCompletionHandler(&handler);
})
.detach();
done_rx
@@ -837,31 +834,37 @@ impl Platform for MacPlatform {
directory: &Path,
suggested_name: Option<&str>,
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned();
let suggested_name = suggested_name.map(|s| s.to_owned());
use objc2_foundation::{NSString, NSURL};
let url = NSURL::from_directory_path(directory);
let suggested_name = suggested_name.map(NSString::from_str);
let (done_tx, done_rx) = oneshot::channel();
let marker = self.1;
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 panel = NSSavePanel::savePanel(marker);
panel.setDirectoryURL(url.as_deref());
if let Some(suggested_name) = suggested_name {
let name_string = ns_string(&suggested_name);
let _: () = msg_send![panel, setNameFieldStringValue: name_string];
}
if let Some(suggested_name) = suggested_name {
panel.setNameFieldStringValue(&suggested_name);
}
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().map(|mut result| {
let Some(filename) = result.file_name() else {
return result;
let done_tx = Cell::new(Some(done_tx));
let handler = RcBlock::new({
let panel = panel.clone();
move |response: NSModalResponse| {
let Some(done_tx) = done_tx.take() else {
return;
};
let result = if response == NSModalResponseOK {
panel
.URL()
.filter(|url| url.isFileURL())
.and_then(|url| url.to_file_path())
.map(|mut path| {
let Some(filename) = path.file_name() else {
return path;
};
let chunks = filename
.as_bytes()
@@ -875,29 +878,24 @@ impl Platform for MacPlatform {
// This is conditional on OS version because I'd like to get rid of it, so that
// you can manually create a file called `a.sql.s`. That said it seems better
// to break that use-case than breaking `a.sql`.
if chunks.len() == 3
&& chunks[1].starts_with(chunks[2])
if let &[_, second, third] = chunks.as_slice()
&& second.starts_with(third)
&& Self::os_version() >= Version::new(15, 0, 0)
{
let new_filename = OsStr::from_bytes(
&filename.as_bytes()
[..chunks[0].len() + 1 + chunks[1].len()],
)
.to_owned();
result.set_file_name(&new_filename);
path.set_extension("");
}
result
})
}
}
if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(Ok(result));
}
});
let block = block.copy();
let _: () = msg_send![panel, beginWithCompletionHandler: block];
}
path
})
} else {
None
};
_ = done_tx.send(Ok(result));
}
});
panel.beginWithCompletionHandler(&handler);
})
.detach();