mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat(commands): show command suggestions if a command was not found
This commit is contained in:
parent
ee45fca6ab
commit
f0d287b873
7 changed files with 58 additions and 23 deletions
|
|
@ -26,10 +26,10 @@ public class Parameters
|
||||||
// just used for errors, temporarily
|
// just used for errors, temporarily
|
||||||
public string FullCommand { get; init; }
|
public string FullCommand { get; init; }
|
||||||
|
|
||||||
public Parameters(string cmd)
|
public Parameters(string prefix, string cmd)
|
||||||
{
|
{
|
||||||
FullCommand = cmd;
|
FullCommand = cmd;
|
||||||
var result = CommandsMethods.ParseCommand(cmd);
|
var result = CommandsMethods.ParseCommand(prefix, cmd);
|
||||||
if (result is CommandResult.Ok)
|
if (result is CommandResult.Ok)
|
||||||
{
|
{
|
||||||
var command = ((CommandResult.Ok)result).@command;
|
var command = ((CommandResult.Ok)result).@command;
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
||||||
Parameters parameters;
|
Parameters parameters;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
parameters = new Parameters(evt.Content?.Substring(cmdStart));
|
parameters = new Parameters(evt.Content?.Substring(0, cmdStart), evt.Content?.Substring(cmdStart));
|
||||||
}
|
}
|
||||||
catch (PKError e)
|
catch (PKError e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl Display for Command {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(f, " - {}", self.help)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
namespace commands {
|
namespace commands {
|
||||||
CommandResult parse_command(string input);
|
CommandResult parse_command(string prefix, string input);
|
||||||
};
|
};
|
||||||
[Enum]
|
[Enum]
|
||||||
interface CommandResult {
|
interface CommandResult {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ uniffi::include_scaffolding!("commands");
|
||||||
|
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
use smol_str::{format_smolstr, SmolStr};
|
use smol_str::{format_smolstr, SmolStr};
|
||||||
use tree::TreeBranch;
|
use tree::TreeBranch;
|
||||||
|
|
@ -51,7 +52,7 @@ pub struct ParsedCommand {
|
||||||
pub flags: HashMap<String, Option<String>>,
|
pub flags: HashMap<String, Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_command(input: String) -> CommandResult {
|
pub fn parse_command(prefix: String, input: String) -> CommandResult {
|
||||||
let input: SmolStr = input.into();
|
let input: SmolStr = input.into();
|
||||||
let mut local_tree: TreeBranch = COMMAND_TREE.clone();
|
let mut local_tree: TreeBranch = COMMAND_TREE.clone();
|
||||||
|
|
||||||
|
|
@ -91,11 +92,11 @@ pub fn parse_command(input: String) -> CommandResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(None) => {
|
Err(None) => {
|
||||||
if let Some(command_ref) = local_tree.callback() {
|
if let Some(command) = local_tree.command() {
|
||||||
println!("{command_ref} {params:?}");
|
println!("{} {params:?}", command.cb);
|
||||||
return CommandResult::Ok {
|
return CommandResult::Ok {
|
||||||
command: ParsedCommand {
|
command: ParsedCommand {
|
||||||
command_ref: command_ref.into(),
|
command_ref: command.cb.into(),
|
||||||
params,
|
params,
|
||||||
args,
|
args,
|
||||||
flags,
|
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 <https://pluralkit.me/commands>.",
|
||||||
|
);
|
||||||
|
|
||||||
// todo: check if last token is a common incorrect unquote (multi-member names etc)
|
// 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
|
// todo: check if this is a system name in pk;s command
|
||||||
return CommandResult::Err {
|
return CommandResult::Err { error };
|
||||||
error: format!("Unknown command `{input}`. For a list of possible commands, see <https://pluralkit.me/commands>."),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Err(Some(short_circuit)) => {
|
Err(Some(short_circuit)) => {
|
||||||
return CommandResult::Err {
|
return CommandResult::Err {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,12 @@ fn main() {
|
||||||
.intersperse(" ".to_string())
|
.intersperse(" ".to_string())
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
if !cmd.is_empty() {
|
if !cmd.is_empty() {
|
||||||
let parsed = commands::parse_command(cmd);
|
use commands::CommandResult;
|
||||||
println!("{:#?}", parsed);
|
let parsed = commands::parse_command("pk;".to_string(), cmd);
|
||||||
|
match parsed {
|
||||||
|
CommandResult::Ok { command } => println!("{command:#?}"),
|
||||||
|
CommandResult::Err { error } => println!("{error}"),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for command in cmds::all() {
|
for command in cmds::all() {
|
||||||
println!("{}", command);
|
println!("{}", command);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
use ordermap::OrderMap;
|
use ordermap::OrderMap;
|
||||||
use smol_str::SmolStr;
|
|
||||||
|
|
||||||
use crate::{commands::Command, Token};
|
use crate::{commands::Command, Token};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TreeBranch {
|
pub struct TreeBranch {
|
||||||
current_command_key: Option<SmolStr>,
|
current_command: Option<Command>,
|
||||||
branches: OrderMap<Token, TreeBranch>,
|
branches: OrderMap<Token, TreeBranch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeBranch {
|
impl TreeBranch {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current_command_key: None,
|
current_command: None,
|
||||||
branches: OrderMap::new(),
|
branches: OrderMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,10 +19,10 @@ impl TreeBranch {
|
||||||
pub fn register_command(&mut self, command: Command) {
|
pub fn register_command(&mut self, command: Command) {
|
||||||
let mut current_branch = self;
|
let mut current_branch = self;
|
||||||
// iterate over tokens in command
|
// 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
|
// recursively get or create a sub-branch for each token
|
||||||
current_branch = current_branch.branches.entry(token).or_insert(TreeBranch {
|
current_branch = current_branch.branches.entry(token).or_insert(TreeBranch {
|
||||||
current_command_key: None,
|
current_command: None,
|
||||||
branches: OrderMap::new(),
|
branches: OrderMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -31,20 +30,38 @@ impl TreeBranch {
|
||||||
current_branch.branches.insert(
|
current_branch.branches.insert(
|
||||||
Token::Empty,
|
Token::Empty,
|
||||||
TreeBranch {
|
TreeBranch {
|
||||||
current_command_key: Some(command.cb),
|
current_command: Some(command),
|
||||||
branches: OrderMap::new(),
|
branches: OrderMap::new(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn callback(&self) -> Option<SmolStr> {
|
pub fn command(&self) -> Option<Command> {
|
||||||
self.current_command_key.clone()
|
self.current_command.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn possible_tokens(&self) -> impl Iterator<Item = &Token> {
|
pub fn possible_tokens(&self) -> impl Iterator<Item = &Token> {
|
||||||
self.branches.keys()
|
self.branches.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn possible_commands(&self, max_depth: usize) -> Vec<Command> {
|
||||||
|
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> {
|
pub fn get_branch(&self, token: &Token) -> Option<&TreeBranch> {
|
||||||
self.branches.get(token)
|
self.branches.get(token)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue