aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-10 16:47:40 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-10 16:47:40 +0800
commitb18749170b6006e53976dbb6df9f59a3b9c34127 (patch)
treea0f9288fdc9082e26daab218167da1f54521d32b /mingling_macros
parent3bb5afcbe01ad16293a66084dc1ad35f3378a833 (diff)
Add completion macro infrastructure without logic
Diffstat (limited to 'mingling_macros')
-rw-r--r--mingling_macros/src/completion.rs132
-rw-r--r--mingling_macros/src/dispatcher_chain.rs38
-rw-r--r--mingling_macros/src/lib.rs67
3 files changed, 132 insertions, 105 deletions
diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs
index cf66f13..23b509f 100644
--- a/mingling_macros/src/completion.rs
+++ b/mingling_macros/src/completion.rs
@@ -5,91 +5,7 @@
use proc_macro::TokenStream;
use quote::quote;
-use syn::spanned::Spanned;
-use syn::{
- FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input,
-};
-
-/// Extracts the previous type from function arguments
-fn extract_previous_type(sig: &Signature) -> syn::Result<TypePath> {
- // The function should have exactly one parameter: ShellContext
- if sig.inputs.len() != 1 {
- return Err(syn::Error::new(
- sig.inputs.span(),
- "Completion function must have exactly one parameter (ShellContext)",
- ));
- }
-
- let arg = &sig.inputs[0];
- match arg {
- FnArg::Typed(PatType { ty, .. }) => {
- match &**ty {
- Type::Path(type_path) => {
- // Check if it's ShellContext
- let last_segment = type_path.path.segments.last().unwrap();
- if last_segment.ident != "ShellContext" {
- return Err(syn::Error::new(
- ty.span(),
- "Parameter type must be ShellContext",
- ));
- }
- Ok(type_path.clone())
- }
- _ => Err(syn::Error::new(
- ty.span(),
- "Parameter type must be a type path",
- )),
- }
- }
- FnArg::Receiver(_) => Err(syn::Error::new(
- arg.span(),
- "Completion function cannot have self parameter",
- )),
- }
-}
-
-/// Extracts the return type from the function signature
-fn extract_return_type(sig: &Signature) -> syn::Result<TypePath> {
- match &sig.output {
- ReturnType::Type(_, ty) => match &**ty {
- Type::Path(type_path) => {
- // Check if it's Suggest
- let last_segment = type_path.path.segments.last().unwrap();
- if last_segment.ident != "Suggest" {
- return Err(syn::Error::new(ty.span(), "Return type must be Suggest"));
- }
- Ok(type_path.clone())
- }
- _ => Err(syn::Error::new(
- ty.span(),
- "Return type must be a type path",
- )),
- },
- ReturnType::Default => Err(syn::Error::new(
- sig.span(),
- "Completion function must have a return type",
- )),
- }
-}
-
-/// Extracts the parameter name from function arguments
-fn extract_param_name(sig: &Signature) -> syn::Result<Pat> {
- if sig.inputs.len() != 1 {
- return Err(syn::Error::new(
- sig.inputs.span(),
- "Completion function must have exactly one parameter",
- ));
- }
-
- let arg = &sig.inputs[0];
- match arg {
- FnArg::Typed(PatType { pat, .. }) => Ok((**pat).clone()),
- FnArg::Receiver(_) => Err(syn::Error::new(
- arg.span(),
- "Completion function cannot have self parameter",
- )),
- }
-}
+use syn::{Ident, ItemFn, parse_macro_input};
#[cfg(feature = "comp")]
pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -110,25 +26,28 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Validate the function is not async
if input_fn.sig.asyncness.is_some() {
+ use syn::spanned::Spanned;
+
return syn::Error::new(input_fn.sig.span(), "Completion function cannot be async")
.to_compile_error()
.into();
}
- // Extract the parameter name
- let param_name = match extract_param_name(&input_fn.sig) {
- Ok(name) => name,
- Err(e) => return e.to_compile_error().into(),
- };
+ // Get the function signature parts
+ let sig = &input_fn.sig;
+ let inputs = &sig.inputs;
+ let output = &sig.output;
- // Extract and validate the parameter type (must be ShellContext)
- if let Err(e) = extract_previous_type(&input_fn.sig) {
- return e.to_compile_error().into();
- }
+ // Check that the function has exactly one parameter
+ if inputs.len() != 1 {
+ use syn::spanned::Spanned;
- // Extract and validate the return type (must be Suggest)
- if let Err(e) = extract_return_type(&input_fn.sig) {
- return e.to_compile_error().into();
+ return syn::Error::new(
+ inputs.span(),
+ "Completion function must have exactly one parameter",
+ )
+ .to_compile_error()
+ .into();
}
// Get the function body
@@ -142,7 +61,7 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let vis = &input_fn.vis;
// Get function name
- let fn_name = &input_fn.sig.ident;
+ let fn_name = &sig.ident;
// Generate struct name from function name using pascal_case
let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string());
@@ -156,20 +75,27 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
impl ::mingling::Completion for #struct_name {
type Previous = #previous_type_ident;
- fn comp(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest {
- // This is just to prevent warnings about imported ShellContext and Suggest
- let _ = ShellContext::default();
- let _ = Suggest::file_comp();
+ fn comp(#inputs) #output {
#fn_body
}
}
// Keep the original function for internal use
#(#fn_attrs)*
- #vis fn #fn_name(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest {
+ #vis fn #fn_name(#inputs) #output {
#fn_body
}
};
+ let completion_entry = quote! {
+ Self::#previous_type_ident => <#struct_name as ::mingling::Completion>::comp(ctx),
+ };
+
+ let mut completions = crate::COMPLETIONS.lock().unwrap();
+ let completion_str = completion_entry.to_string();
+ if !completions.contains(&completion_str) {
+ completions.push(completion_str);
+ }
+
expanded.into()
}
diff --git a/mingling_macros/src/dispatcher_chain.rs b/mingling_macros/src/dispatcher_chain.rs
index f531424..4038600 100644
--- a/mingling_macros/src/dispatcher_chain.rs
+++ b/mingling_macros/src/dispatcher_chain.rs
@@ -1,7 +1,7 @@
//! Dispatcher Chain and Dispatcher Render Macros
//!
//! This module provides macros for creating dispatcher chain and dispatcher render structs
-//! with automatic implementations of the `DispatcherChain` trait.
+//! with automatic implementations of the `Dispatcher` trait.
use proc_macro::TokenStream;
use quote::quote;
@@ -60,6 +60,13 @@ impl Parse for DispatcherChainInput {
}
}
+// NOTICE: This implementation contains significant code duplication between the explicit
+// and default cases in both `dispatcher_chain` and `dispatcher_render` functions.
+// The logic for handling default vs explicit group names and generating the appropriate
+// code should be extracted into common helper functions to reduce redundancy.
+// Additionally, the token stream generation patterns are nearly identical between
+// the two main functions and could benefit from refactoring.
+
pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
// Parse the input
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
@@ -87,6 +94,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
let command_name_str = command_name.value();
+ let comp_entry = get_comp_entry(&pack);
+
let expanded = if use_default {
// For default case, use ThisProgram
quote! {
@@ -95,6 +104,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
::mingling::macros::pack!(ThisProgram, #pack = Vec<String>);
+ #comp_entry
+
impl ::mingling::Dispatcher<ThisProgram> for #command_struct {
fn node(&self) -> ::mingling::Node {
::mingling::macros::node!(#command_name_str)
@@ -115,6 +126,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
::mingling::macros::pack!(#group_name, #pack = Vec<String>);
+ #comp_entry
+
impl ::mingling::Dispatcher<#group_name> for #command_struct {
fn node(&self) -> ::mingling::Node {
::mingling::macros::node!(#command_name_str)
@@ -159,6 +172,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream {
let command_name_str = command_name.value();
+ let comp_entry = get_comp_entry(&pack);
+
let expanded = if use_default {
// For default case, use ThisProgram
quote! {
@@ -167,6 +182,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream {
::mingling::macros::pack!(ThisProgram, #pack = Vec<String>);
+ #comp_entry
+
impl ::mingling::Dispatcher for #command_struct {
fn node(&self) -> ::mingling::Node {
::mingling::macros::node!(#command_name_str)
@@ -187,6 +204,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream {
::mingling::macros::pack!(#group_name, #pack = Vec<String>);
+ #comp_entry
+
impl ::mingling::Dispatcher for #command_struct {
fn node(&self) -> ::mingling::Node {
::mingling::macros::node!(#command_name_str)
@@ -203,3 +222,20 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream {
expanded.into()
}
+
+#[cfg(feature = "comp")]
+fn get_comp_entry(entry_name: &Ident) -> proc_macro2::TokenStream {
+ let comp_entry = quote! {
+ impl ::mingling::CompletionEntry for #entry_name {
+ fn get_input(self) -> Vec<String> {
+ self.inner.clone()
+ }
+ }
+ };
+ comp_entry
+}
+
+#[cfg(not(feature = "comp"))]
+fn get_comp_entry(_entry_name: &Ident) -> proc_macro2::TokenStream {
+ quote! {}
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index d324ddc..7291f0e 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -24,10 +24,13 @@ mod suggest;
use once_cell::sync::Lazy;
use std::sync::Mutex;
-// Global variable declarations for storing chain and renderer mappings
+// Global variables
#[cfg(feature = "general_renderer")]
pub(crate) static GENERAL_RENDERERS: Lazy<Mutex<Vec<String>>> =
Lazy::new(|| Mutex::new(Vec::new()));
+#[cfg(feature = "comp")]
+pub(crate) static COMPLETIONS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
+
pub(crate) static PACKED_TYPES: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub(crate) static CHAINS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub(crate) static RENDERERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
@@ -107,6 +110,13 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
let mut packed_types = PACKED_TYPES.lock().unwrap().clone();
packed_types.push("DispatcherNotFound".to_string());
packed_types.push("RendererNotFound".to_string());
+
+ #[cfg(feature = "comp")]
+ {
+ packed_types.push("CompletionContext".to_string());
+ packed_types.push("CompletionSuggest".to_string());
+ }
+
packed_types.sort();
packed_types.dedup();
let renderers = RENDERERS.lock().unwrap().clone();
@@ -117,6 +127,9 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
#[cfg(feature = "general_renderer")]
let general_renderers = GENERAL_RENDERERS.lock().unwrap().clone();
+ #[cfg(feature = "comp")]
+ let completions = COMPLETIONS.lock().unwrap().clone();
+
let packed_types: Vec<proc_macro2::TokenStream> = packed_types
.iter()
.map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
@@ -164,6 +177,55 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
#[cfg(not(feature = "general_renderer"))]
let general_render = quote! {};
+ #[cfg(feature = "comp")]
+ let comp_dispatcher = quote! {
+ ::mingling::macros::dispatcher!(#name, "__comp", CompletionDispatcher => CompletionContext);
+ ::mingling::macros::pack!(
+ #name,
+ CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
+ );
+
+ #[::mingling::macros::chain]
+ async fn __completion(prev: CompletionContext) -> NextProcess {
+ let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ match read_ctx {
+ Ok(ctx) => {
+ let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx);
+ CompletionSuggest::new((ctx, suggest)).to_render()
+ }
+ Err(_) => std::process::exit(1),
+ }
+ }
+
+ #[::mingling::macros::renderer]
+ fn __render_completion(prev: CompletionSuggest) {
+ let (ctx, suggest) = prev.inner;
+ ::mingling::CompletionHelper::render_suggest::<#name>(ctx, suggest);
+ }
+ };
+
+ #[cfg(not(feature = "comp"))]
+ let comp_dispatcher = quote! {};
+
+ #[cfg(feature = "comp")]
+ let completion_tokens: Vec<proc_macro2::TokenStream> = completions
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ #[cfg(feature = "comp")]
+ let comp = quote! {
+ fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest {
+ match any.member_id {
+ #(#completion_tokens)*
+ _ => ::mingling::Suggest::FileCompletion,
+ }
+ }
+ };
+
+ #[cfg(not(feature = "comp"))]
+ let comp = quote! {};
+
let expanded = quote! {
::mingling::macros::pack!(#name, RendererNotFound = String);
::mingling::macros::pack!(#name, DispatcherNotFound = Vec<String>);
@@ -176,6 +238,8 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
#(#packed_types),*
}
+ #comp_dispatcher
+
impl ::std::fmt::Display for #name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
@@ -212,6 +276,7 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
}
}
#general_render
+ #comp
}
impl #name {