aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2023-10-12 20:14:29 -0500
committermat <git@matdoes.dev>2023-10-12 20:14:29 -0500
commit38db231ea8fa0fb223e16637db0b6ec65b2b81ef (patch)
treef8f61b04ad0b498b85bffb133c51af1670a33ddf
parentd5f424b8c2fba9b3283aef36fe9e1e051636614c (diff)
downloadazalea-drasl-38db231ea8fa0fb223e16637db0b6ec65b2b81ef.tar.xz
brigadier usages
-rwxr-xr-xazalea-brigadier/src/builder/argument_builder.rs5
-rwxr-xr-xazalea-brigadier/src/command_dispatcher.rs184
-rwxr-xr-xazalea-brigadier/src/context/command_context.rs4
-rwxr-xr-xazalea-brigadier/src/context/command_context_builder.rs6
-rwxr-xr-xazalea-brigadier/src/context/parsed_command_node.rs2
-rwxr-xr-xazalea-brigadier/src/context/string_range.rs2
-rwxr-xr-xazalea-brigadier/src/suggestion/mod.rs102
-rwxr-xr-xazalea-brigadier/src/suggestion/suggestions.rs39
-rwxr-xr-xazalea-brigadier/src/suggestion/suggestions_builder.rs50
-rwxr-xr-xazalea-brigadier/src/tree/mod.rs20
-rw-r--r--azalea-brigadier/tests/arguments/mod.rs6
-rwxr-xr-xazalea-brigadier/tests/builder/argument_builder_test.rs34
-rw-r--r--azalea-brigadier/tests/builder/mod.rs3
-rwxr-xr-xazalea-brigadier/tests/command_dispatcher_usages_test.rs142
-rw-r--r--azalea-brigadier/tests/context/mod.rs2
-rw-r--r--azalea-brigadier/tests/exceptions/mod.rs2
-rw-r--r--azalea-brigadier/tests/mod.rs6
-rw-r--r--azalea-brigadier/tests/suggestion/mod.rs3
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestion_test.rs29
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestions_builder_test.rs133
-rwxr-xr-xazalea-brigadier/tests/suggestion/suggestions_test.rs54
-rw-r--r--azalea-brigadier/tests/tree/mod.rs1
-rwxr-xr-xazalea-chat/src/base_component.rs2
-rwxr-xr-xazalea-chat/src/component.rs2
-rwxr-xr-xazalea-chat/src/style.rs4
-rwxr-xr-xazalea-chat/src/text_component.rs2
-rwxr-xr-xazalea-chat/src/translatable_component.rs4
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs16
28 files changed, 757 insertions, 102 deletions
diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs
index 643a3bd0..10191463 100755
--- a/azalea-brigadier/src/builder/argument_builder.rs
+++ b/azalea-brigadier/src/builder/argument_builder.rs
@@ -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> {
diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs
index 672a250b..42737fba 100755
--- a/azalea-brigadier/src/command_dispatcher.rs
+++ b/azalea-brigadier/src/command_dispatcher.rs
@@ -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> {
diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs
index f78fe758..4d93006e 100755
--- a/azalea-brigadier/src/context/command_context.rs
+++ b/azalea-brigadier/src/context/command_context.rs
@@ -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,
diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs
index 2fc8d4ac..99c40dac 100755
--- a/azalea-brigadier/src/context/command_context_builder.rs
+++ b/azalea-brigadier/src/context/command_context_builder.rs
@@ -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(),
diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs
index bba5d121..2d69c72e 100755
--- a/azalea-brigadier/src/context/parsed_command_node.rs
+++ b/azalea-brigadier/src/context/parsed_command_node.rs
@@ -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,
}
}
}
diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/context/string_range.rs
index 8ca88624..75163405 100755
--- a/azalea-brigadier/src/context/string_range.rs
+++ b/azalea-brigadier/src/context/string_range.rs
@@ -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,
diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs
index c404adc7..bc0e7608 100755
--- a/azalea-brigadier/src/suggestion/mod.rs
+++ b/azalea-brigadier/src/suggestion/mod.rs
@@ -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>,
}
+#[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,
+ }
+ }
+}
+
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 input.to_string();
+ return text;
}
- let mut result = String::with_capacity(self.text.len());
+ 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(())
}
diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs
index 2a8b5e9e..69877786 100755
--- a/azalea-brigadier/src/suggestion/suggestions.rs
+++ b/azalea-brigadier/src/suggestion/suggestions.rs
@@ -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 })
}
diff --git a/azalea-brigadier/src/suggestion/suggestions_builder.rs b/azalea-brigadier/src/suggestion/suggestions_builder.rs
index 66f17fb1..469a7b98 100755
--- a/azalea-brigadier/src/suggestion/suggestions_builder.rs
+++ b/azalea-brigadier/src/suggestion/suggestions_builder.rs
@@ -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)
}
}
diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs
index cec972dc..8ab3526e 100755
--- a/azalea-brigadier/src/tree/mod.rs
+++ b/azalea-brigadier/src/tree/mod.rs
@@ -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(),
diff --git a/azalea-brigadier/tests/arguments/mod.rs b/azalea-brigadier/tests/arguments/mod.rs
new file mode 100644
index 00000000..29d656d1
--- /dev/null
+++ b/azalea-brigadier/tests/arguments/mod.rs
@@ -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;
diff --git a/azalea-brigadier/tests/builder/argument_builder_test.rs b/azalea-brigadier/tests/builder/argument_builder_test.rs
index ee44f5e6..d5f940dd 100755
--- a/azalea-brigadier/tests/builder/argument_builder_test.rs
+++ b/azalea-brigadier/tests/builder/argument_builder_test.rs
@@ -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));
diff --git a/azalea-brigadier/tests/builder/mod.rs b/azalea-brigadier/tests/builder/mod.rs
new file mode 100644
index 00000000..21944c68
--- /dev/null
+++ b/azalea-brigadier/tests/builder/mod.rs
@@ -0,0 +1,3 @@
+mod argument_builder_test;
+mod literal_argument_builder_test;
+mod required_argument_builder_test;
diff --git a/azalea-brigadier/tests/command_dispatcher_usages_test.rs b/azalea-brigadier/tests/command_dispatcher_usages_test.rs
index 8b137891..d7baff89 100755
--- a/azalea-brigadier/tests/command_dispatcher_usages_test.rs
+++ b/azalea-brigadier/tests/command_dispatcher_usages_test.rs
@@ -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);
+}
diff --git a/azalea-brigadier/tests/context/mod.rs b/azalea-brigadier/tests/context/mod.rs
new file mode 100644
index 00000000..e74dce59
--- /dev/null
+++ b/azalea-brigadier/tests/context/mod.rs
@@ -0,0 +1,2 @@
+mod command_context_test;
+mod parsed_argument_test;
diff --git a/azalea-brigadier/tests/exceptions/mod.rs b/azalea-brigadier/tests/exceptions/mod.rs
new file mode 100644
index 00000000..72292c4d
--- /dev/null
+++ b/azalea-brigadier/tests/exceptions/mod.rs
@@ -0,0 +1,2 @@
+mod dynamic_command_syntax_exception_type_test;
+mod simple_command_syntax_exception_type_test;
diff --git a/azalea-brigadier/tests/mod.rs b/azalea-brigadier/tests/mod.rs
new file mode 100644
index 00000000..14c99a24
--- /dev/null
+++ b/azalea-brigadier/tests/mod.rs
@@ -0,0 +1,6 @@
+mod arguments;
+mod builder;
+mod context;
+mod exceptions;
+mod suggestion;
+mod tree;
diff --git a/azalea-brigadier/tests/suggestion/mod.rs b/azalea-brigadier/tests/suggestion/mod.rs
new file mode 100644
index 00000000..7b96af2b
--- /dev/null
+++ b/azalea-brigadier/tests/suggestion/mod.rs
@@ -0,0 +1,3 @@
+mod suggestion_test;
+mod suggestions_builder_test;
+mod suggestions_test; \ No newline at end of file
diff --git a/azalea-brigadier/tests/suggestion/suggestion_test.rs b/azalea-brigadier/tests/suggestion/suggestion_test.rs
index 9ba95807..e3c70c25 100755
--- a/azalea-brigadier/tests/suggestion/suggestion_test.rs
+++ b/azalea-brigadier/tests/suggestion/suggestion_test.rs
@@ -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!"));
-} \ No newline at end of file
+ assert_eq!(
+ suggestion.expand("Hello world!", StringRange::between(0, 12)),
+ Suggestion::new(StringRange::between(0, 12), "Hello strangers!")
+ );
+}
diff --git a/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs b/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
index e69de29b..08ce65d4 100755
--- a/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
+++ b/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
@@ -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);
+}
diff --git a/azalea-brigadier/tests/suggestion/suggestions_test.rs b/azalea-brigadier/tests/suggestion/suggestions_test.rs
index 28a8266d..12a2c73b 100755
--- a/azalea-brigadier/tests/suggestion/suggestions_test.rs
+++ b/azalea-brigadier/tests/suggestion/suggestions_test.rs
@@ -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")]);
-} \ No newline at end of file
+ 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);
+}
diff --git a/azalea-brigadier/tests/tree/mod.rs b/azalea-brigadier/tests/tree/mod.rs
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/azalea-brigadier/tests/tree/mod.rs
@@ -0,0 +1 @@
+
diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs
index 43b35aef..dcc28ecc 100755
--- a/azalea-chat/src/base_component.rs
+++ b/azalea-chat/src/base_component.rs
@@ -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")]
diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs
index e087713c..fb7e0522 100755
--- a/azalea-chat/src/component.rs
+++ b/azalea-chat/src/component.rs
@@ -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),
diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs
index 9c0d645e..ba4d6e72 100755
--- a/azalea-chat/src/style.rs
+++ b/azalea-chat/src/style.rs
@@ -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>,
diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs
index 42932d0e..fefd2cb8 100755
--- a/azalea-chat/src/text_component.rs
+++ b/azalea-chat/src/text_component.rs
@@ -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,
diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs
index a1c72e35..56c6507e 100755
--- a/azalea-chat/src/translatable_component.rs
+++ b/azalea-chat/src/translatable_component.rs
@@ -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,
diff --git a/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs b/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs
index 88c6f29e..d1ad1240 100755
--- a/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs
@@ -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[..]);