Introduce app menus in zed2 (#3511)

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2023-12-06 18:17:02 +01:00 committed by GitHub
commit 7c9e2f6b7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 716 additions and 297 deletions

View file

@ -993,7 +993,7 @@ mod tests {
use super::*; use super::*;
use crate::display_map::inlay_map::InlayMap; use crate::display_map::inlay_map::InlayMap;
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; 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 multi_buffer::MultiBuffer;
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
@ -1185,11 +1185,7 @@ mod tests {
fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) { fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
cx.update(|cx| init_test(cx)); cx.update(|cx| init_test(cx));
let font_id = cx let font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
.test_platform
.text_system()
.font_id(&font("Helvetica"))
.unwrap();
let text = "one two three\nfour five six\nseven eight"; let text = "one two three\nfour five six\nseven eight";

View file

@ -1032,7 +1032,7 @@ mod tests {
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
MultiBuffer, MultiBuffer,
}; };
use gpui::{font, px, test::observe, Platform}; use gpui::{font, px, test::observe};
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
use smol::stream::StreamExt; use smol::stream::StreamExt;

View file

@ -92,6 +92,7 @@ use std::{
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive}, ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path, path::Path,
sync::Arc, sync::Arc,
sync::Weak,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
pub use sum_tree::Bias; pub use sum_tree::Bias;
@ -420,6 +421,25 @@ pub fn init(cx: &mut AppContext) {
}, },
) )
.detach(); .detach();
cx.on_action(move |_: &workspace::NewFile, cx| {
let app_state = cx.global::<Weak<workspace::AppState>>();
if let Some(app_state) = app_state.upgrade() {
workspace::open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
}
});
cx.on_action(move |_: &workspace::NewWindow, cx| {
let app_state = cx.global::<Weak<workspace::AppState>>();
if let Some(app_state) = app_state.upgrade() {
workspace::open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
}
});
} }
trait InvalidationRegion { trait InvalidationRegion {

View file

@ -12,7 +12,7 @@ use futures::StreamExt;
use gpui::{ use gpui::{
div, div,
serde_json::{self, json}, serde_json::{self, json},
Div, Flatten, Platform, TestAppContext, VisualTestContext, WindowBounds, WindowOptions, Div, Flatten, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
}; };
use indoc::indoc; use indoc::indoc;
use language::{ use language::{
@ -3238,9 +3238,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
the lazy dog"}); the lazy dog"});
cx.update_editor(|e, cx| e.copy(&Copy, cx)); cx.update_editor(|e, cx| e.copy(&Copy, cx));
assert_eq!( assert_eq!(
cx.test_platform cx.read_from_clipboard().map(|item| item.text().to_owned()),
.read_from_clipboard()
.map(|item| item.text().to_owned()),
Some("fox jumps over\n".to_owned()) Some("fox jumps over\n".to_owned())
); );

View file

@ -15,10 +15,10 @@ use smol::future::FutureExt;
pub use test_context::*; pub use test_context::*;
use crate::{ use crate::{
current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView, current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, 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, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
WindowHandle, WindowId, WindowHandle, WindowId,
@ -39,7 +39,10 @@ use std::{
sync::{atomic::Ordering::SeqCst, Arc}, sync::{atomic::Ordering::SeqCst, Arc},
time::Duration, time::Duration,
}; };
use util::http::{self, HttpClient}; use util::{
http::{self, HttpClient},
ResultExt,
};
/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows. /// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
/// Strongly consider removing after stabilization. /// Strongly consider removing after stabilization.
@ -275,6 +278,8 @@ impl AppContext {
}), }),
}); });
init_app_menus(platform.as_ref(), &mut *app.borrow_mut());
platform.on_quit(Box::new({ platform.on_quit(Box::new({
let cx = app.clone(); let cx = app.clone();
move || { move || {
@ -425,6 +430,10 @@ impl AppContext {
.collect() .collect()
} }
pub fn active_window(&self) -> Option<AnyWindowHandle> {
self.platform.active_window()
}
/// Opens a new window with the given option and the root view returned by the given function. /// Opens a new window with the given option and the root view returned by the given function.
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
/// functionality. /// functionality.
@ -1015,6 +1024,90 @@ impl AppContext {
activate(); activate();
subscription subscription
} }
pub(crate) fn clear_pending_keystrokes(&mut self) {
for window in self.windows() {
window
.update(self, |_, cx| {
cx.window
.current_frame
.dispatch_tree
.clear_pending_keystrokes()
})
.ok();
}
}
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
if let Some(window) = self.active_window() {
if let Ok(window_action_available) =
window.update(self, |_, cx| cx.is_action_available(action))
{
return window_action_available;
}
}
self.global_action_listeners
.contains_key(&action.as_any().type_id())
}
pub fn set_menus(&mut self, menus: Vec<Menu>) {
self.platform.set_menus(menus, &self.keymap.lock());
}
pub fn dispatch_action(&mut self, action: &dyn Action) {
if let Some(active_window) = self.active_window() {
active_window
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
.log_err();
} else {
self.propagate_event = true;
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
}
}
}
} }
impl Context for AppContext { impl Context for AppContext {

View file

@ -1,9 +1,10 @@
use crate::{ use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent, BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter,
KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions, TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
}; };
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
@ -16,6 +17,7 @@ pub struct TestAppContext {
pub foreground_executor: ForegroundExecutor, pub foreground_executor: ForegroundExecutor,
pub dispatcher: TestDispatcher, pub dispatcher: TestDispatcher,
pub test_platform: Rc<TestPlatform>, pub test_platform: Rc<TestPlatform>,
text_system: Arc<TextSystem>,
} }
impl Context for TestAppContext { impl Context for TestAppContext {
@ -82,6 +84,7 @@ impl TestAppContext {
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone()); let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
let asset_source = Arc::new(()); let asset_source = Arc::new(());
let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
Self { Self {
app: AppContext::new(platform.clone(), asset_source, http_client), app: AppContext::new(platform.clone(), asset_source, http_client),
@ -89,6 +92,7 @@ impl TestAppContext {
foreground_executor, foreground_executor,
dispatcher: dispatcher.clone(), dispatcher: dispatcher.clone(),
test_platform: platform, test_platform: platform,
text_system,
} }
} }
@ -155,6 +159,18 @@ impl TestAppContext {
(view, Box::leak(cx)) (view, Box::leak(cx))
} }
pub fn text_system(&self) -> &Arc<TextSystem> {
&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<ClipboardItem> {
self.test_platform.read_from_clipboard()
}
pub fn simulate_new_path_selection( pub fn simulate_new_path_selection(
&self, &self,
select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>, select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,

View file

@ -28,7 +28,7 @@ pub(crate) struct DispatchTree {
pub(crate) struct DispatchNode { pub(crate) struct DispatchNode {
pub key_listeners: SmallVec<[KeyListener; 2]>, pub key_listeners: SmallVec<[KeyListener; 2]>,
pub action_listeners: SmallVec<[DispatchActionListener; 16]>, pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
pub context: KeyContext, pub context: Option<KeyContext>,
parent: Option<DispatchNodeId>, parent: Option<DispatchNodeId>,
} }
@ -70,33 +70,33 @@ impl DispatchTree {
}); });
self.node_stack.push(node_id); self.node_stack.push(node_id);
if let Some(context) = context { if let Some(context) = context {
self.active_node().context = context.clone(); self.active_node().context = Some(context.clone());
self.context_stack.push(context); self.context_stack.push(context);
} }
} }
pub fn pop_node(&mut self) { pub fn pop_node(&mut self) {
let node_id = self.node_stack.pop().unwrap(); let node_id = self.node_stack.pop().unwrap();
if !self.nodes[node_id.0].context.is_empty() { if self.nodes[node_id.0].context.is_some() {
self.context_stack.pop(); self.context_stack.pop();
} }
} }
pub fn clear_keystroke_matchers(&mut self) { pub fn clear_pending_keystrokes(&mut self) {
self.keystroke_matchers.clear(); self.keystroke_matchers.clear();
} }
/// Preserve keystroke matchers from previous frames to support multi-stroke /// Preserve keystroke matchers from previous frames to support multi-stroke
/// bindings across multiple frames. /// bindings across multiple frames.
pub fn preserve_keystroke_matchers(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) { pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) { if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
let dispatch_path = self.dispatch_path(node_id); let dispatch_path = self.dispatch_path(node_id);
self.context_stack.clear(); self.context_stack.clear();
for node_id in dispatch_path { for node_id in dispatch_path {
let node = self.node(node_id); let node = self.node(node_id);
if !node.context.is_empty() { if let Some(context) = node.context.clone() {
self.context_stack.push(node.context.clone()); self.context_stack.push(context);
} }
if let Some((context_stack, matcher)) = old_tree if let Some((context_stack, matcher)) = old_tree
@ -161,6 +161,20 @@ impl DispatchTree {
actions actions
} }
pub fn is_action_available(&self, action: &dyn Action, target: DispatchNodeId) -> bool {
for node_id in self.dispatch_path(target) {
let node = &self.nodes[node_id.0];
if node
.action_listeners
.iter()
.any(|listener| listener.action_type == action.as_any().type_id())
{
return true;
}
}
false
}
pub fn bindings_for_action( pub fn bindings_for_action(
&self, &self,
action: &dyn Action, action: &dyn Action,

View file

@ -1,3 +1,4 @@
mod app_menu;
mod keystroke; mod keystroke;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod mac; mod mac;
@ -5,10 +6,10 @@ mod mac;
mod test; mod test;
use crate::{ use crate::{
point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
SharedString, Size, TaskLabel, Scene, SharedString, Size, TaskLabel,
}; };
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use async_task::Runnable; use async_task::Runnable;
@ -32,6 +33,7 @@ use std::{
}; };
use uuid::Uuid; use uuid::Uuid;
pub use app_menu::*;
pub use keystroke::*; pub use keystroke::*;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub use mac::*; pub use mac::*;
@ -44,7 +46,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
Rc::new(MacPlatform::new()) Rc::new(MacPlatform::new())
} }
pub trait Platform: 'static { pub(crate) trait Platform: 'static {
fn background_executor(&self) -> BackgroundExecutor; fn background_executor(&self) -> BackgroundExecutor;
fn foreground_executor(&self) -> ForegroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>; fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@ -59,7 +61,7 @@ pub trait Platform: 'static {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>; fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>; fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn main_window(&self) -> Option<AnyWindowHandle>; fn active_window(&self) -> Option<AnyWindowHandle>;
fn open_window( fn open_window(
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
@ -90,6 +92,11 @@ pub trait Platform: 'static {
fn on_reopen(&self, callback: Box<dyn FnMut()>); fn on_reopen(&self, callback: Box<dyn FnMut()>);
fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>); fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
fn os_name(&self) -> &'static str; fn os_name(&self) -> &'static str;
fn os_version(&self) -> Result<SemanticVersion>; fn os_version(&self) -> Result<SemanticVersion>;
fn app_version(&self) -> Result<SemanticVersion>; fn app_version(&self) -> Result<SemanticVersion>;

View file

@ -0,0 +1,77 @@
use crate::{Action, AppContext, Platform};
use util::ResultExt;
pub struct Menu<'a> {
pub name: &'a str,
pub items: Vec<MenuItem<'a>>,
}
pub enum MenuItem<'a> {
Separator,
Submenu(Menu<'a>),
Action {
name: &'a str,
action: Box<dyn Action>,
os_action: Option<OsAction>,
},
}
impl<'a> MenuItem<'a> {
pub fn separator() -> Self {
Self::Separator
}
pub fn submenu(menu: Menu<'a>) -> Self {
Self::Submenu(menu)
}
pub fn action(name: &'a str, action: impl Action) -> Self {
Self::Action {
name,
action: Box::new(action),
os_action: None,
}
}
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
Self::Action {
name,
action: Box::new(action),
os_action: Some(os_action),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum OsAction {
Cut,
Copy,
Paste,
SelectAll,
Undo,
Redo,
}
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 || {
cx.update(|cx| cx.clear_pending_keystrokes()).ok();
}
}));
platform.on_validate_app_menu_command(Box::new({
let cx = cx.to_async();
move |action| {
cx.update(|cx| cx.is_action_available(action))
.unwrap_or(false)
}
}));
platform.on_app_menu_action(Box::new({
let cx = cx.to_async();
move |action| {
cx.update(|cx| cx.dispatch_action(action)).log_err();
}
}));
}

View file

@ -1,18 +1,19 @@
use super::BoolExt; use super::{events::key_to_native, BoolExt};
use crate::{ use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
SemanticVersion, VideoTimestamp, WindowOptions, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
appkit::{ appkit::{
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
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::{ foundation::{
NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString, NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
NSUInteger, NSURL, NSUInteger, NSURL,
@ -155,12 +156,12 @@ pub struct MacPlatformState {
reopen: Option<Box<dyn FnMut()>>, reopen: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>, quit: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(InputEvent) -> bool>>, event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
// menu_command: Option<Box<dyn FnMut(&dyn Action)>>, menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
// validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>, validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
will_open_menu: Option<Box<dyn FnMut()>>, will_open_menu: Option<Box<dyn FnMut()>>,
menu_actions: Vec<Box<dyn Action>>,
open_urls: Option<Box<dyn FnMut(Vec<String>)>>, open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
finish_launching: Option<Box<dyn FnOnce()>>, finish_launching: Option<Box<dyn FnOnce()>>,
// menu_actions: Vec<Box<dyn Action>>,
} }
impl MacPlatform { impl MacPlatform {
@ -179,12 +180,12 @@ impl MacPlatform {
reopen: None, reopen: None,
quit: None, quit: None,
event: None, event: None,
menu_command: None,
validate_menu_command: None,
will_open_menu: None, will_open_menu: None,
menu_actions: Default::default(),
open_urls: None, open_urls: None,
finish_launching: None, finish_launching: None,
// menu_command: None,
// validate_menu_command: None,
// menu_actions: Default::default(),
})) }))
} }
@ -200,151 +201,153 @@ impl MacPlatform {
} }
} }
// unsafe fn create_menu_bar( unsafe fn create_menu_bar(
// &self, &self,
// menus: Vec<Menu>, menus: Vec<Menu>,
// delegate: id, delegate: id,
// actions: &mut Vec<Box<dyn Action>>, actions: &mut Vec<Box<dyn Action>>,
// keystroke_matcher: &KeymapMatcher, keymap: &Keymap,
// ) -> id { ) -> id {
// let application_menu = NSMenu::new(nil).autorelease(); let application_menu = NSMenu::new(nil).autorelease();
// application_menu.setDelegate_(delegate); application_menu.setDelegate_(delegate);
// for menu_config in menus { for menu_config in menus {
// let menu = NSMenu::new(nil).autorelease(); let menu = NSMenu::new(nil).autorelease();
// menu.setTitle_(ns_string(menu_config.name)); menu.setTitle_(ns_string(menu_config.name));
// menu.setDelegate_(delegate); menu.setDelegate_(delegate);
// for item_config in menu_config.items { for item_config in menu_config.items {
// menu.addItem_(self.create_menu_item( menu.addItem_(self.create_menu_item(item_config, delegate, actions, keymap));
// item_config, }
// delegate,
// actions,
// keystroke_matcher,
// ));
// }
// let menu_item = NSMenuItem::new(nil).autorelease(); let menu_item = NSMenuItem::new(nil).autorelease();
// menu_item.setSubmenu_(menu); menu_item.setSubmenu_(menu);
// application_menu.addItem_(menu_item); application_menu.addItem_(menu_item);
// if menu_config.name == "Window" { if menu_config.name == "Window" {
// let app: id = msg_send![APP_CLASS, sharedApplication]; let app: id = msg_send![APP_CLASS, sharedApplication];
// app.setWindowsMenu_(menu); app.setWindowsMenu_(menu);
// } }
// } }
// application_menu application_menu
// } }
// unsafe fn create_menu_item( unsafe fn create_menu_item(
// &self, &self,
// item: MenuItem, item: MenuItem,
// delegate: id, delegate: id,
// actions: &mut Vec<Box<dyn Action>>, actions: &mut Vec<Box<dyn Action>>,
// keystroke_matcher: &KeymapMatcher, keymap: &Keymap,
// ) -> id { ) -> id {
// match item { match item {
// MenuItem::Separator => NSMenuItem::separatorItem(nil), MenuItem::Separator => NSMenuItem::separatorItem(nil),
// MenuItem::Action { MenuItem::Action {
// name, name,
// action, action,
// os_action, os_action,
// } => { } => {
// // TODO let keystrokes = keymap
// let keystrokes = keystroke_matcher .bindings_for_action(action.type_id())
// .bindings_for_action(action.id()) .find(|binding| binding.action().partial_eq(action.as_ref()))
// .find(|binding| binding.action().eq(action.as_ref())) .map(|binding| binding.keystrokes());
// .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; let selector = match os_action {
// if let Some(keystrokes) = keystrokes { Some(crate::OsAction::Cut) => selector("cut:"),
// if keystrokes.len() == 1 { Some(crate::OsAction::Copy) => selector("copy:"),
// let keystroke = &keystrokes[0]; Some(crate::OsAction::Paste) => selector("paste:"),
// let mut mask = NSEventModifierFlags::empty(); Some(crate::OsAction::SelectAll) => selector("selectAll:"),
// for (modifier, flag) in &[ Some(crate::OsAction::Undo) => selector("undo:"),
// (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), Some(crate::OsAction::Redo) => selector("redo:"),
// (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), None => selector("handleGPUIMenuItem:"),
// (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), };
// (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
// ] {
// if *modifier {
// mask |= *flag;
// }
// }
// item = NSMenuItem::alloc(nil) let item;
// .initWithTitle_action_keyEquivalent_( if let Some(keystrokes) = keystrokes {
// ns_string(name), if keystrokes.len() == 1 {
// selector, let keystroke = &keystrokes[0];
// ns_string(key_to_native(&keystroke.key).as_ref()), let mut mask = NSEventModifierFlags::empty();
// ) for (modifier, flag) in &[
// .autorelease(); (
// item.setKeyEquivalentModifierMask_(mask); keystroke.modifiers.command,
// } NSEventModifierFlags::NSCommandKeyMask,
// // For multi-keystroke bindings, render the keystroke as part of the title. ),
// else { (
// use std::fmt::Write; keystroke.modifiers.control,
NSEventModifierFlags::NSControlKeyMask,
),
(
keystroke.modifiers.alt,
NSEventModifierFlags::NSAlternateKeyMask,
),
(
keystroke.modifiers.shift,
NSEventModifierFlags::NSShiftKeyMask,
),
] {
if *modifier {
mask |= *flag;
}
}
// let mut name = format!("{name} ["); item = NSMenuItem::alloc(nil)
// for (i, keystroke) in keystrokes.iter().enumerate() { .initWithTitle_action_keyEquivalent_(
// if i > 0 { ns_string(name),
// name.push(' '); selector,
// } ns_string(key_to_native(&keystroke.key).as_ref()),
// write!(&mut name, "{}", keystroke).unwrap(); )
// } .autorelease();
// name.push(']'); item.setKeyEquivalentModifierMask_(mask);
}
// For multi-keystroke bindings, render the keystroke as part of the title.
else {
use std::fmt::Write;
// item = NSMenuItem::alloc(nil) let mut name = format!("{name} [");
// .initWithTitle_action_keyEquivalent_( for (i, keystroke) in keystrokes.iter().enumerate() {
// ns_string(&name), if i > 0 {
// selector, name.push(' ');
// ns_string(""), }
// ) write!(&mut name, "{}", keystroke).unwrap();
// .autorelease(); }
// } name.push(']');
// } else {
// item = NSMenuItem::alloc(nil)
// .initWithTitle_action_keyEquivalent_(
// ns_string(name),
// selector,
// ns_string(""),
// )
// .autorelease();
// }
// let tag = actions.len() as NSInteger; item = NSMenuItem::alloc(nil)
// let _: () = msg_send![item, setTag: tag]; .initWithTitle_action_keyEquivalent_(
// actions.push(action); ns_string(&name),
// item selector,
// } ns_string(""),
// MenuItem::Submenu(Menu { name, items }) => { )
// let item = NSMenuItem::new(nil).autorelease(); .autorelease();
// let submenu = NSMenu::new(nil).autorelease(); }
// submenu.setDelegate_(delegate); } else {
// for item in items { item = NSMenuItem::alloc(nil)
// submenu.addItem_(self.create_menu_item( .initWithTitle_action_keyEquivalent_(
// item, ns_string(name),
// delegate, selector,
// actions, ns_string(""),
// keystroke_matcher, )
// )); .autorelease();
// } }
// item.setSubmenu_(submenu);
// item.setTitle_(ns_string(name)); let tag = actions.len() as NSInteger;
// item 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, keymap));
}
item.setSubmenu_(submenu);
item.setTitle_(ns_string(name));
item
}
}
}
} }
impl Platform for MacPlatform { impl Platform for MacPlatform {
@ -479,8 +482,8 @@ impl Platform for MacPlatform {
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
} }
fn main_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
MacWindow::main_window() MacWindow::active_window()
} }
fn open_window( fn open_window(
@ -631,6 +634,18 @@ impl Platform for MacPlatform {
self.0.lock().event = Some(callback); self.0.lock().event = Some(callback);
} }
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.0.lock().menu_command = Some(callback);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.0.lock().will_open_menu = Some(callback);
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.0.lock().validate_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str { fn os_name(&self) -> &'static str {
"macOS" "macOS"
} }
@ -673,6 +688,15 @@ impl Platform for MacPlatform {
} }
} }
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
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, keymap));
}
}
fn local_timezone(&self) -> UtcOffset { fn local_timezone(&self) -> UtcOffset {
unsafe { unsafe {
let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone]; let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
@ -681,32 +705,6 @@ impl Platform for MacPlatform {
} }
} }
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
// self.0.lock().menu_command = Some(callback);
// }
// fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
// self.0.lock().will_open_menu = Some(callback);
// }
// fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
// self.0.lock().validate_menu_command = Some(callback);
// }
// fn set_menus(&self, menus: Vec<Menu>, 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<PathBuf> { fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
unsafe { unsafe {
let bundle: id = NSBundle::mainBundle(); let bundle: id = NSBundle::mainBundle();
@ -956,7 +954,7 @@ unsafe fn path_from_objc(path: id) -> PathBuf {
PathBuf::from(path) PathBuf::from(path)
} }
unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform { unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR); let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
assert!(!platform_ptr.is_null()); assert!(!platform_ptr.is_null());
&*(platform_ptr as *const MacPlatform) &*(platform_ptr as *const MacPlatform)
@ -965,7 +963,7 @@ unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
unsafe { unsafe {
if let Some(event) = InputEvent::from_native(native_event, None) { if let Some(event) = InputEvent::from_native(native_event, None) {
let platform = get_foreground_platform(this); let platform = get_mac_platform(this);
if let Some(callback) = platform.0.lock().event.as_mut() { if let Some(callback) = platform.0.lock().event.as_mut() {
if !callback(event) { if !callback(event) {
return; return;
@ -981,7 +979,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
let app: id = msg_send![APP_CLASS, sharedApplication]; let app: id = msg_send![APP_CLASS, sharedApplication];
app.setActivationPolicy_(NSApplicationActivationPolicyRegular); app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
let platform = get_foreground_platform(this); let platform = get_mac_platform(this);
let callback = platform.0.lock().finish_launching.take(); let callback = platform.0.lock().finish_launching.take();
if let Some(callback) = callback { if let Some(callback) = callback {
callback(); callback();
@ -991,7 +989,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
if !has_open_windows { if !has_open_windows {
let platform = unsafe { get_foreground_platform(this) }; let platform = unsafe { get_mac_platform(this) };
if let Some(callback) = platform.0.lock().reopen.as_mut() { if let Some(callback) = platform.0.lock().reopen.as_mut() {
callback(); callback();
} }
@ -999,21 +997,21 @@ extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_wi
} }
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
let platform = unsafe { get_foreground_platform(this) }; let platform = unsafe { get_mac_platform(this) };
if let Some(callback) = platform.0.lock().become_active.as_mut() { if let Some(callback) = platform.0.lock().become_active.as_mut() {
callback(); callback();
} }
} }
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
let platform = unsafe { get_foreground_platform(this) }; let platform = unsafe { get_mac_platform(this) };
if let Some(callback) = platform.0.lock().resign_active.as_mut() { if let Some(callback) = platform.0.lock().resign_active.as_mut() {
callback(); callback();
} }
} }
extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
let platform = unsafe { get_foreground_platform(this) }; let platform = unsafe { get_mac_platform(this) };
if let Some(callback) = platform.0.lock().quit.as_mut() { if let Some(callback) = platform.0.lock().quit.as_mut() {
callback(); callback();
} }
@ -1035,49 +1033,47 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
let platform = unsafe { get_foreground_platform(this) }; let platform = unsafe { get_mac_platform(this) };
if let Some(callback) = platform.0.lock().open_urls.as_mut() { if let Some(callback) = platform.0.lock().open_urls.as_mut() {
callback(urls); callback(urls);
} }
} }
extern "C" fn handle_menu_item(__this: &mut Object, _: Sel, __item: id) { extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
todo!() unsafe {
// unsafe { let platform = get_mac_platform(this);
// let platform = get_foreground_platform(this); let mut platform = platform.0.lock();
// let mut platform = platform.0.lock(); if let Some(mut callback) = platform.menu_command.take() {
// if let Some(mut callback) = platform.menu_command.take() { let tag: NSInteger = msg_send![item, tag];
// let tag: NSInteger = msg_send![item, tag]; let index = tag as usize;
// let index = tag as usize; if let Some(action) = platform.menu_actions.get(index) {
// if let Some(action) = platform.menu_actions.get(index) { callback(action.as_ref());
// callback(action.as_ref()); }
// } platform.menu_command = Some(callback);
// platform.menu_command = Some(callback); }
// } }
// }
} }
extern "C" fn validate_menu_item(__this: &mut Object, _: Sel, __item: id) -> bool { extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
todo!() unsafe {
// unsafe { let mut result = false;
// let mut result = false; let platform = get_mac_platform(this);
// let platform = get_foreground_platform(this); let mut platform = platform.0.lock();
// let mut platform = platform.0.lock(); if let Some(mut callback) = platform.validate_menu_command.take() {
// if let Some(mut callback) = platform.validate_menu_command.take() { let tag: NSInteger = msg_send![item, tag];
// let tag: NSInteger = msg_send![item, tag]; let index = tag as usize;
// let index = tag as usize; if let Some(action) = platform.menu_actions.get(index) {
// if let Some(action) = platform.menu_actions.get(index) { result = callback(action.as_ref());
// result = callback(action.as_ref()); }
// } platform.validate_menu_command = Some(callback);
// platform.validate_menu_command = Some(callback); }
// } result
// result }
// }
} }
extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
unsafe { unsafe {
let platform = get_foreground_platform(this); let platform = get_mac_platform(this);
let mut platform = platform.0.lock(); let mut platform = platform.0.lock();
if let Some(mut callback) = platform.will_open_menu.take() { if let Some(mut callback) = platform.will_open_menu.take() {
callback(); callback();

View file

@ -662,7 +662,7 @@ impl MacWindow {
} }
} }
pub fn main_window() -> Option<AnyWindowHandle> { pub fn active_window() -> Option<AnyWindowHandle> {
unsafe { unsafe {
let app = NSApplication::sharedApplication(nil); let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow]; let main_window: id = msg_send![app, mainWindow];

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::VecDeque; use collections::VecDeque;
@ -127,7 +127,7 @@ impl Platform for TestPlatform {
self.displays().iter().find(|d| d.id() == id).cloned() self.displays().iter().find(|d| d.id() == id).cloned()
} }
fn main_window(&self) -> Option<crate::AnyWindowHandle> { fn active_window(&self) -> Option<crate::AnyWindowHandle> {
unimplemented!() unimplemented!()
} }
@ -212,6 +212,14 @@ impl Platform for TestPlatform {
unimplemented!() unimplemented!()
} }
fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
fn os_name(&self) -> &'static str { fn os_name(&self) -> &'static str {
"test" "test"
} }

View file

@ -430,7 +430,7 @@ impl<'a> WindowContext<'a> {
self.window self.window
.current_frame .current_frame
.dispatch_tree .dispatch_tree
.clear_keystroke_matchers(); .clear_pending_keystrokes();
self.app.push_effect(Effect::FocusChanged { self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle, window_handle: self.window.handle,
focused: Some(focus_id), focused: Some(focus_id),
@ -804,6 +804,22 @@ impl<'a> WindowContext<'a> {
); );
} }
pub fn is_action_available(&self, action: &dyn Action) -> bool {
let target = self
.focused()
.and_then(|focused_handle| {
self.window
.current_frame
.dispatch_tree
.focusable_node_id(focused_handle.id)
})
.unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
self.window
.current_frame
.dispatch_tree
.is_action_available(action, target)
}
/// The position of the mouse relative to the window. /// The position of the mouse relative to the window.
pub fn mouse_position(&self) -> Point<Pixels> { pub fn mouse_position(&self) -> Point<Pixels> {
self.window.mouse_position self.window.mouse_position
@ -1190,7 +1206,7 @@ impl<'a> WindowContext<'a> {
self.window self.window
.current_frame .current_frame
.dispatch_tree .dispatch_tree
.preserve_keystroke_matchers( .preserve_pending_keystrokes(
&mut self.window.previous_frame.dispatch_tree, &mut self.window.previous_frame.dispatch_tree,
self.window.focus, self.window.focus,
); );
@ -1377,8 +1393,8 @@ impl<'a> WindowContext<'a> {
for node_id in &dispatch_path { for node_id in &dispatch_path {
let node = self.window.current_frame.dispatch_tree.node(*node_id); let node = self.window.current_frame.dispatch_tree.node(*node_id);
if !node.context.is_empty() { if let Some(context) = node.context.clone() {
context_stack.push(node.context.clone()); context_stack.push(context);
} }
for key_listener in node.key_listeners.clone() { for key_listener in node.key_listeners.clone() {
@ -1402,7 +1418,7 @@ impl<'a> WindowContext<'a> {
// Match keystrokes // Match keystrokes
let node = self.window.current_frame.dispatch_tree.node(*node_id); let node = self.window.current_frame.dispatch_tree.node(*node_id);
if !node.context.is_empty() { if node.context.is_some() {
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() { if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
if let Some(found) = self if let Some(found) = self
.window .window
@ -1547,7 +1563,7 @@ impl<'a> WindowContext<'a> {
let context_stack = dispatch_tree let context_stack = dispatch_tree
.dispatch_path(node_id) .dispatch_path(node_id)
.into_iter() .into_iter()
.map(|node_id| dispatch_tree.node(node_id).context.clone()) .filter_map(|node_id| dispatch_tree.node(node_id).context.clone())
.collect(); .collect();
dispatch_tree.bindings_for_action(action, &context_stack) dispatch_tree.bindings_for_action(action, &context_stack)
} }

View file

@ -132,39 +132,3 @@ pub fn load_default_keymap(cx: &mut AppContext) {
// KeymapFile::load_asset(asset_path, cx).unwrap(); // KeymapFile::load_asset(asset_path, cx).unwrap();
// } // }
} }
pub fn handle_keymap_file_changes(
mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
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::<BaseKeymap>(cx));
// drop(settings_subscription);
// settings_subscription = Some(cx.update(|cx| {
// cx.observe_global::<SettingsStore, _>(move |cx| {
// let new_base_keymap = *settings::get::<BaseKeymap>(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());
}

View file

@ -0,0 +1,175 @@
use gpui::{Menu, MenuItem, OsAction};
#[cfg(target_os = "macos")]
pub fn app_menus() -> Vec<Menu<'static>> {
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(),
},
),
],
},
]
}

View file

@ -22,8 +22,7 @@ use node_runtime::RealNodeRuntime;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{ use settings::{
default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
Settings, SettingsStore,
}; };
use simplelog::ConfigBuilder; use simplelog::ConfigBuilder;
use smol::process::Command; use smol::process::Command;
@ -51,8 +50,9 @@ use uuid::Uuid;
use welcome::{show_welcome_experience, FIRST_OPEN}; use welcome::{show_welcome_experience, FIRST_OPEN};
use workspace::{AppState, WorkspaceStore}; use workspace::{AppState, WorkspaceStore};
use zed2::{ use zed2::{
build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace, app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance,
OpenListener, OpenRequest,
}; };
mod open_listener; mod open_listener;
@ -224,7 +224,7 @@ fn main() {
// feedback::init(cx); // feedback::init(cx);
welcome::init(cx); welcome::init(cx);
// cx.set_menus(menus::menus()); cx.set_menus(app_menus());
initialize_workspace(app_state.clone(), cx); initialize_workspace(app_state.clone(), cx);
if stdout_is_a_pty() { if stdout_is_a_pty() {

View file

@ -1,11 +1,13 @@
#![allow(unused_variables, unused_mut)] #![allow(unused_variables, unused_mut)]
//todo!() //todo!()
mod app_menus;
mod assets; mod assets;
pub mod languages; pub mod languages;
mod only_instance; mod only_instance;
mod open_listener; mod open_listener;
pub use app_menus::*;
pub use assets::*; pub use assets::*;
use breadcrumbs::Breadcrumbs; use breadcrumbs::Breadcrumbs;
use collections::VecDeque; use collections::VecDeque;
@ -18,9 +20,10 @@ pub use only_instance::*;
pub use open_listener::*; pub use open_listener::*;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use futures::{channel::mpsc, StreamExt};
use project_panel::ProjectPanel; use project_panel::ProjectPanel;
use quick_action_bar::QuickActionBar; use quick_action_bar::QuickActionBar;
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 std::{borrow::Cow, ops::Deref, sync::Arc};
use terminal_view::terminal_panel::TerminalPanel; use terminal_view::terminal_panel::TerminalPanel;
use util::{ use util::{
@ -565,6 +568,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
.detach(); .detach();
} }
pub fn handle_keymap_file_changes(
mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
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::<BaseKeymap>(cx));
// drop(settings_subscription);
// settings_subscription = Some(cx.update(|cx| {
// cx.observe_global::<SettingsStore, _>(move |cx| {
// let new_base_keymap = *settings::get::<BaseKeymap>(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( fn open_local_settings_file(
workspace: &mut Workspace, workspace: &mut Workspace,
_: &OpenLocalSettings, _: &OpenLocalSettings,