From 7e5186e4a0d78c4fa07c3b3b8ad8218019b32ac5 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 0a1277468007aca3a0e362ad8500491919bd9745 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 334de06322f4a4c79bc1d9bbf7908eb9a080766d 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 f656b387b3fa6e63628ff8c2cd5d88a63efda899 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 7ebcbdc0cb5b7f8014f8d68ec9aed2d184b2dcf2 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 6873662c472561e228dfb90409f656be2705be89 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"}