diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 6d4215468a..b4f77df1eb 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -54,8 +54,7 @@ pub trait Platform: Send + Sync { fn set_cursor_style(&self, style: CursorStyle); fn local_timezone(&self) -> UtcOffset; - - fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result; + fn path_for_auxiliary_executable(&self, name: &str) -> Result; } pub(crate) trait ForegroundPlatform { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 813cf550d0..5789b2f611 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -14,9 +14,7 @@ use cocoa::{ NSPasteboardTypeString, NSSavePanel, NSWindow, }, base::{id, nil, selector, YES}, - foundation::{ - NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSUInteger, NSURL, - }, + foundation::{NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSURL}, }; use core_foundation::{ base::{CFType, CFTypeRef, OSStatus, TCFType as _}, @@ -38,8 +36,8 @@ use ptr::null_mut; use std::{ cell::{Cell, RefCell}, convert::TryInto, - ffi::{c_void, CStr}, - os::raw::c_char, + ffi::{c_void, CStr, OsStr}, + os::{raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, ptr, rc::Rc, @@ -48,9 +46,6 @@ use std::{ }; use time::UtcOffset; -#[allow(non_upper_case_globals)] -const NSUTF8StringEncoding: NSUInteger = 4; - const MAC_PLATFORM_IVAR: &'static str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -274,10 +269,9 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { for i in 0..urls.count() { let url = urls.objectAtIndex(i); if url.isFileURL() == YES { - let path = std::ffi::CStr::from_ptr(url.path().UTF8String()) - .to_string_lossy() - .to_string(); - result.push(PathBuf::from(path)); + if let Ok(path) = ns_url_to_path(url) { + result.push(path) + } } } Some(result) @@ -305,19 +299,13 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { 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 = None; + if response == NSModalResponse::NSModalResponseOk { let url = panel.URL(); if url.isFileURL() == YES { - let path = std::ffi::CStr::from_ptr(url.path().UTF8String()) - .to_string_lossy() - .to_string(); - Some(PathBuf::from(path)) - } else { - None + result = ns_url_to_path(panel.URL()).ok() } - } else { - None - }; + } if let Some(mut done_tx) = done_tx.take() { let _ = postage::sink::Sink::try_send(&mut done_tx, result); @@ -612,22 +600,18 @@ impl platform::Platform for MacPlatform { } } - fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result { + fn path_for_auxiliary_executable(&self, name: &str) -> Result { unsafe { let bundle: id = NSBundle::mainBundle(); if bundle.is_null() { Err(anyhow!("app is not running inside a bundle")) } else { - let name = name.map_or(nil, |name| ns_string(name)); - let extension = extension.map_or(nil, |extension| ns_string(extension)); - let path: id = msg_send![bundle, pathForResource: name ofType: extension]; - if path.is_null() { - Err(anyhow!("resource could not be found")) + let name = ns_string(name); + let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; + if url.is_null() { + Err(anyhow!("resource not found")) } else { - let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; - let bytes = path.UTF8String() as *const u8; - let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); - Ok(PathBuf::from(path)) + ns_url_to_path(url) } } } @@ -717,9 +701,7 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { .into_iter() .filter_map(|i| { let path = urls.objectAtIndex(i); - match dbg!( - CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() - ) { + match CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() { Ok(string) => Some(string.to_string()), Err(err) => { log::error!("error converting path to string: {}", err); @@ -754,6 +736,20 @@ unsafe fn ns_string(string: &str) -> id { NSString::alloc(nil).init_str(string).autorelease() } +unsafe fn ns_url_to_path(url: id) -> Result { + let path: *mut c_char = msg_send![url, fileSystemRepresentation]; + if path.is_null() { + Err(anyhow!( + "url is not a file path: {}", + CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy() + )) + } else { + Ok(PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(path).to_bytes(), + ))) + } +} + mod security { #![allow(non_upper_case_globals)] use super::*; diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index e0dd3059fc..296d4ea90d 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -163,7 +163,7 @@ impl super::Platform for Platform { UtcOffset::UTC } - fn path_for_resource(&self, _name: Option<&str>, _extension: Option<&str>) -> Result { + fn path_for_auxiliary_executable(&self, _name: &str) -> Result { Err(anyhow!("app not running inside a bundle")) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e9ed886557..439b3e63ee 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -38,7 +38,8 @@ actions!( DebugElements, OpenSettings, IncreaseBufferFontSize, - DecreaseBufferFontSize + DecreaseBufferFontSize, + InstallCommandLineTool, ] ); @@ -66,6 +67,20 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.refresh_windows(); }); }); + cx.add_global_action(move |_: &InstallCommandLineTool, cx| { + cx.spawn(|cx| async move { + let path = cx.platform().path_for_auxiliary_executable("cli")?; + let link_path = "/usr/local/bin/zed"; + smol::fs::unix::symlink(link_path, path.as_path()).await?; + log::info!( + "created symlink {} -> {}", + link_path, + path.to_string_lossy() + ); + Ok::<_, anyhow::Error>(()) + }) + .detach(); + }); cx.add_action({ let app_state = app_state.clone(); move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| {