Add a DispatchTree which will replace the existing key dispatch strategy

Instead of freezing a stack, we will record the entire dispatch tree so we can
change focus.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2023-11-10 11:56:14 -07:00
parent 1c02690199
commit 74a0d9316a
14 changed files with 724 additions and 491 deletions

View file

@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
use gpui::{ use gpui::{
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext, action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render, InputHandler, KeyBindingContext, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext, ViewContext, VisualContext, WeakView, WindowContext,
}; };
@ -646,7 +646,7 @@ pub struct Editor {
collapse_matches: bool, collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>, autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakView<Workspace>, i64)>, workspace: Option<(WeakView<Workspace>, i64)>,
keymap_context_layers: BTreeMap<TypeId, DispatchContext>, keymap_context_layers: BTreeMap<TypeId, KeyBindingContext>,
input_enabled: bool, input_enabled: bool,
read_only: bool, read_only: bool,
leader_peer_id: Option<PeerId>, leader_peer_id: Option<PeerId>,
@ -1980,9 +1980,9 @@ impl Editor {
this this
} }
fn dispatch_context(&self, cx: &AppContext) -> DispatchContext { fn dispatch_context(&self, cx: &AppContext) -> KeyBindingContext {
let mut dispatch_context = DispatchContext::default(); let mut dispatch_context = KeyBindingContext::default();
dispatch_context.insert("Editor"); dispatch_context.add("Editor");
let mode = match self.mode { let mode = match self.mode {
EditorMode::SingleLine => "single_line", EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height", EditorMode::AutoHeight { .. } => "auto_height",
@ -1990,17 +1990,17 @@ impl Editor {
}; };
dispatch_context.set("mode", mode); dispatch_context.set("mode", mode);
if self.pending_rename.is_some() { if self.pending_rename.is_some() {
dispatch_context.insert("renaming"); dispatch_context.add("renaming");
} }
if self.context_menu_visible() { if self.context_menu_visible() {
match self.context_menu.read().as_ref() { match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => { Some(ContextMenu::Completions(_)) => {
dispatch_context.insert("menu"); dispatch_context.add("menu");
dispatch_context.insert("showing_completions") dispatch_context.add("showing_completions")
} }
Some(ContextMenu::CodeActions(_)) => { Some(ContextMenu::CodeActions(_)) => {
dispatch_context.insert("menu"); dispatch_context.add("menu");
dispatch_context.insert("showing_code_actions") dispatch_context.add("showing_code_actions")
} }
None => {} None => {}
} }

View file

@ -16,11 +16,12 @@ use anyhow::Result;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
use gpui::{ use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, KeyBindingContext, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent,
Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
WrappedLineLayout,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting; 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_prev),
build_action_listener(Editor::context_menu_next), build_action_listener(Editor::context_menu_next),
build_action_listener(Editor::context_menu_last), 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( listener: impl Fn(
&mut Editor, &mut Editor,
&T, &T,
&[&DispatchContext], &[&KeyBindingContext],
DispatchPhase, DispatchPhase,
&mut ViewContext<Editor>, &mut ViewContext<Editor>,
) -> Option<Box<dyn Action>> ) -> Option<Box<dyn Action>>

View file

@ -0,0 +1 @@

View file

@ -1,6 +1,6 @@
use crate::SharedString; use crate::SharedString;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet}; use collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize; use serde::Deserialize;
@ -186,401 +186,3 @@ macro_rules! actions {
actions!($($rest)*); 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())),
)
);
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -3,6 +3,7 @@ mod action;
mod app; mod app;
mod assets; mod assets;
mod color; mod color;
mod dispatch;
mod element; mod element;
mod elements; mod elements;
mod executor; mod executor;

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, 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, Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
StyleRefinement, Task, View, ViewContext, StyleRefinement, Task, View, ViewContext,
}; };
@ -167,7 +167,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
fn context<C>(mut self, context: C) -> Self fn context<C>(mut self, context: C) -> Self
where where
Self: Sized, Self: Sized,
C: TryInto<DispatchContext>, C: TryInto<KeyBindingContext>,
C::Error: Debug, C::Error: Debug,
{ {
self.stateless_interactivity().dispatch_context = self.stateless_interactivity().dispatch_context =
@ -403,24 +403,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
) -> R { ) -> R {
if let Some(stateful) = self.as_stateful_mut() { if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| { 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_dispatch_context(stateful.dispatch_context.clone(), |cx| {
cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f) 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; type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteractivity<V> { pub struct StatelessInteractivity<V> {
pub dispatch_context: DispatchContext, pub dispatch_context: KeyBindingContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>, pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>, pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
@ -910,7 +892,7 @@ impl InteractiveElementState {
impl<V> Default for StatelessInteractivity<V> { impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self { fn default() -> Self {
Self { Self {
dispatch_context: DispatchContext::default(), dispatch_context: KeyBindingContext::default(),
mouse_down_listeners: SmallVec::new(), mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(), mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(),
@ -1254,7 +1236,7 @@ pub type KeyListener<V> = Box<
dyn Fn( dyn Fn(
&mut V, &mut V,
&dyn Any, &dyn Any,
&[&DispatchContext], &[&KeyBindingContext],
DispatchPhase, DispatchPhase,
&mut ViewContext<V>, &mut ViewContext<V>,
) -> Option<Box<dyn Action>> ) -> Option<Box<dyn Action>>

View file

@ -1,11 +1,11 @@
use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke}; use crate::{Action, KeyBindingContext, KeyBindingContextPredicate, KeyMatch, Keystroke};
use anyhow::Result; use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
pub struct KeyBinding { pub struct KeyBinding {
action: Box<dyn Action>, action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>, pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<DispatchContextPredicate>, pub(super) context_predicate: Option<KeyBindingContextPredicate>,
} }
impl KeyBinding { impl KeyBinding {
@ -15,7 +15,7 @@ impl KeyBinding {
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> { pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context { let context = if let Some(context) = context {
Some(DispatchContextPredicate::parse(context)?) Some(KeyBindingContextPredicate::parse(context)?)
} else { } else {
None 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 self.context_predicate
.as_ref() .as_ref()
.map(|predicate| predicate.eval(contexts)) .map(|predicate| predicate.eval(contexts))
@ -42,7 +42,7 @@ impl KeyBinding {
pub fn match_keystrokes( pub fn match_keystrokes(
&self, &self,
pending_keystrokes: &[Keystroke], pending_keystrokes: &[Keystroke],
contexts: &[&DispatchContext], contexts: &[KeyBindingContext],
) -> KeyMatch { ) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes) if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts) && self.matches_context(contexts)
@ -61,7 +61,7 @@ impl KeyBinding {
pub fn keystrokes_for_action( pub fn keystrokes_for_action(
&self, &self,
action: &dyn Action, action: &dyn Action,
contexts: &[&DispatchContext], contexts: &[KeyBindingContext],
) -> Option<SmallVec<[Keystroke; 2]>> { ) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) { if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone()) Some(self.keystrokes.clone())

View file

@ -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())),
)
);
}
}

View file

@ -1,4 +1,4 @@
use crate::{DispatchContextPredicate, KeyBinding, Keystroke}; use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
use collections::HashSet; use collections::HashSet;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap}; use std::{any::TypeId, collections::HashMap};
@ -11,7 +11,7 @@ pub struct Keymap {
bindings: Vec<KeyBinding>, bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>, binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes: disabled_keystrokes:
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>, HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion, version: KeymapVersion,
} }

View file

@ -1,15 +1,15 @@
use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke}; use crate::{Action, KeyBindingContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex; use parking_lot::Mutex;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::sync::Arc; use std::sync::Arc;
pub struct KeyMatcher { pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>, pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>, keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion, keymap_version: KeymapVersion,
} }
impl KeyMatcher { impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self { pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version(); let keymap_version = keymap.lock().version();
Self { Self {
@ -44,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke( pub fn match_keystroke(
&mut self, &mut self,
keystroke: &Keystroke, keystroke: &Keystroke,
context_stack: &[&DispatchContext], context_stack: &[KeyBindingContext],
) -> KeyMatch { ) -> KeyMatch {
let keymap = self.keymap.lock(); let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke. // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@ -86,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action( pub fn keystrokes_for_action(
&self, &self,
action: &dyn Action, action: &dyn Action,
contexts: &[&DispatchContext], contexts: &[KeyBindingContext],
) -> Option<SmallVec<[Keystroke; 2]>> { ) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap self.keymap
.lock() .lock()

View file

@ -1,7 +1,9 @@
mod binding; mod binding;
mod context;
mod keymap; mod keymap;
mod matcher; mod matcher;
pub use binding::*; pub use binding::*;
pub use context::*;
pub use keymap::*; pub use keymap::*;
pub use matcher::*; pub use matcher::*;

View file

@ -1,15 +1,15 @@
use crate::{ use crate::{
build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, DevicePixels, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent,
FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero,
IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, KeyBindingContext, KeyListener, KeyMatch, Keystroke, KeystrokeMatcher, LayoutId, Model,
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
@ -64,7 +64,7 @@ type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) +
type AnyKeyListener = Box< type AnyKeyListener = Box<
dyn Fn( dyn Fn(
&dyn Any, &dyn Any,
&[&DispatchContext], &[&KeyBindingContext],
DispatchPhase, DispatchPhase,
&mut WindowContext, &mut WindowContext,
) -> Option<Box<dyn Action>> ) -> Option<Box<dyn Action>>
@ -230,7 +230,7 @@ pub struct Window {
#[derive(Default)] #[derive(Default)]
pub(crate) struct Frame { pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>, element_states: HashMap<GlobalElementId, AnyBox>,
key_matchers: HashMap<GlobalElementId, KeyMatcher>, key_matchers: HashMap<GlobalElementId, KeystrokeMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>, mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
pub(crate) focus_listeners: Vec<AnyFocusListener>, pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>, pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
@ -337,7 +337,7 @@ pub(crate) enum KeyDispatchStackFrame {
event_type: TypeId, event_type: TypeId,
listener: AnyKeyListener, listener: AnyKeyListener,
}, },
Context(DispatchContext), Context(KeyBindingContext),
} }
/// Indicates which region of the window is visible. Content falling outside of this mask will not be /// 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() { } 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_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
let key_event_type = any_key_event.type_id(); 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() { for (ix, frame) in key_dispatch_stack.iter().enumerate() {
match frame { match frame {
@ -1300,7 +1300,7 @@ impl<'a> WindowContext<'a> {
&mut self, &mut self,
element_id: &GlobalElementId, element_id: &GlobalElementId,
keystroke: &Keystroke, keystroke: &Keystroke,
context_stack: &[&DispatchContext], context_stack: &[KeyBindingContext],
) -> KeyMatch { ) -> KeyMatch {
let key_match = self let key_match = self
.window .window
@ -1621,7 +1621,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
.previous_frame .previous_frame
.key_matchers .key_matchers
.remove(&global_id) .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 handle = self.view().downgrade();
let listener = Box::new( let listener = Box::new(
move |event: &dyn Any, move |event: &dyn Any,
context_stack: &[&DispatchContext], context_stack: &[&KeyBindingContext],
phase: DispatchPhase, phase: DispatchPhase,
cx: &mut WindowContext<'_>| { cx: &mut WindowContext<'_>| {
handle handle
@ -2154,7 +2154,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
pub fn with_key_dispatch_context<R>( pub fn with_key_dispatch_context<R>(
&mut self, &mut self,
context: DispatchContext, context: KeyBindingContext,
f: impl FnOnce(&mut Self) -> R, f: impl FnOnce(&mut Self) -> R,
) -> R { ) -> R {
if context.is_empty() { if context.is_empty() {

View file

@ -37,11 +37,11 @@ use futures::{
}; };
use gpui::{ use gpui::{
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render, FocusHandle, GlobalPixels, KeyBindingContext, Model, ModelContext, ParentElement, Point,
Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription, Render, Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled,
Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowOptions, WindowHandle, WindowOptions,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools; use itertools::Itertools;
@ -3743,8 +3743,8 @@ impl Render for Workspace {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let mut context = DispatchContext::default(); let mut context = KeyBindingContext::default();
context.insert("Workspace"); context.add("Workspace");
cx.with_key_dispatch_context(context, |cx| { cx.with_key_dispatch_context(context, |cx| {
div() div()
.relative() .relative()