zed/crates/gpui2/src/action.rs

479 lines
14 KiB
Rust
Raw Normal View History

2023-10-19 17:26:52 +00:00
use crate::SharedString;
2023-10-21 15:52:47 +00:00
use anyhow::{anyhow, Context, Result};
2023-10-19 17:03:10 +00:00
use collections::{HashMap, HashSet};
2023-10-21 15:52:47 +00:00
use serde::Deserialize;
use std::any::{type_name, Any};
2023-10-19 17:26:52 +00:00
pub trait Action: std::fmt::Debug + 'static {
2023-10-21 15:52:47 +00:00
fn qualified_name() -> SharedString
where
Self: Sized;
fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
where
Self: Sized;
2023-10-20 13:13:53 +00:00
fn partial_eq(&self, action: &dyn Action) -> bool;
2023-10-19 17:26:52 +00:00
fn boxed_clone(&self) -> Box<dyn Action>;
fn as_any(&self) -> &dyn Any;
}
2023-10-19 17:03:10 +00:00
// actions defines structs that can be used as actions.
#[macro_export]
macro_rules! actions {
() => {};
( $name:ident ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
struct $name;
};
( $name:ident { $($token:tt)* } ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
struct $name { $($token)* }
};
( $name:ident, $($rest:tt)* ) => {
actions!($name);
actions!($($rest)*);
};
( $name:ident { $($token:tt)* }, $($rest:tt)* ) => {
actions!($name { $($token)* });
actions!($($rest)*);
};
}
2023-10-21 15:52:47 +00:00
impl<A> Action for A
2023-10-20 13:13:53 +00:00
where
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
2023-10-20 13:13:53 +00:00
{
2023-10-21 15:52:47 +00:00
fn qualified_name() -> SharedString {
// todo!() remove the 2 replacement when migration is done
type_name::<A>().replace("2::", "::").into()
2023-10-21 15:52:47 +00:00
}
fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
where
Self: Sized,
{
let action = if let Some(params) = params {
serde_json::from_value(params).context("failed to deserialize action")?
} else {
Self::default()
};
Ok(Box::new(action))
}
2023-10-20 13:13:53 +00:00
fn partial_eq(&self, action: &dyn Action) -> bool {
action
.as_any()
.downcast_ref::<Self>()
.map_or(false, |a| self == a)
}
fn boxed_clone(&self) -> Box<dyn Action> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
2023-10-19 17:03:10 +00:00
#[derive(Clone, Debug, Default, Eq, PartialEq)]
2023-10-19 19:28:20 +00:00
pub struct DispatchContext {
2023-10-19 17:26:52 +00:00
set: HashSet<SharedString>,
map: HashMap<SharedString, SharedString>,
2023-10-19 17:03:10 +00:00
}
2023-10-20 09:44:19 +00:00
impl<'a> TryFrom<&'a str> for DispatchContext {
type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result<Self> {
Self::parse(value)
}
}
2023-10-19 19:28:20 +00:00
impl DispatchContext {
2023-10-20 09:44:19 +00:00
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()
2023-10-20 10:12:06 +00:00
.take_while(|c| is_identifier_char(*c))
2023-10-20 09:44:19 +00:00
.collect::<String>();
source = skip_whitespace(&source[key.len()..]);
if let Some(suffix) = source.strip_prefix('=') {
source = skip_whitespace(suffix);
let value = source
.chars()
2023-10-20 10:12:06 +00:00
.take_while(|c| is_identifier_char(*c))
2023-10-20 09:44:19 +00:00
.collect::<String>();
source = skip_whitespace(&source[value.len()..]);
context.set(key, value);
} else {
context.insert(key);
2023-10-19 17:03:10 +00:00
}
2023-10-20 09:44:19 +00:00
Self::parse_expr(source, context)
2023-10-19 17:03:10 +00:00
}
2023-10-20 09:08:24 +00:00
pub fn is_empty(&self) -> bool {
self.set.is_empty() && self.map.is_empty()
}
2023-10-19 17:03:10 +00:00
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());
}
}
2023-10-20 09:44:19 +00:00
pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
2023-10-19 17:03:10 +00:00
self.set.insert(identifier.into());
}
2023-10-20 09:44:19 +00:00
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
2023-10-19 17:03:10 +00:00
self.map.insert(key.into(), value.into());
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
2023-10-20 09:08:24 +00:00
pub enum DispatchContextPredicate {
2023-10-19 17:26:52 +00:00
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
2023-10-20 09:08:24 +00:00
Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Not(Box<DispatchContextPredicate>),
And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
2023-10-19 17:03:10 +00:00
}
2023-10-20 09:08:24 +00:00
impl DispatchContextPredicate {
2023-10-19 17:03:10 +00:00
pub fn parse(source: &str) -> Result<Self> {
2023-10-20 09:44:19 +00:00
let source = skip_whitespace(source);
2023-10-19 17:03:10 +00:00
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
} else {
Ok(predicate)
}
}
2023-10-19 19:28:20 +00:00
pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
2023-10-20 10:12:06 +00:00
let Some(context) = contexts.last() else {
2023-10-19 17:03:10 +00:00
return false;
};
match self {
2023-10-21 15:52:47 +00:00
Self::Identifier(name) => context.set.contains(name),
2023-10-19 17:03:10 +00:00
Self::Equal(left, right) => context
.map
2023-10-21 15:52:47 +00:00
.get(left)
2023-10-19 17:03:10 +00:00
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
2023-10-21 15:52:47 +00:00
.get(left)
2023-10-19 17:03:10 +00:00
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
2023-10-20 10:12:06 +00:00
Self::Child(parent, child) => {
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
}
2023-10-19 17:03:10 +00:00
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)> {
2023-10-20 09:08:24 +00:00
type Op = fn(
DispatchContextPredicate,
DispatchContextPredicate,
) -> Result<DispatchContextPredicate>;
2023-10-19 17:03:10 +00:00
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 {
2023-10-20 09:44:19 +00:00
source = skip_whitespace(&source[operator.len()..]);
2023-10-19 17:03:10 +00:00
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 {
'(' => {
2023-10-20 09:44:19 +00:00
source = skip_whitespace(&source[1..]);
2023-10-19 17:03:10 +00:00
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
2023-10-20 09:44:19 +00:00
source = skip_whitespace(&rest[1..]);
2023-10-19 17:03:10 +00:00
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
2023-10-20 09:44:19 +00:00
let source = skip_whitespace(&source[1..]);
2023-10-19 17:03:10 +00:00
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
2023-10-20 09:08:24 +00:00
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
2023-10-19 17:03:10 +00:00
}
2023-10-20 10:12:06 +00:00
_ if is_identifier_char(next) => {
2023-10-19 17:03:10 +00:00
let len = source
2023-10-20 10:12:06 +00:00
.find(|c: char| !is_identifier_char(c))
2023-10-19 17:03:10 +00:00
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
2023-10-20 09:44:19 +00:00
source = skip_whitespace(rest);
2023-10-19 17:03:10 +00:00
Ok((
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::Identifier(identifier.to_string().into()),
2023-10-19 17:03:10 +00:00
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;
2023-10-20 10:12:06 +00:00
fn is_identifier_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
2023-10-20 09:44:19 +00:00
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
2023-10-19 17:03:10 +00:00
#[cfg(test)]
mod tests {
2023-10-20 09:44:19 +00:00
use super::*;
use DispatchContextPredicate::*;
#[test]
fn test_actions_definition() {
{
actions!(A, B { field: i32 }, C, D, E, F {}, G);
}
{
actions!(
A,
B { field: i32 },
C,
D,
E,
F {},
G, // Don't wrap, test the trailing comma
);
}
}
2023-10-20 09:44:19 +00:00
#[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);
}
2023-10-19 17:03:10 +00:00
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("abc12").unwrap(),
2023-10-19 17:03:10 +00:00
Identifier("abc12".into())
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("_1a").unwrap(),
2023-10-19 17:03:10 +00:00
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("!abc").unwrap(),
2023-10-19 17:03:10 +00:00
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse(" ! ! abc").unwrap(),
2023-10-19 17:03:10 +00:00
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a == b").unwrap(),
2023-10-19 17:03:10 +00:00
Equal("a".into(), "b".into())
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("c!=d").unwrap(),
2023-10-19 17:03:10 +00:00
NotEqual("c".into(), "d".into())
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("c == !d")
2023-10-19 17:03:10 +00:00
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a || b").unwrap(),
2023-10-19 17:03:10 +00:00
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a || !b && c").unwrap(),
2023-10-19 17:03:10 +00:00
Or(
Box::new(Identifier("a".into())),
Box::new(And(
Box::new(Not(Box::new(Identifier("b".into())))),
Box::new(Identifier("c".into()))
))
)
);
assert_eq!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
2023-10-19 17:03:10 +00:00
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!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
2023-10-19 17:03:10 +00:00
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!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a && b && c && d").unwrap(),
2023-10-19 17:03:10 +00:00
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!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
2023-10-19 17:03:10 +00:00
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!(
2023-10-20 09:08:24 +00:00
DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
2023-10-19 17:03:10 +00:00
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}