diff --git a/gpui/src/app.rs b/gpui/src/app.rs index e5f40c6ba5..4f5fb7b905 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -69,7 +69,7 @@ pub trait UpdateView { pub struct Menu<'a> { pub name: &'a str, - pub items: &'a [MenuItem<'a>], + pub items: Vec>, } pub enum MenuItem<'a> { @@ -77,6 +77,7 @@ pub enum MenuItem<'a> { name: &'a str, keystroke: Option<&'a str>, action: &'a str, + arg: Option>, }, Separator, } @@ -127,9 +128,27 @@ impl App { let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?); let app = Self(Rc::new(RefCell::new(MutableAppContext::new( foreground, - platform, + platform.clone(), asset_source, )))); + + let ctx = app.0.clone(); + platform.on_menu_command(Box::new(move |command, arg| { + let mut ctx = ctx.borrow_mut(); + if let Some(key_window_id) = ctx.platform.key_window_id() { + if let Some((presenter, _)) = + ctx.presenters_and_platform_windows.get(&key_window_id) + { + let presenter = presenter.clone(); + let path = presenter.borrow().dispatch_path(ctx.as_ref()); + if ctx.dispatch_action_any(key_window_id, &path, command, arg.unwrap_or(&())) { + return; + } + } + } + ctx.dispatch_global_action_any(command, arg.unwrap_or(&())); + })); + app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); Ok(app) } @@ -169,20 +188,6 @@ impl App { self } - pub fn on_menu_command(self, mut callback: F) -> Self - where - F: 'static + FnMut(&str, &mut MutableAppContext), - { - let ctx = self.0.clone(); - self.0 - .borrow() - .platform - .on_menu_command(Box::new(move |command| { - callback(command, &mut *ctx.borrow_mut()) - })); - self - } - pub fn on_open_files(self, mut callback: F) -> Self where F: 'static + FnMut(Vec, &mut MutableAppContext), @@ -197,10 +202,6 @@ impl App { self } - pub fn set_menus(&self, menus: &[Menu]) { - self.0.borrow().platform.set_menus(menus); - } - pub fn run(self, on_finish_launching: F) where F: 'static + FnOnce(&mut MutableAppContext), @@ -383,8 +384,8 @@ pub struct MutableAppContext { subscriptions: HashMap>, observations: HashMap>, window_invalidations: HashMap, - invalidation_callbacks: - HashMap>, + presenters_and_platform_windows: + HashMap>, Box)>, debug_elements_callbacks: HashMap crate::json::Value>>, foreground: Rc, future_handlers: Rc>>, @@ -422,7 +423,7 @@ impl MutableAppContext { subscriptions: HashMap::new(), observations: HashMap::new(), window_invalidations: HashMap::new(), - invalidation_callbacks: HashMap::new(), + presenters_and_platform_windows: HashMap::new(), debug_elements_callbacks: HashMap::new(), foreground, future_handlers: Default::default(), @@ -454,15 +455,6 @@ impl MutableAppContext { &self.ctx.background } - pub fn on_window_invalidated(&mut self, window_id: usize, callback: F) - where - F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext), - { - self.invalidation_callbacks - .insert(window_id, Box::new(callback)); - self.update_windows(); - } - pub fn on_debug_elements(&mut self, window_id: usize, callback: F) where F: 'static + Fn(&AppContext) -> crate::json::Value, @@ -573,6 +565,10 @@ impl MutableAppContext { result } + pub fn set_menus(&self, menus: Vec) { + self.platform.set_menus(menus); + } + pub fn dispatch_action( &mut self, window_id: usize, @@ -634,7 +630,7 @@ impl MutableAppContext { } if !halted_dispatch { - self.dispatch_global_action_with_dyn_arg(name, arg); + self.dispatch_global_action_any(name, arg); } self.flush_effects(); @@ -642,10 +638,10 @@ impl MutableAppContext { } pub fn dispatch_global_action(&mut self, name: &str, arg: T) { - self.dispatch_global_action_with_dyn_arg(name, Box::new(arg).as_ref()); + self.dispatch_global_action_any(name, Box::new(arg).as_ref()); } - fn dispatch_global_action_with_dyn_arg(&mut self, name: &str, arg: &dyn Any) { + fn dispatch_global_action_any(&mut self, name: &str, arg: &dyn Any) { if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) { self.pending_flushes += 1; for handler in handlers.iter_mut().rev() { @@ -741,87 +737,75 @@ impl MutableAppContext { } fn open_platform_window(&mut self, window_id: usize) { - match self.platform.open_window( + let mut window = self.platform.open_window( + window_id, WindowOptions { bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)), title: "Zed".into(), }, self.foreground.clone(), - ) { - Err(e) => log::error!("error opening window: {}", e), - Ok(mut window) => { - let text_layout_cache = TextLayoutCache::new(self.platform.fonts()); - let presenter = Rc::new(RefCell::new(Presenter::new( - window_id, - self.font_cache.clone(), - text_layout_cache, - self.assets.clone(), - self, - ))); + ); + let text_layout_cache = TextLayoutCache::new(self.platform.fonts()); + let presenter = Rc::new(RefCell::new(Presenter::new( + window_id, + self.font_cache.clone(), + text_layout_cache, + self.assets.clone(), + self, + ))); - { - let mut app = self.upgrade(); - let presenter = presenter.clone(); - window.on_event(Box::new(move |event| { - app.update(|ctx| { - if let Event::KeyDown { keystroke, .. } = &event { - if ctx - .dispatch_keystroke( - window_id, - presenter.borrow().dispatch_path(ctx.as_ref()), - keystroke, - ) - .unwrap() - { - return; - } - } + { + let mut app = self.upgrade(); + let presenter = presenter.clone(); + window.on_event(Box::new(move |event| { + app.update(|ctx| { + if let Event::KeyDown { keystroke, .. } = &event { + if ctx + .dispatch_keystroke( + window_id, + presenter.borrow().dispatch_path(ctx.as_ref()), + keystroke, + ) + .unwrap() + { + return; + } + } - let actions = - presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); - for action in actions { - ctx.dispatch_action_any( - window_id, - &action.path, - action.name, - action.arg.as_ref(), - ); - } - }) - })); - } - - { - let mut app = self.upgrade(); - let presenter = presenter.clone(); - window.on_resize(Box::new(move |window| { - app.update(|ctx| { - let scene = presenter.borrow_mut().build_scene( - window.size(), - window.scale_factor(), - ctx, - ); - window.present_scene(scene); - }) - })); - } - - { - let presenter = presenter.clone(); - self.on_window_invalidated(window_id, move |invalidation, ctx| { - let mut presenter = presenter.borrow_mut(); - presenter.invalidate(invalidation, ctx.as_ref()); - let scene = - presenter.build_scene(window.size(), window.scale_factor(), ctx); - window.present_scene(scene); - }); - } - - self.on_debug_elements(window_id, move |ctx| { - presenter.borrow().debug_elements(ctx).unwrap() - }); - } + let actions = presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); + for action in actions { + ctx.dispatch_action_any( + window_id, + &action.path, + action.name, + action.arg.as_ref(), + ); + } + }) + })); } + + { + let mut app = self.upgrade(); + let presenter = presenter.clone(); + window.on_resize(Box::new(move |window| { + app.update(|ctx| { + let scene = presenter.borrow_mut().build_scene( + window.size(), + window.scale_factor(), + ctx, + ); + window.present_scene(scene); + }) + })); + } + + self.presenters_and_platform_windows + .insert(window_id, (presenter.clone(), window)); + + self.on_debug_elements(window_id, move |ctx| { + presenter.borrow().debug_elements(ctx).unwrap() + }); } pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle @@ -922,9 +906,17 @@ impl MutableAppContext { std::mem::swap(&mut invalidations, &mut self.window_invalidations); for (window_id, invalidation) in invalidations { - if let Some(mut callback) = self.invalidation_callbacks.remove(&window_id) { - callback(invalidation, self); - self.invalidation_callbacks.insert(window_id, callback); + if let Some((presenter, mut window)) = + self.presenters_and_platform_windows.remove(&window_id) + { + { + let mut presenter = presenter.borrow_mut(); + presenter.invalidate(invalidation, self.as_ref()); + let scene = presenter.build_scene(window.size(), window.scale_factor(), self); + window.present_scene(scene); + } + self.presenters_and_platform_windows + .insert(window_id, (presenter, window)); } } } diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs index 4201100a6d..4fc0f160a3 100644 --- a/gpui/src/platform/mac/platform.rs +++ b/gpui/src/platform/mac/platform.rs @@ -1,6 +1,5 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem}; -use anyhow::Result; use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, @@ -20,6 +19,7 @@ use objc::{ }; use ptr::null_mut; use std::{ + any::Any, cell::RefCell, ffi::{c_void, CStr}, os::raw::c_char, @@ -76,7 +76,7 @@ pub struct MacPlatform { dispatcher: Arc, fonts: Arc, callbacks: RefCell, - menu_item_actions: RefCell>, + menu_item_actions: RefCell>)>>, } #[derive(Default)] @@ -84,7 +84,7 @@ struct Callbacks { become_active: Option>, resign_active: Option>, event: Option bool>>, - menu_command: Option>, + menu_command: Option)>>, open_files: Option)>>, finish_launching: Option ()>>, } @@ -99,7 +99,7 @@ impl MacPlatform { } } - unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id { + unsafe fn create_menu_bar(&self, menus: Vec) -> id { let menu_bar = NSMenu::new(nil).autorelease(); let mut menu_item_actions = self.menu_item_actions.borrow_mut(); menu_item_actions.clear(); @@ -107,8 +107,9 @@ impl MacPlatform { for menu_config in menus { let menu_bar_item = NSMenuItem::new(nil).autorelease(); let menu = NSMenu::new(nil).autorelease(); + let menu_name = menu_config.name; - menu.setTitle_(ns_string(menu_config.name)); + menu.setTitle_(ns_string(menu_name)); for item_config in menu_config.items { let item; @@ -121,12 +122,13 @@ impl MacPlatform { name, keystroke, action, + arg, } => { 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 + menu_name, name, err ) }); @@ -161,7 +163,7 @@ impl MacPlatform { let tag = menu_item_actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; - menu_item_actions.push(action.to_string()); + menu_item_actions.push((action.to_string(), arg)); } } @@ -189,7 +191,7 @@ impl platform::Platform for MacPlatform { self.callbacks.borrow_mut().event = Some(callback); } - fn on_menu_command(&self, callback: Box) { + fn on_menu_command(&self, callback: Box)>) { self.callbacks.borrow_mut().menu_command = Some(callback); } @@ -231,10 +233,15 @@ impl platform::Platform for MacPlatform { fn open_window( &self, + id: usize, options: platform::WindowOptions, executor: Rc, - ) -> Result> { - Ok(Box::new(Window::open(options, executor, self.fonts())?)) + ) -> Box { + Box::new(Window::open(id, options, executor, self.fonts())) + } + + fn key_window_id(&self) -> Option { + Window::key_window_id() } fn prompt_for_paths( @@ -292,7 +299,7 @@ impl platform::Platform for MacPlatform { } } - fn set_menus(&self, menus: &[Menu]) { + fn set_menus(&self, menus: Vec) { unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setMainMenu_(self.create_menu_bar(menus)); @@ -375,8 +382,8 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_item_actions.borrow().get(index) { - callback(&action); + if let Some((action, arg)) = platform.menu_item_actions.borrow().get(index) { + callback(action, arg.as_ref().map(Box::as_ref)); } } } diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index 1c6b64a4c2..52296c69c3 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -9,7 +9,6 @@ use crate::{ scene::Layer, Scene, }; -use anyhow::{anyhow, Result}; use cocoa::foundation::NSUInteger; use metal::{MTLPixelFormat, MTLResourceOptions, NSRange}; use shaders::{ToFloat2 as _, ToUchar4 as _}; @@ -41,10 +40,10 @@ impl Renderer { device: metal::Device, pixel_format: metal::MTLPixelFormat, fonts: Arc, - ) -> Result { + ) -> Self { let library = device .new_library_with_data(SHADERS_METALLIB) - .map_err(|message| anyhow!("error building metal library: {}", message))?; + .expect("error building metal library"); let unit_vertices = [ (0., 0.).to_float2(), @@ -73,7 +72,7 @@ impl Renderer { "quad_vertex", "quad_fragment", pixel_format, - )?; + ); let shadow_pipeline_state = build_pipeline_state( &device, &library, @@ -81,7 +80,7 @@ impl Renderer { "shadow_vertex", "shadow_fragment", pixel_format, - )?; + ); let sprite_pipeline_state = build_pipeline_state( &device, &library, @@ -89,7 +88,7 @@ impl Renderer { "sprite_vertex", "sprite_fragment", pixel_format, - )?; + ); let path_atlas_pipeline_state = build_path_atlas_pipeline_state( &device, &library, @@ -97,8 +96,8 @@ impl Renderer { "path_atlas_vertex", "path_atlas_fragment", MTLPixelFormat::R8Unorm, - )?; - Ok(Self { + ); + Self { sprite_cache, path_atlases, quad_pipeline_state, @@ -107,7 +106,7 @@ impl Renderer { path_atlas_pipeline_state, unit_vertices, instances, - }) + } } pub fn render( @@ -713,13 +712,13 @@ fn build_pipeline_state( vertex_fn_name: &str, fragment_fn_name: &str, pixel_format: metal::MTLPixelFormat, -) -> Result { +) -> metal::RenderPipelineState { let vertex_fn = library .get_function(vertex_fn_name, None) - .map_err(|message| anyhow!("error locating vertex function: {}", message))?; + .expect("error locating vertex function"); let fragment_fn = library .get_function(fragment_fn_name, None) - .map_err(|message| anyhow!("error locating fragment function: {}", message))?; + .expect("error locating fragment function"); let descriptor = metal::RenderPipelineDescriptor::new(); descriptor.set_label(label); @@ -737,7 +736,7 @@ fn build_pipeline_state( device .new_render_pipeline_state(&descriptor) - .map_err(|message| anyhow!("could not create render pipeline state: {}", message)) + .expect("could not create render pipeline state") } fn build_path_atlas_pipeline_state( @@ -747,13 +746,13 @@ fn build_path_atlas_pipeline_state( vertex_fn_name: &str, fragment_fn_name: &str, pixel_format: metal::MTLPixelFormat, -) -> Result { +) -> metal::RenderPipelineState { let vertex_fn = library .get_function(vertex_fn_name, None) - .map_err(|message| anyhow!("error locating vertex function: {}", message))?; + .expect("error locating vertex function"); let fragment_fn = library .get_function(fragment_fn_name, None) - .map_err(|message| anyhow!("error locating fragment function: {}", message))?; + .expect("error locating fragment function"); let descriptor = metal::RenderPipelineDescriptor::new(); descriptor.set_label(label); @@ -771,7 +770,7 @@ fn build_path_atlas_pipeline_state( device .new_render_pipeline_state(&descriptor) - .map_err(|message| anyhow!("could not create render pipeline state: {}", message)) + .expect("could not create render pipeline state") } mod shaders { diff --git a/gpui/src/platform/mac/window.rs b/gpui/src/platform/mac/window.rs index e9e6318b5d..d45077e3b5 100644 --- a/gpui/src/platform/mac/window.rs +++ b/gpui/src/platform/mac/window.rs @@ -4,11 +4,10 @@ use crate::{ platform::{self, Event, WindowContext}, Scene, }; -use anyhow::{anyhow, Result}; use cocoa::{ appkit::{ - NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, - NSWindow, NSWindowStyleMask, + NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, + NSViewWidthSizable, NSWindow, NSWindowStyleMask, }, base::{id, nil}, foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString}, @@ -118,6 +117,7 @@ unsafe fn build_classes() { pub struct Window(Rc>); struct WindowState { + id: usize, native_window: id, event_callback: Option>, resize_callback: Option>, @@ -131,10 +131,11 @@ struct WindowState { impl Window { pub fn open( + id: usize, options: platform::WindowOptions, executor: Rc, fonts: Arc, - ) -> Result { + ) -> Self { const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm; unsafe { @@ -153,13 +154,10 @@ impl Window { NSBackingStoreBuffered, NO, ); + assert!(!native_window.is_null()); - if native_window == nil { - return Err(anyhow!("window returned nil from initializer")); - } - - let device = metal::Device::system_default() - .ok_or_else(|| anyhow!("could not find default metal device"))?; + let device = + metal::Device::system_default().expect("could not find default metal device"); let layer: id = msg_send![class!(CAMetalLayer), layer]; let _: () = msg_send![layer, setDevice: device.as_ptr()]; @@ -175,18 +173,17 @@ impl Window { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); - if native_view == nil { - return Err(anyhow!("view return nil from initializer")); - } + assert!(!native_view.is_null()); let window = Self(Rc::new(RefCell::new(WindowState { + id, native_window, event_callback: None, resize_callback: None, synthetic_drag_counter: 0, executor, scene_to_render: Default::default(), - renderer: Renderer::new(device.clone(), PIXEL_FORMAT, fonts)?, + renderer: Renderer::new(device.clone(), PIXEL_FORMAT, fonts), command_queue: device.new_command_queue(), layer, }))); @@ -227,7 +224,20 @@ impl Window { pool.drain(); - Ok(window) + window + } + } + + pub fn key_window_id() -> Option { + unsafe { + let app = NSApplication::sharedApplication(nil); + let key_window: id = msg_send![app, keyWindow]; + if key_window.is_null() { + None + } else { + let id = get_window_state(&*key_window).borrow().id; + Some(id) + } } } } diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index 39825c941e..52148ee27c 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -17,13 +17,12 @@ use crate::{ text_layout::Line, Menu, Scene, }; -use anyhow::Result; use async_task::Runnable; pub use event::Event; -use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc}; +use std::{any::Any, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; pub trait Platform { - fn on_menu_command(&self, callback: Box); + fn on_menu_command(&self, callback: Box)>); fn on_become_active(&self, callback: Box); fn on_resign_active(&self, callback: Box); fn on_event(&self, callback: Box bool>); @@ -36,13 +35,15 @@ pub trait Platform { fn activate(&self, ignoring_other_apps: bool); fn open_window( &self, + id: usize, options: WindowOptions, executor: Rc, - ) -> Result>; + ) -> Box; + fn key_window_id(&self) -> Option; fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; fn quit(&self); fn copy(&self, text: &str); - fn set_menus(&self, menus: &[Menu]); + fn set_menus(&self, menus: Vec); } pub trait Dispatcher: Send + Sync { diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index f1d6bead66..eed3709cba 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -1,6 +1,6 @@ use pathfinder_geometry::vector::Vector2F; -use std::rc::Rc; use std::sync::Arc; +use std::{any::Any, rc::Rc}; struct Platform { dispatcher: Arc, @@ -27,7 +27,7 @@ impl Platform { } impl super::Platform for Platform { - fn on_menu_command(&self, _: Box) {} + fn on_menu_command(&self, _: Box)>) {} fn on_become_active(&self, _: Box) {} @@ -53,13 +53,18 @@ impl super::Platform for Platform { fn open_window( &self, + _: usize, options: super::WindowOptions, _executor: Rc, - ) -> anyhow::Result> { - Ok(Box::new(Window::new(options.bounds.size()))) + ) -> Box { + Box::new(Window::new(options.bounds.size())) } - fn set_menus(&self, _menus: &[crate::Menu]) {} + fn key_window_id(&self) -> Option { + None + } + + fn set_menus(&self, _menus: Vec) {} fn quit(&self) {} diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 752df470c5..14c2369258 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -10,6 +10,6 @@ mod test; mod time; mod timer; mod util; -mod watch; +pub mod watch; pub mod workspace; mod worktree; diff --git a/zed/src/main.rs b/zed/src/main.rs index 407b4952c1..3aaddff884 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,5 +1,4 @@ use fs::OpenOptions; -use gpui::PathPromptOptions; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; @@ -13,29 +12,8 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); - app.set_menus(menus::MENUS); - app.on_menu_command({ - let settings_rx = settings_rx.clone(); - move |command, ctx| match command { - "app:open" => { - if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions { - files: true, - directories: true, - multiple: true, - }) { - ctx.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths, - settings: settings_rx.clone(), - }, - ); - } - } - _ => ctx.dispatch_global_action(command, ()), - } - }) - .run(move |ctx| { + app.run(move |ctx| { + ctx.set_menus(menus::menus(settings_rx.clone())); workspace::init(ctx); editor::init(ctx); file_finder::init(ctx); diff --git a/zed/src/menus.rs b/zed/src/menus.rs index cda749c30e..08afb1e990 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -1,60 +1,71 @@ +use crate::{settings::Settings, watch::Receiver}; 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: "Open…", - keystroke: Some("cmd-o"), - action: "app:open", - }], - }, - Menu { - name: "Edit", - 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", - }, - ], - }, -]; +pub fn menus(settings: Receiver) -> Vec> { + vec![ + Menu { + name: "Zed", + items: vec![ + MenuItem::Action { + name: "About Zed…", + keystroke: None, + action: "app:about-zed", + arg: None, + }, + MenuItem::Separator, + MenuItem::Action { + name: "Quit", + keystroke: Some("cmd-q"), + action: "app:quit", + arg: None, + }, + ], + }, + Menu { + name: "File", + items: vec![MenuItem::Action { + name: "Open…", + keystroke: Some("cmd-o"), + action: "workspace:open", + arg: Some(Box::new(settings)), + }], + }, + Menu { + name: "Edit", + items: vec![ + MenuItem::Action { + name: "Undo", + keystroke: Some("cmd-z"), + action: "buffer:undo", + arg: None, + }, + MenuItem::Action { + name: "Redo", + keystroke: Some("cmd-Z"), + action: "buffer:redo", + arg: None, + }, + MenuItem::Separator, + MenuItem::Action { + name: "Cut", + keystroke: Some("cmd-x"), + action: "buffer:cut", + arg: None, + }, + MenuItem::Action { + name: "Copy", + keystroke: Some("cmd-c"), + action: "buffer:copy", + arg: None, + }, + MenuItem::Action { + name: "Paste", + keystroke: Some("cmd-v"), + action: "buffer:paste", + arg: None, + }, + ], + }, + ] +} diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index a441eb8b8b..4cf765b0aa 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -8,11 +8,15 @@ pub use pane_group::*; pub use workspace::*; pub use workspace_view::*; -use crate::{settings::Settings, watch}; -use gpui::MutableAppContext; +use crate::{ + settings::Settings, + watch::{self, Receiver}, +}; +use gpui::{MutableAppContext, PathPromptOptions}; use std::path::PathBuf; pub fn init(app: &mut MutableAppContext) { + app.add_global_action("workspace:open", open); app.add_global_action("workspace:open_paths", open_paths); app.add_global_action("app:quit", quit); pane::init(app); @@ -24,6 +28,22 @@ pub struct OpenParams { pub settings: watch::Receiver, } +fn open(settings: &Receiver, ctx: &mut MutableAppContext) { + if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }) { + ctx.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths, + settings: settings.clone(), + }, + ); + } +} + fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { log::info!("open paths {:?}", params.paths);