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