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