covey_plugin/lib.rs
1pub mod manifest;
2pub mod rank;
3
4mod list;
5use std::{
6 path::PathBuf,
7 sync::{LazyLock, OnceLock},
8};
9
10pub use list::{Icon, List, ListItem, ListStyle};
11mod action;
12pub use action::{Action, Actions};
13mod input;
14pub use input::{Input, SelectionRange};
15mod plugin;
16pub use plugin::Plugin;
17mod server;
18pub use server::run_server;
19mod menu;
20pub use menu::Menu;
21pub mod spawn;
22mod store;
23
24#[allow(clippy::pedantic)]
25mod proto {
26 tonic::include_proto!("plugin");
27}
28
29pub use anyhow::{self, Result};
30
31/// ID of this plugin.
32///
33/// This should be set to `env!("CARGO_PKG_NAME")` of the plugin, by
34/// inputting string to [`main`].
35pub static PLUGIN_ID: OnceLock<&'static str> = OnceLock::new();
36
37/// Assigned directory of this plugin, where extra data can be stored.
38///
39/// This is <data-dir>/covey/plugins/<plugin-id>/. The directory should already
40/// contain this plugin's binary (with the name of <plugin-id>) and
41/// a `manifest.toml`.
42///
43/// [`covey_plugin`](crate) will also add an `activations.json` file. See
44/// the [`rank`] module for more details.
45///
46/// This depends on the [`PLUGIN_ID`] static being set before first being called.
47pub fn plugin_data_dir() -> &'static PathBuf {
48 static DIR: LazyLock<PathBuf> = LazyLock::new(|| {
49 dirs::data_dir()
50 .expect("data dir should exist")
51 .join("covey")
52 .join("plugins")
53 .join(
54 PLUGIN_ID
55 .get()
56 .expect("plugin id should be initialised in main"),
57 )
58 });
59 &*DIR
60}
61
62/// Clones variables into an async closure (by calling [`ToOwned::to_owned`]).
63///
64/// The closure will automatically be turned into a
65/// `move || async move {...}` closure. Closure arguments are supported.
66///
67/// All variables will be cloned immediately for the closure to own
68/// the variable. The variable will also be cloned every time the
69/// closure is called, so that the async body can also own the variables.
70///
71/// # Examples
72/// Expansion:
73/// ```
74/// # use covey_plugin::clone_async;
75/// let thing = String::from("important info");
76/// let foo = String::from("bar");
77/// clone_async!(thing, foo, || {
78/// println!("some {thing} from {foo}");
79/// });
80/// // Expands to:
81/// // let thing = thing.to_owned();
82/// // let foo = foo.to_owned();
83/// // move || {
84/// // let thing = thing.to_owned();
85/// // let foo = foo.to_owned();
86/// // async move {
87/// // println!("some {thing} from {foo}");
88/// // }
89/// // }
90/// ```
91///
92/// This will most often be used in the context of adding callbacks to
93/// list items.
94/// ```ignore
95/// # use covey_plugin::{clone_async, ListItem};
96/// let thing = String::from("important info");
97/// let foo = String::from("bar");
98/// ListItem::new("title")
99/// .on_activate(clone_async!(thing, || {
100/// println!("some {thing}");
101/// todo!()
102/// }))
103/// .on_hotkey_activate(clone_async!(foo, |hotkey| {
104/// println!("foo {foo}! got hotkey {hotkey:?}");
105/// todo!()
106/// }));
107/// ```
108///
109/// If you have a more complex expression that you want to clone, you can
110/// bind it to an identifier using `ident = expr` syntax.
111/// ```ignore
112/// # use covey_plugin::{clone_async, ListItem};
113/// let thing = String::from("important info");
114/// let item = ListItem::new("get me out of here");
115/// ListItem::new("i got u")
116/// .on_activate(clone_async!(thing, title = item.title, || {
117/// println!("got {title} out of there with {thing}");
118/// todo!()
119/// }));
120/// ```
121#[macro_export]
122macro_rules! clone_async {
123 // this isn't correctly matched with the below so need a special case
124 // for empty closure args
125
126 (
127 $( $ident:ident $(= $expr:expr)? , )*
128 || $($tt:tt)*
129 ) => {
130 {
131 $(let $ident = ($crate::__clone_helper_choose_first!($($expr,)? $ident)).to_owned();)*
132 async move || {
133 // TODO: this extra clone isn't necessary.
134 // with an async closure, the future can *borrow* from variables
135 // captured (and owned) by the closure. however, this isn't clear
136 // from the types and the compiler errors are sub-par right now.
137 // this macro name also suggests that all variables should be owned.
138 // This will always clone the captured variables every time this
139 // closure is called, which is simpler to reason about and
140 // this has little impact to performance anyways.
141 $(let $ident = $ident.to_owned();)*
142 { $($tt)* }
143 }
144 }
145 };
146
147 (
148 $( $ident:ident $(= $expr:expr)? , )*
149 | $($args:pat_param),* $(,)? | $($tt:tt)*
150 ) => {
151 {
152 $(let $ident = ($crate::__clone_helper_choose_first!($($expr,)? $ident)).to_owned();)*
153 async move | $($args),* | {
154 $(let $ident = $ident.to_owned();)*
155 { $($tt)* }
156 }
157 }
158 };
159
160}
161
162/// Private implementation detail of [`clone_async!`].
163#[doc(hidden)]
164#[macro_export]
165macro_rules! __clone_helper_choose_first {
166 ($a:expr) => {
167 $a
168 };
169 ($a:expr, $b: expr) => {
170 $a
171 };
172}