Skip to content

Commit

Permalink
Implement inline choice (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
kangalio authored Dec 21, 2023
1 parent ebdc4ca commit 6167311
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 9 deletions.
32 changes: 32 additions & 0 deletions examples/feature_showcase/choice_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
2 changes: 2 additions & 0 deletions examples/feature_showcase/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions macros/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct ParamArgs {
description_localized: Vec<crate::util::Tuple2<String>>,
autocomplete: Option<syn::Path>,
channel_types: Option<crate::util::List<syn::Ident>>,
choices: Option<crate::util::List<syn::Lit>>,
min: Option<syn::Lit>,
max: Option<syn::Lit>,
min_length: Option<syn::Lit>,
Expand Down
43 changes: 34 additions & 9 deletions macros/src/command/slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,34 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr
None => 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) = &param.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) = &param.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![] },
};

Expand Down Expand Up @@ -148,9 +165,17 @@ pub fn generate_slash_action(inv: &Invocation) -> Result<proc_macro2::TokenStrea
let param_types = inv
.parameters
.iter()
.map(|p| match p.args.flag {
true => 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::<Vec<_>>();

Expand Down
15 changes: 15 additions & 0 deletions src/slash_argument/slash_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>
($ctx:ident, $interaction:ident, $args:ident => $name:literal: Option<$type:ty $(,)*>) => {
if let Some(arg) = $args.iter().find(|arg| arg.name == $name) {
Expand Down

0 comments on commit 6167311

Please sign in to comment.