From 8a11053f1f1563220ac34ef0aa8ce4a2f8efbf28 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Oct 2023 11:44:19 +0200 Subject: [PATCH] Checkpoint --- crates/gpui3/src/action.rs | 88 ++++++++++++++++++++------ crates/gpui3/src/interactive.rs | 2 +- crates/storybook2/src/stories/focus.rs | 29 +++++++-- crates/util/src/arc_cow.rs | 12 +++- 4 files changed, 104 insertions(+), 27 deletions(-) diff --git a/crates/gpui3/src/action.rs b/crates/gpui3/src/action.rs index eb73de00de..0c4a45ddfd 100644 --- a/crates/gpui3/src/action.rs +++ b/crates/gpui3/src/action.rs @@ -15,12 +15,45 @@ pub struct DispatchContext { map: HashMap, } +impl<'a> TryFrom<&'a str> for DispatchContext { + type Error = anyhow::Error; + + fn try_from(value: &'a str) -> Result { + Self::parse(value) + } +} + impl DispatchContext { - pub fn new() -> Self { - DispatchContext { - set: HashSet::default(), - map: HashMap::default(), + pub fn parse(source: &str) -> Result { + 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(|ch| ch.is_alphanumeric()) + .collect::(); + source = skip_whitespace(&source[key.len()..]); + if let Some(suffix) = source.strip_prefix('=') { + source = skip_whitespace(suffix); + let value = source + .chars() + .take_while(|ch| ch.is_alphanumeric()) + .collect::(); + 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 { @@ -41,11 +74,11 @@ impl DispatchContext { } } - pub fn add_identifier>(&mut self, identifier: I) { + pub fn insert>(&mut self, identifier: I) { self.set.insert(identifier.into()); } - pub fn add_key, S2: Into>(&mut self, key: S1, value: S2) { + pub fn set, S2: Into>(&mut self, key: S1, value: S2) { self.map.insert(key.into(), value.into()); } } @@ -63,7 +96,7 @@ pub enum DispatchContextPredicate { impl DispatchContextPredicate { pub fn parse(source: &str) -> Result { - let source = Self::skip_whitespace(source); + 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:?}")) @@ -113,7 +146,7 @@ impl DispatchContextPredicate { ("!=", PRECEDENCE_EQ, Self::new_neq as Op), ] { if source.starts_with(operator) && precedence >= min_precedence { - source = Self::skip_whitespace(&source[operator.len()..]); + source = skip_whitespace(&source[operator.len()..]); let (right, rest) = Self::parse_expr(source, precedence + 1)?; predicate = constructor(predicate, right)?; source = rest; @@ -133,17 +166,17 @@ impl DispatchContextPredicate { .ok_or_else(|| anyhow!("unexpected eof"))?; match next { '(' => { - source = Self::skip_whitespace(&source[1..]); + source = skip_whitespace(&source[1..]); let (predicate, rest) = Self::parse_expr(source, 0)?; if rest.starts_with(')') { - source = Self::skip_whitespace(&rest[1..]); + source = skip_whitespace(&rest[1..]); Ok((predicate, source)) } else { Err(anyhow!("expected a ')'")) } } '!' => { - let source = Self::skip_whitespace(&source[1..]); + let source = skip_whitespace(&source[1..]); let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?; Ok((DispatchContextPredicate::Not(Box::new(predicate)), source)) } @@ -152,7 +185,7 @@ impl DispatchContextPredicate { .find(|c: char| !(c.is_alphanumeric() || c == '_')) .unwrap_or(source.len()); let (identifier, rest) = source.split_at(len); - source = Self::skip_whitespace(rest); + source = skip_whitespace(rest); Ok(( DispatchContextPredicate::Identifier(identifier.to_string().into()), source, @@ -162,13 +195,6 @@ impl DispatchContextPredicate { } } - fn skip_whitespace(source: &str) -> &str { - let len = source - .find(|c: char| !c.is_whitespace()) - .unwrap_or(source.len()); - &source[len..] - } - fn new_or(self, other: Self) -> Result { Ok(Self::Or(Box::new(self), Box::new(other))) } @@ -204,9 +230,31 @@ const PRECEDENCE_AND: u32 = 3; const PRECEDENCE_EQ: u32 = 4; const PRECEDENCE_NOT: u32 = 5; +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::DispatchContextPredicate::{self, *}; + use super::*; + use DispatchContextPredicate::*; + + #[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() { diff --git a/crates/gpui3/src/interactive.rs b/crates/gpui3/src/interactive.rs index ee8c69c911..4ad29f818f 100644 --- a/crates/gpui3/src/interactive.rs +++ b/crates/gpui3/src/interactive.rs @@ -595,7 +595,7 @@ pub struct InteractiveElementState { impl Default for StatelessInteraction { fn default() -> Self { Self { - dispatch_context: DispatchContext::new(), + dispatch_context: DispatchContext::default(), mouse_down_listeners: SmallVec::new(), mouse_up_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(), diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 48b5dbd54f..8fa096e131 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -39,6 +39,23 @@ impl Action for ActionB { } } +#[derive(Clone)] +struct ActionC; + +impl Action for ActionC { + fn eq(&self, action: &dyn Action) -> bool { + action.as_any().downcast_ref::().is_some() + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + pub struct FocusStory { text: View<()>, } @@ -46,8 +63,9 @@ pub struct FocusStory { impl FocusStory { pub fn view(cx: &mut WindowContext) -> View<()> { cx.bind_keys([ - KeyBinding::new("cmd-a", ActionA, None), - KeyBinding::new("cmd-b", ActionB, None), + KeyBinding::new("cmd-a", ActionA, Some("parent")), + KeyBinding::new("cmd-a", ActionB, Some("child-1")), + KeyBinding::new("cmd-c", ActionC, None), ]); let theme = rose_pine(); @@ -63,6 +81,7 @@ impl FocusStory { let child_2 = cx.focus_handle(); view(cx.entity(|cx| ()), move |_, cx| { div() + .context("parent") .on_action(|_, action: &ActionA, phase, cx| { println!("Action A dispatched on parent during {:?}", phase); }) @@ -86,7 +105,8 @@ impl FocusStory { .focus_in(|style| style.bg(color_3)) .child( div() - .id("child 1") + .id("child-1") + .context("child-1") .on_action(|_, action: &ActionA, phase, cx| { println!("Action A dispatched on child 1 during {:?}", phase); }) @@ -110,7 +130,8 @@ impl FocusStory { ) .child( div() - .id("child 2") + .id("child-2") + .context("child-2") .on_action(|_, action: &ActionB, phase, cx| { println!("Action B dispatched on child 2 during {:?}", phase); }) diff --git a/crates/util/src/arc_cow.rs b/crates/util/src/arc_cow.rs index 2865130150..86b998ff06 100644 --- a/crates/util/src/arc_cow.rs +++ b/crates/util/src/arc_cow.rs @@ -1,16 +1,24 @@ use std::{ borrow::Cow, fmt::{self, Debug}, + hash::{Hash, Hasher}, sync::Arc, }; -#[derive(PartialEq, Eq)] pub enum ArcCow<'a, T: ?Sized> { Borrowed(&'a T), Owned(Arc), } -use std::hash::{Hash, Hasher}; +impl<'a, T: ?Sized + PartialEq> PartialEq for ArcCow<'a, T> { + fn eq(&self, other: &Self) -> bool { + let a = self.as_ref(); + let b = other.as_ref(); + a == b + } +} + +impl<'a, T: ?Sized + Eq> Eq for ArcCow<'a, T> {} impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> { fn hash(&self, state: &mut H) {