Detailed changes
@@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
use gpui::{
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
- DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
- HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
+ EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
+ InputHandler, KeyBindingContext, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -646,7 +646,7 @@ pub struct Editor {
collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakView<Workspace>, i64)>,
- keymap_context_layers: BTreeMap<TypeId, DispatchContext>,
+ keymap_context_layers: BTreeMap<TypeId, KeyBindingContext>,
input_enabled: bool,
read_only: bool,
leader_peer_id: Option<PeerId>,
@@ -1980,9 +1980,9 @@ impl Editor {
this
}
- fn dispatch_context(&self, cx: &AppContext) -> DispatchContext {
- let mut dispatch_context = DispatchContext::default();
- dispatch_context.insert("Editor");
+ fn dispatch_context(&self, cx: &AppContext) -> KeyBindingContext {
+ let mut dispatch_context = KeyBindingContext::default();
+ dispatch_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
@@ -1990,17 +1990,17 @@ impl Editor {
};
dispatch_context.set("mode", mode);
if self.pending_rename.is_some() {
- dispatch_context.insert("renaming");
+ dispatch_context.add("renaming");
}
if self.context_menu_visible() {
match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_completions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_completions")
}
Some(ContextMenu::CodeActions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_code_actions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_code_actions")
}
None => {}
}
@@ -16,11 +16,12 @@ use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
- BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
- Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
- InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
- Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
+ BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
+ ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
+ KeyBindingContext, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers,
+ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent,
+ ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
+ WrappedLineLayout,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -4157,21 +4158,6 @@ fn build_key_listeners(
build_action_listener(Editor::context_menu_prev),
build_action_listener(Editor::context_menu_next),
build_action_listener(Editor::context_menu_last),
- build_key_listener(
- move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
- if phase == DispatchPhase::Bubble {
- if let KeyMatch::Some(action) = cx.match_keystroke(
- &global_element_id,
- &key_down.keystroke,
- dispatch_context,
- ) {
- return Some(action);
- }
- }
-
- None
- },
- ),
]
}
@@ -4179,7 +4165,7 @@ fn build_key_listener<T: 'static>(
listener: impl Fn(
&mut Editor,
&T,
- &[&DispatchContext],
+ &[&KeyBindingContext],
DispatchPhase,
&mut ViewContext<Editor>,
) -> Option<Box<dyn Action>>
@@ -0,0 +1 @@
+
@@ -1,6 +1,6 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
-use collections::{HashMap, HashSet};
+use collections::HashMap;
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
@@ -186,401 +186,3 @@ macro_rules! actions {
actions!($($rest)*);
};
}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct DispatchContext {
- set: HashSet<SharedString>,
- map: HashMap<SharedString, SharedString>,
-}
-
-impl<'a> TryFrom<&'a str> for DispatchContext {
- type Error = anyhow::Error;
-
- fn try_from(value: &'a str) -> Result<Self> {
- Self::parse(value)
- }
-}
-
-impl DispatchContext {
- pub fn parse(source: &str) -> Result<Self> {
- let mut context = Self::default();
- let source = skip_whitespace(source);
- Self::parse_expr(&source, &mut context)?;
- Ok(context)
- }
-
- fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
- if source.is_empty() {
- return Ok(());
- }
-
- let key = source
- .chars()
- .take_while(|c| is_identifier_char(*c))
- .collect::<String>();
- source = skip_whitespace(&source[key.len()..]);
- if let Some(suffix) = source.strip_prefix('=') {
- source = skip_whitespace(suffix);
- let value = source
- .chars()
- .take_while(|c| is_identifier_char(*c))
- .collect::<String>();
- source = skip_whitespace(&source[value.len()..]);
- context.set(key, value);
- } else {
- context.insert(key);
- }
-
- Self::parse_expr(source, context)
- }
-
- pub fn is_empty(&self) -> bool {
- self.set.is_empty() && self.map.is_empty()
- }
-
- pub fn clear(&mut self) {
- self.set.clear();
- self.map.clear();
- }
-
- pub fn extend(&mut self, other: &Self) {
- for v in &other.set {
- self.set.insert(v.clone());
- }
- for (k, v) in &other.map {
- self.map.insert(k.clone(), v.clone());
- }
- }
-
- pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
- self.set.insert(identifier.into());
- }
-
- pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
- self.map.insert(key.into(), value.into());
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum DispatchContextPredicate {
- Identifier(SharedString),
- Equal(SharedString, SharedString),
- NotEqual(SharedString, SharedString),
- Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
- Not(Box<DispatchContextPredicate>),
- And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
- Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
-}
-
-impl DispatchContextPredicate {
- pub fn parse(source: &str) -> Result<Self> {
- let source = skip_whitespace(source);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if let Some(next) = rest.chars().next() {
- Err(anyhow!("unexpected character {next:?}"))
- } else {
- Ok(predicate)
- }
- }
-
- pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
- let Some(context) = contexts.last() else {
- return false;
- };
- match self {
- Self::Identifier(name) => context.set.contains(name),
- Self::Equal(left, right) => context
- .map
- .get(left)
- .map(|value| value == right)
- .unwrap_or(false),
- Self::NotEqual(left, right) => context
- .map
- .get(left)
- .map(|value| value != right)
- .unwrap_or(true),
- Self::Not(pred) => !pred.eval(contexts),
- Self::Child(parent, child) => {
- parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
- }
- Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
- Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
- }
- }
-
- fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
- type Op = fn(
- DispatchContextPredicate,
- DispatchContextPredicate,
- ) -> Result<DispatchContextPredicate>;
-
- let (mut predicate, rest) = Self::parse_primary(source)?;
- source = rest;
-
- 'parse: loop {
- for (operator, precedence, constructor) in [
- (">", PRECEDENCE_CHILD, Self::new_child as Op),
- ("&&", PRECEDENCE_AND, Self::new_and as Op),
- ("||", PRECEDENCE_OR, Self::new_or as Op),
- ("==", PRECEDENCE_EQ, Self::new_eq as Op),
- ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
- ] {
- if source.starts_with(operator) && precedence >= min_precedence {
- source = skip_whitespace(&source[operator.len()..]);
- let (right, rest) = Self::parse_expr(source, precedence + 1)?;
- predicate = constructor(predicate, right)?;
- source = rest;
- continue 'parse;
- }
- }
- break;
- }
-
- Ok((predicate, source))
- }
-
- fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
- let next = source
- .chars()
- .next()
- .ok_or_else(|| anyhow!("unexpected eof"))?;
- match next {
- '(' => {
- source = skip_whitespace(&source[1..]);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if rest.starts_with(')') {
- source = skip_whitespace(&rest[1..]);
- Ok((predicate, source))
- } else {
- Err(anyhow!("expected a ')'"))
- }
- }
- '!' => {
- let source = skip_whitespace(&source[1..]);
- let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
- Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
- }
- _ if is_identifier_char(next) => {
- let len = source
- .find(|c: char| !is_identifier_char(c))
- .unwrap_or(source.len());
- let (identifier, rest) = source.split_at(len);
- source = skip_whitespace(rest);
- Ok((
- DispatchContextPredicate::Identifier(identifier.to_string().into()),
- source,
- ))
- }
- _ => Err(anyhow!("unexpected character {next:?}")),
- }
- }
-
- fn new_or(self, other: Self) -> Result<Self> {
- Ok(Self::Or(Box::new(self), Box::new(other)))
- }
-
- fn new_and(self, other: Self) -> Result<Self> {
- Ok(Self::And(Box::new(self), Box::new(other)))
- }
-
- fn new_child(self, other: Self) -> Result<Self> {
- Ok(Self::Child(Box::new(self), Box::new(other)))
- }
-
- fn new_eq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::Equal(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-
- fn new_neq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::NotEqual(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-}
-
-const PRECEDENCE_CHILD: u32 = 1;
-const PRECEDENCE_OR: u32 = 2;
-const PRECEDENCE_AND: u32 = 3;
-const PRECEDENCE_EQ: u32 = 4;
-const PRECEDENCE_NOT: u32 = 5;
-
-fn is_identifier_char(c: char) -> bool {
- c.is_alphanumeric() || c == '_' || c == '-'
-}
-
-fn skip_whitespace(source: &str) -> &str {
- let len = source
- .find(|c: char| !c.is_whitespace())
- .unwrap_or(source.len());
- &source[len..]
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate as gpui;
- use DispatchContextPredicate::*;
-
- #[test]
- fn test_actions_definition() {
- {
- actions!(A, B, C, D, E, F, G);
- }
-
- {
- actions!(
- A,
- B,
- C,
- D,
- E,
- F,
- G, // Don't wrap, test the trailing comma
- );
- }
- }
-
- #[test]
- fn test_parse_context() {
- let mut expected = DispatchContext::default();
- expected.set("foo", "bar");
- expected.insert("baz");
- assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
- assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
- assert_eq!(
- DispatchContext::parse(" baz foo = bar baz").unwrap(),
- expected
- );
- assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
- }
-
- #[test]
- fn test_parse_identifiers() {
- // Identifiers
- assert_eq!(
- DispatchContextPredicate::parse("abc12").unwrap(),
- Identifier("abc12".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("_1a").unwrap(),
- Identifier("_1a".into())
- );
- }
-
- #[test]
- fn test_parse_negations() {
- assert_eq!(
- DispatchContextPredicate::parse("!abc").unwrap(),
- Not(Box::new(Identifier("abc".into())))
- );
- assert_eq!(
- DispatchContextPredicate::parse(" ! ! abc").unwrap(),
- Not(Box::new(Not(Box::new(Identifier("abc".into())))))
- );
- }
-
- #[test]
- fn test_parse_equality_operators() {
- assert_eq!(
- DispatchContextPredicate::parse("a == b").unwrap(),
- Equal("a".into(), "b".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("c!=d").unwrap(),
- NotEqual("c".into(), "d".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("c == !d")
- .unwrap_err()
- .to_string(),
- "operands must be identifiers"
- );
- }
-
- #[test]
- fn test_parse_boolean_operators() {
- assert_eq!(
- DispatchContextPredicate::parse("a || b").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a || !b && c").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(And(
- Box::new(Not(Box::new(Identifier("b".into())))),
- Box::new(Identifier("c".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
- Or(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(And(
- Box::new(Identifier("c".into())),
- Box::new(Identifier("d".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
- Or(
- Box::new(And(
- Box::new(Equal("a".into(), "b".into())),
- Box::new(Identifier("c".into()))
- )),
- Box::new(And(
- Box::new(Equal("d".into(), "e".into())),
- Box::new(Identifier("f".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a && b && c && d").unwrap(),
- And(
- Box::new(And(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(Identifier("c".into())),
- )),
- Box::new(Identifier("d".into()))
- ),
- );
- }
-
- #[test]
- fn test_parse_parenthesized_expressions() {
- assert_eq!(
- DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
- And(
- Box::new(Identifier("a".into())),
- Box::new(Or(
- Box::new(Equal("b".into(), "c".into())),
- Box::new(NotEqual("d".into(), "e".into())),
- )),
- ),
- );
- assert_eq!(
- DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into())),
- )
- );
- }
-}
@@ -0,0 +1,225 @@
+use crate::{
+ Action, DispatchPhase, FocusId, KeyBindingContext, KeyDownEvent, KeyMatch, Keymap,
+ KeystrokeMatcher, WindowContext,
+};
+use collections::HashMap;
+use parking_lot::Mutex;
+use smallvec::SmallVec;
+use std::{any::Any, sync::Arc};
+
+// trait KeyListener -> FnMut(&E, &mut V, &mut ViewContext<V>)
+type AnyKeyListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
+type AnyActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+pub struct DispatchNodeId(usize);
+
+pub struct DispatchTree {
+ node_stack: Vec<DispatchNodeId>,
+ context_stack: Vec<KeyBindingContext>,
+ nodes: Vec<DispatchNode>,
+ focused: Option<FocusId>,
+ focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
+ keystroke_matchers: HashMap<SmallVec<[KeyBindingContext; 4]>, KeystrokeMatcher>,
+ keymap: Arc<Mutex<Keymap>>,
+}
+
+#[derive(Default)]
+pub struct DispatchNode {
+ key_listeners: SmallVec<[AnyKeyListener; 2]>,
+ action_listeners: SmallVec<[AnyActionListener; 16]>,
+ context: KeyBindingContext,
+ parent: Option<DispatchNodeId>,
+}
+
+impl DispatchTree {
+ pub fn clear(&mut self) {
+ self.node_stack.clear();
+ self.nodes.clear();
+ }
+
+ pub fn push_node(&mut self, context: Option<KeyBindingContext>, old_tree: &mut Self) {
+ let parent = self.node_stack.last().copied();
+ let node_id = DispatchNodeId(self.nodes.len());
+ self.nodes.push(DispatchNode {
+ parent,
+ ..Default::default()
+ });
+ self.node_stack.push(node_id);
+ if let Some(context) = context {
+ self.context_stack.push(context);
+ if let Some((context_stack, matcher)) = old_tree
+ .keystroke_matchers
+ .remove_entry(self.context_stack.as_slice())
+ {
+ self.keystroke_matchers.insert(context_stack, matcher);
+ }
+ }
+ }
+
+ pub fn pop_node(&mut self) -> DispatchNodeId {
+ self.node_stack.pop().unwrap()
+ }
+
+ pub fn on_key_event(&mut self, listener: AnyKeyListener) {
+ self.active_node().key_listeners.push(listener);
+ }
+
+ pub fn on_action(&mut self, listener: AnyActionListener) {
+ self.active_node().action_listeners.push(listener);
+ }
+
+ pub fn make_focusable(&mut self, focus_id: FocusId) {
+ self.focusable_node_ids
+ .insert(focus_id, self.active_node_id());
+ }
+
+ pub fn set_focus(&mut self, focus_id: Option<FocusId>) {
+ self.focused = focus_id;
+ }
+
+ pub fn active_node(&mut self) -> &mut DispatchNode {
+ let node_id = self.active_node_id();
+ &mut self.nodes[node_id.0]
+ }
+
+ fn active_node_id(&self) -> DispatchNodeId {
+ *self.node_stack.last().unwrap()
+ }
+
+ /// Returns the DispatchNodeIds from the root of the tree to the given target node id.
+ fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
+ let mut dispatch_path: SmallVec<[DispatchNodeId; 32]> = SmallVec::new();
+ let mut current_node_id = Some(target);
+ while let Some(node_id) = current_node_id {
+ dispatch_path.push(node_id);
+ current_node_id = self.nodes[node_id.0].parent;
+ }
+ dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node.
+ dispatch_path
+ }
+
+ pub fn dispatch_key(&mut self, event: &dyn Any, cx: &mut WindowContext) {
+ if let Some(focused_node_id) = self
+ .focused
+ .and_then(|focus_id| self.focusable_node_ids.get(&focus_id))
+ .copied()
+ {
+ self.dispatch_key_on_node(focused_node_id, event, cx);
+ }
+ }
+
+ fn dispatch_key_on_node(
+ &mut self,
+ node_id: DispatchNodeId,
+ event: &dyn Any,
+ cx: &mut WindowContext,
+ ) {
+ let dispatch_path = self.dispatch_path(node_id);
+
+ // Capture phase
+ self.context_stack.clear();
+ cx.propagate_event = true;
+ for node_id in &dispatch_path {
+ let node = &self.nodes[node_id.0];
+ if !node.context.is_empty() {
+ self.context_stack.push(node.context.clone());
+ }
+
+ for key_listener in &node.key_listeners {
+ key_listener(event, DispatchPhase::Capture, cx);
+ if !cx.propagate_event {
+ return;
+ }
+ }
+ }
+
+ // Bubble phase
+ for node_id in dispatch_path.iter().rev() {
+ let node = &self.nodes[node_id.0];
+
+ // Handle low level key events
+ for key_listener in &node.key_listeners {
+ key_listener(event, DispatchPhase::Bubble, cx);
+ if !cx.propagate_event {
+ return;
+ }
+ }
+
+ // Match keystrokes
+ if !node.context.is_empty() {
+ if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
+ if !self
+ .keystroke_matchers
+ .contains_key(self.context_stack.as_slice())
+ {
+ let keystroke_contexts = self.context_stack.iter().cloned().collect();
+ self.keystroke_matchers.insert(
+ keystroke_contexts,
+ KeystrokeMatcher::new(self.keymap.clone()),
+ );
+ }
+
+ if let Some(keystroke_matcher) = self
+ .keystroke_matchers
+ .get_mut(self.context_stack.as_slice())
+ {
+ if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(
+ &key_down_event.keystroke,
+ self.context_stack.as_slice(),
+ ) {
+ self.dispatch_action_on_node(*node_id, action, cx);
+ if !cx.propagate_event {
+ return;
+ }
+ }
+ }
+ }
+
+ self.context_stack.pop();
+ }
+ }
+ }
+
+ pub fn dispatch_action(&self, action: Box<dyn Action>, cx: &mut WindowContext) {
+ if let Some(focused_node_id) = self
+ .focused
+ .and_then(|focus_id| self.focusable_node_ids.get(&focus_id))
+ .copied()
+ {
+ self.dispatch_action_on_node(focused_node_id, action, cx);
+ }
+ }
+
+ fn dispatch_action_on_node(
+ &self,
+ node_id: DispatchNodeId,
+ action: Box<dyn Action>,
+ cx: &mut WindowContext,
+ ) {
+ let dispatch_path = self.dispatch_path(node_id);
+
+ // Capture phase
+ for node_id in &dispatch_path {
+ let node = &self.nodes[node_id.0];
+ for action_listener in &node.action_listeners {
+ action_listener(&action, DispatchPhase::Capture, cx);
+ if !cx.propagate_event {
+ return;
+ }
+ }
+ }
+
+ // Bubble phase
+ for node_id in dispatch_path.iter().rev() {
+ let node = &self.nodes[node_id.0];
+ for action_listener in &node.action_listeners {
+ cx.propagate_event = false; // Actions stop propagation by default during the bubble phase
+ action_listener(&action, DispatchPhase::Capture, cx);
+ if !cx.propagate_event {
+ return;
+ }
+ }
+ }
+ }
+}
@@ -3,6 +3,7 @@ mod action;
mod app;
mod assets;
mod color;
+mod dispatch;
mod element;
mod elements;
mod executor;
@@ -1,6 +1,6 @@
use crate::{
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
- Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
+ Component, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyBindingContext, KeyMatch,
Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
StyleRefinement, Task, View, ViewContext,
};
@@ -167,7 +167,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
fn context<C>(mut self, context: C) -> Self
where
Self: Sized,
- C: TryInto<DispatchContext>,
+ C: TryInto<KeyBindingContext>,
C::Error: Debug,
{
self.stateless_interactivity().dispatch_context =
@@ -403,24 +403,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
) -> R {
if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| {
- // In addition to any key down/up listeners registered directly on the element,
- // we also add a key listener to match actions from the keymap.
- stateful.key_listeners.push((
- TypeId::of::<KeyDownEvent>(),
- Box::new(move |_, key_down, context, phase, cx| {
- if phase == DispatchPhase::Bubble {
- let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
- if let KeyMatch::Some(action) =
- cx.match_keystroke(&global_id, &key_down.keystroke, context)
- {
- return Some(action);
- }
- }
-
- None
- }),
- ));
-
cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
})
@@ -808,7 +790,7 @@ impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteractivity<V> {
- pub dispatch_context: DispatchContext,
+ pub dispatch_context: KeyBindingContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
@@ -910,7 +892,7 @@ impl InteractiveElementState {
impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self {
Self {
- dispatch_context: DispatchContext::default(),
+ dispatch_context: KeyBindingContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
@@ -1254,7 +1236,7 @@ pub type KeyListener<V> = Box<
dyn Fn(
&mut V,
&dyn Any,
- &[&DispatchContext],
+ &[&KeyBindingContext],
DispatchPhase,
&mut ViewContext<V>,
) -> Option<Box<dyn Action>>
@@ -1,11 +1,11 @@
-use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
+use crate::{Action, KeyBindingContext, KeyBindingContextPredicate, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
- pub(super) context_predicate: Option<DispatchContextPredicate>,
+ pub(super) context_predicate: Option<KeyBindingContextPredicate>,
}
impl KeyBinding {
@@ -15,7 +15,7 @@ impl KeyBinding {
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
- Some(DispatchContextPredicate::parse(context)?)
+ Some(KeyBindingContextPredicate::parse(context)?)
} else {
None
};
@@ -32,7 +32,7 @@ impl KeyBinding {
})
}
- pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
+ pub fn matches_context(&self, contexts: &[KeyBindingContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
@@ -42,7 +42,7 @@ impl KeyBinding {
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
- contexts: &[&DispatchContext],
+ contexts: &[KeyBindingContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
@@ -61,7 +61,7 @@ impl KeyBinding {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyBindingContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
@@ -0,0 +1,434 @@
+use crate::SharedString;
+use anyhow::{anyhow, Result};
+use smallvec::SmallVec;
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
+pub struct KeyBindingContext(SmallVec<[ContextEntry; 8]>);
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+struct ContextEntry {
+ key: SharedString,
+ value: Option<SharedString>,
+}
+
+impl<'a> TryFrom<&'a str> for KeyBindingContext {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'a str) -> Result<Self> {
+ Self::parse(value)
+ }
+}
+
+impl KeyBindingContext {
+ pub fn parse(source: &str) -> Result<Self> {
+ let mut context = Self::default();
+ let source = skip_whitespace(source);
+ Self::parse_expr(&source, &mut context)?;
+ Ok(context)
+ }
+
+ fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
+ if source.is_empty() {
+ return Ok(());
+ }
+
+ let key = source
+ .chars()
+ .take_while(|c| is_identifier_char(*c))
+ .collect::<String>();
+ source = skip_whitespace(&source[key.len()..]);
+ if let Some(suffix) = source.strip_prefix('=') {
+ source = skip_whitespace(suffix);
+ let value = source
+ .chars()
+ .take_while(|c| is_identifier_char(*c))
+ .collect::<String>();
+ source = skip_whitespace(&source[value.len()..]);
+ context.set(key, value);
+ } else {
+ context.add(key);
+ }
+
+ Self::parse_expr(source, context)
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ pub fn clear(&mut self) {
+ self.0.clear();
+ }
+
+ pub fn extend(&mut self, other: &Self) {
+ for entry in &other.0 {
+ if !self.contains(&entry.key) {
+ self.0.push(entry.clone());
+ }
+ }
+ }
+
+ pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
+ let key = identifier.into();
+
+ if !self.contains(&key) {
+ self.0.push(ContextEntry { key, value: None })
+ }
+ }
+
+ pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
+ let key = key.into();
+ if !self.contains(&key) {
+ self.0.push(ContextEntry {
+ key,
+ value: Some(value.into()),
+ })
+ }
+ }
+
+ pub fn contains(&self, key: &str) -> bool {
+ self.0.iter().any(|entry| entry.key.as_ref() == key)
+ }
+
+ pub fn get(&self, key: &str) -> Option<&SharedString> {
+ self.0
+ .iter()
+ .find(|entry| entry.key.as_ref() == key)?
+ .value
+ .as_ref()
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub enum KeyBindingContextPredicate {
+ Identifier(SharedString),
+ Equal(SharedString, SharedString),
+ NotEqual(SharedString, SharedString),
+ Child(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+ Not(Box<KeyBindingContextPredicate>),
+ And(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+ Or(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+}
+
+impl KeyBindingContextPredicate {
+ pub fn parse(source: &str) -> Result<Self> {
+ let source = skip_whitespace(source);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if let Some(next) = rest.chars().next() {
+ Err(anyhow!("unexpected character {next:?}"))
+ } else {
+ Ok(predicate)
+ }
+ }
+
+ pub fn eval(&self, contexts: &[KeyBindingContext]) -> bool {
+ let Some(context) = contexts.last() else {
+ return false;
+ };
+ match self {
+ Self::Identifier(name) => context.contains(name),
+ Self::Equal(left, right) => context
+ .get(left)
+ .map(|value| value == right)
+ .unwrap_or(false),
+ Self::NotEqual(left, right) => context
+ .get(left)
+ .map(|value| value != right)
+ .unwrap_or(true),
+ Self::Not(pred) => !pred.eval(contexts),
+ Self::Child(parent, child) => {
+ parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
+ }
+ Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
+ Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
+ }
+ }
+
+ fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
+ type Op = fn(
+ KeyBindingContextPredicate,
+ KeyBindingContextPredicate,
+ ) -> Result<KeyBindingContextPredicate>;
+
+ let (mut predicate, rest) = Self::parse_primary(source)?;
+ source = rest;
+
+ 'parse: loop {
+ for (operator, precedence, constructor) in [
+ (">", PRECEDENCE_CHILD, Self::new_child as Op),
+ ("&&", PRECEDENCE_AND, Self::new_and as Op),
+ ("||", PRECEDENCE_OR, Self::new_or as Op),
+ ("==", PRECEDENCE_EQ, Self::new_eq as Op),
+ ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
+ ] {
+ if source.starts_with(operator) && precedence >= min_precedence {
+ source = skip_whitespace(&source[operator.len()..]);
+ let (right, rest) = Self::parse_expr(source, precedence + 1)?;
+ predicate = constructor(predicate, right)?;
+ source = rest;
+ continue 'parse;
+ }
+ }
+ break;
+ }
+
+ Ok((predicate, source))
+ }
+
+ fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
+ let next = source
+ .chars()
+ .next()
+ .ok_or_else(|| anyhow!("unexpected eof"))?;
+ match next {
+ '(' => {
+ source = skip_whitespace(&source[1..]);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if rest.starts_with(')') {
+ source = skip_whitespace(&rest[1..]);
+ Ok((predicate, source))
+ } else {
+ Err(anyhow!("expected a ')'"))
+ }
+ }
+ '!' => {
+ let source = skip_whitespace(&source[1..]);
+ let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
+ Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
+ }
+ _ if is_identifier_char(next) => {
+ let len = source
+ .find(|c: char| !is_identifier_char(c))
+ .unwrap_or(source.len());
+ let (identifier, rest) = source.split_at(len);
+ source = skip_whitespace(rest);
+ Ok((
+ KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
+ source,
+ ))
+ }
+ _ => Err(anyhow!("unexpected character {next:?}")),
+ }
+ }
+
+ fn new_or(self, other: Self) -> Result<Self> {
+ Ok(Self::Or(Box::new(self), Box::new(other)))
+ }
+
+ fn new_and(self, other: Self) -> Result<Self> {
+ Ok(Self::And(Box::new(self), Box::new(other)))
+ }
+
+ fn new_child(self, other: Self) -> Result<Self> {
+ Ok(Self::Child(Box::new(self), Box::new(other)))
+ }
+
+ fn new_eq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::Equal(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+
+ fn new_neq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::NotEqual(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+}
+
+const PRECEDENCE_CHILD: u32 = 1;
+const PRECEDENCE_OR: u32 = 2;
+const PRECEDENCE_AND: u32 = 3;
+const PRECEDENCE_EQ: u32 = 4;
+const PRECEDENCE_NOT: u32 = 5;
+
+fn is_identifier_char(c: char) -> bool {
+ c.is_alphanumeric() || c == '_' || c == '-'
+}
+
+fn skip_whitespace(source: &str) -> &str {
+ let len = source
+ .find(|c: char| !c.is_whitespace())
+ .unwrap_or(source.len());
+ &source[len..]
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate as gpui;
+ use KeyBindingContextPredicate::*;
+
+ #[test]
+ fn test_actions_definition() {
+ {
+ actions!(A, B, C, D, E, F, G);
+ }
+
+ {
+ actions!(
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G, // Don't wrap, test the trailing comma
+ );
+ }
+ }
+
+ #[test]
+ fn test_parse_context() {
+ let mut expected = KeyBindingContext::default();
+ expected.set("foo", "bar");
+ expected.add("baz");
+ assert_eq!(KeyBindingContext::parse("baz foo=bar").unwrap(), expected);
+ assert_eq!(KeyBindingContext::parse("foo = bar baz").unwrap(), expected);
+ assert_eq!(
+ KeyBindingContext::parse(" baz foo = bar baz").unwrap(),
+ expected
+ );
+ assert_eq!(
+ KeyBindingContext::parse(" foo = bar baz").unwrap(),
+ expected
+ );
+ }
+
+ #[test]
+ fn test_parse_identifiers() {
+ // Identifiers
+ assert_eq!(
+ KeyBindingContextPredicate::parse("abc12").unwrap(),
+ Identifier("abc12".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("_1a").unwrap(),
+ Identifier("_1a".into())
+ );
+ }
+
+ #[test]
+ fn test_parse_negations() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("!abc").unwrap(),
+ Not(Box::new(Identifier("abc".into())))
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
+ Not(Box::new(Not(Box::new(Identifier("abc".into())))))
+ );
+ }
+
+ #[test]
+ fn test_parse_equality_operators() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a == b").unwrap(),
+ Equal("a".into(), "b".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("c!=d").unwrap(),
+ NotEqual("c".into(), "d".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("c == !d")
+ .unwrap_err()
+ .to_string(),
+ "operands must be identifiers"
+ );
+ }
+
+ #[test]
+ fn test_parse_boolean_operators() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a || b").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(And(
+ Box::new(Not(Box::new(Identifier("b".into())))),
+ Box::new(Identifier("c".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(And(
+ Box::new(Identifier("c".into())),
+ Box::new(Identifier("d".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Equal("a".into(), "b".into())),
+ Box::new(Identifier("c".into()))
+ )),
+ Box::new(And(
+ Box::new(Equal("d".into(), "e".into())),
+ Box::new(Identifier("f".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
+ And(
+ Box::new(And(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(Identifier("c".into())),
+ )),
+ Box::new(Identifier("d".into()))
+ ),
+ );
+ }
+
+ #[test]
+ fn test_parse_parenthesized_expressions() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
+ And(
+ Box::new(Identifier("a".into())),
+ Box::new(Or(
+ Box::new(Equal("b".into(), "c".into())),
+ Box::new(NotEqual("d".into(), "e".into())),
+ )),
+ ),
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into())),
+ )
+ );
+ }
+}
@@ -1,4 +1,4 @@
-use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
+use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
use collections::HashSet;
use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
@@ -11,7 +11,7 @@ pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
- HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
+ HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion,
}
@@ -1,15 +1,15 @@
-use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
+use crate::{Action, KeyBindingContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
-pub struct KeyMatcher {
+pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
-impl KeyMatcher {
+impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
Self {
@@ -44,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
+ context_stack: &[KeyBindingContext],
) -> KeyMatch {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -86,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyBindingContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
@@ -1,7 +1,9 @@
mod binding;
+mod context;
mod keymap;
mod matcher;
pub use binding::*;
+pub use context::*;
pub use keymap::*;
pub use matcher::*;
@@ -1,15 +1,15 @@
use crate::{
build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
- DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
- FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
- IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
- MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
- PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
- PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
- TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
- WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+ DevicePixels, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent,
+ FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero,
+ KeyBindingContext, KeyListener, KeyMatch, Keystroke, KeystrokeMatcher, LayoutId, Model,
+ ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
+ MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
+ PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
+ RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+ Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
+ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -64,7 +64,7 @@ type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) +
type AnyKeyListener = Box<
dyn Fn(
&dyn Any,
- &[&DispatchContext],
+ &[&KeyBindingContext],
DispatchPhase,
&mut WindowContext,
) -> Option<Box<dyn Action>>
@@ -230,7 +230,7 @@ pub struct Window {
#[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
- key_matchers: HashMap<GlobalElementId, KeyMatcher>,
+ key_matchers: HashMap<GlobalElementId, KeystrokeMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
@@ -337,7 +337,7 @@ pub(crate) enum KeyDispatchStackFrame {
event_type: TypeId,
listener: AnyKeyListener,
},
- Context(DispatchContext),
+ Context(KeyBindingContext),
}
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
@@ -1228,7 +1228,7 @@ impl<'a> WindowContext<'a> {
} else if let Some(any_key_event) = event.keyboard_event() {
let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
let key_event_type = any_key_event.type_id();
- let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
+ let mut context_stack = SmallVec::<[&KeyBindingContext; 16]>::new();
for (ix, frame) in key_dispatch_stack.iter().enumerate() {
match frame {
@@ -1300,7 +1300,7 @@ impl<'a> WindowContext<'a> {
&mut self,
element_id: &GlobalElementId,
keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
+ context_stack: &[KeyBindingContext],
) -> KeyMatch {
let key_match = self
.window
@@ -1621,7 +1621,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
.previous_frame
.key_matchers
.remove(&global_id)
- .unwrap_or_else(|| KeyMatcher::new(keymap)),
+ .unwrap_or_else(|| KeystrokeMatcher::new(keymap)),
);
}
@@ -2120,7 +2120,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
let handle = self.view().downgrade();
let listener = Box::new(
move |event: &dyn Any,
- context_stack: &[&DispatchContext],
+ context_stack: &[&KeyBindingContext],
phase: DispatchPhase,
cx: &mut WindowContext<'_>| {
handle
@@ -2154,7 +2154,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
pub fn with_key_dispatch_context<R>(
&mut self,
- context: DispatchContext,
+ context: KeyBindingContext,
f: impl FnOnce(&mut Self) -> R,
) -> R {
if context.is_empty() {
@@ -37,11 +37,11 @@ use futures::{
};
use gpui::{
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
- AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
- EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
- Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
- Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
- WindowOptions,
+ AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
+ FocusHandle, GlobalPixels, KeyBindingContext, Model, ModelContext, ParentElement, Point,
+ Render, Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled,
+ Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
+ WindowHandle, WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
@@ -3743,8 +3743,8 @@ impl Render for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let mut context = DispatchContext::default();
- context.insert("Workspace");
+ let mut context = KeyBindingContext::default();
+ context.add("Workspace");
cx.with_key_dispatch_context(context, |cx| {
div()
.relative()