From f673c6aa6ecdf7ae03ef11f81b8ac6bc8bc1a15b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 7 Apr 2021 17:48:22 -0700 Subject: [PATCH 1/6] Start work on a native application menu Add an application menu with a quit command, bound to command-q --- gpui/src/platform/mac/app.rs | 7 ++--- gpui/src/platform/mac/runner.rs | 53 ++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index daf8335f60..b3b2a788cf 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -1,8 +1,7 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; -use cocoa::base::id; -use objc::{class, msg_send, sel, sel_impl}; +use cocoa::{appkit::NSApplication, base::nil}; use std::{rc::Rc, sync::Arc}; pub struct App { @@ -26,8 +25,8 @@ impl platform::App for App { fn activate(&self, ignoring_other_apps: bool) { unsafe { - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()]; + let app = NSApplication::sharedApplication(nil); + app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc()); } } diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index c407bf59d7..4b0ebe7dfd 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -1,7 +1,10 @@ use crate::platform::Event; use cocoa::{ - appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - base::{id, nil}, + appkit::{ + NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSMenu, + NSMenuItem, NSWindow, + }, + base::{id, nil, selector}, foundation::{NSArray, NSAutoreleasePool, NSString}, }; use ctor::ctor; @@ -106,16 +109,16 @@ impl crate::platform::Runner for Runner { let pool = NSAutoreleasePool::new(nil); let app: id = msg_send![APP_CLASS, sharedApplication]; - let _: () = msg_send![ - app, - setActivationPolicy: NSApplicationActivationPolicyRegular - ]; - (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; + + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - let _: () = msg_send![app, setDelegate: app_delegate]; - let _: () = msg_send![app, run]; - let _: () = msg_send![pool, drain]; + app.setMainMenu_(create_menu_bar()); + app.setDelegate_(app_delegate); + app.run(); + pool.drain(); + // The Runner is done running when we get here, so we can reinstantiate the Box and drop it. Box::from_raw(self_ptr); } @@ -186,3 +189,33 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { callback(paths); } } + +unsafe fn create_menu_bar() -> id { + let menu_bar = NSMenu::new(nil).autorelease(); + + // App menu + let app_menu_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Application"), + Sel::from_ptr(ptr::null()), + ns_string(""), + ) + .autorelease(); + let quit_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Quit"), + selector("terminate:"), + ns_string("q\0"), + ) + .autorelease(); + let app_menu = NSMenu::new(nil).autorelease(); + app_menu.addItem_(quit_item); + app_menu_item.setSubmenu_(app_menu); + menu_bar.addItem_(app_menu_item); + + menu_bar +} + +unsafe fn ns_string(string: &str) -> id { + NSString::alloc(nil).init_str(string).autorelease() +} From bc4f5ea3df75532a5b15286c02950f39c44349f0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 7 Apr 2021 17:49:44 -0700 Subject: [PATCH 2/6] Add a stub of a native 'File' menu --- gpui/src/platform/mac/runner.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index 4b0ebe7dfd..a4b7281753 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -213,6 +213,27 @@ unsafe fn create_menu_bar() -> id { app_menu_item.setSubmenu_(app_menu); menu_bar.addItem_(app_menu_item); + // File menu + let file_menu_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("File"), + Sel::from_ptr(ptr::null()), + ns_string(""), + ) + .autorelease(); + let open_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Open"), + selector("openDocument:"), + ns_string("o\0"), + ) + .autorelease(); + let file_menu = NSMenu::new(nil).autorelease(); + file_menu.setTitle_(ns_string("File")); + file_menu.addItem_(open_item); + file_menu_item.setSubmenu_(file_menu); + menu_bar.addItem_(file_menu_item); + menu_bar } From fffbc0de2986d731510305283dbe1b09b932bc89 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 15:56:21 -0700 Subject: [PATCH 3/6] Create an API for assigning the menubar contents --- gpui/src/app.rs | 18 ++++ gpui/src/platform/mac/app.rs | 8 ++ gpui/src/platform/mac/runner.rs | 159 ++++++++++++++++++++++---------- gpui/src/platform/mod.rs | 7 +- gpui/src/platform/test.rs | 2 + zed/src/lib.rs | 1 + zed/src/main.rs | 24 +++-- zed/src/menus.rs | 52 +++++++++++ zed/src/workspace/mod.rs | 5 + 9 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 zed/src/menus.rs diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 8dbc34b893..8863bb05eb 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -66,6 +66,20 @@ pub trait UpdateView { F: FnOnce(&mut T, &mut ViewContext) -> S; } +pub struct Menu<'a> { + pub name: &'a str, + pub items: &'a [MenuItem<'a>], +} + +pub enum MenuItem<'a> { + Action { + name: &'a str, + keystroke: Option<&'a str>, + action: &'a str, + }, + Separator, +} + #[derive(Clone)] pub struct App(Rc>); @@ -365,6 +379,10 @@ impl MutableAppContext { &self.ctx } + pub fn platform(&self) -> Arc { + self.platform.clone() + } + pub fn foreground_executor(&self) -> &Rc { &self.foreground } diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index b3b2a788cf..b77cb2ee51 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -2,6 +2,7 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; use cocoa::{appkit::NSApplication, base::nil}; +use objc::{msg_send, sel, sel_impl}; use std::{rc::Rc, sync::Arc}; pub struct App { @@ -41,4 +42,11 @@ impl platform::App for App { fn fonts(&self) -> Arc { self.fonts.clone() } + + fn quit(&self) { + unsafe { + let app = NSApplication::sharedApplication(nil); + let _: () = msg_send![app, terminate: nil]; + } + } } diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index a4b7281753..1783e03ea0 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -1,11 +1,11 @@ -use crate::platform::Event; +use crate::{keymap::Keystroke, platform::Event, Menu, MenuItem}; use cocoa::{ appkit::{ - NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSMenu, - NSMenuItem, NSWindow, + NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, + NSEventModifierFlags, NSMenu, NSMenuItem, NSWindow, }, base::{id, nil, selector}, - foundation::{NSArray, NSAutoreleasePool, NSString}, + foundation::{NSArray, NSAutoreleasePool, NSInteger, NSString}, }; use ctor::ctor; use objc::{ @@ -53,6 +53,10 @@ unsafe fn build_classes() { sel!(applicationDidResignActive:), did_resign_active as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method( + sel!(handleGPUIMenuItem:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(application:openFiles:), open_files as extern "C" fn(&mut Object, Sel, id, id), @@ -68,12 +72,89 @@ pub struct Runner { resign_active_callback: Option>, event_callback: Option bool>>, open_files_callback: Option)>>, + menu_command_callback: Option>, + menu_item_actions: Vec, } impl Runner { pub fn new() -> Self { Default::default() } + + unsafe fn create_menu_bar(&mut self, menus: &[Menu]) -> id { + let menu_bar = NSMenu::new(nil).autorelease(); + self.menu_item_actions.clear(); + + for menu_config in menus { + let menu_bar_item = NSMenuItem::new(nil).autorelease(); + let menu = NSMenu::new(nil).autorelease(); + + menu.setTitle_(ns_string(menu_config.name)); + + for item_config in menu_config.items { + let item; + + match item_config { + MenuItem::Separator => { + item = NSMenuItem::separatorItem(nil); + } + MenuItem::Action { + name, + keystroke, + action, + } => { + if let Some(keystroke) = keystroke { + let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| { + panic!( + "Invalid keystroke for menu item {}:{} - {:?}", + menu_config.name, name, err + ) + }); + + let mut mask = NSEventModifierFlags::empty(); + for (modifier, flag) in &[ + (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), + (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), + (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), + ] { + if *modifier { + mask |= *flag; + } + } + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector("handleGPUIMenuItem:"), + ns_string(&keystroke.key), + ) + .autorelease(); + item.setKeyEquivalentModifierMask_(mask); + } else { + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector("handleGPUIMenuItem:"), + ns_string(""), + ) + .autorelease(); + } + + let tag = self.menu_item_actions.len() as NSInteger; + let _: () = msg_send![item, setTag: tag]; + self.menu_item_actions.push(action.to_string()); + } + } + + menu.addItem_(item); + } + + menu_bar_item.setSubmenu_(menu); + menu_bar.addItem_(menu_bar_item); + } + + menu_bar + } } impl crate::platform::Runner for Runner { @@ -82,6 +163,11 @@ impl crate::platform::Runner for Runner { self } + fn on_menu_command(mut self, callback: F) -> Self { + self.menu_command_callback = Some(Box::new(callback)); + self + } + fn on_become_active(mut self, callback: F) -> Self { log::info!("become active"); self.become_active_callback = Some(Box::new(callback)); @@ -103,6 +189,14 @@ impl crate::platform::Runner for Runner { self } + fn set_menus(mut self, menus: &[Menu]) -> Self { + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setMainMenu_(self.create_menu_bar(menus)); + } + self + } + fn run(self) { unsafe { let self_ptr = Box::into_raw(Box::new(self)); @@ -114,7 +208,6 @@ impl crate::platform::Runner for Runner { app.setActivationPolicy_(NSApplicationActivationPolicyRegular); (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - app.setMainMenu_(create_menu_bar()); app.setDelegate_(app_delegate); app.run(); pool.drain(); @@ -190,51 +283,17 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { } } -unsafe fn create_menu_bar() -> id { - let menu_bar = NSMenu::new(nil).autorelease(); - - // App menu - let app_menu_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Application"), - Sel::from_ptr(ptr::null()), - ns_string(""), - ) - .autorelease(); - let quit_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Quit"), - selector("terminate:"), - ns_string("q\0"), - ) - .autorelease(); - let app_menu = NSMenu::new(nil).autorelease(); - app_menu.addItem_(quit_item); - app_menu_item.setSubmenu_(app_menu); - menu_bar.addItem_(app_menu_item); - - // File menu - let file_menu_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("File"), - Sel::from_ptr(ptr::null()), - ns_string(""), - ) - .autorelease(); - let open_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Open"), - selector("openDocument:"), - ns_string("o\0"), - ) - .autorelease(); - let file_menu = NSMenu::new(nil).autorelease(); - file_menu.setTitle_(ns_string("File")); - file_menu.addItem_(open_item); - file_menu_item.setSubmenu_(file_menu); - menu_bar.addItem_(file_menu_item); - - menu_bar +extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { + unsafe { + let runner = get_runner(this); + if let Some(callback) = runner.menu_command_callback.as_mut() { + let tag: NSInteger = msg_send![item, tag]; + let index = tag as usize; + if let Some(action) = runner.menu_item_actions.get(index) { + callback(&action); + } + } + } } unsafe fn ns_string(string: &str) -> id { diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index 5bd7e4659e..bbd2dc9838 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -15,7 +15,7 @@ use crate::{ vector::Vector2F, }, text_layout::Line, - Scene, + Menu, Scene, }; use anyhow::Result; use async_task::Runnable; @@ -23,11 +23,13 @@ pub use event::Event; use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc}; pub trait Runner { - fn on_finish_launching(self, callback: F) -> Self where; + fn on_finish_launching(self, callback: F) -> Self; + fn on_menu_command(self, callback: F) -> Self; fn on_become_active(self, callback: F) -> Self; fn on_resign_active(self, callback: F) -> Self; fn on_event bool>(self, callback: F) -> Self; fn on_open_files)>(self, callback: F) -> Self; + fn set_menus(self, menus: &[Menu]) -> Self; fn run(self); } @@ -40,6 +42,7 @@ pub trait App { executor: Rc, ) -> Result>; fn fonts(&self) -> Arc; + fn quit(&self); } pub trait Dispatcher: Send + Sync { diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 545ab10e34..067184adea 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -46,6 +46,8 @@ impl super::App for App { fn fonts(&self) -> std::sync::Arc { self.fonts.clone() } + + fn quit(&self) {} } impl Window { diff --git a/zed/src/lib.rs b/zed/src/lib.rs index a66a892ebf..752df470c5 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -1,6 +1,7 @@ pub mod assets; pub mod editor; pub mod file_finder; +pub mod menus; mod operation_queue; pub mod settings; mod sum_tree; diff --git a/zed/src/main.rs b/zed/src/main.rs index ed52fb6163..08fb098784 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -4,7 +4,7 @@ use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; use zed::{ - assets, editor, file_finder, settings, + assets, editor, file_finder, menus, settings, workspace::{self, OpenParams}, }; @@ -14,10 +14,18 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); - { - let mut app = app.clone(); - platform::runner() - .on_finish_launching(move || { + platform::runner() + .set_menus(menus::MENUS) + .on_menu_command({ + let app = app.clone(); + move |command| { + log::info!("menu command: {}", command); + app.dispatch_global_action(command, ()) + } + }) + .on_finish_launching({ + let mut app = app.clone(); + move || { workspace::init(&mut app); editor::init(&mut app); file_finder::init(&mut app); @@ -36,9 +44,9 @@ fn main() { }, ); } - }) - .run(); - } + } + }) + .run(); } fn init_logger() { diff --git a/zed/src/menus.rs b/zed/src/menus.rs new file mode 100644 index 0000000000..c6ef18b746 --- /dev/null +++ b/zed/src/menus.rs @@ -0,0 +1,52 @@ +use gpui::{Menu, MenuItem}; + +#[cfg(target_os = "macos")] +pub const MENUS: &'static [Menu] = &[ + Menu { + name: "Zed", + items: &[ + MenuItem::Action { + name: "About Zed...", + keystroke: None, + action: "app:about-zed", + }, + MenuItem::Separator, + MenuItem::Action { + name: "Quit", + keystroke: Some("cmd-q"), + action: "app:quit", + }, + ], + }, + Menu { + name: "File", + items: &[ + MenuItem::Action { + name: "Undo", + keystroke: Some("cmd-z"), + action: "editor:undo", + }, + MenuItem::Action { + name: "Redo", + keystroke: Some("cmd-Z"), + action: "editor:redo", + }, + MenuItem::Separator, + MenuItem::Action { + name: "Cut", + keystroke: Some("cmd-x"), + action: "editor:cut", + }, + MenuItem::Action { + name: "Copy", + keystroke: Some("cmd-c"), + action: "editor:copy", + }, + MenuItem::Action { + name: "Paste", + keystroke: Some("cmd-v"), + action: "editor:paste", + }, + ], + }, +]; diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index c58fa864d2..b7a76f9445 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -14,6 +14,7 @@ use std::path::PathBuf; pub fn init(app: &mut App) { app.add_global_action("workspace:open_paths", open_paths); + app.add_global_action("app:quit", quit); pane::init(app); workspace_view::init(app); } @@ -50,6 +51,10 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { app.add_window(|ctx| WorkspaceView::new(workspace, params.settings.clone(), ctx)); } +fn quit(_: &(), app: &mut MutableAppContext) { + app.platform().quit(); +} + #[cfg(test)] mod tests { use super::*; From e6279311df05c980b1ce89c4bb53085a9e7acd54 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 16:11:45 -0700 Subject: [PATCH 4/6] Call SetActivationPolicy at the proper time If this method is called too early, the menu bar won't be clickable on startup until the window loses focus. Calling it once the application finishes launching seems to fix the issue. See https://github.com/glfw/glfw/issues/1648 --- gpui/src/platform/mac/runner.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index 1783e03ea0..2c2c3ecab4 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -205,7 +205,6 @@ impl crate::platform::Runner for Runner { let app: id = msg_send![APP_CLASS, sharedApplication]; let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - app.setActivationPolicy_(NSApplicationActivationPolicyRegular); (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); app.setDelegate_(app_delegate); @@ -241,9 +240,14 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { } extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { - let runner = unsafe { get_runner(this) }; - if let Some(callback) = runner.finish_launching_callback.take() { - callback(); + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + + let runner = get_runner(this); + if let Some(callback) = runner.finish_launching_callback.take() { + callback(); + } } } From 4c184f50f79ecf0d9844b1c7568dbb37f041baba Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 22:25:54 -0700 Subject: [PATCH 5/6] Implement File > Open menu item --- Cargo.lock | 18 ++++++----------- Cargo.toml | 6 ++++++ gpui/src/platform/mac/app.rs | 39 ++++++++++++++++++++++++++++++++++-- gpui/src/platform/mod.rs | 7 +++++++ gpui/src/platform/test.rs | 4 ++++ zed/src/main.rs | 23 +++++++++++++++++---- zed/src/menus.rs | 10 ++++++++- 7 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38d75ed246..ce7dedfdf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,8 +399,7 @@ dependencies = [ [[package]] name = "cocoa" version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "bitflags", "block", @@ -415,8 +414,7 @@ dependencies = [ [[package]] name = "cocoa-foundation" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "bitflags", "block", @@ -445,8 +443,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "core-foundation-sys", "libc", @@ -455,14 +452,12 @@ dependencies = [ [[package]] name = "core-foundation-sys" version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" [[package]] name = "core-graphics" version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "bitflags", "core-foundation", @@ -474,8 +469,7 @@ dependencies = [ [[package]] name = "core-graphics-types" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "bitflags", "core-foundation", diff --git a/Cargo.toml b/Cargo.toml index 83f3ad985a..e062eb99eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,9 @@ members = ["zed", "gpui"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} + +# TODO - Remove when this is merged: https://github.com/servo/core-foundation-rs/pull/454 +cocoa = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +cocoa-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +core-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +core-graphics = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index b77cb2ee51..849f112d45 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -1,9 +1,13 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; -use cocoa::{appkit::NSApplication, base::nil}; +use cocoa::{ + appkit::{NSApplication, NSOpenPanel, NSModalResponse}, + base::nil, + foundation::{NSArray, NSString, NSURL}, +}; use objc::{msg_send, sel, sel_impl}; -use std::{rc::Rc, sync::Arc}; +use std::{path::PathBuf, rc::Rc, sync::Arc}; pub struct App { dispatcher: Arc, @@ -39,6 +43,37 @@ impl platform::App for App { Ok(Box::new(Window::open(options, executor, self.fonts())?)) } + fn prompt_for_paths( + &self, + options: platform::PathPromptOptions, + ) -> Option> { + 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 response = panel.runModal(); + if response == NSModalResponse::NSModalResponseOk { + let mut result = Vec::new(); + let urls = panel.URLs(); + for i in 0..urls.count() { + let url = urls.objectAtIndex(i); + let string = url.absoluteString(); + let string = std::ffi::CStr::from_ptr(string.UTF8String()) + .to_string_lossy() + .to_string(); + if let Some(path) = string.strip_prefix("file://") { + result.push(PathBuf::from(path)); + } + } + Some(result) + } else { + None + } + } + } + fn fonts(&self) -> Arc { self.fonts.clone() } diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index bbd2dc9838..9ece82fb78 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -41,6 +41,7 @@ pub trait App { options: WindowOptions, executor: Rc, ) -> Result>; + fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; fn fonts(&self) -> Arc; fn quit(&self); } @@ -66,6 +67,12 @@ pub struct WindowOptions<'a> { pub title: Option<&'a str>, } +pub struct PathPromptOptions { + pub files: bool, + pub directories: bool, + pub multiple: bool, +} + pub trait FontSystem: Send + Sync { fn load_family(&self, name: &str) -> anyhow::Result>; fn select_font( diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 067184adea..fdb497ae97 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -48,6 +48,10 @@ impl super::App for App { } fn quit(&self) {} + + fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option> { + None + } } impl Window { diff --git a/zed/src/main.rs b/zed/src/main.rs index 08fb098784..82a8a73106 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,5 +1,5 @@ use fs::OpenOptions; -use gpui::platform::{current as platform, Runner as _}; +use gpui::platform::{current as platform, PathPromptOptions, Runner as _}; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; @@ -18,9 +18,24 @@ fn main() { .set_menus(menus::MENUS) .on_menu_command({ let app = app.clone(); - move |command| { - log::info!("menu command: {}", command); - app.dispatch_global_action(command, ()) + let settings_rx = settings_rx.clone(); + move |command| match command { + "app:open" => { + if let Some(paths) = app.platform().prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }) { + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths, + settings: settings_rx.clone(), + }, + ); + } + } + _ => app.dispatch_global_action(command, ()), } }) .on_finish_launching({ diff --git a/zed/src/menus.rs b/zed/src/menus.rs index c6ef18b746..cda749c30e 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -6,7 +6,7 @@ pub const MENUS: &'static [Menu] = &[ name: "Zed", items: &[ MenuItem::Action { - name: "About Zed...", + name: "About Zed…", keystroke: None, action: "app:about-zed", }, @@ -20,6 +20,14 @@ pub const MENUS: &'static [Menu] = &[ }, Menu { name: "File", + items: &[MenuItem::Action { + name: "Open…", + keystroke: Some("cmd-o"), + action: "app:open", + }], + }, + Menu { + name: "Edit", items: &[ MenuItem::Action { name: "Undo", From da0aebb0ec9f9a7258bf684091096f5dba3cb50b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 9 Apr 2021 08:45:23 -0700 Subject: [PATCH 6/6] Use upstream git revision of core-foundation-rs --- Cargo.lock | 12 ++++++------ Cargo.toml | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce7dedfdf6..b497ce1981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "cocoa" version = "0.24.0" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "cocoa-foundation" version = "0.1.0" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -443,7 +443,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" version = "0.9.1" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "core-foundation-sys", "libc", @@ -452,12 +452,12 @@ dependencies = [ [[package]] name = "core-foundation-sys" version = "0.8.2" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" [[package]] name = "core-graphics" version = "0.22.2" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "core-foundation", @@ -469,7 +469,7 @@ dependencies = [ [[package]] name = "core-graphics-types" version = "0.1.1" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "core-foundation", diff --git a/Cargo.toml b/Cargo.toml index e062eb99eb..b60e33d042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ members = ["zed", "gpui"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -# TODO - Remove when this is merged: https://github.com/servo/core-foundation-rs/pull/454 -cocoa = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -cocoa-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -core-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -core-graphics = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/454 +cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}