From f0d287b8731910e78a4cbef08d87f943e2e28501 Mon Sep 17 00:00:00 2001 From: dusk Date: Sat, 11 Jan 2025 22:38:29 +0900 Subject: [PATCH] feat(commands): show command suggestions if a command was not found --- PluralKit.Bot/CommandSystem/Parameters.cs | 4 +-- PluralKit.Bot/Handlers/MessageCreated.cs | 2 +- crates/commands/src/commands.rs | 2 +- crates/commands/src/commands.udl | 2 +- crates/commands/src/lib.rs | 30 +++++++++++++++------ crates/commands/src/main.rs | 8 ++++-- crates/commands/src/tree.rs | 33 +++++++++++++++++------ 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index e5e0efd2..d9acf1c7 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -26,10 +26,10 @@ public class Parameters // just used for errors, temporarily public string FullCommand { get; init; } - public Parameters(string cmd) + public Parameters(string prefix, string cmd) { FullCommand = cmd; - var result = CommandsMethods.ParseCommand(cmd); + var result = CommandsMethods.ParseCommand(prefix, cmd); if (result is CommandResult.Ok) { var command = ((CommandResult.Ok)result).@command; diff --git a/PluralKit.Bot/Handlers/MessageCreated.cs b/PluralKit.Bot/Handlers/MessageCreated.cs index a2b34483..594b178a 100644 --- a/PluralKit.Bot/Handlers/MessageCreated.cs +++ b/PluralKit.Bot/Handlers/MessageCreated.cs @@ -142,7 +142,7 @@ public class MessageCreated: IEventHandler Parameters parameters; try { - parameters = new Parameters(evt.Content?.Substring(cmdStart)); + parameters = new Parameters(evt.Content?.Substring(0, cmdStart), evt.Content?.Substring(cmdStart)); } catch (PKError e) { diff --git a/crates/commands/src/commands.rs b/crates/commands/src/commands.rs index 68836497..8c2c5357 100644 --- a/crates/commands/src/commands.rs +++ b/crates/commands/src/commands.rs @@ -57,7 +57,7 @@ impl Display for Command { write!(f, " ")?; } } - write!(f, " - {}", self.help) + Ok(()) } } diff --git a/crates/commands/src/commands.udl b/crates/commands/src/commands.udl index 9eb372a3..53bf8dbd 100644 --- a/crates/commands/src/commands.udl +++ b/crates/commands/src/commands.udl @@ -1,5 +1,5 @@ namespace commands { - CommandResult parse_command(string input); + CommandResult parse_command(string prefix, string input); }; [Enum] interface CommandResult { diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index 24228f9b..64bcf33a 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -9,6 +9,7 @@ uniffi::include_scaffolding!("commands"); use core::panic; use std::collections::HashMap; +use std::fmt::Write; use smol_str::{format_smolstr, SmolStr}; use tree::TreeBranch; @@ -51,7 +52,7 @@ pub struct ParsedCommand { pub flags: HashMap>, } -pub fn parse_command(input: String) -> CommandResult { +pub fn parse_command(prefix: String, input: String) -> CommandResult { let input: SmolStr = input.into(); let mut local_tree: TreeBranch = COMMAND_TREE.clone(); @@ -91,11 +92,11 @@ pub fn parse_command(input: String) -> CommandResult { } } Err(None) => { - if let Some(command_ref) = local_tree.callback() { - println!("{command_ref} {params:?}"); + if let Some(command) = local_tree.command() { + println!("{} {params:?}", command.cb); return CommandResult::Ok { command: ParsedCommand { - command_ref: command_ref.into(), + command_ref: command.cb.into(), params, args, flags, @@ -103,13 +104,26 @@ pub fn parse_command(input: String) -> CommandResult { }; } - println!("{possible_tokens:?}"); + let mut error = format!("Unknown command `{prefix}{input}`."); + + let possible_commands = local_tree.possible_commands(2); + if !possible_commands.is_empty() { + error.push_str(" Perhaps you meant to use one of the commands below:\n"); + for command in possible_commands { + writeln!(&mut error, "- **{prefix}{command}** - *{}*", command.help) + .expect("oom"); + } + } else { + error.push_str("\n"); + } + + error.push_str( + "For a list of possible commands, see .", + ); // todo: check if last token is a common incorrect unquote (multi-member names etc) // todo: check if this is a system name in pk;s command - return CommandResult::Err { - error: format!("Unknown command `{input}`. For a list of possible commands, see ."), - }; + return CommandResult::Err { error }; } Err(Some(short_circuit)) => { return CommandResult::Err { diff --git a/crates/commands/src/main.rs b/crates/commands/src/main.rs index eb5748e5..110915ea 100644 --- a/crates/commands/src/main.rs +++ b/crates/commands/src/main.rs @@ -8,8 +8,12 @@ fn main() { .intersperse(" ".to_string()) .collect::(); if !cmd.is_empty() { - let parsed = commands::parse_command(cmd); - println!("{:#?}", parsed); + use commands::CommandResult; + let parsed = commands::parse_command("pk;".to_string(), cmd); + match parsed { + CommandResult::Ok { command } => println!("{command:#?}"), + CommandResult::Err { error } => println!("{error}"), + } } else { for command in cmds::all() { println!("{}", command); diff --git a/crates/commands/src/tree.rs b/crates/commands/src/tree.rs index 911c21f0..520208df 100644 --- a/crates/commands/src/tree.rs +++ b/crates/commands/src/tree.rs @@ -1,18 +1,17 @@ use ordermap::OrderMap; -use smol_str::SmolStr; use crate::{commands::Command, Token}; #[derive(Debug, Clone)] pub struct TreeBranch { - current_command_key: Option, + current_command: Option, branches: OrderMap, } impl TreeBranch { pub fn empty() -> Self { Self { - current_command_key: None, + current_command: None, branches: OrderMap::new(), } } @@ -20,10 +19,10 @@ impl TreeBranch { pub fn register_command(&mut self, command: Command) { let mut current_branch = self; // iterate over tokens in command - for token in command.tokens { + for token in command.tokens.clone() { // recursively get or create a sub-branch for each token current_branch = current_branch.branches.entry(token).or_insert(TreeBranch { - current_command_key: None, + current_command: None, branches: OrderMap::new(), }) } @@ -31,20 +30,38 @@ impl TreeBranch { current_branch.branches.insert( Token::Empty, TreeBranch { - current_command_key: Some(command.cb), + current_command: Some(command), branches: OrderMap::new(), }, ); } - pub fn callback(&self) -> Option { - self.current_command_key.clone() + pub fn command(&self) -> Option { + self.current_command.clone() } pub fn possible_tokens(&self) -> impl Iterator { self.branches.keys() } + pub fn possible_commands(&self, max_depth: usize) -> Vec { + if max_depth == 0 { + return Vec::new(); + } + let mut commands = Vec::new(); + for token in self.possible_tokens() { + if let Some(tree) = self.get_branch(token) { + if let Some(command) = tree.command() { + commands.push(command); + // we dont need to look further if we found a command + continue; + } + commands.append(&mut tree.possible_commands(max_depth - 1)); + } + } + commands + } + pub fn get_branch(&self, token: &Token) -> Option<&TreeBranch> { self.branches.get(token) }