From 6167311216065b953c6dc64406e56a31e52cd9a1 Mon Sep 17 00:00:00 2001 From: kangalio Date: Thu, 21 Dec 2023 10:20:43 +0100 Subject: [PATCH] Implement inline choice (#232) --- examples/feature_showcase/choice_parameter.rs | 32 ++++++++++++++ examples/feature_showcase/main.rs | 2 + macros/src/command/mod.rs | 1 + macros/src/command/slash.rs | 43 +++++++++++++++---- src/slash_argument/slash_macro.rs | 15 +++++++ 5 files changed, 84 insertions(+), 9 deletions(-) diff --git a/examples/feature_showcase/choice_parameter.rs b/examples/feature_showcase/choice_parameter.rs index 89c5b72d9fe8..932eb16495ca 100644 --- a/examples/feature_showcase/choice_parameter.rs +++ b/examples/feature_showcase/choice_parameter.rs @@ -20,3 +20,35 @@ pub async fn choice( ctx.say(format!("You entered {:?}", choice)).await?; Ok(()) } + +// For simple choices, you can also declare the options inline +// +// Features: supports duplicate options and theoretically any type that implements Display +// +// Limitations: due to macro limitations (partially self-imposed, partially external), poise +// currently does not support Options parameters, and only supports parameter types that can be +// constructed from a literal (https://doc.rust-lang.org/reference/expressions/literal-expr.html). + +#[poise::command(slash_command)] +pub async fn inline_choice( + ctx: Context<'_>, + #[description = "Which continent are you from"] + #[choices("Europe", "Asia", "Africa", "America", "Australia", "Antarctica")] + continent: &'static str, +) -> Result<(), Error> { + ctx.say(format!("{} is a great continent!", continent)) + .await?; + Ok(()) +} + +#[poise::command(slash_command)] +pub async fn inline_choice_int( + ctx: Context<'_>, + #[description = "Choose a number"] + #[choices(1, 2, 3, 4, 5, 4, 3, 2, 1)] + number: u32, +) -> Result<(), Error> { + ctx.say(format!("You chose {}... for better or for worse", number)) + .await?; + Ok(()) +} diff --git a/examples/feature_showcase/main.rs b/examples/feature_showcase/main.rs index f27b70385bf4..101e7a11d243 100644 --- a/examples/feature_showcase/main.rs +++ b/examples/feature_showcase/main.rs @@ -51,6 +51,8 @@ async fn main() { checks::lennyface(), checks::permissions_v2(), choice_parameter::choice(), + choice_parameter::inline_choice(), + choice_parameter::inline_choice_int(), code_block_parameter::code(), collector::boop(), context_menu::user_info(), diff --git a/macros/src/command/mod.rs b/macros/src/command/mod.rs index 802f83248a88..fe8b8a221dec 100644 --- a/macros/src/command/mod.rs +++ b/macros/src/command/mod.rs @@ -68,6 +68,7 @@ struct ParamArgs { description_localized: Vec>, autocomplete: Option, channel_types: Option>, + choices: Option>, min: Option, max: Option, min_length: Option, diff --git a/macros/src/command/slash.rs b/macros/src/command/slash.rs index d7a36d2dac4a..41c004c837be 100644 --- a/macros/src/command/slash.rs +++ b/macros/src/command/slash.rs @@ -76,17 +76,34 @@ pub fn generate_parameters(inv: &Invocation) -> Result quote::quote! {}, }; let type_setter = match inv.args.slash_command { - true => quote::quote! { Some(|o| { - poise::create_slash_argument!(#type_, o) - #min_value_setter #max_value_setter - #min_length_setter #max_length_setter - }) }, + true => { + if let Some(_choices) = ¶m.args.choices { + quote::quote! { Some(|o| o.kind(::poise::serenity_prelude::CommandOptionType::Integer)) } + } else { + quote::quote! { Some(|o| { + poise::create_slash_argument!(#type_, o) + #min_value_setter #max_value_setter + #min_length_setter #max_length_setter + }) } + } + } false => quote::quote! { None }, }; // TODO: theoretically a problem that we don't store choices for non slash commands // TODO: move this to poise::CommandParameter::choices (is there a reason not to?) let choices = match inv.args.slash_command { - true => quote::quote! { poise::slash_argument_choices!(#type_) }, + true => { + if let Some(choices) = ¶m.args.choices { + let choices = &choices.0; + quote::quote! { vec![#( ::poise::CommandParameterChoice { + name: ToString::to_string(&#choices), + localizations: Default::default(), + __non_exhaustive: (), + } ),*] } + } else { + quote::quote! { poise::slash_argument_choices!(#type_) } + } + } false => quote::quote! { vec![] }, }; @@ -148,9 +165,17 @@ pub fn generate_slash_action(inv: &Invocation) -> Result syn::parse_quote! { FLAG }, - false => p.type_.clone(), + .map(|p| { + let t = &p.type_; + if p.args.flag { + quote::quote! { FLAG } + } else if let Some(choices) = &p.args.choices { + let choice_indices = (0..choices.0.len()).map(syn::Index::from); + let choice_vals = &choices.0; + quote::quote! { INLINE_CHOICE #t [#(#choice_indices: #choice_vals),*] } + } else { + quote::quote! { #t } + } }) .collect::>(); diff --git a/src/slash_argument/slash_macro.rs b/src/slash_argument/slash_macro.rs index 8f9baeb2b481..e4b0003a06bd 100644 --- a/src/slash_argument/slash_macro.rs +++ b/src/slash_argument/slash_macro.rs @@ -112,6 +112,21 @@ impl std::error::Error for SlashArgError { #[doc(hidden)] #[macro_export] macro_rules! _parse_slash { + // Extract #[choices(...)] (no Option supported ;-;) + ($ctx:ident, $interaction:ident, $args:ident => $name:literal: INLINE_CHOICE $type:ty [$($index:literal: $value:literal),*]) => { + if let Some(arg) = $args.iter().find(|arg| arg.name == $name) { + let $crate::serenity_prelude::ResolvedValue::Integer(index) = arg.value else { + return Err($crate::SlashArgError::new_command_structure_mismatch("expected integer, as the index for an inline choice parameter")); + }; + match index { + $( $index => $value, )* + _ => return Err($crate::SlashArgError::new_command_structure_mismatch("out of range index for inline choice parameter")), + } + } else { + return Err($crate::SlashArgError::new_command_structure_mismatch("a required argument is missing")); + } + }; + // Extract Option ($ctx:ident, $interaction:ident, $args:ident => $name:literal: Option<$type:ty $(,)*>) => { if let Some(arg) = $args.iter().find(|arg| arg.name == $name) {