mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
fix command suggestions breaking if specifying a parameter
This commit is contained in:
parent
dc9b7b3e6b
commit
9122e64a41
2 changed files with 97 additions and 3 deletions
|
|
@ -52,6 +52,14 @@ pub fn parse_command(
|
||||||
let mut filtered_tokens: Vec<Token> = Vec::new();
|
let mut filtered_tokens: Vec<Token> = Vec::new();
|
||||||
let mut last_optional_param_error: Option<(SmolStr, SmolStr)> = None;
|
let mut last_optional_param_error: Option<(SmolStr, SmolStr)> = None;
|
||||||
|
|
||||||
|
// track the best attempt at parsing (deepest matched tokens)
|
||||||
|
// so we can use it for error messages/suggestions even if we backtrack later
|
||||||
|
let mut best_attempt: Option<(
|
||||||
|
Tree,
|
||||||
|
Vec<(Tree, (Token, TokenMatchResult, usize), usize)>,
|
||||||
|
usize,
|
||||||
|
)> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut possible_tokens = local_tree
|
let mut possible_tokens = local_tree
|
||||||
.possible_tokens()
|
.possible_tokens()
|
||||||
|
|
@ -115,6 +123,17 @@ pub fn parse_command(
|
||||||
(found_token.clone(), result.clone(), *new_pos),
|
(found_token.clone(), result.clone(), *new_pos),
|
||||||
current_pos,
|
current_pos,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// update best attempt if we're deeper
|
||||||
|
if best_attempt.as_ref().map(|x| x.1.len()).unwrap_or(0) < matched_tokens.len()
|
||||||
|
{
|
||||||
|
best_attempt = Some((
|
||||||
|
next_tree.clone(),
|
||||||
|
matched_tokens.clone(),
|
||||||
|
*new_pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
filtered_tokens.clear(); // new branch, new tokens
|
filtered_tokens.clear(); // new branch, new tokens
|
||||||
local_tree = next_tree.clone();
|
local_tree = next_tree.clone();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -145,10 +164,29 @@ pub fn parse_command(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restore best attempt if it's deeper than current state
|
||||||
|
// this helps when we backtracked out of the correct path because of a later error
|
||||||
|
if let Some((best_tree, best_matched, best_pos)) = best_attempt {
|
||||||
|
if best_matched.len() > matched_tokens.len() {
|
||||||
|
local_tree = best_tree;
|
||||||
|
matched_tokens = best_matched;
|
||||||
|
current_pos = best_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut error = format!("Unknown command `{prefix}{input}`.");
|
let mut error = format!("Unknown command `{prefix}{input}`.");
|
||||||
|
|
||||||
let possible_commands =
|
// normalize input by replacing parameters with placeholders
|
||||||
rank_possible_commands(&input, local_tree.possible_commands(usize::MAX));
|
let mut normalized_input = String::new();
|
||||||
|
for (_, (token, _, _), _) in &matched_tokens {
|
||||||
|
write!(&mut normalized_input, "{token} ").unwrap();
|
||||||
|
}
|
||||||
|
normalized_input.push_str(&input[current_pos..].trim_start());
|
||||||
|
|
||||||
|
let possible_commands = rank_possible_commands(
|
||||||
|
&normalized_input,
|
||||||
|
local_tree.possible_commands(usize::MAX),
|
||||||
|
);
|
||||||
if possible_commands.is_empty().not() {
|
if possible_commands.is_empty().not() {
|
||||||
error.push_str(" Perhaps you meant one of the following commands:\n");
|
error.push_str(" Perhaps you meant one of the following commands:\n");
|
||||||
fmt_commands_list(&mut error, &prefix, possible_commands);
|
fmt_commands_list(&mut error, &prefix, possible_commands);
|
||||||
|
|
@ -339,7 +377,6 @@ fn next_token<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: should probably move this somewhere else
|
// todo: should probably move this somewhere else
|
||||||
/// returns true if wrote possible commands, false if not
|
|
||||||
fn rank_possible_commands(
|
fn rank_possible_commands(
|
||||||
input: &str,
|
input: &str,
|
||||||
possible_commands: impl IntoIterator<Item = &Command>,
|
possible_commands: impl IntoIterator<Item = &Command>,
|
||||||
|
|
|
||||||
57
crates/command_parser/tests/ranking.rs
Normal file
57
crates/command_parser/tests/ranking.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use command_parser::{parse_command, Tree, command::Command, parameter::*, tokens};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_typoed_command_with_parameter() {
|
||||||
|
let message_token = ("message", ["msg", "messageinfo"]);
|
||||||
|
let author_token = ("author", ["sender", "a"]);
|
||||||
|
|
||||||
|
// message <optional msg ref> author
|
||||||
|
let cmd = Command::new(
|
||||||
|
tokens!(message_token, Optional(MESSAGE_REF), author_token),
|
||||||
|
"message_author"
|
||||||
|
).help("Shows the author of a proxied message");
|
||||||
|
|
||||||
|
let mut tree = Tree::default();
|
||||||
|
tree.register_command(cmd);
|
||||||
|
|
||||||
|
let input = "message 1 auth";
|
||||||
|
let result = parse_command(tree, "pk;".to_string(), input.to_string());
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => panic!("Should have failed to parse"),
|
||||||
|
Err(msg) => {
|
||||||
|
println!("Error: {}", msg);
|
||||||
|
assert!(msg.contains("Perhaps you meant one of the following commands"));
|
||||||
|
assert!(msg.contains("message <target message link/id> author"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_typoed_command_with_flags() {
|
||||||
|
let message_token = ("message", ["msg", "messageinfo"]);
|
||||||
|
let author_token = ("author", ["sender", "a"]);
|
||||||
|
|
||||||
|
let cmd = Command::new(
|
||||||
|
tokens!(message_token, author_token),
|
||||||
|
"message_author"
|
||||||
|
)
|
||||||
|
.flag(("flag", ["f"]))
|
||||||
|
.flag(("flag2", ["f2"]))
|
||||||
|
.help("Shows the author of a proxied message");
|
||||||
|
|
||||||
|
let mut tree = Tree::default();
|
||||||
|
tree.register_command(cmd);
|
||||||
|
|
||||||
|
let input = "message auth -f -flag2";
|
||||||
|
let result = parse_command(tree, "pk;".to_string(), input.to_string());
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => panic!("Should have failed to parse"),
|
||||||
|
Err(msg) => {
|
||||||
|
println!("Error: {}", msg);
|
||||||
|
assert!(msg.contains("Perhaps you meant one of the following commands"));
|
||||||
|
assert!(msg.contains("message author"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue