Skip to content

Commit

Permalink
brigadier suggestions
Browse files Browse the repository at this point in the history
closes #109
  • Loading branch information
mat-1 committed Oct 13, 2023
1 parent f505ace commit 79ad1e9
Show file tree
Hide file tree
Showing 32 changed files with 738 additions and 59 deletions.
14 changes: 13 additions & 1 deletion azalea-brigadier/src/arguments/argument_type.rs
Original file line number Diff line number Diff line change
@@ -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![]
}
}
19 changes: 18 additions & 1 deletion azalea-brigadier/src/arguments/bool_argument_type.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions azalea-brigadier/src/arguments/double_argument_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions azalea-brigadier/src/arguments/float_argument_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions azalea-brigadier/src/arguments/integer_argument_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions azalea-brigadier/src/arguments/long_argument_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions azalea-brigadier/src/arguments/string_argument_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 15 additions & 1 deletion azalea-brigadier/src/builder/required_argument_builder.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions azalea-brigadier/src/command_dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
exceptions::{BuiltInExceptions, CommandSyntaxException},
parse_results::ParseResults,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
tree::CommandNode,
};
use std::{
Expand Down Expand Up @@ -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> {
Expand Down
39 changes: 38 additions & 1 deletion azalea-brigadier/src/context/command_context_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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> {
Expand Down
1 change: 1 addition & 0 deletions azalea-brigadier/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions azalea-brigadier/src/context/suggestion_context.rs
Original file line number Diff line number Diff line change
@@ -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,
}
24 changes: 11 additions & 13 deletions azalea-brigadier/src/suggestion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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()),
Expand All @@ -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();
}
Expand Down Expand Up @@ -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(())
}
}
Loading

0 comments on commit 79ad1e9

Please sign in to comment.