anyrun_macros/
lib.rs

1use proc_macro::{Span, TokenStream};
2use quote::quote;
3use syn::{parse_macro_input, parse_quote, Ident, ReturnType, Type};
4
5/// The function to handle the selection of an item. Takes a `Match` as its first argument, and the second argument can be one of:
6/// - &T
7/// - &mut T
8/// - <Nothing>
9/// where T is the type returned by `init`.
10///
11/// Should return a `HandleResult` with the appropriate action.
12#[proc_macro_attribute]
13pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
14    let function = parse_macro_input!(item as syn::ItemFn);
15    let fn_name = &function.sig.ident;
16
17    let data = if function.sig.inputs.len() == 2 {
18        if match function.sig.inputs.last() {
19            Some(syn::FnArg::Typed(pat)) => match &*pat.ty {
20                Type::Reference(reference) => {
21                    reference.mutability.is_some()
22                }
23                _ => return quote! { compile_error!("Last argument must be either a reference to the shared data or should not be present at all.") }.into(),
24            },
25            Some(_) => return quote! { compile_error!("`self` argument, really?") }.into(),
26            None => unreachable!(),
27        } {
28            quote! {
29                ANYRUN_INTERNAL_DATA.write().unwrap().as_mut().unwrap(),
30            }
31        } else {
32            quote! {
33                ANYRUN_INTERNAL_DATA.read().unwrap().as_ref().unwrap(),
34            }
35        }
36    } else {
37        quote! {}
38    };
39
40    quote! {
41        #[::abi_stable::sabi_extern_fn]
42        fn anyrun_internal_handle_selection(
43            selection: ::anyrun_plugin::anyrun_interface::Match,
44        ) -> ::anyrun_plugin::anyrun_interface::HandleResult {
45            #function
46
47            #fn_name(
48                selection,
49                #data
50            )
51        }
52    }
53    .into()
54}
55
56/// Function that takes the current text input as an `RString` as the first argument, and the second argument can be one of:
57/// - &T
58/// - &mut T
59/// - <Nothing>
60/// where T is the type returned by `init`.
61///
62/// It should return an `RVec` of `Match`es.
63#[proc_macro_attribute]
64pub fn get_matches(_attr: TokenStream, item: TokenStream) -> TokenStream {
65    let function = parse_macro_input!(item as syn::ItemFn);
66    let fn_name = &function.sig.ident;
67
68    let fn_call = if function.sig.inputs.len() == 2 {
69        let data = if match function.sig.inputs.last() {
70            Some(syn::FnArg::Typed(pat)) => match &*pat.ty {
71                Type::Reference(reference) => {
72                    reference.mutability.is_some()
73                }
74                _ => return quote! { compile_error!("Last argument must be either a reference to the shared data or should not be present at all.") }.into(),
75            },
76            Some(_) => return quote! { compile_error!("`self` argument, really?") }.into(),
77            None => unreachable!(),
78        } {
79            quote! {
80                ANYRUN_INTERNAL_DATA.write().unwrap().as_mut()
81            }
82        } else {
83            quote! {
84                ANYRUN_INTERNAL_DATA.read().unwrap().as_ref()
85            }
86        };
87        quote! {
88            if let Some(data) = #data {
89                #fn_name(input, data)
90            } else {
91                ::abi_stable::std_types::RVec::new()
92            }
93        }
94    } else {
95        quote! {
96            #fn_name(input)
97        }
98    };
99
100    quote! {
101        #[::abi_stable::sabi_extern_fn]
102        fn anyrun_internal_get_matches(input: ::abi_stable::std_types::RString) -> u64 {
103            #function
104
105            let current_id =
106                ANYRUN_INTERNAL_ID_COUNTER.load(::std::sync::atomic::Ordering::Relaxed);
107            ANYRUN_INTERNAL_ID_COUNTER
108                .store(current_id + 1, ::std::sync::atomic::Ordering::Relaxed);
109
110            let handle = ::std::thread::spawn(move || {
111                #fn_call
112            });
113
114            *ANYRUN_INTERNAL_THREAD.lock().unwrap() = Some((handle, current_id));
115
116            current_id
117        }
118    }
119    .into()
120}
121
122/// Function that returns the plugin info as a `PluginInfo` object. Takes no arguments.
123#[proc_macro_attribute]
124pub fn info(_attr: TokenStream, item: TokenStream) -> TokenStream {
125    let function = parse_macro_input!(item as syn::ItemFn);
126    let fn_name = &function.sig.ident;
127
128    quote! {
129        #[::abi_stable::sabi_extern_fn]
130        fn anyrun_internal_info() -> ::anyrun_plugin::anyrun_interface::PluginInfo {
131            #function
132
133            #fn_name()
134        }
135    }
136    .into()
137}
138
139/// Function that takes an `RString` as the only argument, which points to the anyrun config directory. Returns the data
140/// the plugin operates on. This data is accessible as both a normal borrow and a mutable borrow to `get_matches` and `handler`.
141#[proc_macro_attribute]
142pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream {
143    let function = parse_macro_input!(item as syn::ItemFn);
144    let fn_name = &function.sig.ident;
145    let data_type = match &function.sig.output {
146        ReturnType::Default => quote! {()},
147        ReturnType::Type(_, data_type) => quote! {#data_type},
148    };
149
150    quote! {
151        static ANYRUN_INTERNAL_THREAD: ::std::sync::Mutex<
152            Option<(
153                ::std::thread::JoinHandle<
154                    ::abi_stable::std_types::RVec<::anyrun_plugin::anyrun_interface::Match>,
155                >,
156                u64,
157            )>,
158        > = ::std::sync::Mutex::new(None);
159        static ANYRUN_INTERNAL_ID_COUNTER: ::std::sync::atomic::AtomicU64 =
160            ::std::sync::atomic::AtomicU64::new(0);
161        static ANYRUN_INTERNAL_DATA: ::std::sync::RwLock<Option<#data_type>> =
162            ::std::sync::RwLock::new(None);
163
164        #[::abi_stable::export_root_module]
165        fn anyrun_internal_init_root_module() -> ::anyrun_plugin::anyrun_interface::PluginRef {
166            use ::abi_stable::prefix_type::PrefixTypeTrait;
167            ::anyrun_plugin::anyrun_interface::Plugin {
168                init: anyrun_internal_init,
169                info: anyrun_internal_info,
170                get_matches: anyrun_internal_get_matches,
171                poll_matches: anyrun_internal_poll_matches,
172                handle_selection: anyrun_internal_handle_selection,
173            }
174            .leak_into_prefix()
175        }
176
177        #[::abi_stable::sabi_extern_fn]
178        fn anyrun_internal_poll_matches(id: u64) -> ::anyrun_plugin::anyrun_interface::PollResult {
179            match ANYRUN_INTERNAL_THREAD.try_lock() {
180                Ok(thread) => match thread.as_ref() {
181                    Some((thread, task_id)) => {
182                        if *task_id == id {
183                            if !thread.is_finished() {
184                                return ::anyrun_plugin::anyrun_interface::PollResult::Pending;
185                            }
186                        } else {
187                            return ::anyrun_plugin::anyrun_interface::PollResult::Cancelled;
188                        }
189                    }
190                    None => return ::anyrun_plugin::anyrun_interface::PollResult::Cancelled,
191                },
192                Err(_) => return ::anyrun_plugin::anyrun_interface::PollResult::Pending,
193            }
194
195            let (thread, _) = ANYRUN_INTERNAL_THREAD.lock().unwrap().take().unwrap();
196            ::anyrun_plugin::anyrun_interface::PollResult::Ready(thread.join().unwrap())
197        }
198
199        #[::abi_stable::sabi_extern_fn]
200        fn anyrun_internal_init(config_dir: ::abi_stable::std_types::RString) {
201            #function
202
203            ::std::thread::spawn(|| {
204                let mut lock = ANYRUN_INTERNAL_DATA.write().unwrap();
205                *lock = Some(#fn_name(config_dir));
206            });
207        }
208    }
209    .into()
210}
211
212#[proc_macro_attribute]
213pub fn config_args(_attr: TokenStream, item: TokenStream) -> TokenStream {
214    let item = parse_macro_input!(item as syn::ItemStruct);
215    let ident = &item.ident;
216
217    let mut opt_item = item.clone();
218
219    opt_item.attrs = vec![parse_quote!(#[derive(::clap::Args)])];
220    opt_item.ident = Ident::new(&format!("{}Args", opt_item.ident), Span::call_site().into());
221
222    let opt_ident = &opt_item.ident;
223
224    let mut operations = quote!();
225
226    for field in opt_item.fields.iter_mut() {
227        let ty = &field.ty;
228        let ident = &field.ident;
229        field.ty = Type::Verbatim(quote!(Option<#ty>));
230        field.attrs = vec![parse_quote!(#[arg(long)])];
231
232        operations = quote! {
233            #operations
234            if let Some(val) = opt.#ident {
235                self.#ident = val;
236            }
237        }
238    }
239
240    quote! {
241        #item
242
243        #opt_item
244
245        impl #ident {
246            fn merge_opt(&mut self, opt: #opt_ident) {
247                #operations
248            }
249        }
250    }
251    .into()
252}