diff options
| author | mat <git@matdoes.dev> | 2023-10-12 22:01:15 -0500 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2023-10-12 22:01:15 -0500 |
| commit | 79ad1e93bf6ce2b7c2da6925a7c85b33bb76f154 (patch) | |
| tree | 19004abf51cb19eb3b5cc13c61d670889cb228a0 /azalea-brigadier/src | |
| parent | f505ace721d4c214cd32143febd0a066b6b95ce5 (diff) | |
| download | azalea-drasl-79ad1e93bf6ce2b7c2da6925a7c85b33bb76f154.tar.xz | |
brigadier suggestions
closes #109
Diffstat (limited to 'azalea-brigadier/src')
| -rwxr-xr-x | azalea-brigadier/src/arguments/argument_type.rs | 14 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/bool_argument_type.rs | 19 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/double_argument_type.rs | 7 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/float_argument_type.rs | 7 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/integer_argument_type.rs | 7 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/long_argument_type.rs | 7 | ||||
| -rw-r--r-- | azalea-brigadier/src/arguments/string_argument_type.rs | 11 | ||||
| -rwxr-xr-x | azalea-brigadier/src/builder/required_argument_builder.rs | 16 | ||||
| -rwxr-xr-x | azalea-brigadier/src/command_dispatcher.rs | 36 | ||||
| -rwxr-xr-x | azalea-brigadier/src/context/command_context_builder.rs | 39 | ||||
| -rwxr-xr-x | azalea-brigadier/src/context/mod.rs | 1 | ||||
| -rw-r--r-- | azalea-brigadier/src/context/suggestion_context.rs | 11 | ||||
| -rwxr-xr-x | azalea-brigadier/src/suggestion/mod.rs | 24 | ||||
| -rwxr-xr-x | azalea-brigadier/src/suggestion/suggestions.rs | 36 | ||||
| -rwxr-xr-x | azalea-brigadier/src/suggestion/suggestions_builder.rs | 24 | ||||
| -rwxr-xr-x | azalea-brigadier/src/tree/mod.rs | 24 |
16 files changed, 227 insertions, 56 deletions
diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs index f44233e1..d7bfa7d6 100755 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -1,7 +1,19 @@ use std::{any::Any, sync::Arc}; -use crate::{exceptions::CommandSyntaxException, string_reader::StringReader}; +use crate::{ + exceptions::CommandSyntaxException, + string_reader::StringReader, + suggestion::{Suggestions, SuggestionsBuilder}, +}; pub trait ArgumentType { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException>; + + fn list_suggestions(&self, _builder: SuggestionsBuilder) -> Suggestions { + Suggestions::default() + } + + fn examples(&self) -> Vec<String> { + vec![] + } } diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs index 2e348c7b..a73a9da5 100644 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -1,7 +1,10 @@ use std::{any::Any, sync::Arc}; use crate::{ - context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader, + context::CommandContext, + exceptions::CommandSyntaxException, + string_reader::StringReader, + suggestion::{Suggestions, SuggestionsBuilder}, }; use super::ArgumentType; @@ -13,6 +16,20 @@ impl ArgumentType for Boolean { fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { Ok(Arc::new(reader.read_boolean()?)) } + + fn list_suggestions(&self, mut builder: SuggestionsBuilder) -> Suggestions { + if "true".starts_with(builder.remaining_lowercase()) { + builder = builder.suggest("true"); + } + if "false".starts_with(builder.remaining_lowercase()) { + builder = builder.suggest("false"); + } + builder.build() + } + + fn examples(&self) -> Vec<String> { + vec!["true".to_string(), "false".to_string()] + } } pub fn bool() -> impl ArgumentType { diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs index 9502a680..ea99f1cf 100644 --- a/azalea-brigadier/src/arguments/double_argument_type.rs +++ b/azalea-brigadier/src/arguments/double_argument_type.rs @@ -40,6 +40,13 @@ impl ArgumentType for Double { } Ok(Arc::new(result)) } + + fn examples(&self) -> Vec<String> { + vec!["0", "1.2", ".5", "-1", "-.5", "-1234.56"] + .into_iter() + .map(|s| s.to_string()) + .collect() + } } pub fn double() -> impl ArgumentType { diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs index a2831a08..2333499a 100644 --- a/azalea-brigadier/src/arguments/float_argument_type.rs +++ b/azalea-brigadier/src/arguments/float_argument_type.rs @@ -40,6 +40,13 @@ impl ArgumentType for Float { } Ok(Arc::new(result)) } + + fn examples(&self) -> Vec<String> { + vec!["0", "1.2", ".5", "-1", "-.5", "-1234.56"] + .into_iter() + .map(|s| s.to_string()) + .collect() + } } pub fn float() -> impl ArgumentType { diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs index a31a6e70..cc4755ee 100644 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs @@ -40,6 +40,13 @@ impl ArgumentType for Integer { } Ok(Arc::new(result)) } + + fn examples(&self) -> Vec<String> { + vec!["0", "123", "-123"] + .into_iter() + .map(|s| s.to_string()) + .collect() + } } pub fn integer() -> impl ArgumentType { diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs index d557881a..4e36abee 100644 --- a/azalea-brigadier/src/arguments/long_argument_type.rs +++ b/azalea-brigadier/src/arguments/long_argument_type.rs @@ -40,6 +40,13 @@ impl ArgumentType for Long { } Ok(Arc::new(result)) } + + fn examples(&self) -> Vec<String> { + vec!["0", "123", "-123"] + .into_iter() + .map(|s| s.to_string()) + .collect() + } } pub fn long() -> impl ArgumentType { diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs index 9fd70d13..d38fbc79 100644 --- a/azalea-brigadier/src/arguments/string_argument_type.rs +++ b/azalea-brigadier/src/arguments/string_argument_type.rs @@ -29,6 +29,17 @@ impl ArgumentType for StringArgument { }; Ok(Arc::new(result)) } + + fn examples(&self) -> Vec<String> { + match self { + StringArgument::SingleWord => vec!["word", "words_with_underscores"], + StringArgument::QuotablePhrase => vec!["\"quoted phrase\"", "word", "\"\""], + StringArgument::GreedyPhrase => vec!["word", "words with spaces", "\"and symbols\""], + } + .into_iter() + .map(|s| s.to_string()) + .collect() + } } /// Match up until the next space. diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 60fa713f..1c79f619 100755 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -1,6 +1,9 @@ use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; use crate::{ - arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader, + arguments::ArgumentType, + exceptions::CommandSyntaxException, + string_reader::StringReader, + suggestion::{Suggestions, SuggestionsBuilder}, }; use std::{any::Any, fmt::Debug, sync::Arc}; @@ -22,6 +25,17 @@ impl Argument { pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> { self.parser.parse(reader) } + + pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions { + // TODO: custom suggestions + // https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71 + + self.parser.list_suggestions(builder) + } + + pub fn examples(&self) -> Vec<String> { + self.parser.examples() + } } impl From<Argument> for ArgumentBuilderType { diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index 0ec07a54..3b5987fc 100755 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -6,6 +6,7 @@ use crate::{ exceptions::{BuiltInExceptions, CommandSyntaxException}, parse_results::ParseResults, string_reader::StringReader, + suggestion::{Suggestions, SuggestionsBuilder}, tree::CommandNode, }; use std::{ @@ -474,6 +475,41 @@ impl<S> CommandDispatcher<S> { Some(this) } + + pub fn get_completion_suggestions(parse: ParseResults<S>) -> Suggestions { + let cursor = parse.reader.total_length(); + Self::get_completion_suggestions_with_cursor(parse, cursor) + } + + pub fn get_completion_suggestions_with_cursor( + parse: ParseResults<S>, + cursor: usize, + ) -> Suggestions { + let context = parse.context; + + let node_before_cursor = context.find_suggestion_context(cursor); + let parent = node_before_cursor.parent; + let start = usize::min(node_before_cursor.start_pos, cursor); + + let full_input = parse.reader.string(); + let truncated_input = full_input[..cursor].to_string(); + let truncated_input_lowercase = truncated_input.to_lowercase(); + + let mut all_suggestions = Vec::new(); + for node in parent.read().children.values() { + let suggestions = node.read().list_suggestions( + context.build(&truncated_input), + SuggestionsBuilder::new_with_lowercase( + &truncated_input, + &truncated_input_lowercase, + start, + ), + ); + all_suggestions.push(suggestions); + } + + Suggestions::merge(full_input, &all_suggestions) + } } impl<S> Default for CommandDispatcher<S> { diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs index 99c40dac..75295825 100755 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -2,7 +2,7 @@ use parking_lot::RwLock; use super::{ command_context::CommandContext, parsed_command_node::ParsedCommandNode, - string_range::StringRange, ParsedArgument, + string_range::StringRange, suggestion_context::SuggestionContext, ParsedArgument, }; use crate::{ command_dispatcher::CommandDispatcher, @@ -99,6 +99,43 @@ impl<'a, S> CommandContextBuilder<'a, S> { input: input.to_string(), } } + + pub fn find_suggestion_context(&self, cursor: usize) -> SuggestionContext<S> { + if self.range.start() > cursor { + panic!("Can't find node before cursor"); + } + + if self.range.end() < cursor { + if let Some(child) = &self.child { + child.find_suggestion_context(cursor) + } else if let Some(last) = self.nodes.last() { + SuggestionContext { + parent: Arc::clone(&last.node), + start_pos: last.range.end() + 1, + } + } else { + SuggestionContext { + parent: Arc::clone(&self.root), + start_pos: self.range.start(), + } + } + } else { + let mut prev = &self.root; + for node in &self.nodes { + if node.range.start() <= cursor && cursor <= node.range.end() { + return SuggestionContext { + parent: Arc::clone(prev), + start_pos: node.range.start(), + }; + } + prev = &node.node; + } + SuggestionContext { + parent: Arc::clone(prev), + start_pos: self.range.start(), + } + } + } } impl<S> Debug for CommandContextBuilder<'_, S> { diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs index d535602a..28e1a12e 100755 --- a/azalea-brigadier/src/context/mod.rs +++ b/azalea-brigadier/src/context/mod.rs @@ -3,6 +3,7 @@ mod command_context_builder; mod parsed_argument; mod parsed_command_node; mod string_range; +pub mod suggestion_context; pub use command_context::CommandContext; pub use command_context_builder::CommandContextBuilder; diff --git a/azalea-brigadier/src/context/suggestion_context.rs b/azalea-brigadier/src/context/suggestion_context.rs new file mode 100644 index 00000000..58a73fdb --- /dev/null +++ b/azalea-brigadier/src/context/suggestion_context.rs @@ -0,0 +1,11 @@ +use std::sync::Arc; + +use parking_lot::RwLock; + +use crate::tree::CommandNode; + +#[derive(Debug)] +pub struct SuggestionContext<S> { + pub parent: Arc<RwLock<CommandNode<S>>>, + pub start_pos: usize, +} diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs index bc0e7608..fbebfe8a 100755 --- a/azalea-brigadier/src/suggestion/mod.rs +++ b/azalea-brigadier/src/suggestion/mod.rs @@ -20,13 +20,10 @@ pub use suggestions_builder::SuggestionsBuilder; /// The `M` generic is the type of the tooltip, so for example a `String` or /// just `()` if you don't care about it. #[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Suggestion<M = ()> -where - M: Clone, -{ +pub struct Suggestion { pub range: StringRange, value: SuggestionValue, - pub tooltip: Option<M>, + pub tooltip: Option<String>, } #[derive(Debug, Clone, Hash, Eq, PartialEq)] @@ -35,18 +32,16 @@ pub enum SuggestionValue { Text(String), } -impl Suggestion<()> { - pub fn new(range: StringRange, text: &str) -> Suggestion<()> { +impl Suggestion { + pub fn new(range: StringRange, text: &str) -> Suggestion { Suggestion { range, value: SuggestionValue::Text(text.to_string()), tooltip: None, } } -} -impl<M: Clone> Suggestion<M> { - pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: M) -> Self { + pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: String) -> Self { Self { range, value: SuggestionValue::Text(text.to_string()), @@ -71,7 +66,7 @@ impl<M: Clone> Suggestion<M> { result } - pub fn expand(&self, command: &str, range: StringRange) -> Suggestion<M> { + pub fn expand(&self, command: &str, range: StringRange) -> Suggestion { if range == self.range { return self.clone(); } @@ -140,10 +135,13 @@ impl PartialOrd for SuggestionValue { } #[cfg(feature = "azalea-buf")] -impl McBufWritable for Suggestion<FormattedText> { +impl McBufWritable for Suggestion { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { self.value.to_string().write_into(buf)?; - self.tooltip.write_into(buf)?; + self.tooltip + .clone() + .map(FormattedText::from) + .write_into(buf)?; Ok(()) } } diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index a5119d88..487e4233 100755 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -12,21 +12,18 @@ use azalea_chat::FormattedText; use std::io::{Cursor, Write}; use std::{collections::HashSet, hash::Hash}; -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Suggestions<M> -where - M: Clone + PartialEq + Hash, -{ +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] +pub struct Suggestions { range: StringRange, - suggestions: Vec<Suggestion<M>>, + suggestions: Vec<Suggestion>, } -impl<M: Clone + Eq + Hash> Suggestions<M> { - pub fn new(range: StringRange, suggestions: Vec<Suggestion<M>>) -> Self { +impl Suggestions { + pub fn new(range: StringRange, suggestions: Vec<Suggestion>) -> Self { Self { range, suggestions } } - pub fn merge(command: &str, input: &[Suggestions<M>]) -> Self { + pub fn merge(command: &str, input: &[Suggestions]) -> Self { if input.is_empty() { return Suggestions::default(); } else if input.len() == 1 { @@ -41,7 +38,7 @@ impl<M: Clone + Eq + Hash> Suggestions<M> { Suggestions::create(command, &texts) } - pub fn create(command: &str, suggestions: &HashSet<Suggestion<M>>) -> Self { + pub fn create(command: &str, suggestions: &HashSet<Suggestion>) -> Self { if suggestions.is_empty() { return Suggestions::default(); }; @@ -70,7 +67,7 @@ impl<M: Clone + Eq + Hash> Suggestions<M> { self.suggestions.is_empty() } - pub fn list(&self) -> &[Suggestion<M>] { + pub fn list(&self) -> &[Suggestion] { &self.suggestions } @@ -79,19 +76,8 @@ impl<M: Clone + Eq + Hash> Suggestions<M> { } } -// this can't be derived because that'd require the generic to have `Default` -// too even if it's not actually necessary -impl<M: Clone + Hash + Eq> Default for Suggestions<M> { - fn default() -> Self { - Self { - range: StringRange::default(), - suggestions: Vec::new(), - } - } -} - #[cfg(feature = "azalea-buf")] -impl McBufReadable for Suggestions<FormattedText> { +impl McBufReadable for Suggestions { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { #[derive(McBuf)] struct StandaloneSuggestion { @@ -109,7 +95,7 @@ impl McBufReadable for Suggestions<FormattedText> { .into_iter() .map(|s| Suggestion { value: SuggestionValue::Text(s.text), - tooltip: s.tooltip, + tooltip: s.tooltip.map(|t| t.to_string()), range, }) .collect::<Vec<_>>(); @@ -120,7 +106,7 @@ impl McBufReadable for Suggestions<FormattedText> { } #[cfg(feature = "azalea-buf")] -impl McBufWritable for Suggestions<FormattedText> { +impl McBufWritable for Suggestions { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { (self.range.start() as u32).var_write_into(buf)?; (self.range.length() as u32).var_write_into(buf)?; diff --git a/azalea-brigadier/src/suggestion/suggestions_builder.rs b/azalea-brigadier/src/suggestion/suggestions_builder.rs index 469a7b98..4e6296dd 100755 --- a/azalea-brigadier/src/suggestion/suggestions_builder.rs +++ b/azalea-brigadier/src/suggestion/suggestions_builder.rs @@ -1,24 +1,20 @@ use std::collections::HashSet; -use std::hash::Hash; use crate::context::StringRange; use super::{Suggestion, SuggestionValue, Suggestions}; #[derive(PartialEq, Debug)] -pub struct SuggestionsBuilder<M = ()> -where - M: Clone + Eq + Hash, -{ +pub struct SuggestionsBuilder { input: String, input_lowercase: String, start: usize, remaining: String, remaining_lowercase: String, - result: HashSet<Suggestion<M>>, + result: HashSet<Suggestion>, } -impl SuggestionsBuilder<()> { +impl SuggestionsBuilder { pub fn new(input: &str, start: usize) -> Self { Self::new_with_lowercase(input, input.to_lowercase().as_str(), start) } @@ -35,7 +31,7 @@ impl SuggestionsBuilder<()> { } } -impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> { +impl SuggestionsBuilder { pub fn input(&self) -> &str { &self.input } @@ -52,7 +48,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> { &self.remaining_lowercase } - pub fn build(&self) -> Suggestions<M> { + pub fn build(&self) -> Suggestions { Suggestions::create(&self.input, &self.result) } @@ -68,7 +64,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> { self } - pub fn suggest_with_tooltip(mut self, text: &str, tooltip: M) -> Self { + pub fn suggest_with_tooltip(mut self, text: &str, tooltip: String) -> Self { if text == self.remaining { return self; } @@ -89,7 +85,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> { self } - pub fn suggest_integer_with_tooltip(mut self, value: i32, tooltip: M) -> Self { + pub fn suggest_integer_with_tooltip(mut self, value: i32, tooltip: String) -> Self { self.result.insert(Suggestion { range: StringRange::between(self.start, self.input.len()), value: SuggestionValue::Integer(value), @@ -99,16 +95,16 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> { } #[allow(clippy::should_implement_trait)] - pub fn add(mut self, other: SuggestionsBuilder<M>) -> Self { + pub fn add(mut self, other: SuggestionsBuilder) -> Self { self.result.extend(other.result); self } - pub fn create_offset(&self, start: usize) -> SuggestionsBuilder<()> { + pub fn create_offset(&self, start: usize) -> SuggestionsBuilder { SuggestionsBuilder::new_with_lowercase(&self.input, &self.input_lowercase, start) } - pub fn restart(&self) -> SuggestionsBuilder<()> { + pub fn restart(&self) -> SuggestionsBuilder { self.create_offset(self.start) } } diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs index be2efef1..a2b1f38a 100755 --- a/azalea-brigadier/src/tree/mod.rs +++ b/azalea-brigadier/src/tree/mod.rs @@ -9,6 +9,7 @@ use crate::{ exceptions::{BuiltInExceptions, CommandSyntaxException}, modifier::RedirectModifier, string_reader::StringReader, + suggestion::{Suggestions, SuggestionsBuilder}, }; use std::{ collections::{BTreeMap, HashMap}, @@ -209,6 +210,29 @@ impl<S> CommandNode<S> { } None } + + pub fn list_suggestions( + &self, + // context is here because that's how it is in mojang's brigadier, but we haven't + // implemented custom suggestions yet so this is unused rn + _context: CommandContext<S>, + builder: SuggestionsBuilder, + ) -> Suggestions { + match &self.value { + ArgumentBuilderType::Literal(literal) => { + if literal + .value + .to_lowercase() + .starts_with(builder.remaining_lowercase()) + { + builder.suggest(&literal.value).build() + } else { + Suggestions::default() + } + } + ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder), + } + } } impl<S> Debug for CommandNode<S> { |
