mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 14:42:32 +00:00
brigadier usages
This commit is contained in:
parent
d5f424b8c2
commit
38db231ea8
28 changed files with 760 additions and 105 deletions
|
@ -16,6 +16,7 @@ pub enum ArgumentBuilderType {
|
|||
}
|
||||
|
||||
/// A node that hasn't yet been built.
|
||||
#[derive(Clone)]
|
||||
pub struct ArgumentBuilder<S> {
|
||||
arguments: CommandNode<S>,
|
||||
|
||||
|
@ -134,6 +135,10 @@ impl<S> ArgumentBuilder<S> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn arguments(&self) -> &CommandNode<S> {
|
||||
&self.arguments
|
||||
}
|
||||
|
||||
/// Manually build this node into a [`CommandNode`]. You probably don't need
|
||||
/// to do this yourself.
|
||||
pub fn build(self) -> CommandNode<S> {
|
||||
|
|
|
@ -8,7 +8,13 @@ use crate::{
|
|||
string_reader::StringReader,
|
||||
tree::CommandNode,
|
||||
};
|
||||
use std::{cmp::Ordering, collections::HashMap, mem, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet},
|
||||
mem,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// The root of the command tree. You need to make this to register commands.
|
||||
///
|
||||
|
@ -297,6 +303,182 @@ impl<S> CommandDispatcher<S> {
|
|||
})
|
||||
// Ok(if forked { successful_forks } else { result })
|
||||
}
|
||||
|
||||
pub fn get_all_usage(
|
||||
&self,
|
||||
node: &CommandNode<S>,
|
||||
source: Arc<S>,
|
||||
restricted: bool,
|
||||
) -> Vec<String> {
|
||||
let mut result = vec![];
|
||||
self.get_all_usage_recursive(node, source, &mut result, "", restricted);
|
||||
result
|
||||
}
|
||||
|
||||
fn get_all_usage_recursive(
|
||||
&self,
|
||||
node: &CommandNode<S>,
|
||||
source: Arc<S>,
|
||||
result: &mut Vec<String>,
|
||||
prefix: &str,
|
||||
restricted: bool,
|
||||
) {
|
||||
if restricted && !node.can_use(source.clone()) {
|
||||
return;
|
||||
}
|
||||
if node.command.is_some() {
|
||||
result.push(prefix.to_owned());
|
||||
}
|
||||
if let Some(redirect) = &node.redirect {
|
||||
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
|
||||
"...".to_string()
|
||||
} else {
|
||||
format!("-> {}", redirect.read().usage_text())
|
||||
};
|
||||
if prefix.is_empty() {
|
||||
result.push(format!("{} {redirect}", node.usage_text()));
|
||||
} else {
|
||||
result.push(format!("{prefix} {redirect}"));
|
||||
}
|
||||
} else {
|
||||
for child in node.children.values() {
|
||||
let child = child.read();
|
||||
self.get_all_usage_recursive(
|
||||
&child,
|
||||
Arc::clone(&source),
|
||||
result,
|
||||
if prefix.is_empty() {
|
||||
child.usage_text()
|
||||
} else {
|
||||
format!("{prefix} {}", child.usage_text())
|
||||
}
|
||||
.as_str(),
|
||||
restricted,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the possible executable commands from a specified node.
|
||||
///
|
||||
/// You may use [`Self::root`] as a target to get usage data for the entire
|
||||
/// command tree.
|
||||
pub fn get_smart_usage(
|
||||
&self,
|
||||
node: &CommandNode<S>,
|
||||
source: Arc<S>,
|
||||
) -> Vec<(Arc<RwLock<CommandNode<S>>>, String)> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let optional = node.command.is_some();
|
||||
for child in node.children.values() {
|
||||
let usage =
|
||||
self.get_smart_usage_recursive(&child.read(), source.clone(), optional, false);
|
||||
if let Some(usage) = usage {
|
||||
result.push((child.clone(), usage));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn get_smart_usage_recursive(
|
||||
&self,
|
||||
node: &CommandNode<S>,
|
||||
source: Arc<S>,
|
||||
optional: bool,
|
||||
deep: bool,
|
||||
) -> Option<String> {
|
||||
if !node.can_use(source.clone()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let this = if optional {
|
||||
format!("[{}]", node.usage_text())
|
||||
} else {
|
||||
node.usage_text()
|
||||
};
|
||||
let child_optional = node.command.is_some();
|
||||
let open = if child_optional { "[" } else { "(" };
|
||||
let close = if child_optional { "]" } else { ")" };
|
||||
|
||||
if deep {
|
||||
return Some(this);
|
||||
}
|
||||
|
||||
if let Some(redirect) = &node.redirect {
|
||||
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
|
||||
"...".to_string()
|
||||
} else {
|
||||
format!("-> {}", redirect.read().usage_text())
|
||||
};
|
||||
return Some(format!("{this} {redirect}"));
|
||||
}
|
||||
|
||||
let children = node
|
||||
.children
|
||||
.values()
|
||||
.filter(|child| child.read().can_use(source.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
match children.len().cmp(&1) {
|
||||
Ordering::Less => {}
|
||||
Ordering::Equal => {
|
||||
let usage = self.get_smart_usage_recursive(
|
||||
&children[0].read(),
|
||||
source.clone(),
|
||||
child_optional,
|
||||
child_optional,
|
||||
);
|
||||
if let Some(usage) = usage {
|
||||
return Some(format!("{this} {usage}"));
|
||||
}
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let mut child_usage = HashSet::new();
|
||||
for child in &children {
|
||||
let usage = self.get_smart_usage_recursive(
|
||||
&child.read(),
|
||||
source.clone(),
|
||||
child_optional,
|
||||
true,
|
||||
);
|
||||
if let Some(usage) = usage {
|
||||
child_usage.insert(usage);
|
||||
}
|
||||
}
|
||||
match child_usage.len().cmp(&1) {
|
||||
Ordering::Less => {}
|
||||
Ordering::Equal => {
|
||||
let usage = child_usage.into_iter().next().unwrap();
|
||||
let usage = if child_optional {
|
||||
format!("[{}]", usage)
|
||||
} else {
|
||||
usage
|
||||
};
|
||||
return Some(format!("{this} {usage}"));
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let mut builder = String::new();
|
||||
builder.push_str(open);
|
||||
let mut count = 0;
|
||||
for child in children {
|
||||
if count > 0 {
|
||||
builder.push('|');
|
||||
}
|
||||
builder.push_str(&child.read().usage_text());
|
||||
count += 1;
|
||||
}
|
||||
if count > 0 {
|
||||
builder.push_str(close);
|
||||
return Some(format!("{this} {builder}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for CommandDispatcher<S> {
|
||||
|
|
|
@ -30,7 +30,7 @@ impl<S> Clone for CommandContext<S> {
|
|||
command: self.command.clone(),
|
||||
root_node: self.root_node.clone(),
|
||||
nodes: self.nodes.clone(),
|
||||
range: self.range.clone(),
|
||||
range: self.range,
|
||||
child: self.child.clone(),
|
||||
modifier: self.modifier.clone(),
|
||||
forks: self.forks,
|
||||
|
@ -67,7 +67,7 @@ impl<S> CommandContext<S> {
|
|||
command: self.command.clone(),
|
||||
root_node: self.root_node.clone(),
|
||||
nodes: self.nodes.clone(),
|
||||
range: self.range.clone(),
|
||||
range: self.range,
|
||||
child: self.child.clone(),
|
||||
modifier: self.modifier.clone(),
|
||||
forks: self.forks,
|
||||
|
|
|
@ -34,7 +34,7 @@ impl<S> Clone for CommandContextBuilder<'_, S> {
|
|||
source: self.source.clone(),
|
||||
command: self.command.clone(),
|
||||
child: self.child.clone(),
|
||||
range: self.range.clone(),
|
||||
range: self.range,
|
||||
modifier: self.modifier.clone(),
|
||||
forks: self.forks,
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ impl<'a, S> CommandContextBuilder<'a, S> {
|
|||
pub fn with_node(&mut self, node: Arc<RwLock<CommandNode<S>>>, range: StringRange) -> &Self {
|
||||
self.nodes.push(ParsedCommandNode {
|
||||
node: node.clone(),
|
||||
range: range.clone(),
|
||||
range,
|
||||
});
|
||||
self.range = StringRange::encompassing(&self.range, &range);
|
||||
self.modifier = node.read().modifier.clone();
|
||||
|
@ -93,7 +93,7 @@ impl<'a, S> CommandContextBuilder<'a, S> {
|
|||
source: self.source.clone(),
|
||||
command: self.command.clone(),
|
||||
child: self.child.clone().map(|c| Rc::new(c.build(input))),
|
||||
range: self.range.clone(),
|
||||
range: self.range,
|
||||
forks: self.forks,
|
||||
modifier: self.modifier.clone(),
|
||||
input: input.to_string(),
|
||||
|
|
|
@ -14,7 +14,7 @@ impl<S> Clone for ParsedCommandNode<S> {
|
|||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
node: self.node.clone(),
|
||||
range: self.range.clone(),
|
||||
range: self.range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::cmp;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Copy)]
|
||||
pub struct StringRange {
|
||||
start: usize,
|
||||
end: usize,
|
||||
|
|
|
@ -8,6 +8,10 @@ use azalea_buf::McBufWritable;
|
|||
use azalea_chat::FormattedText;
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use std::io::Write;
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
hash::Hash,
|
||||
};
|
||||
pub use suggestions::Suggestions;
|
||||
pub use suggestions_builder::SuggestionsBuilder;
|
||||
|
||||
|
@ -16,22 +20,50 @@ 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 = String> {
|
||||
pub text: String,
|
||||
pub struct Suggestion<M = ()>
|
||||
where
|
||||
M: Clone,
|
||||
{
|
||||
pub range: StringRange,
|
||||
value: SuggestionValue,
|
||||
pub tooltip: Option<M>,
|
||||
}
|
||||
|
||||
impl<M: Clone> Suggestion<M> {
|
||||
pub fn apply(&self, input: &str) -> String {
|
||||
if self.range.start() == 0 && self.range.end() == input.len() {
|
||||
return input.to_string();
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum SuggestionValue {
|
||||
Integer(i32),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl Suggestion<()> {
|
||||
pub fn new(range: StringRange, text: &str) -> Suggestion<()> {
|
||||
Suggestion {
|
||||
range,
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
tooltip: None,
|
||||
}
|
||||
let mut result = String::with_capacity(self.text.len());
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Clone> Suggestion<M> {
|
||||
pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: M) -> Self {
|
||||
Self {
|
||||
range,
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
tooltip: Some(tooltip),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(&self, input: &str) -> String {
|
||||
let text = self.value.to_string();
|
||||
if self.range.start() == 0 && self.range.end() == input.len() {
|
||||
return text;
|
||||
}
|
||||
let mut result = String::with_capacity(text.len());
|
||||
if self.range.start() > 0 {
|
||||
result.push_str(&input[0..self.range.start()]);
|
||||
}
|
||||
result.push_str(&self.text);
|
||||
result.push_str(&text);
|
||||
if self.range.end() < input.len() {
|
||||
result.push_str(&input[self.range.end()..]);
|
||||
}
|
||||
|
@ -39,30 +71,78 @@ impl<M: Clone> Suggestion<M> {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn expand(&self, command: &str, range: &StringRange) -> Suggestion<M> {
|
||||
if range == &self.range {
|
||||
pub fn expand(&self, command: &str, range: StringRange) -> Suggestion<M> {
|
||||
if range == self.range {
|
||||
return self.clone();
|
||||
}
|
||||
let mut result = String::new();
|
||||
if range.start() < self.range.start() {
|
||||
result.push_str(&command[range.start()..self.range.start()]);
|
||||
}
|
||||
result.push_str(&self.text);
|
||||
result.push_str(&self.value.to_string());
|
||||
if range.end() > self.range.end() {
|
||||
result.push_str(&command[self.range.end()..range.end()]);
|
||||
}
|
||||
Suggestion {
|
||||
range: range.clone(),
|
||||
text: result,
|
||||
range,
|
||||
value: SuggestionValue::Text(result),
|
||||
tooltip: self.tooltip.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
self.value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl SuggestionValue {
|
||||
pub fn cmp_ignore_case(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match (self, other) {
|
||||
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => {
|
||||
a.to_lowercase().cmp(&b.to_lowercase())
|
||||
}
|
||||
(SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
|
||||
_ => {
|
||||
let a = self.to_string();
|
||||
let b = other.to_string();
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SuggestionValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SuggestionValue::Text(text) => write!(f, "{text}"),
|
||||
SuggestionValue::Integer(value) => write!(f, "{value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for SuggestionValue {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match (self, other) {
|
||||
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => a.cmp(b),
|
||||
(SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
|
||||
_ => {
|
||||
let a = self.to_string();
|
||||
let b = other.to_string();
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialOrd for SuggestionValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl McBufWritable for Suggestion<FormattedText> {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
self.text.write_into(buf)?;
|
||||
self.value.to_string().write_into(buf)?;
|
||||
self.tooltip.write_into(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use super::Suggestion;
|
||||
use crate::context::StringRange;
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use crate::suggestion::SuggestionValue;
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{
|
||||
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
||||
};
|
||||
|
@ -11,12 +13,19 @@ use std::io::{Cursor, Write};
|
|||
use std::{collections::HashSet, hash::Hash};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Suggestions<M = String> {
|
||||
pub range: StringRange,
|
||||
pub suggestions: Vec<Suggestion<M>>,
|
||||
pub struct Suggestions<M>
|
||||
where
|
||||
M: Clone + PartialEq + Hash,
|
||||
{
|
||||
range: StringRange,
|
||||
suggestions: Vec<Suggestion<M>>,
|
||||
}
|
||||
|
||||
impl<M: Clone + Eq + Hash> Suggestions<M> {
|
||||
pub fn new(range: StringRange, suggestions: Vec<Suggestion<M>>) -> Self {
|
||||
Self { range, suggestions }
|
||||
}
|
||||
|
||||
pub fn merge(command: &str, input: &[Suggestions<M>]) -> Self {
|
||||
if input.is_empty() {
|
||||
return Suggestions::default();
|
||||
|
@ -45,20 +54,34 @@ impl<M: Clone + Eq + Hash> Suggestions<M> {
|
|||
let range = StringRange::new(start, end);
|
||||
let mut texts = HashSet::new();
|
||||
for suggestion in suggestions {
|
||||
texts.insert(suggestion.expand(command, &range));
|
||||
texts.insert(suggestion.expand(command, range));
|
||||
}
|
||||
let mut sorted = texts.into_iter().collect::<Vec<_>>();
|
||||
sorted.sort_by(|a, b| a.text.cmp(&b.text));
|
||||
|
||||
sorted.sort_by(|a, b| a.value.cmp_ignore_case(&b.value));
|
||||
|
||||
Suggestions {
|
||||
range,
|
||||
suggestions: sorted,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.suggestions.is_empty()
|
||||
}
|
||||
|
||||
pub fn list(&self) -> &[Suggestion<M>] {
|
||||
&self.suggestions
|
||||
}
|
||||
|
||||
pub fn range(&self) -> StringRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
// this can't be derived because that'd require the generic to have `Default`
|
||||
// too even if it's not actually necessary
|
||||
impl<M> Default for Suggestions<M> {
|
||||
impl<M: Clone + Hash + Eq> Default for Suggestions<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
range: StringRange::default(),
|
||||
|
@ -85,12 +108,12 @@ impl McBufReadable for Suggestions<FormattedText> {
|
|||
let mut suggestions = Vec::<StandaloneSuggestion>::read_from(buf)?
|
||||
.into_iter()
|
||||
.map(|s| Suggestion {
|
||||
text: s.text,
|
||||
value: SuggestionValue::Text(s.text),
|
||||
tooltip: s.tooltip,
|
||||
range: range.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
suggestions.sort_by(|a, b| a.text.cmp(&b.text));
|
||||
suggestions.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
|
||||
Ok(Suggestions { range, suggestions })
|
||||
}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::context::StringRange;
|
||||
|
||||
use super::{Suggestion, Suggestions};
|
||||
use super::{Suggestion, SuggestionValue, Suggestions};
|
||||
|
||||
pub struct SuggestionsBuilder {
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct SuggestionsBuilder<M = ()>
|
||||
where
|
||||
M: Clone + Eq + Hash,
|
||||
{
|
||||
input: String,
|
||||
input_lowercase: String,
|
||||
start: usize,
|
||||
remaining: String,
|
||||
remaining_lowercase: String,
|
||||
result: HashSet<Suggestion>,
|
||||
result: HashSet<Suggestion<M>>,
|
||||
}
|
||||
|
||||
impl SuggestionsBuilder {
|
||||
impl SuggestionsBuilder<()> {
|
||||
pub fn new(input: &str, start: usize) -> Self {
|
||||
Self::new_with_lowercase(input, input.to_lowercase().as_str(), start)
|
||||
}
|
||||
|
@ -28,7 +33,9 @@ impl SuggestionsBuilder {
|
|||
result: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
||||
pub fn input(&self) -> &str {
|
||||
&self.input
|
||||
}
|
||||
|
@ -37,7 +44,7 @@ impl SuggestionsBuilder {
|
|||
self.start
|
||||
}
|
||||
|
||||
pub fn remianing(&self) -> &str {
|
||||
pub fn remaining(&self) -> &str {
|
||||
&self.remaining
|
||||
}
|
||||
|
||||
|
@ -45,7 +52,7 @@ impl SuggestionsBuilder {
|
|||
&self.remaining_lowercase
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Suggestions {
|
||||
pub fn build(&self) -> Suggestions<M> {
|
||||
Suggestions::create(&self.input, &self.result)
|
||||
}
|
||||
|
||||
|
@ -55,38 +62,53 @@ impl SuggestionsBuilder {
|
|||
}
|
||||
self.result.insert(Suggestion {
|
||||
range: StringRange::between(self.start, self.input.len()),
|
||||
text: text.to_string(),
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
tooltip: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn suggest_with_tooltip(mut self, text: &str, tooltip: String) -> Self {
|
||||
pub fn suggest_with_tooltip(mut self, text: &str, tooltip: M) -> Self {
|
||||
if text == self.remaining {
|
||||
return self;
|
||||
}
|
||||
self.result.insert(Suggestion {
|
||||
range: StringRange::between(self.start, self.input.len()),
|
||||
text: text.to_string(),
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
tooltip: Some(tooltip),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: integer suggestions
|
||||
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java#L74
|
||||
pub fn suggest_integer(mut self, value: i32) -> Self {
|
||||
self.result.insert(Suggestion {
|
||||
range: StringRange::between(self.start, self.input.len()),
|
||||
value: SuggestionValue::Integer(value),
|
||||
tooltip: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn suggest_integer_with_tooltip(mut self, value: i32, tooltip: M) -> Self {
|
||||
self.result.insert(Suggestion {
|
||||
range: StringRange::between(self.start, self.input.len()),
|
||||
value: SuggestionValue::Integer(value),
|
||||
tooltip: Some(tooltip),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add(mut self, other: SuggestionsBuilder) -> Self {
|
||||
pub fn add(mut self, other: SuggestionsBuilder<M>) -> Self {
|
||||
self.result.extend(other.result);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create_offset(&self, start: usize) -> Self {
|
||||
pub fn create_offset(&self, start: usize) -> SuggestionsBuilder<()> {
|
||||
SuggestionsBuilder::new_with_lowercase(&self.input, &self.input_lowercase, start)
|
||||
}
|
||||
|
||||
pub fn restart(self) -> Self {
|
||||
pub fn restart(&self) -> SuggestionsBuilder<()> {
|
||||
self.create_offset(self.start)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,13 @@ use crate::{
|
|||
modifier::RedirectModifier,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, ptr, sync::Arc};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
ptr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync>>;
|
||||
|
||||
|
@ -19,7 +25,8 @@ pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync
|
|||
pub struct CommandNode<S> {
|
||||
pub value: ArgumentBuilderType,
|
||||
|
||||
pub children: HashMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||
// this is a BTreeMap because children need to be ordered when getting command suggestions
|
||||
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||
pub literals: HashMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||
pub arguments: HashMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||
|
||||
|
@ -125,6 +132,13 @@ impl<S> CommandNode<S> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn usage_text(&self) -> String {
|
||||
match &self.value {
|
||||
ArgumentBuilderType::Argument(argument) => format!("<{}>", argument.name),
|
||||
ArgumentBuilderType::Literal(literal) => literal.value.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child(&self, name: &str) -> Option<Arc<RwLock<CommandNode<S>>>> {
|
||||
self.children.get(name).cloned()
|
||||
}
|
||||
|
@ -216,7 +230,7 @@ impl<S> Default for CommandNode<S> {
|
|||
Self {
|
||||
value: ArgumentBuilderType::Literal(Literal::default()),
|
||||
|
||||
children: HashMap::new(),
|
||||
children: BTreeMap::new(),
|
||||
literals: HashMap::new(),
|
||||
arguments: HashMap::new(),
|
||||
|
||||
|
|
6
azalea-brigadier/tests/arguments/mod.rs
Normal file
6
azalea-brigadier/tests/arguments/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod bool_argument_type_test;
|
||||
mod double_argument_type_test;
|
||||
mod float_argument_type_test;
|
||||
mod integer_argument_type_test;
|
||||
mod long_argument_type_test;
|
||||
mod string_argument_type_test;
|
|
@ -1,41 +1,17 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
arguments::integer_argument_type::integer,
|
||||
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
||||
};
|
||||
|
||||
use super::ArgumentBuilder;
|
||||
|
||||
// public class ArgumentBuilderTest {
|
||||
// private TestableArgumentBuilder<Object> builder;
|
||||
|
||||
// @Before
|
||||
// public void setUp() throws Exception {
|
||||
// builder = new TestableArgumentBuilder<>();
|
||||
// }
|
||||
|
||||
// @Test
|
||||
// public void testArguments() throws Exception {
|
||||
// final RequiredArgumentBuilder<Object, ?> argument = argument("bar",
|
||||
// integer());
|
||||
|
||||
// builder.then(argument);
|
||||
|
||||
// assertThat(builder.getArguments(), hasSize(1));
|
||||
// assertThat(builder.getArguments(), hasItem((CommandNode<Object>)
|
||||
// argument.build())); }
|
||||
use azalea_brigadier::{builder::argument_builder::ArgumentBuilder, prelude::*};
|
||||
|
||||
#[test]
|
||||
fn test_arguments() {
|
||||
let mut builder: ArgumentBuilder<()> = literal("foo");
|
||||
let builder: ArgumentBuilder<()> = literal("foo");
|
||||
|
||||
let argument: ArgumentBuilder<()> = argument("bar", integer());
|
||||
builder.then(argument.clone());
|
||||
assert_eq!(builder.arguments.children.len(), 1);
|
||||
let builder = builder.then(argument.clone());
|
||||
assert_eq!(builder.arguments().children.len(), 1);
|
||||
let built_argument = Rc::new(argument.build());
|
||||
assert!(builder
|
||||
.arguments
|
||||
.arguments()
|
||||
.children
|
||||
.values()
|
||||
.any(|e| *e.read() == *built_argument));
|
||||
|
|
3
azalea-brigadier/tests/builder/mod.rs
Normal file
3
azalea-brigadier/tests/builder/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod argument_builder_test;
|
||||
mod literal_argument_builder_test;
|
||||
mod required_argument_builder_test;
|
|
@ -1 +1,143 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use azalea_brigadier::{prelude::*, tree::CommandNode};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
fn setup() -> CommandDispatcher<()> {
|
||||
let command = |_: &CommandContext<()>| 0;
|
||||
|
||||
let mut subject = CommandDispatcher::new();
|
||||
subject.register(
|
||||
literal("a")
|
||||
.then(
|
||||
literal("1")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command)),
|
||||
)
|
||||
.then(
|
||||
literal("2")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command)),
|
||||
),
|
||||
);
|
||||
subject.register(literal("b").then(literal("1").executes(command)));
|
||||
subject.register(literal("c").executes(command));
|
||||
subject.register(literal("d").requires(|_| false).executes(command));
|
||||
subject.register(
|
||||
literal("e").executes(command).then(
|
||||
literal("1")
|
||||
.executes(command)
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command)),
|
||||
),
|
||||
);
|
||||
subject.register(
|
||||
literal("f")
|
||||
.then(
|
||||
literal("1")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command).requires(|_| false)),
|
||||
)
|
||||
.then(
|
||||
literal("2")
|
||||
.then(literal("i").executes(command).requires(|_| false))
|
||||
.then(literal("ii").executes(command)),
|
||||
),
|
||||
);
|
||||
subject.register(
|
||||
literal("g")
|
||||
.executes(command)
|
||||
.then(literal("1").then(literal("i").executes(command))),
|
||||
);
|
||||
subject.register(
|
||||
literal("h")
|
||||
.executes(command)
|
||||
.then(literal("1").then(literal("i").executes(command)))
|
||||
.then(literal("2").then(literal("i").then(literal("ii").executes(command))))
|
||||
.then(literal("3").executes(command)),
|
||||
);
|
||||
subject.register(
|
||||
literal("i")
|
||||
.executes(command)
|
||||
.then(literal("1").executes(command))
|
||||
.then(literal("2").executes(command)),
|
||||
);
|
||||
subject.register(literal("j").redirect(subject.root.clone()));
|
||||
subject.register(literal("k").redirect(get(&subject, "h")));
|
||||
subject
|
||||
}
|
||||
|
||||
fn get(subject: &CommandDispatcher<()>, command: &str) -> Arc<RwLock<CommandNode<()>>> {
|
||||
subject
|
||||
.parse(command.into(), ())
|
||||
.context
|
||||
.nodes
|
||||
.last()
|
||||
.unwrap()
|
||||
.node
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_usage_no_commands() {
|
||||
let subject = CommandDispatcher::<()>::new();
|
||||
let results = subject.get_all_usage(&subject.root.read(), Arc::new(()), true);
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_usage_no_commands() {
|
||||
let subject = CommandDispatcher::<()>::new();
|
||||
let results = subject.get_smart_usage(&subject.root.read(), Arc::new(()));
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_usage_root() {
|
||||
let subject = setup();
|
||||
let results = subject.get_all_usage(&subject.root.read(), Arc::new(()), true);
|
||||
|
||||
let actual = results.into_iter().collect::<HashSet<_>>();
|
||||
let expected = vec![
|
||||
"a 1 i", "a 1 ii", "a 2 i", "a 2 ii", "b 1", "c", "e", "e 1", "e 1 i", "e 1 ii", "f 1 i",
|
||||
"f 2 ii", "g", "g 1 i", "h", "h 1 i", "h 2 i ii", "h 3", "i", "i 1", "i 2", "j ...",
|
||||
"k -> h",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|s| s.to_owned())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_usage_root() {
|
||||
let subject = setup();
|
||||
let results = subject.get_smart_usage(&subject.root.read(), Arc::new(()));
|
||||
|
||||
let actual = results
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let expected = vec![
|
||||
(get(&subject, "a"), "a (1|2)"),
|
||||
(get(&subject, "b"), "b 1"),
|
||||
(get(&subject, "c"), "c"),
|
||||
(get(&subject, "e"), "e [1]"),
|
||||
(get(&subject, "f"), "f (1|2)"),
|
||||
(get(&subject, "g"), "g [1]"),
|
||||
(get(&subject, "h"), "h [1|2|3]"),
|
||||
(get(&subject, "i"), "i [1|2]"),
|
||||
(get(&subject, "j"), "j ..."),
|
||||
(get(&subject, "k"), "k -> h"),
|
||||
];
|
||||
|
||||
println!("-");
|
||||
|
||||
let expected = expected
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v.to_owned()))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
|
2
azalea-brigadier/tests/context/mod.rs
Normal file
2
azalea-brigadier/tests/context/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod command_context_test;
|
||||
mod parsed_argument_test;
|
2
azalea-brigadier/tests/exceptions/mod.rs
Normal file
2
azalea-brigadier/tests/exceptions/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod dynamic_command_syntax_exception_type_test;
|
||||
mod simple_command_syntax_exception_type_test;
|
6
azalea-brigadier/tests/mod.rs
Normal file
6
azalea-brigadier/tests/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod arguments;
|
||||
mod builder;
|
||||
mod context;
|
||||
mod exceptions;
|
||||
mod suggestion;
|
||||
mod tree;
|
3
azalea-brigadier/tests/suggestion/mod.rs
Normal file
3
azalea-brigadier/tests/suggestion/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod suggestion_test;
|
||||
mod suggestions_builder_test;
|
||||
mod suggestions_test;
|
|
@ -1,7 +1,12 @@
|
|||
use azalea_brigadier::{context::StringRange, suggestion::Suggestion};
|
||||
|
||||
#[test]
|
||||
fn apply_insertation_start() {
|
||||
let suggestion = Suggestion::new(StringRange::at(0), "And so I said: ");
|
||||
assert_eq!(suggestion.apply("Hello world!"), "And so I said: Hello world!");
|
||||
assert_eq!(
|
||||
suggestion.apply("Hello world!"),
|
||||
"And so I said: Hello world!"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -49,23 +54,35 @@ fn expand_unchanged() {
|
|||
#[test]
|
||||
fn expand_left() {
|
||||
let suggestion = Suggestion::new(StringRange::at(1), "oo");
|
||||
assert_eq!(suggestion.expand("f", StringRange::between(0, 1)), Suggestion::new(StringRange::between(0, 1), "foo"));
|
||||
assert_eq!(
|
||||
suggestion.expand("f", StringRange::between(0, 1)),
|
||||
Suggestion::new(StringRange::between(0, 1), "foo")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_right() {
|
||||
let suggestion = Suggestion::new(StringRange::at(0), "minecraft:");
|
||||
assert_eq!(suggestion.expand("fish", StringRange::between(0, 4)), Suggestion::new(StringRange::between(0, 4), "minecraft:fish"));
|
||||
assert_eq!(
|
||||
suggestion.expand("fish", StringRange::between(0, 4)),
|
||||
Suggestion::new(StringRange::between(0, 4), "minecraft:fish")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_both() {
|
||||
let suggestion = Suggestion::new(StringRange::at(11), "minecraft:");
|
||||
assert_eq!(suggestion.expand("give Steve fish_block", StringRange::between(5, 21)), Suggestion::new(StringRange::between(5, 21), "Steve minecraft:fish_block"));
|
||||
assert_eq!(
|
||||
suggestion.expand("give Steve fish_block", StringRange::between(5, 21)),
|
||||
Suggestion::new(StringRange::between(5, 21), "Steve minecraft:fish_block")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_replacement() {
|
||||
let suggestion = Suggestion::new(StringRange::between(6, 11), "strangers");
|
||||
assert_eq!(suggestion.expand("Hello world!", StringRange::between(0, 12)), Suggestion::new(StringRange::between(0, 12), "Hello strangers!"));
|
||||
assert_eq!(
|
||||
suggestion.expand("Hello world!", StringRange::between(0, 12)),
|
||||
Suggestion::new(StringRange::between(0, 12), "Hello strangers!")
|
||||
);
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use azalea_brigadier::{
|
||||
context::StringRange,
|
||||
suggestion::{Suggestion, SuggestionsBuilder},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn suggest_appends() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder.suggest("orld!").build();
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::between(6, 7), "orld!")]
|
||||
);
|
||||
assert_eq!(result.range(), StringRange::between(6, 7));
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_replaces() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder.suggest("everybody").build();
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::between(6, 7), "everybody")]
|
||||
);
|
||||
assert_eq!(result.range(), StringRange::between(6, 7));
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_noop() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder.suggest("w").build();
|
||||
assert_eq!(result.list(), vec![]);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_multiple() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder
|
||||
.suggest("world!")
|
||||
.suggest("everybody")
|
||||
.suggest("weekend")
|
||||
.build();
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(6, 7), "everybody"),
|
||||
Suggestion::new(StringRange::between(6, 7), "weekend"),
|
||||
Suggestion::new(StringRange::between(6, 7), "world!"),
|
||||
]
|
||||
);
|
||||
assert_eq!(result.range(), StringRange::between(6, 7));
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let builder = builder.suggest("won't be included in restart");
|
||||
let other = builder.restart();
|
||||
assert_ne!(other, builder);
|
||||
assert_eq!(other.input(), builder.input());
|
||||
assert_eq!(other.start(), builder.start());
|
||||
assert_eq!(other.remaining(), builder.remaining());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_alphabetical() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder
|
||||
.suggest("2")
|
||||
.suggest("4")
|
||||
.suggest("6")
|
||||
.suggest("8")
|
||||
.suggest("30")
|
||||
.suggest("32")
|
||||
.build();
|
||||
let actual = result.list().iter().map(|s| s.text()).collect::<Vec<_>>();
|
||||
assert_eq!(actual, vec!["2", "30", "32", "4", "6", "8"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_numerical() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder
|
||||
.suggest_integer(2)
|
||||
.suggest_integer(4)
|
||||
.suggest_integer(6)
|
||||
.suggest_integer(8)
|
||||
.suggest_integer(30)
|
||||
.suggest_integer(32)
|
||||
.build();
|
||||
let actual = result.list().iter().map(|s| s.text()).collect::<Vec<_>>();
|
||||
assert_eq!(actual, vec!["2", "4", "6", "8", "30", "32"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_mixed() {
|
||||
let builder = SuggestionsBuilder::new("Hello w", 6);
|
||||
let result = builder
|
||||
.suggest("11")
|
||||
.suggest("22")
|
||||
.suggest("33")
|
||||
.suggest("a")
|
||||
.suggest("b")
|
||||
.suggest("c")
|
||||
.suggest_integer(2)
|
||||
.suggest_integer(4)
|
||||
.suggest_integer(6)
|
||||
.suggest_integer(8)
|
||||
.suggest_integer(30)
|
||||
.suggest_integer(32)
|
||||
.suggest("3a")
|
||||
.suggest("a3")
|
||||
.build();
|
||||
let actual = result
|
||||
.list()
|
||||
.iter()
|
||||
.map(|s| s.text())
|
||||
.collect::<HashSet<_>>();
|
||||
// mojang please
|
||||
let expected = vec![
|
||||
"11", "2", "22", "33", "3a", "4", "6", "8", "30", "32", "a", "a3", "b", "c",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
|
@ -1,20 +1,58 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use azalea_brigadier::{
|
||||
context::StringRange,
|
||||
suggestion::{Suggestion, Suggestions},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn merge_empty() {
|
||||
let merged = Suggestions::merge("foo b", vec![]);
|
||||
let merged = Suggestions::<()>::merge("foo b", &[]);
|
||||
assert!(merged.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_single() {
|
||||
let suggestions = Suggestions::new(StringRange::at(5), vec![Suggestion::new(StringRange::at(5), "ar")]);
|
||||
let merged = Suggestions::merge("foo b", vec![suggestions]);
|
||||
let suggestions = Suggestions::new(
|
||||
StringRange::at(5),
|
||||
vec![Suggestion::new(StringRange::at(5), "ar")],
|
||||
);
|
||||
let merged = Suggestions::merge("foo b", &[suggestions.clone()]);
|
||||
assert_eq!(merged, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_multiple() {
|
||||
let a = Suggestions::new(StringRange::at(5), vec![Suggestion::new(StringRange::at(5), "ar"), Suggestion::new(StringRange::at(5), "az"), Suggestion::new(StringRange::at(5), "Az")]);
|
||||
let b = Suggestions::new(StringRange::between(4, 5), vec![Suggestion::new(StringRange::between(4, 5), "foo"), Suggestion::new(StringRange::between(4, 5), "qux"), Suggestion::new(StringRange::between(4, 5), "apple"), Suggestion::new(StringRange::between(4, 5), "Bar")]);
|
||||
let merged = Suggestions::merge("foo b", vec![a, b]);
|
||||
assert_eq!(merged.get_list(), vec![Suggestion::new(StringRange::between(4, 5), "apple"), Suggestion::new(StringRange::between(4, 5), "ar"), Suggestion::new(StringRange::between(4, 5), "Az"), Suggestion::new(StringRange::between(4, 5), "bar"), Suggestion::new(StringRange::between(4, 5), "Bar"), Suggestion::new(StringRange::between(4, 5), "baz"), Suggestion::new(StringRange::between(4, 5), "bAz"), Suggestion::new(StringRange::between(4, 5), "foo"), Suggestion::new(StringRange::between(4, 5), "qux")]);
|
||||
let a = Suggestions::new(
|
||||
StringRange::at(5),
|
||||
vec![
|
||||
Suggestion::new(StringRange::at(5), "ar"),
|
||||
Suggestion::new(StringRange::at(5), "az"),
|
||||
Suggestion::new(StringRange::at(5), "Az"),
|
||||
],
|
||||
);
|
||||
let b = Suggestions::new(
|
||||
StringRange::between(4, 5),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(4, 5), "foo"),
|
||||
Suggestion::new(StringRange::between(4, 5), "qux"),
|
||||
Suggestion::new(StringRange::between(4, 5), "apple"),
|
||||
Suggestion::new(StringRange::between(4, 5), "Bar"),
|
||||
],
|
||||
);
|
||||
let merged = Suggestions::merge("foo b", &[a, b]);
|
||||
|
||||
let actual = merged.list().iter().cloned().collect::<HashSet<_>>();
|
||||
let expected = vec![
|
||||
Suggestion::new(StringRange::between(4, 5), "apple"),
|
||||
Suggestion::new(StringRange::between(4, 5), "bar"),
|
||||
Suggestion::new(StringRange::between(4, 5), "Bar"),
|
||||
Suggestion::new(StringRange::between(4, 5), "baz"),
|
||||
Suggestion::new(StringRange::between(4, 5), "bAz"),
|
||||
Suggestion::new(StringRange::between(4, 5), "foo"),
|
||||
Suggestion::new(StringRange::between(4, 5), "qux"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
1
azalea-brigadier/tests/tree/mod.rs
Normal file
1
azalea-brigadier/tests/tree/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{style::Style, FormattedText};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)]
|
||||
pub struct BaseComponent {
|
||||
// implements mutablecomponent
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
};
|
||||
|
||||
/// A chat component, basically anything you can see in chat.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
|
||||
#[serde(untagged)]
|
||||
pub enum FormattedText {
|
||||
Text(TextComponent),
|
||||
|
|
|
@ -6,7 +6,7 @@ use once_cell::sync::Lazy;
|
|||
use serde::{ser::SerializeStruct, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct TextColor {
|
||||
pub value: u32,
|
||||
pub name: Option<String>,
|
||||
|
@ -290,7 +290,7 @@ impl TryFrom<ChatFormatting> for TextColor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Style {
|
||||
// These are options instead of just bools because None is different than false in this case
|
||||
pub color: Option<TextColor>,
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSer
|
|||
use std::fmt::Display;
|
||||
|
||||
/// A component that contains text that's the same in all locales.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct TextComponent {
|
||||
pub base: BaseComponent,
|
||||
pub text: String,
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
};
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)]
|
||||
#[serde(untagged)]
|
||||
pub enum StringOrComponent {
|
||||
String(String),
|
||||
|
@ -13,7 +13,7 @@ pub enum StringOrComponent {
|
|||
}
|
||||
|
||||
/// A message whose content depends on the client's language.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TranslatableComponent {
|
||||
pub base: BaseComponent,
|
||||
pub key: String,
|
||||
|
|
|
@ -19,14 +19,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_suggestions() {
|
||||
let suggestions = Suggestions {
|
||||
range: StringRange::new(0, 5),
|
||||
suggestions: vec![Suggestion {
|
||||
text: "foo".to_string(),
|
||||
range: StringRange::new(1, 4),
|
||||
tooltip: Some(FormattedText::from("bar".to_string())),
|
||||
}],
|
||||
};
|
||||
let suggestions = Suggestions::new(
|
||||
StringRange::new(0, 5),
|
||||
vec![Suggestion::new_with_tooltip(
|
||||
StringRange::new(1, 4),
|
||||
"foo",
|
||||
FormattedText::from("bar".to_string()),
|
||||
)],
|
||||
);
|
||||
let mut buf = Vec::new();
|
||||
suggestions.write_into(&mut buf).unwrap();
|
||||
let mut cursor = Cursor::new(&buf[..]);
|
||||
|
|
Loading…
Reference in a new issue