use proc_macro::{Span, TokenStream}; use quote::quote; use syn::{parse_macro_input, parse_quote, Attribute, Ident, ReturnType, Type}; /// The function to handle the selection of an item. Takes a `Match` as its first argument, and the second argument can be one of: /// - &T /// - &mut T /// - /// where T is the type returned by `init`. /// /// Should return a `HandleResult` with the appropriate action. #[proc_macro_attribute] pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as syn::ItemFn); let fn_name = &function.sig.ident; let data = if function.sig.inputs.len() == 2 { if match function.sig.inputs.last() { Some(syn::FnArg::Typed(pat)) => match &*pat.ty { Type::Reference(reference) => { reference.mutability.is_some() } _ => return quote! { compile_error!("Last argument must be either a reference to the shared data or should not be present at all.") }.into(), }, Some(_) => return quote! { compile_error!("`self` argument, really?") }.into(), None => unreachable!(), } { quote! { ANYRUN_INTERNAL_DATA.write().unwrap().as_mut().unwrap(), } } else { quote! { ANYRUN_INTERNAL_DATA.read().unwrap().as_ref().unwrap(), } } } else { quote! {} }; quote! { #[::abi_stable::sabi_extern_fn] fn anyrun_internal_handle_selection( selection: ::anyrun_plugin::anyrun_interface::Match, ) -> ::anyrun_plugin::anyrun_interface::HandleResult { #function #fn_name( selection, #data ) } } .into() } /// Function that takes the current text input as an `RString` as the first argument, and the second argument can be one of: /// - &T /// - &mut T /// - /// where T is the type returned by `init`. /// /// It should return an `RVec` of `Match`es. #[proc_macro_attribute] pub fn get_matches(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as syn::ItemFn); let fn_name = &function.sig.ident; let fn_call = if function.sig.inputs.len() == 2 { let data = if match function.sig.inputs.last() { Some(syn::FnArg::Typed(pat)) => match &*pat.ty { Type::Reference(reference) => { reference.mutability.is_some() } _ => return quote! { compile_error!("Last argument must be either a reference to the shared data or should not be present at all.") }.into(), }, Some(_) => return quote! { compile_error!("`self` argument, really?") }.into(), None => unreachable!(), } { quote! { ANYRUN_INTERNAL_DATA.write().unwrap().as_mut() } } else { quote! { ANYRUN_INTERNAL_DATA.read().unwrap().as_ref() } }; quote! { if let Some(data) = #data { #fn_name(input, data) } else { ::abi_stable::std_types::RVec::new() } } } else { quote! { #fn_name(input) } }; quote! { #[::abi_stable::sabi_extern_fn] fn anyrun_internal_get_matches(input: ::abi_stable::std_types::RString) -> u64 { #function let current_id = ANYRUN_INTERNAL_ID_COUNTER.load(::std::sync::atomic::Ordering::Relaxed); ANYRUN_INTERNAL_ID_COUNTER .store(current_id + 1, ::std::sync::atomic::Ordering::Relaxed); let handle = ::std::thread::spawn(move || { #fn_call }); *ANYRUN_INTERNAL_THREAD.lock().unwrap() = Some((handle, current_id)); current_id } } .into() } /// Function that returns the plugin info as a `PluginInfo` object. Takes no arguments. #[proc_macro_attribute] pub fn info(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as syn::ItemFn); let fn_name = &function.sig.ident; quote! { #[::abi_stable::sabi_extern_fn] fn anyrun_internal_info() -> ::anyrun_plugin::anyrun_interface::PluginInfo { #function #fn_name() } } .into() } /// Function that takes an `RString` as the only argument, which points to the anyrun config directory. Returns the data /// the plugin operates on. This data is accessible as both a normal borrow and a mutable borrow to `get_matches` and `handler`. #[proc_macro_attribute] pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as syn::ItemFn); let fn_name = &function.sig.ident; let data_type = match &function.sig.output { ReturnType::Default => quote! {()}, ReturnType::Type(_, data_type) => quote! {#data_type}, }; quote! { static ANYRUN_INTERNAL_THREAD: ::std::sync::Mutex< Option<( ::std::thread::JoinHandle< ::abi_stable::std_types::RVec<::anyrun_plugin::anyrun_interface::Match>, >, u64, )>, > = ::std::sync::Mutex::new(None); static ANYRUN_INTERNAL_ID_COUNTER: ::std::sync::atomic::AtomicU64 = ::std::sync::atomic::AtomicU64::new(0); static ANYRUN_INTERNAL_DATA: ::std::sync::RwLock> = ::std::sync::RwLock::new(None); #[::abi_stable::export_root_module] fn anyrun_internal_init_root_module() -> ::anyrun_plugin::anyrun_interface::PluginRef { use ::abi_stable::prefix_type::PrefixTypeTrait; ::anyrun_plugin::anyrun_interface::Plugin { init: anyrun_internal_init, info: anyrun_internal_info, get_matches: anyrun_internal_get_matches, poll_matches: anyrun_internal_poll_matches, handle_selection: anyrun_internal_handle_selection, } .leak_into_prefix() } #[::abi_stable::sabi_extern_fn] fn anyrun_internal_poll_matches(id: u64) -> ::anyrun_plugin::anyrun_interface::PollResult { match ANYRUN_INTERNAL_THREAD.try_lock() { Ok(thread) => match thread.as_ref() { Some((thread, task_id)) => { if *task_id == id { if !thread.is_finished() { return ::anyrun_plugin::anyrun_interface::PollResult::Pending; } } else { return ::anyrun_plugin::anyrun_interface::PollResult::Cancelled; } } None => return ::anyrun_plugin::anyrun_interface::PollResult::Cancelled, }, Err(_) => return ::anyrun_plugin::anyrun_interface::PollResult::Pending, } let (thread, _) = ANYRUN_INTERNAL_THREAD.lock().unwrap().take().unwrap(); ::anyrun_plugin::anyrun_interface::PollResult::Ready(thread.join().unwrap()) } #[::abi_stable::sabi_extern_fn] fn anyrun_internal_init(config_dir: ::abi_stable::std_types::RString) { #function ::std::thread::spawn(|| { let mut lock = ANYRUN_INTERNAL_DATA.write().unwrap(); *lock = Some(#fn_name(config_dir)); }); } } .into() } #[proc_macro_attribute] pub fn config_args(_attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as syn::ItemStruct); let ident = &item.ident; let mut opt_item = item.clone(); opt_item.attrs = vec![parse_quote!(#[derive(::clap::Args)])]; opt_item.ident = Ident::new(&format!("{}Args", opt_item.ident), Span::call_site().into()); let opt_ident = &opt_item.ident; let mut operations = quote!(); for field in opt_item.fields.iter_mut() { let ty = &field.ty; let ident = &field.ident; field.ty = Type::Verbatim(quote!(Option<#ty>)); field.attrs = vec![parse_quote!(#[arg(long)])]; operations = quote! { #operations if let Some(val) = opt.#ident { self.#ident = val; } } } quote! { #item #opt_item impl #ident { fn merge_opt(&mut self, opt: #opt_ident) { #operations } } } .into() }