Checkpoint

This commit is contained in:
Antonio Scandurra 2023-10-20 11:44:19 +02:00
parent b0acaed02f
commit 8a11053f1f
4 changed files with 104 additions and 27 deletions

View file

@ -15,12 +15,45 @@ pub struct DispatchContext {
map: HashMap<SharedString, 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 { impl DispatchContext {
pub fn new() -> Self { pub fn parse(source: &str) -> Result<Self> {
DispatchContext { let mut context = Self::default();
set: HashSet::default(), let source = skip_whitespace(source);
map: HashMap::default(), 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::<String>();
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::<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 { pub fn is_empty(&self) -> bool {
@ -41,11 +74,11 @@ impl DispatchContext {
} }
} }
pub fn add_identifier<I: Into<SharedString>>(&mut self, identifier: I) { pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
self.set.insert(identifier.into()); self.set.insert(identifier.into());
} }
pub fn add_key<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) { pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
self.map.insert(key.into(), value.into()); self.map.insert(key.into(), value.into());
} }
} }
@ -63,7 +96,7 @@ pub enum DispatchContextPredicate {
impl DispatchContextPredicate { impl DispatchContextPredicate {
pub fn parse(source: &str) -> Result<Self> { pub fn parse(source: &str) -> Result<Self> {
let source = Self::skip_whitespace(source); let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?; let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() { if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}")) Err(anyhow!("unexpected character {next:?}"))
@ -113,7 +146,7 @@ impl DispatchContextPredicate {
("!=", PRECEDENCE_EQ, Self::new_neq as Op), ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
] { ] {
if source.starts_with(operator) && precedence >= min_precedence { 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)?; let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?; predicate = constructor(predicate, right)?;
source = rest; source = rest;
@ -133,17 +166,17 @@ impl DispatchContextPredicate {
.ok_or_else(|| anyhow!("unexpected eof"))?; .ok_or_else(|| anyhow!("unexpected eof"))?;
match next { match next {
'(' => { '(' => {
source = Self::skip_whitespace(&source[1..]); source = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?; let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') { if rest.starts_with(')') {
source = Self::skip_whitespace(&rest[1..]); source = skip_whitespace(&rest[1..]);
Ok((predicate, source)) Ok((predicate, source))
} else { } else {
Err(anyhow!("expected a ')'")) 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)?; let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source)) Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
} }
@ -152,7 +185,7 @@ impl DispatchContextPredicate {
.find(|c: char| !(c.is_alphanumeric() || c == '_')) .find(|c: char| !(c.is_alphanumeric() || c == '_'))
.unwrap_or(source.len()); .unwrap_or(source.len());
let (identifier, rest) = source.split_at(len); let (identifier, rest) = source.split_at(len);
source = Self::skip_whitespace(rest); source = skip_whitespace(rest);
Ok(( Ok((
DispatchContextPredicate::Identifier(identifier.to_string().into()), DispatchContextPredicate::Identifier(identifier.to_string().into()),
source, 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<Self> { fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other))) 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_EQ: u32 = 4;
const PRECEDENCE_NOT: u32 = 5; 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)] #[cfg(test)]
mod tests { 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] #[test]
fn test_parse_identifiers() { fn test_parse_identifiers() {

View file

@ -595,7 +595,7 @@ pub struct InteractiveElementState {
impl<V> Default for StatelessInteraction<V> { impl<V> Default for StatelessInteraction<V> {
fn default() -> Self { fn default() -> Self {
Self { Self {
dispatch_context: DispatchContext::new(), dispatch_context: DispatchContext::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(),

View file

@ -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::<Self>().is_some()
}
fn boxed_clone(&self) -> Box<dyn Action> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct FocusStory { pub struct FocusStory {
text: View<()>, text: View<()>,
} }
@ -46,8 +63,9 @@ pub struct FocusStory {
impl FocusStory { impl FocusStory {
pub fn view(cx: &mut WindowContext) -> View<()> { pub fn view(cx: &mut WindowContext) -> View<()> {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("cmd-a", ActionA, None), KeyBinding::new("cmd-a", ActionA, Some("parent")),
KeyBinding::new("cmd-b", ActionB, None), KeyBinding::new("cmd-a", ActionB, Some("child-1")),
KeyBinding::new("cmd-c", ActionC, None),
]); ]);
let theme = rose_pine(); let theme = rose_pine();
@ -63,6 +81,7 @@ impl FocusStory {
let child_2 = cx.focus_handle(); let child_2 = cx.focus_handle();
view(cx.entity(|cx| ()), move |_, cx| { view(cx.entity(|cx| ()), move |_, cx| {
div() div()
.context("parent")
.on_action(|_, action: &ActionA, phase, cx| { .on_action(|_, action: &ActionA, phase, cx| {
println!("Action A dispatched on parent during {:?}", phase); println!("Action A dispatched on parent during {:?}", phase);
}) })
@ -86,7 +105,8 @@ impl FocusStory {
.focus_in(|style| style.bg(color_3)) .focus_in(|style| style.bg(color_3))
.child( .child(
div() div()
.id("child 1") .id("child-1")
.context("child-1")
.on_action(|_, action: &ActionA, phase, cx| { .on_action(|_, action: &ActionA, phase, cx| {
println!("Action A dispatched on child 1 during {:?}", phase); println!("Action A dispatched on child 1 during {:?}", phase);
}) })
@ -110,7 +130,8 @@ impl FocusStory {
) )
.child( .child(
div() div()
.id("child 2") .id("child-2")
.context("child-2")
.on_action(|_, action: &ActionB, phase, cx| { .on_action(|_, action: &ActionB, phase, cx| {
println!("Action B dispatched on child 2 during {:?}", phase); println!("Action B dispatched on child 2 during {:?}", phase);
}) })

View file

@ -1,16 +1,24 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
fmt::{self, Debug}, fmt::{self, Debug},
hash::{Hash, Hasher},
sync::Arc, sync::Arc,
}; };
#[derive(PartialEq, Eq)]
pub enum ArcCow<'a, T: ?Sized> { pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T), Borrowed(&'a T),
Owned(Arc<T>), Owned(Arc<T>),
} }
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> { impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {