diff --git a/crates/editor2/src/display_map/block_map.rs b/crates/editor2/src/display_map/block_map.rs index 64e46549fd6c7b9ae2576ca68d7c0f2af52b750e..cc0095bca94f30f6d65866c640092cb384d2cce3 100644 --- a/crates/editor2/src/display_map/block_map.rs +++ b/crates/editor2/src/display_map/block_map.rs @@ -993,7 +993,7 @@ mod tests { use super::*; use crate::display_map::inlay_map::InlayMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use gpui::{div, font, px, Element, Platform as _}; + use gpui::{div, font, px, Element}; use multi_buffer::MultiBuffer; use rand::prelude::*; use settings::SettingsStore; @@ -1185,11 +1185,7 @@ mod tests { fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) { cx.update(|cx| init_test(cx)); - let font_id = cx - .test_platform - .text_system() - .font_id(&font("Helvetica")) - .unwrap(); + let font_id = cx.text_system().font_id(&font("Helvetica")).unwrap(); let text = "one two three\nfour five six\nseven eight"; diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs index a2ac0ec849bfb9b26983c897a2ae3cc2ebd9878c..ca9db7754baec0b1477c19d0f8efaa98fd8f14c3 100644 --- a/crates/editor2/src/display_map/wrap_map.rs +++ b/crates/editor2/src/display_map/wrap_map.rs @@ -1032,7 +1032,7 @@ mod tests { display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, MultiBuffer, }; - use gpui::{font, px, test::observe, Platform}; + use gpui::{font, px, test::observe}; use rand::prelude::*; use settings::SettingsStore; use smol::stream::StreamExt; diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 424da8987eb6d673f0e789d4b8ae8b1620967045..b5f156f494936cba312d9b44365d1a903a803c4e 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -12,7 +12,7 @@ use futures::StreamExt; use gpui::{ div, serde_json::{self, json}, - Div, Flatten, Platform, TestAppContext, VisualTestContext, WindowBounds, WindowOptions, + Div, Flatten, TestAppContext, VisualTestContext, WindowBounds, WindowOptions, }; use indoc::indoc; use language::{ @@ -3238,9 +3238,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { the lazy dog"}); cx.update_editor(|e, cx| e.copy(&Copy, cx)); assert_eq!( - cx.test_platform - .read_from_clipboard() - .map(|item| item.text().to_owned()), + cx.read_from_clipboard().map(|item| item.text().to_owned()), Some("fox jumps over\n".to_owned()) ); diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 74712feb287468ef67917235b544ffd85ecf8400..79beca75da7f89e83ff2dcd9079f45c29ff153e2 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -15,10 +15,10 @@ use smol::future::FutureExt; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView, - AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, + current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, + AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, - ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, + ForegroundExecutor, KeyBinding, Keymap, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, @@ -278,6 +278,8 @@ impl AppContext { }), }); + init_app_menus(platform.as_ref(), &mut *app.borrow_mut()); + platform.on_quit(Box::new({ let cx = app.clone(); move || { @@ -1059,6 +1061,19 @@ impl AppContext { .contains_key(&action.as_any().type_id()) } + pub fn set_menus(&mut self, menus: Vec) { + if let Some(active_window) = self.active_window() { + active_window + .update(self, |_, cx| { + cx.platform + .set_menus(menus, Some(&cx.window.current_frame.dispatch_tree)); + }) + .ok(); + } else { + self.platform.set_menus(menus, None); + } + } + pub fn dispatch_action(&mut self, action: &dyn Action) { self.propagate_event = true; diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index a9403de9bce63a62dfedf455d997de34dff4d856..cbd70e52ffab4d539abfe6a177f8a112825b8402 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,9 +1,10 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent, - KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result, - Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, - VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions, + BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter, + ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform, + PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, + TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -16,6 +17,7 @@ pub struct TestAppContext { pub foreground_executor: ForegroundExecutor, pub dispatcher: TestDispatcher, pub test_platform: Rc, + text_system: Arc, } impl Context for TestAppContext { @@ -82,6 +84,7 @@ impl TestAppContext { let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone()); let asset_source = Arc::new(()); let http_client = util::http::FakeHttpClient::with_404_response(); + let text_system = Arc::new(TextSystem::new(platform.text_system())); Self { app: AppContext::new(platform.clone(), asset_source, http_client), @@ -89,6 +92,7 @@ impl TestAppContext { foreground_executor, dispatcher: dispatcher.clone(), test_platform: platform, + text_system, } } @@ -155,6 +159,18 @@ impl TestAppContext { (view, Box::leak(cx)) } + pub fn text_system(&self) -> &Arc { + &self.text_system + } + + pub fn write_to_clipboard(&self, item: ClipboardItem) { + self.test_platform.write_to_clipboard(item) + } + + pub fn read_from_clipboard(&self) -> Option { + self.test_platform.read_from_clipboard() + } + pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 96be670af7dbc92af50571ab228dcaa663f90a75..7bcd91a5e07dbc03a52fedd6837b9e06c542c6f4 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -6,10 +6,10 @@ mod mac; mod test; use crate::{ - point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, - FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, - Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, - SharedString, Size, TaskLabel, + point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, DispatchTree, + Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, + LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, + Scene, SharedString, Size, TaskLabel, }; use anyhow::{anyhow, bail}; use async_task::Runnable; @@ -46,7 +46,7 @@ pub(crate) fn current_platform() -> Rc { Rc::new(MacPlatform::new()) } -pub trait Platform: 'static { +pub(crate) trait Platform: 'static { fn background_executor(&self) -> BackgroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor; fn text_system(&self) -> Arc; @@ -92,6 +92,7 @@ pub trait Platform: 'static { fn on_reopen(&self, callback: Box); fn on_event(&self, callback: Box bool>); + fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>); fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); diff --git a/crates/gpui2/src/platform/app_menu.rs b/crates/gpui2/src/platform/app_menu.rs index bfac1366b286ce58f1c423a2da13157881e7c3c8..10fe2cf33ae6d8b830efc92f72bba7f5157de4b9 100644 --- a/crates/gpui2/src/platform/app_menu.rs +++ b/crates/gpui2/src/platform/app_menu.rs @@ -52,7 +52,7 @@ pub enum OsAction { Redo, } -pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) { +pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &mut AppContext) { platform.on_will_open_app_menu(Box::new({ let cx = cx.to_async(); move || { diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index d7fc37f0def0736d189a891c733ed8e09997be49..8a5ee676f7c9e76364e6aea27a9b064a917cca36 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,8 +1,8 @@ -use super::BoolExt; +use super::{events::key_to_native, BoolExt}; use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, InputEvent, KeystrokeMatcher, MacDispatcher, MacDisplay, MacDisplayLinker, - MacTextSystem, MacWindow, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, + DisplayId, ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, + MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; @@ -10,10 +10,10 @@ use block::ConcreteBlock; use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, - NSSavePanel, NSWindow, + NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, + NSPasteboardTypeString, NSSavePanel, NSWindow, }, - base::{id, nil, BOOL, YES}, + base::{id, nil, selector, BOOL, YES}, foundation::{ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString, NSUInteger, NSURL, @@ -201,151 +201,155 @@ impl MacPlatform { } } - // unsafe fn create_menu_bar( - // &self, - // menus: Vec, - // delegate: id, - // actions: &mut Vec>, - // keystroke_matcher: &KeymapMatcher, - // ) -> id { - // let application_menu = NSMenu::new(nil).autorelease(); - // application_menu.setDelegate_(delegate); - - // for menu_config in menus { - // let menu = NSMenu::new(nil).autorelease(); - // menu.setTitle_(ns_string(menu_config.name)); - // menu.setDelegate_(delegate); - - // for item_config in menu_config.items { - // menu.addItem_(self.create_menu_item( - // item_config, - // delegate, - // actions, - // keystroke_matcher, - // )); - // } - - // let menu_item = NSMenuItem::new(nil).autorelease(); - // menu_item.setSubmenu_(menu); - // application_menu.addItem_(menu_item); - - // if menu_config.name == "Window" { - // let app: id = msg_send![APP_CLASS, sharedApplication]; - // app.setWindowsMenu_(menu); - // } - // } - - // application_menu - // } + unsafe fn create_menu_bar( + &self, + menus: Vec, + delegate: id, + actions: &mut Vec>, + dispatch_tree: Option<&DispatchTree>, + ) -> id { + let application_menu = NSMenu::new(nil).autorelease(); + application_menu.setDelegate_(delegate); + + for menu_config in menus { + let menu = NSMenu::new(nil).autorelease(); + menu.setTitle_(ns_string(menu_config.name)); + menu.setDelegate_(delegate); + + for item_config in menu_config.items { + menu.addItem_(self.create_menu_item(item_config, delegate, actions, dispatch_tree)); + } + + let menu_item = NSMenuItem::new(nil).autorelease(); + menu_item.setSubmenu_(menu); + application_menu.addItem_(menu_item); + + if menu_config.name == "Window" { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setWindowsMenu_(menu); + } + } + + application_menu + } unsafe fn create_menu_item( &self, item: MenuItem, delegate: id, actions: &mut Vec>, - keystroke_matcher: &KeystrokeMatcher, + dispatch_tree: Option<&DispatchTree>, ) -> id { - todo!() - // match item { - // MenuItem::Separator => NSMenuItem::separatorItem(nil), - // MenuItem::Action { - // name, - // action, - // os_action, - // } => { - // // TODO - // let keystrokes = keystroke_matcher - // .bindings_for_action(action.id()) - // .find(|binding| binding.action().eq(action.as_ref())) - // .map(|binding| binding.keystrokes()); - // let selector = match os_action { - // Some(crate::OsAction::Cut) => selector("cut:"), - // Some(crate::OsAction::Copy) => selector("copy:"), - // Some(crate::OsAction::Paste) => selector("paste:"), - // Some(crate::OsAction::SelectAll) => selector("selectAll:"), - // Some(crate::OsAction::Undo) => selector("undo:"), - // Some(crate::OsAction::Redo) => selector("redo:"), - // None => selector("handleGPUIMenuItem:"), - // }; - - // let item; - // if let Some(keystrokes) = keystrokes { - // if keystrokes.len() == 1 { - // let keystroke = &keystrokes[0]; - // let mut mask = NSEventModifierFlags::empty(); - // for (modifier, flag) in &[ - // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), - // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), - // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), - // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), - // ] { - // if *modifier { - // mask |= *flag; - // } - // } - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(key_to_native(&keystroke.key).as_ref()), - // ) - // .autorelease(); - // item.setKeyEquivalentModifierMask_(mask); - // } - // // For multi-keystroke bindings, render the keystroke as part of the title. - // else { - // use std::fmt::Write; - - // let mut name = format!("{name} ["); - // for (i, keystroke) in keystrokes.iter().enumerate() { - // if i > 0 { - // name.push(' '); - // } - // write!(&mut name, "{}", keystroke).unwrap(); - // } - // name.push(']'); - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(&name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - // } else { - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - - // let tag = actions.len() as NSInteger; - // let _: () = msg_send![item, setTag: tag]; - // actions.push(action); - // item - // } - // MenuItem::Submenu(Menu { name, items }) => { - // let item = NSMenuItem::new(nil).autorelease(); - // let submenu = NSMenu::new(nil).autorelease(); - // submenu.setDelegate_(delegate); - // for item in items { - // submenu.addItem_(self.create_menu_item( - // item, - // delegate, - // actions, - // keystroke_matcher, - // )); - // } - // item.setSubmenu_(submenu); - // item.setTitle_(ns_string(name)); - // item - // } - // } + match item { + MenuItem::Separator => NSMenuItem::separatorItem(nil), + MenuItem::Action { + name, + action, + os_action, + } => { + let bindings = dispatch_tree + .map(|tree| tree.bindings_for_action(action.as_ref(), &tree.context_stack)) + .unwrap_or_default(); + let keystrokes = bindings + .iter() + .find(|binding| binding.action().partial_eq(action.as_ref())) + .map(|binding| binding.keystrokes()); + + let selector = match os_action { + Some(crate::OsAction::Cut) => selector("cut:"), + Some(crate::OsAction::Copy) => selector("copy:"), + Some(crate::OsAction::Paste) => selector("paste:"), + Some(crate::OsAction::SelectAll) => selector("selectAll:"), + Some(crate::OsAction::Undo) => selector("undo:"), + Some(crate::OsAction::Redo) => selector("redo:"), + None => selector("handleGPUIMenuItem:"), + }; + + let item; + if let Some(keystrokes) = keystrokes { + if keystrokes.len() == 1 { + let keystroke = &keystrokes[0]; + let mut mask = NSEventModifierFlags::empty(); + for (modifier, flag) in &[ + ( + keystroke.modifiers.command, + NSEventModifierFlags::NSCommandKeyMask, + ), + ( + keystroke.modifiers.control, + NSEventModifierFlags::NSControlKeyMask, + ), + ( + keystroke.modifiers.alt, + NSEventModifierFlags::NSAlternateKeyMask, + ), + ( + keystroke.modifiers.shift, + NSEventModifierFlags::NSShiftKeyMask, + ), + ] { + if *modifier { + mask |= *flag; + } + } + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector, + ns_string(key_to_native(&keystroke.key).as_ref()), + ) + .autorelease(); + item.setKeyEquivalentModifierMask_(mask); + } + // For multi-keystroke bindings, render the keystroke as part of the title. + else { + use std::fmt::Write; + + let mut name = format!("{name} ["); + for (i, keystroke) in keystrokes.iter().enumerate() { + if i > 0 { + name.push(' '); + } + write!(&mut name, "{}", keystroke).unwrap(); + } + name.push(']'); + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(&name), + selector, + ns_string(""), + ) + .autorelease(); + } + } else { + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector, + ns_string(""), + ) + .autorelease(); + } + + let tag = actions.len() as NSInteger; + let _: () = msg_send![item, setTag: tag]; + actions.push(action); + item + } + MenuItem::Submenu(Menu { name, items }) => { + let item = NSMenuItem::new(nil).autorelease(); + let submenu = NSMenu::new(nil).autorelease(); + submenu.setDelegate_(delegate); + for item in items { + submenu.addItem_(self.create_menu_item(item, delegate, actions, dispatch_tree)); + } + item.setSubmenu_(submenu); + item.setTitle_(ns_string(name)); + item + } + } } } @@ -633,6 +637,18 @@ impl Platform for MacPlatform { self.0.lock().event = Some(callback); } + fn on_app_menu_action(&self, callback: Box) { + self.0.lock().menu_command = Some(callback); + } + + fn on_will_open_app_menu(&self, callback: Box) { + self.0.lock().will_open_menu = Some(callback); + } + + fn on_validate_app_menu_command(&self, callback: Box bool>) { + self.0.lock().validate_menu_command = Some(callback); + } + fn os_name(&self) -> &'static str { "macOS" } @@ -675,6 +691,15 @@ impl Platform for MacPlatform { } } + fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>) { + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + let mut state = self.0.lock(); + let actions = &mut state.menu_actions; + app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, dispatch_tree)); + } + } + fn local_timezone(&self) -> UtcOffset { unsafe { let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone]; @@ -683,32 +708,6 @@ impl Platform for MacPlatform { } } - fn on_app_menu_action(&self, callback: Box) { - self.0.lock().menu_command = Some(callback); - } - - fn on_will_open_app_menu(&self, callback: Box) { - self.0.lock().will_open_menu = Some(callback); - } - - fn on_validate_app_menu_command(&self, callback: Box bool>) { - self.0.lock().validate_menu_command = Some(callback); - } - - // fn set_menus(&self, menus: Vec, keystroke_matcher: &KeymapMatcher) { - // unsafe { - // let app: id = msg_send![APP_CLASS, sharedApplication]; - // let mut state = self.0.lock(); - // let actions = &mut state.menu_actions; - // app.setMainMenu_(self.create_menu_bar( - // menus, - // app.delegate(), - // actions, - // keystroke_matcher, - // )); - // } - // } - fn path_for_auxiliary_executable(&self, name: &str) -> Result { unsafe { let bundle: id = NSBundle::mainBundle(); diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 952a9a96cac98e85863931f3e726d71b85e58896..b2a9279df4fbbaee20a507d3c795d596fa142a2a 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -1,6 +1,7 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, DisplayId, + ForegroundExecutor, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, + WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -205,6 +206,10 @@ impl Platform for TestPlatform { unimplemented!() } + fn set_menus(&self, _menus: Vec, _dispatch_tree: Option<&DispatchTree>) { + unimplemented!() + } + fn on_app_menu_action(&self, _callback: Box) { unimplemented!() } diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index c28e281895771a398e8a214961687df5002b1ccd..590079c51b52fe77a2c83ec4a862b27a0202ad1a 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -132,39 +132,3 @@ pub fn load_default_keymap(cx: &mut AppContext) { // KeymapFile::load_asset(asset_path, cx).unwrap(); // } } - -pub fn handle_keymap_file_changes( - mut user_keymap_file_rx: mpsc::UnboundedReceiver, - cx: &mut AppContext, -) { - cx.spawn(move |cx| async move { - // let mut settings_subscription = None; - while let Some(user_keymap_content) = user_keymap_file_rx.next().await { - if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { - cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok(); - - // todo!() - // let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); - // drop(settings_subscription); - // settings_subscription = Some(cx.update(|cx| { - // cx.observe_global::(move |cx| { - // let new_base_keymap = *settings::get::(cx); - // if new_base_keymap != old_base_keymap { - // old_base_keymap = new_base_keymap.clone(); - // reload_keymaps(cx, &keymap_content); - // } - // }) - // })); - } - } - }) - .detach(); -} - -fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { - // todo!() - // cx.clear_bindings(); - load_default_keymap(cx); - keymap_content.clone().add_to_cx(cx).log_err(); - // cx.set_menus(menus::menus()); -} diff --git a/crates/zed2/src/app_menus.rs b/crates/zed2/src/app_menus.rs new file mode 100644 index 0000000000000000000000000000000000000000..70b04e8f9be774d3e7d28e610f65bbabfb80c9b0 --- /dev/null +++ b/crates/zed2/src/app_menus.rs @@ -0,0 +1,175 @@ +use gpui::{Menu, MenuItem, OsAction}; + +#[cfg(target_os = "macos")] +pub fn app_menus() -> Vec> { + vec![ + Menu { + name: "Zed", + items: vec![ + MenuItem::action("About Zed…", super::About), + MenuItem::action("Check for Updates", auto_update::Check), + MenuItem::separator(), + MenuItem::submenu(Menu { + name: "Preferences", + items: vec![ + MenuItem::action("Open Settings", super::OpenSettings), + MenuItem::action("Open Key Bindings", super::OpenKeymap), + MenuItem::action("Open Default Settings", super::OpenDefaultSettings), + MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), + MenuItem::action("Open Local Settings", super::OpenLocalSettings), + MenuItem::action("Select Theme", theme_selector::Toggle), + ], + }), + MenuItem::action("Install CLI", install_cli::Install), + MenuItem::separator(), + MenuItem::action("Hide Zed", super::Hide), + MenuItem::action("Hide Others", super::HideOthers), + MenuItem::action("Show All", super::ShowAll), + MenuItem::action("Quit", super::Quit), + ], + }, + Menu { + name: "File", + items: vec![ + MenuItem::action("New", workspace::NewFile), + MenuItem::action("New Window", workspace::NewWindow), + MenuItem::separator(), + MenuItem::action("Open…", workspace::Open), + // MenuItem::action("Open Recent...", recent_projects::OpenRecent), + MenuItem::separator(), + MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject), + MenuItem::action("Save", workspace::Save { save_intent: None }), + MenuItem::action("Save As…", workspace::SaveAs), + MenuItem::action("Save All", workspace::SaveAll { save_intent: None }), + MenuItem::action( + "Close Editor", + workspace::CloseActiveItem { save_intent: None }, + ), + MenuItem::action("Close Window", workspace::CloseWindow), + ], + }, + Menu { + name: "Edit", + items: vec![ + MenuItem::os_action("Undo", editor::Undo, OsAction::Undo), + MenuItem::os_action("Redo", editor::Redo, OsAction::Redo), + MenuItem::separator(), + MenuItem::os_action("Cut", editor::Cut, OsAction::Cut), + MenuItem::os_action("Copy", editor::Copy, OsAction::Copy), + MenuItem::os_action("Paste", editor::Paste, OsAction::Paste), + MenuItem::separator(), + MenuItem::action("Find", search::buffer_search::Deploy { focus: true }), + MenuItem::action("Find In Project", workspace::NewSearch), + MenuItem::separator(), + MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()), + MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette), + ], + }, + Menu { + name: "Selection", + items: vec![ + MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll), + MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode), + MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode), + MenuItem::separator(), + MenuItem::action("Add Cursor Above", editor::AddSelectionAbove), + MenuItem::action("Add Cursor Below", editor::AddSelectionBelow), + MenuItem::action( + "Select Next Occurrence", + editor::SelectNext { + replace_newest: false, + }, + ), + MenuItem::separator(), + MenuItem::action("Move Line Up", editor::MoveLineUp), + MenuItem::action("Move Line Down", editor::MoveLineDown), + MenuItem::action("Duplicate Selection", editor::DuplicateLine), + ], + }, + Menu { + name: "View", + items: vec![ + MenuItem::action("Zoom In", super::IncreaseBufferFontSize), + MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), + MenuItem::action("Reset Zoom", super::ResetBufferFontSize), + MenuItem::separator(), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), + MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), + MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), + MenuItem::action("Close All Docks", workspace::CloseAllDocks), + MenuItem::submenu(Menu { + name: "Editor Layout", + items: vec![ + MenuItem::action("Split Up", workspace::SplitUp), + MenuItem::action("Split Down", workspace::SplitDown), + MenuItem::action("Split Left", workspace::SplitLeft), + MenuItem::action("Split Right", workspace::SplitRight), + ], + }), + MenuItem::separator(), + MenuItem::action("Project Panel", project_panel::ToggleFocus), + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::action("Diagnostics", diagnostics::Deploy), + MenuItem::separator(), + ], + }, + Menu { + name: "Go", + items: vec![ + MenuItem::action("Back", workspace::GoBack), + MenuItem::action("Forward", workspace::GoForward), + MenuItem::separator(), + MenuItem::action("Go to File", file_finder::Toggle), + // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), + MenuItem::action("Go to Symbol in Editor", outline::Toggle), + MenuItem::action("Go to Definition", editor::GoToDefinition), + MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition), + MenuItem::action("Find All References", editor::FindAllReferences), + MenuItem::action("Go to Line/Column", go_to_line::Toggle), + MenuItem::separator(), + MenuItem::action("Next Problem", editor::GoToDiagnostic), + MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic), + ], + }, + Menu { + name: "Window", + items: vec![ + MenuItem::action("Minimize", super::Minimize), + MenuItem::action("Zoom", super::Zoom), + MenuItem::separator(), + ], + }, + Menu { + name: "Help", + items: vec![ + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::separator(), + MenuItem::action("View Telemetry", crate::OpenTelemetryLog), + MenuItem::action("View Dependency Licenses", crate::OpenLicenses), + MenuItem::action("Show Welcome", workspace::Welcome), + MenuItem::separator(), + // todo!(): Needs `feedback2` crate. + // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), + // MenuItem::action( + // "Copy System Specs Into Clipboard", + // feedback::CopySystemSpecsIntoClipboard, + // ), + // MenuItem::action("File Bug Report", feedback::FileBugReport), + // MenuItem::action("Request Feature", feedback::RequestFeature), + MenuItem::separator(), + MenuItem::action( + "Documentation", + crate::OpenBrowser { + url: "https://zed.dev/docs".into(), + }, + ), + MenuItem::action( + "Zed Twitter", + crate::OpenBrowser { + url: "https://twitter.com/zeddotdev".into(), + }, + ), + ], + }, + ] +} diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 6ca5d1a805b53ab1dabd43591b7353c0231aa365..7faafb2440c7e311641219461c2d79934702ddb9 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -22,8 +22,7 @@ use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use settings::{ - default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, - Settings, SettingsStore, + default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; use smol::process::Command; @@ -51,8 +50,9 @@ use uuid::Uuid; use welcome::{show_welcome_experience, FIRST_OPEN}; use workspace::{AppState, WorkspaceStore}; use zed2::{ - build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace, - languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, + app_menus, build_window_options, ensure_only_instance, handle_cli_connection, + handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance, + OpenListener, OpenRequest, }; mod open_listener; @@ -224,7 +224,7 @@ fn main() { // feedback::init(cx); welcome::init(cx); - // cx.set_menus(menus::menus()); + cx.set_menus(app_menus()); initialize_workspace(app_state.clone(), cx); if stdout_is_a_pty() { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index abe8e7a86f19802f2ab8e7347ba87ba4b6a08050..7e69a2aee3ec799a7e5c6876da39e45386c6487f 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -1,11 +1,13 @@ #![allow(unused_variables, unused_mut)] //todo!() +mod app_menus; mod assets; pub mod languages; mod only_instance; mod open_listener; +pub use app_menus::*; pub use assets::*; use breadcrumbs::Breadcrumbs; use collections::VecDeque; @@ -18,8 +20,9 @@ pub use only_instance::*; pub use open_listener::*; use anyhow::{anyhow, Context as _}; +use futures::{channel::mpsc, StreamExt}; use project_panel::ProjectPanel; -use settings::{initial_local_settings_content, Settings}; +use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings}; use std::{borrow::Cow, ops::Deref, sync::Arc}; use terminal_view::terminal_panel::TerminalPanel; use util::{ @@ -561,6 +564,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { .detach(); } +pub fn handle_keymap_file_changes( + mut user_keymap_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + cx.spawn(move |cx| async move { + // let mut settings_subscription = None; + while let Some(user_keymap_content) = user_keymap_file_rx.next().await { + if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { + cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok(); + + // todo!() + // let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); + // drop(settings_subscription); + // settings_subscription = Some(cx.update(|cx| { + // cx.observe_global::(move |cx| { + // let new_base_keymap = *settings::get::(cx); + // if new_base_keymap != old_base_keymap { + // old_base_keymap = new_base_keymap.clone(); + // reload_keymaps(cx, &keymap_content); + // } + // }) + // })); + } + } + }) + .detach(); +} + +fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { + // todo!() + // cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + cx.set_menus(app_menus()); +} + fn open_local_settings_file( workspace: &mut Workspace, _: &OpenLocalSettings,