Checkpoint
This commit is contained in:
parent
b0acaed02f
commit
8a11053f1f
4 changed files with 104 additions and 27 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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);
|
||||||
})
|
})
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue