From 24639ec90097ce06a8fe5312699d3f05984b0589 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 22 Aug 2021 11:58:19 -0600 Subject: [PATCH] WIP --- gpui/src/app.rs | 279 ++++++++++++++---------- gpui/src/keymap.rs | 85 ++++---- gpui/src/platform.rs | 4 +- gpui/src/platform/mac/platform.rs | 17 +- gpui/src/platform/test.rs | 4 +- gpui/src/presenter.rs | 30 +-- zed/src/editor.rs | 339 +++++++++++++----------------- zed/src/editor/element.rs | 8 +- 8 files changed, 378 insertions(+), 388 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 0bcc0a4c29..f4ecdf48ff 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -92,6 +92,104 @@ pub trait UpdateView { F: FnOnce(&mut T, &mut ViewContext) -> S; } +pub trait Action: 'static + AnyAction { + type Argument: 'static + Clone; + + const NAME: &'static str; +} + +pub trait AnyAction { + fn id(&self) -> TypeId; + fn arg_as_any(&self) -> &dyn Any; + fn boxed_clone(&self) -> Box; + fn boxed_clone_as_any(&self) -> Box; +} + +impl Action for () { + type Argument = (); + + const NAME: &'static str = "()"; +} + +impl AnyAction for () { + fn id(&self) -> TypeId { + TypeId::of::<()>() + } + + fn arg_as_any(&self) -> &dyn Any { + &() + } + + fn boxed_clone(&self) -> Box { + Box::new(()) + } + + fn boxed_clone_as_any(&self) -> Box { + Box::new(()) + } +} + +#[macro_export] +macro_rules! action { + ($name:ident, $arg:ty) => { + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct $name(pub $arg); + + impl $crate::Action for $name { + type Argument = $arg; + + const NAME: &'static str = stringify!($name); + } + + impl $crate::AnyAction for $name { + fn id(&self) -> std::any::TypeId { + std::any::TypeId::of::<$name>() + } + + fn arg_as_any(&self) -> &dyn std::any::Any { + &self.0 + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn boxed_clone_as_any(&self) -> Box { + Box::new(self.clone()) + } + } + }; + + ($name:ident) => { + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct $name; + + impl $crate::Action for $name { + type Argument = (); + + const NAME: &'static str = stringify!($name); + } + + impl $crate::AnyAction for $name { + fn id(&self) -> std::any::TypeId { + std::any::TypeId::of::<$name>() + } + + fn arg_as_any(&self) -> &dyn std::any::Any { + &() + } + + fn boxed_clone(&self) -> Box { + Box::new(()) + } + + fn boxed_clone_as_any(&self) -> Box { + Box::new(()) + } + } + }; +} + pub struct Menu<'a> { pub name: &'a str, pub items: Vec>, @@ -101,8 +199,7 @@ pub enum MenuItem<'a> { Action { name: &'a str, keystroke: Option<&'a str>, - action: &'a str, - arg: Option>, + action: Box, }, Separator, } @@ -136,19 +233,19 @@ impl App { )))); let cx = app.0.clone(); - foreground_platform.on_menu_command(Box::new(move |command, arg| { + foreground_platform.on_menu_command(Box::new(move |action| { let mut cx = cx.borrow_mut(); if let Some(key_window_id) = cx.cx.platform.key_window_id() { if let Some((presenter, _)) = cx.presenters_and_platform_windows.get(&key_window_id) { let presenter = presenter.clone(); let path = presenter.borrow().dispatch_path(cx.as_ref()); - cx.dispatch_action_any(key_window_id, &path, command, arg.unwrap_or(&())); + cx.dispatch_action_any(key_window_id, &path, action); } else { - cx.dispatch_global_action_any(command, arg.unwrap_or(&())); + cx.dispatch_global_action_any(action); } } else { - cx.dispatch_global_action_any(command, arg.unwrap_or(&())); + cx.dispatch_global_action_any(action); } })); @@ -258,23 +355,19 @@ impl TestAppContext { cx } - pub fn dispatch_action( + pub fn dispatch_action( &self, window_id: usize, responder_chain: Vec, - name: &str, - arg: T, + action: A, ) { - self.cx.borrow_mut().dispatch_action_any( - window_id, - &responder_chain, - name, - Box::new(arg).as_ref(), - ); + self.cx + .borrow_mut() + .dispatch_action_any(window_id, &responder_chain, &action); } - pub fn dispatch_global_action(&self, name: &str, arg: T) { - self.cx.borrow_mut().dispatch_global_action(name, arg); + pub fn dispatch_global_action(&self, action: A) { + self.cx.borrow_mut().dispatch_global_action(action); } pub fn dispatch_keystroke( @@ -563,17 +656,17 @@ impl ReadViewWith for TestAppContext { } type ActionCallback = - dyn FnMut(&mut dyn AnyView, &dyn Any, &mut MutableAppContext, usize, usize) -> bool; + dyn FnMut(&mut dyn AnyView, &dyn AnyAction, &mut MutableAppContext, usize, usize) -> bool; -type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext); +type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext); pub struct MutableAppContext { weak_self: Option>>, foreground_platform: Rc, assets: Arc, cx: AppContext, - actions: HashMap>>>, - global_actions: HashMap>>, + actions: HashMap>>>, + global_actions: HashMap>>, keystroke_matcher: keymap::Matcher, next_entity_id: usize, next_window_id: usize, @@ -663,69 +756,53 @@ impl MutableAppContext { .map(|debug_elements| debug_elements(&self.cx)) } - pub fn add_action(&mut self, name: S, mut handler: F) + pub fn add_action(&mut self, mut handler: F) where - S: Into, + A: Action, V: View, - T: Any, - F: 'static + FnMut(&mut V, &T, &mut ViewContext), + F: 'static + FnMut(&mut V, &A, &mut ViewContext), { - let name = name.into(); - let name_clone = name.clone(); let handler = Box::new( move |view: &mut dyn AnyView, - arg: &dyn Any, + action: &dyn AnyAction, cx: &mut MutableAppContext, window_id: usize, view_id: usize| { - match arg.downcast_ref() { - Some(arg) => { - let mut cx = ViewContext::new(cx, window_id, view_id); - handler( - view.as_any_mut() - .downcast_mut() - .expect("downcast is type safe"), - arg, - &mut cx, - ); - cx.halt_action_dispatch - } - None => { - log::error!("Could not downcast argument for action {}", name_clone); - false - } - } + let arg = action.arg_as_any().downcast_ref().unwrap(); + let mut cx = ViewContext::new(cx, window_id, view_id); + handler( + view.as_any_mut() + .downcast_mut() + .expect("downcast is type safe"), + arg, + &mut cx, + ); + cx.halt_action_dispatch }, ); self.actions .entry(TypeId::of::()) .or_default() - .entry(name) + .entry(TypeId::of::()) .or_default() .push(handler); } - pub fn add_global_action(&mut self, name: S, mut handler: F) + pub fn add_global_action(&mut self, mut handler: F) where - S: Into, - T: 'static + Any, - F: 'static + FnMut(&T, &mut MutableAppContext), + A: Action, + F: 'static + FnMut(&A, &mut MutableAppContext), { - let name = name.into(); - let name_clone = name.clone(); - let handler = Box::new(move |arg: &dyn Any, cx: &mut MutableAppContext| { - if let Some(arg) = arg.downcast_ref() { - handler(arg, cx); - } else { - log::error!( - "Could not downcast argument for global action {}", - name_clone - ); - } + let handler = Box::new(move |action: &dyn AnyAction, cx: &mut MutableAppContext| { + let arg = action.arg_as_any().downcast_ref().unwrap(); + handler(arg, cx); }); - self.global_actions.entry(name).or_default().push(handler); + self.global_actions + .entry(TypeId::of::()) + .or_default() + .push(handler); } pub fn window_ids(&self) -> impl Iterator + '_ { @@ -838,22 +915,20 @@ impl MutableAppContext { self.pending_effects.extend(notifications); } - pub fn dispatch_action( + pub fn dispatch_action( &mut self, window_id: usize, responder_chain: Vec, - name: &str, - arg: T, + action: &A, ) { - self.dispatch_action_any(window_id, &responder_chain, name, Box::new(arg).as_ref()); + self.dispatch_action_any(window_id, &responder_chain, action); } pub(crate) fn dispatch_action_any( &mut self, window_id: usize, path: &[usize], - name: &str, - arg: &dyn Any, + action: &dyn AnyAction, ) -> bool { self.pending_flushes += 1; let mut halted_dispatch = false; @@ -865,10 +940,11 @@ impl MutableAppContext { if let Some((name, mut handlers)) = self .actions .get_mut(&type_id) - .and_then(|h| h.remove_entry(name)) + .and_then(|h| h.remove_entry(&action.id())) { for handler in handlers.iter_mut().rev() { - let halt_dispatch = handler(view.as_mut(), arg, self, window_id, *view_id); + let halt_dispatch = + handler(view.as_mut(), action, self, window_id, *view_id); if halt_dispatch { halted_dispatch = true; break; @@ -889,22 +965,22 @@ impl MutableAppContext { } if !halted_dispatch { - self.dispatch_global_action_any(name, arg); + self.dispatch_global_action_any(action); } self.flush_effects(); halted_dispatch } - pub fn dispatch_global_action(&mut self, name: &str, arg: T) { - self.dispatch_global_action_any(name, Box::new(arg).as_ref()); + pub fn dispatch_global_action(&mut self, action: A) { + self.dispatch_global_action_any(&action); } - fn dispatch_global_action_any(&mut self, name: &str, arg: &dyn Any) { - if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) { + fn dispatch_global_action_any(&mut self, action: &dyn AnyAction) { + if let Some((name, mut handlers)) = self.global_actions.remove_entry(&action.id()) { self.pending_flushes += 1; for handler in handlers.iter_mut().rev() { - handler(arg, self); + handler(action, self); } self.global_actions.insert(name, handlers); self.flush_effects(); @@ -943,13 +1019,9 @@ impl MutableAppContext { { MatchResult::None => {} MatchResult::Pending => pending = true, - MatchResult::Action { name, arg } => { - if self.dispatch_action_any( - window_id, - &responder_chain[0..=i], - &name, - arg.as_ref().map(|arg| arg.as_ref()).unwrap_or(&()), - ) { + MatchResult::Action(action) => { + if self.dispatch_action_any(window_id, &responder_chain[0..=i], action.as_ref()) + { return Ok(true); } } @@ -3575,31 +3647,29 @@ mod tests { } } - struct ActionArg { - foo: String, - } + action!(Action, &'static str); let actions = Rc::new(RefCell::new(Vec::new())); let actions_clone = actions.clone(); - cx.add_global_action("action", move |_: &ActionArg, _: &mut MutableAppContext| { + cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { actions_clone.borrow_mut().push("global a".to_string()); }); let actions_clone = actions.clone(); - cx.add_global_action("action", move |_: &ActionArg, _: &mut MutableAppContext| { + cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { actions_clone.borrow_mut().push("global b".to_string()); }); let actions_clone = actions.clone(); - cx.add_action("action", move |view: &mut ViewA, arg: &ActionArg, cx| { - assert_eq!(arg.foo, "bar"); + cx.add_action(move |view: &mut ViewA, action: &Action, cx| { + assert_eq!(action.0, "bar"); cx.propagate_action(); actions_clone.borrow_mut().push(format!("{} a", view.id)); }); let actions_clone = actions.clone(); - cx.add_action("action", move |view: &mut ViewA, _: &ActionArg, cx| { + cx.add_action(move |view: &mut ViewA, _: &Action, cx| { if view.id != 1 { cx.propagate_action(); } @@ -3607,13 +3677,13 @@ mod tests { }); let actions_clone = actions.clone(); - cx.add_action("action", move |view: &mut ViewB, _: &ActionArg, cx| { + cx.add_action(move |view: &mut ViewB, action: &Action, cx| { cx.propagate_action(); actions_clone.borrow_mut().push(format!("{} c", view.id)); }); let actions_clone = actions.clone(); - cx.add_action("action", move |view: &mut ViewB, _: &ActionArg, cx| { + cx.add_action(move |view: &mut ViewB, action: &Action, cx| { cx.propagate_action(); actions_clone.borrow_mut().push(format!("{} d", view.id)); }); @@ -3626,8 +3696,7 @@ mod tests { cx.dispatch_action( window_id, vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()], - "action", - ActionArg { foo: "bar".into() }, + &Action("bar"), ); assert_eq!( @@ -3640,8 +3709,7 @@ mod tests { cx.dispatch_action( window_id, vec![view_2.id(), view_3.id(), view_4.id()], - "action", - ActionArg { foo: "bar".into() }, + &Action("bar"), ); assert_eq!( @@ -3654,10 +3722,7 @@ mod tests { fn test_dispatch_keystroke(cx: &mut MutableAppContext) { use std::cell::Cell; - #[derive(Clone)] - struct ActionArg { - key: String, - } + action!(Action, &'static str); struct View { id: usize, @@ -3704,16 +3769,18 @@ mod tests { // This keymap's only binding dispatches an action on view 2 because that view will have // "a" and "b" in its context, but not "c". - let binding = keymap::Binding::new("a", "action", Some("a && b && !c")) - .with_arg(ActionArg { key: "a".into() }); - cx.add_bindings(vec![binding]); + cx.add_bindings(vec![keymap::Binding::new( + "a", + Action("a"), + Some("a && b && !c"), + )]); let handled_action = Rc::new(Cell::new(false)); let handled_action_clone = handled_action.clone(); - cx.add_action("action", move |view: &mut View, arg: &ActionArg, _| { + cx.add_action(move |view: &mut View, action: &Action, _| { handled_action_clone.set(true); assert_eq!(view.id, 2); - assert_eq!(arg.key, "a"); + assert_eq!(action.0, "a"); }); cx.dispatch_keystroke( diff --git a/gpui/src/keymap.rs b/gpui/src/keymap.rs index a3a2cbf58c..38a32e5e8c 100644 --- a/gpui/src/keymap.rs +++ b/gpui/src/keymap.rs @@ -5,6 +5,8 @@ use std::{ }; use tree_sitter::{Language, Node, Parser}; +use crate::{Action, AnyAction}; + extern "C" { fn tree_sitter_context_predicate() -> Language; } @@ -24,8 +26,7 @@ pub struct Keymap(Vec); pub struct Binding { keystrokes: Vec, - action: String, - action_arg: Option>, + action: Box, context: Option, } @@ -70,10 +71,7 @@ where pub enum MatchResult { None, Pending, - Action { - name: String, - arg: Option>, - }, + Action(Box), } impl Matcher { @@ -117,10 +115,7 @@ impl Matcher { { if binding.keystrokes.len() == pending.keystrokes.len() { self.pending.remove(&view_id); - return MatchResult::Action { - name: binding.action.clone(), - arg: binding.action_arg.as_ref().map(|arg| (*arg).boxed_clone()), - }; + return MatchResult::Action(binding.action.boxed_clone()); } else { retain_pending = true; pending.context = Some(cx.clone()); @@ -153,19 +148,26 @@ impl Keymap { } } +mod menu { + use crate::action; + + action!(SelectPrev); + action!(SelectNext); +} + impl Default for Keymap { fn default() -> Self { Self(vec![ - Binding::new("up", "menu:select_prev", Some("menu")), - Binding::new("ctrl-p", "menu:select_prev", Some("menu")), - Binding::new("down", "menu:select_next", Some("menu")), - Binding::new("ctrl-n", "menu:select_next", Some("menu")), + Binding::new("up", menu::SelectPrev, Some("menu")), + Binding::new("ctrl-p", menu::SelectPrev, Some("menu")), + Binding::new("down", menu::SelectNext, Some("menu")), + Binding::new("ctrl-n", menu::SelectNext, Some("menu")), ]) } } impl Binding { - pub fn new>(keystrokes: &str, action: S, context: Option<&str>) -> Self { + pub fn new(keystrokes: &str, action: A, context: Option<&str>) -> Self { let context = if let Some(context) = context { Some(ContextPredicate::parse(context).unwrap()) } else { @@ -177,16 +179,10 @@ impl Binding { .split_whitespace() .map(|key| Keystroke::parse(key).unwrap()) .collect(), - action: action.into(), - action_arg: None, + action: Box::new(action), context, } } - - pub fn with_arg(mut self, arg: T) -> Self { - self.action_arg = Some(Box::new(arg)); - self - } } impl Keystroke { @@ -328,6 +324,8 @@ impl ContextPredicate { #[cfg(test)] mod tests { + use crate::action; + use super::*; #[test] @@ -417,15 +415,19 @@ mod tests { #[test] fn test_matcher() -> anyhow::Result<()> { + action!(A, &'static str); + action!(B); + action!(Ab); + #[derive(Clone, Debug, Eq, PartialEq)] struct ActionArg { a: &'static str, } let keymap = Keymap(vec![ - Binding::new("a", "a", Some("a")).with_arg(ActionArg { a: "b" }), - Binding::new("b", "b", Some("a")), - Binding::new("a b", "a_b", Some("a || b")), + Binding::new("a", A("x"), Some("a")), + Binding::new("b", B, Some("a")), + Binding::new("a b", Ab, Some("a || b")), ]); let mut ctx_a = Context::default(); @@ -437,31 +439,19 @@ mod tests { let mut matcher = Matcher::new(keymap); // Basic match - assert_eq!( - matcher.test_keystroke("a", 1, &ctx_a), - Some(("a".to_string(), Some(ActionArg { a: "b" }))) - ); + assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x"))); // Multi-keystroke match assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None); - assert_eq!( - matcher.test_keystroke::<()>("b", 1, &ctx_b), - Some(("a_b".to_string(), None)) - ); + assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab)); // Failed matches don't interfere with matching subsequent keys assert_eq!(matcher.test_keystroke::<()>("x", 1, &ctx_a), None); - assert_eq!( - matcher.test_keystroke("a", 1, &ctx_a), - Some(("a".to_string(), Some(ActionArg { a: "b" }))) - ); + assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x"))); // Pending keystrokes are cleared when the context changes assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None); - assert_eq!( - matcher.test_keystroke::<()>("b", 1, &ctx_a), - Some(("b".to_string(), None)) - ); + assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B)); let mut ctx_c = Context::default(); ctx_c.set.insert("c".into()); @@ -469,25 +459,22 @@ mod tests { // Pending keystrokes are maintained per-view assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None); assert_eq!(matcher.test_keystroke::<()>("a", 2, &ctx_c), None); - assert_eq!( - matcher.test_keystroke::<()>("b", 1, &ctx_b), - Some(("a_b".to_string(), None)) - ); + assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab)); Ok(()) } impl Matcher { - fn test_keystroke( + fn test_keystroke( &mut self, keystroke: &str, view_id: usize, cx: &Context, - ) -> Option<(String, Option)> { - if let MatchResult::Action { name, arg } = + ) -> Option { + if let MatchResult::Action(action) = self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx) { - Some((name, arg.and_then(|arg| arg.downcast_ref::().cloned()))) + Some(*action.boxed_clone_as_any().downcast().unwrap()) } else { None } diff --git a/gpui/src/platform.rs b/gpui/src/platform.rs index f6930c1ae8..449f6bb962 100644 --- a/gpui/src/platform.rs +++ b/gpui/src/platform.rs @@ -16,7 +16,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, text_layout::LineLayout, - ClipboardItem, Menu, Scene, + AnyAction, ClipboardItem, Menu, Scene, }; use async_task::Runnable; pub use event::Event; @@ -56,7 +56,7 @@ pub(crate) trait ForegroundPlatform { fn on_open_files(&self, callback: Box)>); fn run(&self, on_finish_launching: Box ()>); - fn on_menu_command(&self, callback: Box)>); + fn on_menu_command(&self, callback: Box); fn set_menus(&self, menus: Vec); fn prompt_for_paths( &self, diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs index 794debe4b1..1b4331bedc 100644 --- a/gpui/src/platform/mac/platform.rs +++ b/gpui/src/platform/mac/platform.rs @@ -1,5 +1,7 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; -use crate::{executor, keymap::Keystroke, platform, ClipboardItem, Event, Menu, MenuItem}; +use crate::{ + executor, keymap::Keystroke, platform, AnyAction, ClipboardItem, Event, Menu, MenuItem, +}; use block::ConcreteBlock; use cocoa::{ appkit::{ @@ -90,10 +92,10 @@ pub struct MacForegroundPlatformState { become_active: Option>, resign_active: Option>, event: Option bool>>, - menu_command: Option)>>, + menu_command: Option>, open_files: Option)>>, finish_launching: Option ()>>, - menu_actions: Vec<(String, Option>)>, + menu_actions: Vec>, } impl MacForegroundPlatform { @@ -121,7 +123,6 @@ impl MacForegroundPlatform { name, keystroke, action, - arg, } => { if let Some(keystroke) = keystroke { let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| { @@ -162,7 +163,7 @@ impl MacForegroundPlatform { let tag = state.menu_actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; - state.menu_actions.push((action.to_string(), arg)); + state.menu_actions.push(action); } } @@ -215,7 +216,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { } } - fn on_menu_command(&self, callback: Box)>) { + fn on_menu_command(&self, callback: Box) { self.0.borrow_mut().menu_command = Some(callback); } @@ -623,8 +624,8 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { if let Some(mut callback) = platform.menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some((action, arg)) = platform.menu_actions.get(index) { - callback(action, arg.as_ref().map(Box::as_ref)); + if let Some(action) = platform.menu_actions.get(index) { + callback(action.as_ref()); } platform.menu_command = Some(callback); } diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 86b1153424..40c9304a7a 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -1,4 +1,4 @@ -use crate::ClipboardItem; +use crate::{AnyAction, ClipboardItem}; use parking_lot::Mutex; use pathfinder_geometry::vector::Vector2F; use std::{ @@ -62,7 +62,7 @@ impl super::ForegroundPlatform for ForegroundPlatform { unimplemented!() } - fn on_menu_command(&self, _: Box)>) {} + fn on_menu_command(&self, _: Box) {} fn set_menus(&self, _: Vec) {} diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index 57e40fff20..4e548ad03b 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -5,12 +5,11 @@ use crate::{ json::{self, ToJson}, platform::Event, text_layout::TextLayoutCache, - AssetCache, ElementBox, Scene, + Action, AnyAction, AssetCache, ElementBox, Scene, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use std::{ - any::Any, collections::{HashMap, HashSet}, sync::Arc, }; @@ -144,7 +143,7 @@ impl Presenter { let mut event_cx = EventContext { rendered_views: &mut self.rendered_views, - actions: Default::default(), + dispatched_actions: Default::default(), font_cache: &self.font_cache, text_layout_cache: &self.text_layout_cache, view_stack: Default::default(), @@ -154,18 +153,13 @@ impl Presenter { event_cx.dispatch_event(root_view_id, &event); let invalidated_views = event_cx.invalidated_views; - let actions = event_cx.actions; + let dispatch_directives = event_cx.dispatched_actions; for view_id in invalidated_views { cx.notify_view(self.window_id, view_id); } - for action in actions { - cx.dispatch_action_any( - self.window_id, - &action.path, - action.name, - action.arg.as_ref(), - ); + for directive in dispatch_directives { + cx.dispatch_action_any(self.window_id, &directive.path, directive.action.as_ref()); } } } @@ -183,10 +177,9 @@ impl Presenter { } } -pub struct ActionToDispatch { +pub struct DispatchDirective { pub path: Vec, - pub name: &'static str, - pub arg: Box, + pub action: Box, } pub struct LayoutContext<'a> { @@ -249,7 +242,7 @@ impl<'a> PaintContext<'a> { pub struct EventContext<'a> { rendered_views: &'a mut HashMap, - actions: Vec, + dispatched_actions: Vec, pub font_cache: &'a FontCache, pub text_layout_cache: &'a TextLayoutCache, pub app: &'a mut MutableAppContext, @@ -270,11 +263,10 @@ impl<'a> EventContext<'a> { } } - pub fn dispatch_action(&mut self, name: &'static str, arg: A) { - self.actions.push(ActionToDispatch { + pub fn dispatch_action(&mut self, action: A) { + self.dispatched_actions.push(DispatchDirective { path: self.view_stack.clone(), - name, - arg: Box::new(arg), + action: Box::new(action), }); } diff --git a/zed/src/editor.rs b/zed/src/editor.rs index b3dc4f8a2b..f7ba4c1cb0 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -16,7 +16,7 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - color::Color, font_cache::FamilyId, fonts::Properties as FontProperties, + action, color::Color, font_cache::FamilyId, fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, RenderContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle, @@ -40,224 +40,167 @@ use std::{ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; +action!(Cancel); +action!(Backspace); +action!(Delete); +action!(Newline); +action!(Insert, String); +action!(DeleteLine); +action!(DeleteToPreviousWordBoundary); +action!(DeleteToNextWordBoundary); +action!(DeleteToBeginningOfLine); +action!(DeleteToEndOfLine); +action!(CutToEndOfLine); +action!(DuplicateLine); +action!(MoveLineUp); +action!(MoveLineDown); +action!(Cut); +action!(Copy); +action!(Paste); +action!(Undo); +action!(Redo); +action!(MoveUp); +action!(MoveDown); +action!(MoveLeft); +action!(MoveRight); +action!(MoveToPreviousWordBoundary); +action!(MoveToNextWordBoundary); +action!(MoveToBeginningOfLine); +action!(MoveToEndOfLine); +action!(MoveToBeginning); +action!(MoveToEnd); +action!(SelectUp); +action!(SelectDown); +action!(SelectLeft); +action!(SelectRight); +action!(SelectToPreviousWordBoundary); +action!(SelectToNextWordBoundary); +action!(SelectToBeginningOfLine, bool); +action!(SelectToEndOfLine); +action!(SelectToBeginning); +action!(SelectToEnd); +action!(SelectAll); +action!(SelectLine); +action!(SplitSelectionIntoLines); +action!(AddSelectionAbove); +action!(AddSelectionBelow); +action!(SelectLargerSyntaxNode); +action!(SelectSmallerSyntaxNode); +action!(MoveToEnclosingBracket); +action!(PageUp); +action!(PageDown); +action!(Fold); +action!(Unfold); +action!(FoldSelectedRanges); +action!(Scroll, Vector2F); +action!(Select, SelectPhase); + pub fn init(cx: &mut MutableAppContext) { cx.add_bindings(vec![ - Binding::new("escape", "buffer:cancel", Some("BufferView")), - Binding::new("backspace", "buffer:backspace", Some("BufferView")), - Binding::new("ctrl-h", "buffer:backspace", Some("BufferView")), - Binding::new("delete", "buffer:delete", Some("BufferView")), - Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), - Binding::new("enter", "buffer:newline", Some("BufferView")), - Binding::new("tab", "buffer:insert", Some("BufferView")).with_arg("\t".to_string()), - Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), + Binding::new("escape", Cancel, Some("BufferView")), + Binding::new("backspace", Backspace, Some("BufferView")), + Binding::new("ctrl-h", Backspace, Some("BufferView")), + Binding::new("delete", Delete, Some("BufferView")), + Binding::new("ctrl-d", Delete, Some("BufferView")), + Binding::new("enter", Newline, Some("BufferView")), + Binding::new("tab", Insert("\t".into()), Some("BufferView")), + Binding::new("ctrl-shift-K", DeleteLine, Some("BufferView")), Binding::new( "alt-backspace", - "buffer:delete_to_previous_word_boundary", + DeleteToPreviousWordBoundary, Some("BufferView"), ), - Binding::new( - "alt-h", - "buffer:delete_to_previous_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-delete", - "buffer:delete_to_next_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-d", - "buffer:delete_to_next_word_boundary", - Some("BufferView"), - ), - Binding::new( - "cmd-backspace", - "buffer:delete_to_beginning_of_line", - Some("BufferView"), - ), - Binding::new( - "cmd-delete", - "buffer:delete_to_end_of_line", - Some("BufferView"), - ), - Binding::new("ctrl-k", "buffer:cut_to_end_of_line", Some("BufferView")), - Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")), - Binding::new("ctrl-cmd-up", "buffer:move_line_up", Some("BufferView")), - Binding::new("ctrl-cmd-down", "buffer:move_line_down", Some("BufferView")), - Binding::new("cmd-x", "buffer:cut", Some("BufferView")), - Binding::new("cmd-c", "buffer:copy", Some("BufferView")), - Binding::new("cmd-v", "buffer:paste", Some("BufferView")), - Binding::new("cmd-z", "buffer:undo", Some("BufferView")), - Binding::new("cmd-shift-Z", "buffer:redo", Some("BufferView")), - Binding::new("up", "buffer:move_up", Some("BufferView")), - Binding::new("down", "buffer:move_down", Some("BufferView")), - Binding::new("left", "buffer:move_left", Some("BufferView")), - Binding::new("right", "buffer:move_right", Some("BufferView")), - Binding::new("ctrl-p", "buffer:move_up", Some("BufferView")), - Binding::new("ctrl-n", "buffer:move_down", Some("BufferView")), - Binding::new("ctrl-b", "buffer:move_left", Some("BufferView")), - Binding::new("ctrl-f", "buffer:move_right", Some("BufferView")), - Binding::new( - "alt-left", - "buffer:move_to_previous_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-b", - "buffer:move_to_previous_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-right", - "buffer:move_to_next_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-f", - "buffer:move_to_next_word_boundary", - Some("BufferView"), - ), - Binding::new( - "cmd-left", - "buffer:move_to_beginning_of_line", - Some("BufferView"), - ), - Binding::new( - "ctrl-a", - "buffer:move_to_beginning_of_line", - Some("BufferView"), - ), - Binding::new( - "cmd-right", - "buffer:move_to_end_of_line", - Some("BufferView"), - ), - Binding::new("ctrl-e", "buffer:move_to_end_of_line", Some("BufferView")), - Binding::new("cmd-up", "buffer:move_to_beginning", Some("BufferView")), - Binding::new("cmd-down", "buffer:move_to_end", Some("BufferView")), - Binding::new("shift-up", "buffer:select_up", Some("BufferView")), - Binding::new("ctrl-shift-P", "buffer:select_up", Some("BufferView")), - Binding::new("shift-down", "buffer:select_down", Some("BufferView")), - Binding::new("ctrl-shift-N", "buffer:select_down", Some("BufferView")), - Binding::new("shift-left", "buffer:select_left", Some("BufferView")), - Binding::new("ctrl-shift-B", "buffer:select_left", Some("BufferView")), - Binding::new("shift-right", "buffer:select_right", Some("BufferView")), - Binding::new("ctrl-shift-F", "buffer:select_right", Some("BufferView")), + Binding::new("alt-h", DeleteToPreviousWordBoundary, Some("BufferView")), + Binding::new("alt-delete", DeleteToNextWordBoundary, Some("BufferView")), + Binding::new("alt-d", DeleteToNextWordBoundary, Some("BufferView")), + Binding::new("cmd-backspace", DeleteToBeginningOfLine, Some("BufferView")), + Binding::new("cmd-delete", DeleteToEndOfLine, Some("BufferView")), + Binding::new("ctrl-k", CutToEndOfLine, Some("BufferView")), + Binding::new("cmd-shift-D", DuplicateLine, Some("BufferView")), + Binding::new("ctrl-cmd-up", MoveLineUp, Some("BufferView")), + Binding::new("ctrl-cmd-down", MoveLineDown, Some("BufferView")), + Binding::new("cmd-x", Cut, Some("BufferView")), + Binding::new("cmd-c", Copy, Some("BufferView")), + Binding::new("cmd-v", Paste, Some("BufferView")), + Binding::new("cmd-z", Undo, Some("BufferView")), + Binding::new("cmd-shift-Z", Redo, Some("BufferView")), + Binding::new("up", MoveUp, Some("BufferView")), + Binding::new("down", MoveDown, Some("BufferView")), + Binding::new("left", MoveLeft, Some("BufferView")), + Binding::new("right", MoveRight, Some("BufferView")), + Binding::new("ctrl-p", MoveUp, Some("BufferView")), + Binding::new("ctrl-n", MoveDown, Some("BufferView")), + Binding::new("ctrl-b", MoveLeft, Some("BufferView")), + Binding::new("ctrl-f", MoveRight, Some("BufferView")), + Binding::new("alt-left", MoveToPreviousWordBoundary, Some("BufferView")), + Binding::new("alt-b", MoveToPreviousWordBoundary, Some("BufferView")), + Binding::new("alt-right", MoveToNextWordBoundary, Some("BufferView")), + Binding::new("alt-f", MoveToNextWordBoundary, Some("BufferView")), + Binding::new("cmd-left", MoveToBeginningOfLine, Some("BufferView")), + Binding::new("ctrl-a", MoveToBeginningOfLine, Some("BufferView")), + Binding::new("cmd-right", MoveToEndOfLine, Some("BufferView")), + Binding::new("ctrl-e", MoveToEndOfLine, Some("BufferView")), + Binding::new("cmd-up", MoveToBeginning, Some("BufferView")), + Binding::new("cmd-down", MoveToEnd, Some("BufferView")), + Binding::new("shift-up", SelectUp, Some("BufferView")), + Binding::new("ctrl-shift-P", SelectUp, Some("BufferView")), + Binding::new("shift-down", SelectDown, Some("BufferView")), + Binding::new("ctrl-shift-N", SelectDown, Some("BufferView")), + Binding::new("shift-left", SelectLeft, Some("BufferView")), + Binding::new("ctrl-shift-B", SelectLeft, Some("BufferView")), + Binding::new("shift-right", SelectRight, Some("BufferView")), + Binding::new("ctrl-shift-F", SelectRight, Some("BufferView")), Binding::new( "alt-shift-left", - "buffer:select_to_previous_word_boundary", + SelectToPreviousWordBoundary, Some("BufferView"), ), Binding::new( "alt-shift-B", - "buffer:select_to_previous_word_boundary", + SelectToPreviousWordBoundary, Some("BufferView"), ), Binding::new( "alt-shift-right", - "buffer:select_to_next_word_boundary", - Some("BufferView"), - ), - Binding::new( - "alt-shift-F", - "buffer:select_to_next_word_boundary", + SelectToNextWordBoundary, Some("BufferView"), ), + Binding::new("alt-shift-F", SelectToNextWordBoundary, Some("BufferView")), Binding::new( "cmd-shift-left", - "buffer:select_to_beginning_of_line", + SelectToBeginningOfLine(true), Some("BufferView"), - ) - .with_arg(true), + ), Binding::new( "ctrl-shift-A", - "buffer:select_to_beginning_of_line", - Some("BufferView"), - ) - .with_arg(true), - Binding::new( - "cmd-shift-right", - "buffer:select_to_end_of_line", - Some("BufferView"), - ), - Binding::new( - "ctrl-shift-E", - "buffer:select_to_end_of_line", - Some("BufferView"), - ), - Binding::new( - "cmd-shift-up", - "buffer:select_to_beginning", - Some("BufferView"), - ), - Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")), - Binding::new("cmd-a", "buffer:select_all", Some("BufferView")), - Binding::new("cmd-l", "buffer:select_line", Some("BufferView")), - Binding::new( - "cmd-shift-L", - "buffer:split_selection_into_lines", - Some("BufferView"), - ), - Binding::new( - "cmd-alt-up", - "buffer:add_selection_above", - Some("BufferView"), - ), - Binding::new( - "cmd-ctrl-p", - "buffer:add_selection_above", - Some("BufferView"), - ), - Binding::new( - "cmd-alt-down", - "buffer:add_selection_below", - Some("BufferView"), - ), - Binding::new( - "cmd-ctrl-n", - "buffer:add_selection_below", - Some("BufferView"), - ), - Binding::new( - "alt-up", - "buffer:select_larger_syntax_node", - Some("BufferView"), - ), - Binding::new( - "ctrl-w", - "buffer:select_larger_syntax_node", - Some("BufferView"), - ), - Binding::new( - "alt-down", - "buffer:select_smaller_syntax_node", - Some("BufferView"), - ), - Binding::new( - "ctrl-shift-W", - "buffer:select_smaller_syntax_node", - Some("BufferView"), - ), - Binding::new( - "ctrl-m", - "buffer:move_to_enclosing_bracket", - Some("BufferView"), - ), - Binding::new("pageup", "buffer:page_up", Some("BufferView")), - Binding::new("pagedown", "buffer:page_down", Some("BufferView")), - Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), - Binding::new("alt-cmd-]", "buffer:unfold", Some("BufferView")), - Binding::new( - "alt-cmd-f", - "buffer:fold_selected_ranges", + SelectToBeginningOfLine(true), Some("BufferView"), ), + Binding::new("cmd-shift-right", SelectToEndOfLine, Some("BufferView")), + Binding::new("ctrl-shift-E", SelectToEndOfLine, Some("BufferView")), + Binding::new("cmd-shift-up", SelectToBeginning, Some("BufferView")), + Binding::new("cmd-shift-down", SelectToEnd, Some("BufferView")), + Binding::new("cmd-a", SelectAll, Some("BufferView")), + Binding::new("cmd-l", SelectLine, Some("BufferView")), + Binding::new("cmd-shift-L", SplitSelectionIntoLines, Some("BufferView")), + Binding::new("cmd-alt-up", AddSelectionAbove, Some("BufferView")), + Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("BufferView")), + Binding::new("cmd-alt-down", AddSelectionBelow, Some("BufferView")), + Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("BufferView")), + Binding::new("alt-up", SelectLargerSyntaxNode, Some("BufferView")), + Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("BufferView")), + Binding::new("alt-down", SelectSmallerSyntaxNode, Some("BufferView")), + Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("BufferView")), + Binding::new("ctrl-m", MoveToEnclosingBracket, Some("BufferView")), + Binding::new("pageup", PageUp, Some("BufferView")), + Binding::new("pagedown", PageDown, Some("BufferView")), + Binding::new("alt-cmd-[", Fold, Some("BufferView")), + Binding::new("alt-cmd-]", Unfold, Some("BufferView")), + Binding::new("alt-cmd-f", FoldSelectedRanges, Some("BufferView")), ]); - cx.add_action("buffer:scroll", |this: &mut Editor, scroll_position, cx| { - this.set_scroll_position(*scroll_position, cx) - }); - cx.add_action("buffer:select", Editor::select); + cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx)); + cx.add_action(Editor::select); cx.add_action("buffer:cancel", Editor::cancel); cx.add_action("buffer:insert", Editor::insert); cx.add_action("buffer:newline", Editor::newline); @@ -357,7 +300,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action("buffer:fold_selected_ranges", Editor::fold_selected_ranges); } -pub enum SelectAction { +pub enum SelectPhase { Begin { position: DisplayPoint, add: bool, @@ -612,14 +555,14 @@ impl Editor { } } - fn select(&mut self, arg: &SelectAction, cx: &mut ViewContext) { - match arg { - SelectAction::Begin { position, add } => self.begin_selection(*position, *add, cx), - SelectAction::Update { + fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext) { + match phase { + SelectPhase::Begin { position, add } => self.begin_selection(*position, *add, cx), + SelectPhase::Update { position, scroll_position, } => self.update_selection(*position, *scroll_position, cx), - SelectAction::End => self.end_selection(cx), + SelectPhase::End => self.end_selection(cx), } } diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 297f04cb56..a3da936369 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -1,4 +1,4 @@ -use super::{DisplayPoint, Editor, SelectAction, Snapshot}; +use super::{DisplayPoint, Editor, SelectPhase, Snapshot}; use crate::time::ReplicaId; use gpui::{ color::Color, @@ -55,7 +55,7 @@ impl EditorElement { if paint.text_bounds.contains_point(position) { let snapshot = self.snapshot(cx.app); let position = paint.point_for_position(&snapshot, layout, position); - cx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd }); + cx.dispatch_action("buffer:select", SelectPhase::Begin { position, add: cmd }); true } else { false @@ -64,7 +64,7 @@ impl EditorElement { fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool { if self.view(cx.app.as_ref()).is_selecting() { - cx.dispatch_action("buffer:select", SelectAction::End); + cx.dispatch_action("buffer:select", SelectPhase::End); true } else { false @@ -115,7 +115,7 @@ impl EditorElement { cx.dispatch_action( "buffer:select", - SelectAction::Update { + SelectPhase::Update { position, scroll_position: (snapshot.scroll_position() + scroll_delta).clamp( Vector2F::zero(),