diff --git a/crates/gpui3/src/keymap/keymap_context.rs b/crates/gpui3/src/action.rs similarity index 78% rename from crates/gpui3/src/keymap/keymap_context.rs rename to crates/gpui3/src/action.rs index 8ceac72e8f923a3fab5e434f659a4183db9dd8b0..e8d113532dc59d9a24eda50f725af800065bdd92 100644 --- a/crates/gpui3/src/keymap/keymap_context.rs +++ b/crates/gpui3/src/action.rs @@ -1,16 +1,23 @@ +use crate::SharedString; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet}; -use std::borrow::Cow; +use std::any::Any; + +pub trait Action: Any + Send + Sync { + fn partial_eq(&self, action: &dyn Action) -> bool; + fn boxed_clone(&self) -> Box; + fn as_any(&self) -> &dyn Any; +} #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct KeymapContext { - set: HashSet>, - map: HashMap, Cow<'static, str>>, +pub struct ActionContext { + set: HashSet, + map: HashMap, } -impl KeymapContext { +impl ActionContext { pub fn new() -> Self { - KeymapContext { + ActionContext { set: HashSet::default(), map: HashMap::default(), } @@ -30,31 +37,27 @@ impl KeymapContext { } } - pub fn add_identifier>>(&mut self, identifier: I) { + pub fn add_identifier>(&mut self, identifier: I) { self.set.insert(identifier.into()); } - pub fn add_key>, S2: Into>>( - &mut self, - key: S1, - value: S2, - ) { + pub fn add_key, S2: Into>(&mut self, key: S1, value: S2) { self.map.insert(key.into(), value.into()); } } #[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub enum KeymapContextPredicate { - Identifier(String), - Equal(String, String), - NotEqual(String, String), - Child(Box, Box), - Not(Box), - And(Box, Box), - Or(Box, Box), +pub enum ActionContextPredicate { + Identifier(SharedString), + Equal(SharedString, SharedString), + NotEqual(SharedString, SharedString), + Child(Box, Box), + Not(Box), + And(Box, Box), + Or(Box, Box), } -impl KeymapContextPredicate { +impl ActionContextPredicate { pub fn parse(source: &str) -> Result { let source = Self::skip_whitespace(source); let (predicate, rest) = Self::parse_expr(source, 0)?; @@ -65,20 +68,20 @@ impl KeymapContextPredicate { } } - pub fn eval(&self, contexts: &[KeymapContext]) -> bool { + pub fn eval(&self, contexts: &[ActionContext]) -> bool { let Some(context) = contexts.first() else { return false; }; match self { - Self::Identifier(name) => (&context.set).contains(name.as_str()), + Self::Identifier(name) => context.set.contains(&name), Self::Equal(left, right) => context .map - .get(left.as_str()) + .get(&left) .map(|value| value == right) .unwrap_or(false), Self::NotEqual(left, right) => context .map - .get(left.as_str()) + .get(&left) .map(|value| value != right) .unwrap_or(true), Self::Not(pred) => !pred.eval(contexts), @@ -90,7 +93,7 @@ impl KeymapContextPredicate { fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> { type Op = - fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; + fn(ActionContextPredicate, ActionContextPredicate) -> Result; let (mut predicate, rest) = Self::parse_primary(source)?; source = rest; @@ -136,7 +139,7 @@ impl KeymapContextPredicate { '!' => { let source = Self::skip_whitespace(&source[1..]); let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?; - Ok((KeymapContextPredicate::Not(Box::new(predicate)), source)) + Ok((ActionContextPredicate::Not(Box::new(predicate)), source)) } _ if next.is_alphanumeric() || next == '_' => { let len = source @@ -145,7 +148,7 @@ impl KeymapContextPredicate { let (identifier, rest) = source.split_at(len); source = Self::skip_whitespace(rest); Ok(( - KeymapContextPredicate::Identifier(identifier.into()), + ActionContextPredicate::Identifier(identifier.to_string().into()), source, )) } @@ -197,17 +200,17 @@ const PRECEDENCE_NOT: u32 = 5; #[cfg(test)] mod tests { - use super::KeymapContextPredicate::{self, *}; + use super::ActionContextPredicate::{self, *}; #[test] fn test_parse_identifiers() { // Identifiers assert_eq!( - KeymapContextPredicate::parse("abc12").unwrap(), + ActionContextPredicate::parse("abc12").unwrap(), Identifier("abc12".into()) ); assert_eq!( - KeymapContextPredicate::parse("_1a").unwrap(), + ActionContextPredicate::parse("_1a").unwrap(), Identifier("_1a".into()) ); } @@ -215,11 +218,11 @@ mod tests { #[test] fn test_parse_negations() { assert_eq!( - KeymapContextPredicate::parse("!abc").unwrap(), + ActionContextPredicate::parse("!abc").unwrap(), Not(Box::new(Identifier("abc".into()))) ); assert_eq!( - KeymapContextPredicate::parse(" ! ! abc").unwrap(), + ActionContextPredicate::parse(" ! ! abc").unwrap(), Not(Box::new(Not(Box::new(Identifier("abc".into()))))) ); } @@ -227,15 +230,15 @@ mod tests { #[test] fn test_parse_equality_operators() { assert_eq!( - KeymapContextPredicate::parse("a == b").unwrap(), + ActionContextPredicate::parse("a == b").unwrap(), Equal("a".into(), "b".into()) ); assert_eq!( - KeymapContextPredicate::parse("c!=d").unwrap(), + ActionContextPredicate::parse("c!=d").unwrap(), NotEqual("c".into(), "d".into()) ); assert_eq!( - KeymapContextPredicate::parse("c == !d") + ActionContextPredicate::parse("c == !d") .unwrap_err() .to_string(), "operands must be identifiers" @@ -245,14 +248,14 @@ mod tests { #[test] fn test_parse_boolean_operators() { assert_eq!( - KeymapContextPredicate::parse("a || b").unwrap(), + ActionContextPredicate::parse("a || b").unwrap(), Or( Box::new(Identifier("a".into())), Box::new(Identifier("b".into())) ) ); assert_eq!( - KeymapContextPredicate::parse("a || !b && c").unwrap(), + ActionContextPredicate::parse("a || !b && c").unwrap(), Or( Box::new(Identifier("a".into())), Box::new(And( @@ -262,7 +265,7 @@ mod tests { ) ); assert_eq!( - KeymapContextPredicate::parse("a && b || c&&d").unwrap(), + ActionContextPredicate::parse("a && b || c&&d").unwrap(), Or( Box::new(And( Box::new(Identifier("a".into())), @@ -275,7 +278,7 @@ mod tests { ) ); assert_eq!( - KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(), + ActionContextPredicate::parse("a == b && c || d == e && f").unwrap(), Or( Box::new(And( Box::new(Equal("a".into(), "b".into())), @@ -288,7 +291,7 @@ mod tests { ) ); assert_eq!( - KeymapContextPredicate::parse("a && b && c && d").unwrap(), + ActionContextPredicate::parse("a && b && c && d").unwrap(), And( Box::new(And( Box::new(And( @@ -305,7 +308,7 @@ mod tests { #[test] fn test_parse_parenthesized_expressions() { assert_eq!( - KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(), + ActionContextPredicate::parse("a && (b == c || d != e)").unwrap(), And( Box::new(Identifier("a".into())), Box::new(Or( @@ -315,7 +318,7 @@ mod tests { ), ); assert_eq!( - KeymapContextPredicate::parse(" ( a || b ) ").unwrap(), + ActionContextPredicate::parse(" ( a || b ) ").unwrap(), Or( Box::new(Identifier("a".into())), Box::new(Identifier("b".into())), diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index f2033514fa8dac8c024f0c324f88cff13e4f63d4..c417bcb0dd8955f9fbb2341b6cc3739288d519f9 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -10,14 +10,14 @@ use smallvec::SmallVec; use crate::{ current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, - FocusEvent, FocusHandle, FocusId, LayoutId, MainThread, MainThreadOnly, Platform, - SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, - WindowContext, WindowHandle, WindowId, + FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, + Platform, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, + Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; use futures::Future; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use slotmap::SlotMap; use std::{ any::{type_name, Any, TypeId}, @@ -67,6 +67,7 @@ impl App { unit_entity, entities, windows: SlotMap::with_key(), + keymap: Arc::new(RwLock::new(Keymap::default())), pending_notifications: Default::default(), pending_effects: Default::default(), observers: SubscriberSet::new(), @@ -111,6 +112,7 @@ pub struct AppContext { pub(crate) unit_entity: Handle<()>, pub(crate) entities: EntityMap, pub(crate) windows: SlotMap>, + keymap: Arc>, pub(crate) pending_notifications: HashSet, pending_effects: VecDeque, pub(crate) observers: SubscriberSet, @@ -403,6 +405,14 @@ impl AppContext { pub(crate) fn pop_text_style(&mut self) { self.text_style_stack.pop(); } + + pub fn bind_keys(&mut self, bindings: impl IntoIterator) { + self.keymap.write().add_bindings(bindings); + let window_ids = self.windows.keys().collect::>(); + for window_id in window_ids { + self.update_window(window_id, |cx| cx.notify()).unwrap(); + } + } } impl Context for AppContext { diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 1af9a1cc222386f812fbf1e9c6d535bfafc968e8..fbc9d8d0f2af3141d3b181828d2ed0b9ea5f8d4d 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -1,3 +1,4 @@ +mod action; mod active; mod app; mod assets; @@ -24,6 +25,7 @@ mod util; mod view; mod window; +pub use action::*; pub use active::*; pub use anyhow::Result; pub use app::*; diff --git a/crates/gpui3/src/keymap/binding.rs b/crates/gpui3/src/keymap/binding.rs index e63479ab168f7a119f8957d6dcb8e260def97546..2ac3c10964ec8c48b91e6c7c696e8ab5063e81ca 100644 --- a/crates/gpui3/src/keymap/binding.rs +++ b/crates/gpui3/src/keymap/binding.rs @@ -1,21 +1,21 @@ -use crate::{Action, KeyMatch, KeymapContext, KeymapContextPredicate, Keystroke}; +use crate::{Action, ActionContext, ActionContextPredicate, KeyMatch, Keystroke}; use anyhow::Result; use smallvec::SmallVec; -pub struct Binding { +pub struct KeyBinding { action: Box, pub(super) keystrokes: SmallVec<[Keystroke; 2]>, - pub(super) context_predicate: Option, + pub(super) context_predicate: Option, } -impl Binding { +impl KeyBinding { pub fn new(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self { Self::load(keystrokes, Box::new(action), context_predicate).unwrap() } pub fn load(keystrokes: &str, action: Box, context: Option<&str>) -> Result { let context = if let Some(context) = context { - Some(KeymapContextPredicate::parse(context)?) + Some(ActionContextPredicate::parse(context)?) } else { None }; @@ -32,7 +32,7 @@ impl Binding { }) } - pub fn matches_context(&self, contexts: &[KeymapContext]) -> bool { + pub fn matches_context(&self, contexts: &[ActionContext]) -> bool { self.context_predicate .as_ref() .map(|predicate| predicate.eval(contexts)) @@ -42,7 +42,7 @@ impl Binding { pub fn match_keystrokes( &self, pending_keystrokes: &[Keystroke], - contexts: &[KeymapContext], + contexts: &[ActionContext], ) -> KeyMatch { if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.matches_context(contexts) @@ -61,7 +61,7 @@ impl Binding { pub fn keystrokes_for_action( &self, action: &dyn Action, - contexts: &[KeymapContext], + contexts: &[ActionContext], ) -> Option> { if self.action.partial_eq(action) && self.matches_context(contexts) { Some(self.keystrokes.clone()) diff --git a/crates/gpui3/src/keymap/keymap.rs b/crates/gpui3/src/keymap/keymap.rs index 6bbb87a0e3ae9865644e4e058f8c116fdad10cf3..fffd7d54846570271ea293c956609f4580368348 100644 --- a/crates/gpui3/src/keymap/keymap.rs +++ b/crates/gpui3/src/keymap/keymap.rs @@ -1,4 +1,4 @@ -use crate::{Binding, KeymapContextPredicate, Keystroke}; +use crate::{ActionContextPredicate, KeyBinding, Keystroke}; use collections::HashSet; use smallvec::SmallVec; use std::{any::TypeId, collections::HashMap}; @@ -8,14 +8,14 @@ pub struct KeymapVersion(usize); #[derive(Default)] pub struct Keymap { - bindings: Vec, + bindings: Vec, binding_indices_by_action_id: HashMap>, - disabled_keystrokes: HashMap, HashSet>>, + disabled_keystrokes: HashMap, HashSet>>, version: KeymapVersion, } impl Keymap { - pub fn new(bindings: Vec) -> Self { + pub fn new(bindings: Vec) -> Self { let mut this = Self::default(); this.add_bindings(bindings); this @@ -25,7 +25,7 @@ impl Keymap { self.version } - pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator { + pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator { self.binding_indices_by_action_id .get(&action_id) .map(SmallVec::as_slice) @@ -35,7 +35,7 @@ impl Keymap { .filter(|binding| !self.binding_disabled(binding)) } - pub fn add_bindings>(&mut self, bindings: T) { + pub fn add_bindings>(&mut self, bindings: T) { // todo!("no action") // let no_action_id = (NoAction {}).id(); let mut new_bindings = Vec::new(); @@ -87,14 +87,14 @@ impl Keymap { self.version.0 += 1; } - pub fn bindings(&self) -> Vec<&Binding> { + pub fn bindings(&self) -> Vec<&KeyBinding> { self.bindings .iter() .filter(|binding| !self.binding_disabled(binding)) .collect() } - fn binding_disabled(&self, binding: &Binding) -> bool { + fn binding_disabled(&self, binding: &KeyBinding) -> bool { match self.disabled_keystrokes.get(&binding.keystrokes) { Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate), None => false, diff --git a/crates/gpui3/src/keymap/matcher.rs b/crates/gpui3/src/keymap/matcher.rs index 38d2f38029726561dabede829957b3c78fde99fa..1718a871f99f87553e1ad58c8b49f79b0e4f201f 100644 --- a/crates/gpui3/src/keymap/matcher.rs +++ b/crates/gpui3/src/keymap/matcher.rs @@ -1,13 +1,7 @@ -use crate::{Keymap, KeymapContext, KeymapVersion, Keystroke}; +use crate::{Action, ActionContext, Keymap, KeymapVersion, Keystroke}; use parking_lot::RwLock; use smallvec::SmallVec; -use std::{any::Any, sync::Arc}; - -pub trait Action: Any + Send + Sync { - fn partial_eq(&self, action: &dyn Action) -> bool; - fn boxed_clone(&self) -> Box; - fn as_any(&self) -> &dyn Any; -} +use std::sync::Arc; pub struct KeyMatcher { pending_keystrokes: Vec, @@ -50,7 +44,7 @@ impl KeyMatcher { pub fn match_keystroke( &mut self, keystroke: &Keystroke, - context_stack: &[KeymapContext], + context_stack: &[ActionContext], ) -> KeyMatch { let keymap = self.keymap.read(); // Clear pending keystrokes if the keymap has changed since the last matched keystroke. @@ -92,7 +86,7 @@ impl KeyMatcher { pub fn keystrokes_for_action( &self, action: &dyn Action, - contexts: &[KeymapContext], + contexts: &[ActionContext], ) -> Option> { self.keymap .read() @@ -120,7 +114,7 @@ impl KeyMatch { // use anyhow::Result; // use serde::Deserialize; -// use crate::{actions, impl_actions, keymap_matcher::KeymapContext}; +// use crate::{actions, impl_actions, keymap_matcher::ActionContext}; // use super::*; @@ -128,10 +122,10 @@ impl KeyMatch { // fn test_keymap_and_view_ordering() -> Result<()> { // actions!(test, [EditorAction, ProjectPanelAction]); -// let mut editor = KeymapContext::default(); +// let mut editor = ActionContext::default(); // editor.add_identifier("Editor"); -// let mut project_panel = KeymapContext::default(); +// let mut project_panel = ActionContext::default(); // project_panel.add_identifier("ProjectPanel"); // // Editor 'deeper' in than project panel @@ -160,10 +154,10 @@ impl KeyMatch { // fn test_push_keystroke() -> Result<()> { // actions!(test, [B, AB, C, D, DA, E, EF]); -// let mut context1 = KeymapContext::default(); +// let mut context1 = ActionContext::default(); // context1.add_identifier("1"); -// let mut context2 = KeymapContext::default(); +// let mut context2 = ActionContext::default(); // context2.add_identifier("2"); // let dispatch_path = vec![(2, context2), (1, context1)]; @@ -300,27 +294,27 @@ impl KeyMatch { // fn test_context_predicate_eval() { // let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); -// let mut context = KeymapContext::default(); +// let mut context = ActionContext::default(); // context.add_identifier("a"); // assert!(!predicate.eval(&[context])); -// let mut context = KeymapContext::default(); +// let mut context = ActionContext::default(); // context.add_identifier("a"); // context.add_identifier("b"); // assert!(predicate.eval(&[context])); -// let mut context = KeymapContext::default(); +// let mut context = ActionContext::default(); // context.add_identifier("a"); // context.add_key("c", "x"); // assert!(!predicate.eval(&[context])); -// let mut context = KeymapContext::default(); +// let mut context = ActionContext::default(); // context.add_identifier("a"); // context.add_key("c", "d"); // assert!(predicate.eval(&[context])); // let predicate = KeymapContextPredicate::parse("!a").unwrap(); -// assert!(predicate.eval(&[KeymapContext::default()])); +// assert!(predicate.eval(&[ActionContext::default()])); // } // #[test] @@ -355,8 +349,8 @@ impl KeyMatch { // assert!(!predicate.eval(&contexts[5..])); // assert!(!predicate.eval(&contexts[6..])); -// fn context_set(names: &[&str]) -> KeymapContext { -// let mut keymap = KeymapContext::new(); +// fn context_set(names: &[&str]) -> ActionContext { +// let mut keymap = ActionContext::new(); // names // .iter() // .for_each(|name| keymap.add_identifier(name.to_string())); @@ -386,10 +380,10 @@ impl KeyMatch { // Binding::new("ctrl-`", Backtick, Some("a")), // ]); -// let mut context_a = KeymapContext::default(); +// let mut context_a = ActionContext::default(); // context_a.add_identifier("a"); -// let mut context_b = KeymapContext::default(); +// let mut context_b = ActionContext::default(); // context_b.add_identifier("b"); // let mut matcher = KeymapMatcher::new(keymap); @@ -434,7 +428,7 @@ impl KeyMatch { // ); // matcher.clear_pending(); -// let mut context_c = KeymapContext::default(); +// let mut context_c = ActionContext::default(); // context_c.add_identifier("c"); // // Pending keystrokes are maintained per-view diff --git a/crates/gpui3/src/keymap/mod.rs b/crates/gpui3/src/keymap/mod.rs index 26fcfac477fb73a70f6151ea4c9d2ea155d88d95..449b5427bf288dbf7647ed3ebd2451e15b10dd15 100644 --- a/crates/gpui3/src/keymap/mod.rs +++ b/crates/gpui3/src/keymap/mod.rs @@ -1,9 +1,7 @@ mod binding; mod keymap; -mod keymap_context; mod matcher; pub use binding::*; pub use keymap::*; -pub use keymap_context::*; pub use matcher::*;