Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement inline choice #232

Merged
merged 1 commit into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading