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 daf8335f6081a003a8c6672d3bdee253a754a9d3..b3b2a788cf2a00b95a28d04c997dcd4610c34824 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 c407bf59d7272d899538c4425ea0ca61a526c091..4b0ebe7dfdb8807c88f4013ee5766fe297c7d909 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 4b0ebe7dfdb8807c88f4013ee5766fe297c7d909..a4b7281753ff5b9997d034de91df09ca8f8a3578 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 8dbc34b893c76a28a95204e92e707df7e31acaf7..8863bb05eb8c4117b18eb2180b4affc0d87dc650 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 b3b2a788cf2a00b95a28d04c997dcd4610c34824..b77cb2ee51ade7fc733e431b842a8d8aa5c29e77 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 a4b7281753ff5b9997d034de91df09ca8f8a3578..1783e03ea050feaebd2deb935fd0385f44fa5745 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 5bd7e4659e40011457c6ba50a346bf9d6be628fd..bbd2dc9838c705ceaebcac0f4a5a1122ce4276bc 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 545ab10e34149b4fd65bea4e40933c508fadad44..067184adea27b52f3733fb4a27cb77e97913634b 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 a66a892ebfd5d3c033315f7ee49d48637de6dbd5..752df470c5c8078ea04263223b1bf3539f8e37bd 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 ed52fb6163758aee7ec13a99f5e85d5c6e8f82d1..08fb09878437a1564fb8caccd4f511e3fe27336a 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 0000000000000000000000000000000000000000..c6ef18b74616b4828e2fe5bc4641ceb2aeea368d --- /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 c58fa864d2c00ce7c11d2adc6f90888db6942c5d..b7a76f94458fa15436d8bf0a4c2d2685954630dc 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 1783e03ea050feaebd2deb935fd0385f44fa5745..2c2c3ecab4df1f7a473821b34ec9b47024d49a13 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 38d75ed246ed6c1ed7525fa8da20c5aed33e0f85..ce7dedfdf6f4626dc77d290eb1cf8c4502dbae52 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 83f3ad985a58a5d311c4978c0a53ab6e1e52c7af..e062eb99eb97a890ac07a3c28982555dbb1ad24d 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 b77cb2ee51ade7fc733e431b842a8d8aa5c29e77..849f112d4503e0127a3e7ce3a2bc487810be0a8f 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 bbd2dc9838c705ceaebcac0f4a5a1122ce4276bc..9ece82fb787c52f9a5503babbf130406ad816833 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 067184adea27b52f3733fb4a27cb77e97913634b..fdb497ae9765eb56df0bd8f9116d7bc3b18597cb 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 08fb09878437a1564fb8caccd4f511e3fe27336a..82a8a731064322c4851bf74224de0b33bc9672d7 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 c6ef18b74616b4828e2fe5bc4641ceb2aeea368d..cda749c30e5ee3a4678f678e5d879381756003f8 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 ce7dedfdf6f4626dc77d290eb1cf8c4502dbae52..b497ce19815c045b4503fa6b9c5513f79812a8c8 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 e062eb99eb97a890ac07a3c28982555dbb1ad24d..b60e33d04262cdcd0a57edbbf83ce3634f97ca20 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"}