1use std::any::{Any, TypeId};
2
3use crate::TypeTag;
4
5pub trait Action: 'static {
6 fn id(&self) -> TypeId;
7 fn namespace(&self) -> &'static str;
8 fn name(&self) -> &'static str;
9 fn as_any(&self) -> &dyn Any;
10 fn type_tag(&self) -> TypeTag;
11 fn boxed_clone(&self) -> Box<dyn Action>;
12 fn eq(&self, other: &dyn Action) -> bool;
13
14 fn qualified_name() -> &'static str
15 where
16 Self: Sized;
17 fn from_json_str(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
18 where
19 Self: Sized;
20}
21
22impl std::fmt::Debug for dyn Action {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("dyn Action")
25 .field("namespace", &self.namespace())
26 .field("name", &self.name())
27 .finish()
28 }
29}
30/// Define a set of unit struct types that all implement the `Action` trait.
31///
32/// The first argument is a namespace that will be associated with each of
33/// the given action types, to ensure that they have globally unique
34/// qualified names for use in keymap files.
35#[macro_export]
36macro_rules! actions {
37 ($namespace:path, [ $($name:ident),* $(,)? ]) => {
38 $(
39 #[derive(Clone, Debug, Default, PartialEq, Eq)]
40 pub struct $name;
41 $crate::__impl_action! {
42 $namespace,
43 $name,
44 fn from_json_str(_: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
45 Ok(Box::new(Self))
46 }
47 }
48 )*
49 };
50}
51
52/// Implement the `Action` trait for a set of existing types.
53///
54/// The first argument is a namespace that will be associated with each of
55/// the given action types, to ensure that they have globally unique
56/// qualified names for use in keymap files.
57#[macro_export]
58macro_rules! impl_actions {
59 ($namespace:path, [ $($name:ident),* $(,)? ]) => {
60 $(
61 $crate::__impl_action! {
62 $namespace,
63 $name,
64 fn from_json_str(json: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
65 Ok(Box::new($crate::serde_json::from_value::<Self>(json)?))
66 }
67 }
68 )*
69 };
70}
71
72#[doc(hidden)]
73#[macro_export]
74macro_rules! __impl_action {
75 ($namespace:path, $name:ident, $from_json_fn:item) => {
76 impl $crate::action::Action for $name {
77 fn namespace(&self) -> &'static str {
78 stringify!($namespace)
79 }
80
81 fn name(&self) -> &'static str {
82 stringify!($name)
83 }
84
85 fn qualified_name() -> &'static str {
86 concat!(
87 stringify!($namespace),
88 "::",
89 stringify!($name),
90 )
91 }
92
93 fn id(&self) -> std::any::TypeId {
94 std::any::TypeId::of::<$name>()
95 }
96
97 fn as_any(&self) -> &dyn std::any::Any {
98 self
99 }
100
101 fn boxed_clone(&self) -> Box<dyn $crate::Action> {
102 Box::new(self.clone())
103 }
104
105 fn eq(&self, other: &dyn $crate::Action) -> bool {
106 if let Some(other) = other.as_any().downcast_ref::<Self>() {
107 self == other
108 } else {
109 false
110 }
111 }
112
113 fn type_tag(&self) -> $crate::TypeTag {
114 $crate::TypeTag::new::<Self>()
115 }
116
117 $from_json_fn
118 }
119 };
120}