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}