mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
parent
f505ace721
commit
79ad1e93bf
32 changed files with 738 additions and 59 deletions
|
@ -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![]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
||||
context::CommandContext,
|
||||
exceptions::CommandSyntaxException,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
|
||||
use super::ArgumentType;
|
||||
|
@ -13,6 +16,20 @@ impl ArgumentType for Boolean {
|
|||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
Ok(Arc::new(reader.read_boolean()?))
|
||||
}
|
||||
|
||||
fn list_suggestions(&self, mut builder: SuggestionsBuilder) -> Suggestions {
|
||||
if "true".starts_with(builder.remaining_lowercase()) {
|
||||
builder = builder.suggest("true");
|
||||
}
|
||||
if "false".starts_with(builder.remaining_lowercase()) {
|
||||
builder = builder.suggest("false");
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
vec!["true".to_string(), "false".to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bool() -> impl ArgumentType {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
||||
use crate::{
|
||||
arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader,
|
||||
arguments::ArgumentType,
|
||||
exceptions::CommandSyntaxException,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
use std::{any::Any, fmt::Debug, sync::Arc};
|
||||
|
||||
|
@ -22,6 +25,17 @@ impl Argument {
|
|||
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||
self.parser.parse(reader)
|
||||
}
|
||||
|
||||
pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions {
|
||||
// TODO: custom suggestions
|
||||
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71
|
||||
|
||||
self.parser.list_suggestions(builder)
|
||||
}
|
||||
|
||||
pub fn examples(&self) -> Vec<String> {
|
||||
self.parser.examples()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Argument> for ArgumentBuilderType {
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
parse_results::ParseResults,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
tree::CommandNode,
|
||||
};
|
||||
use std::{
|
||||
|
@ -474,6 +475,41 @@ impl<S> CommandDispatcher<S> {
|
|||
|
||||
Some(this)
|
||||
}
|
||||
|
||||
pub fn get_completion_suggestions(parse: ParseResults<S>) -> Suggestions {
|
||||
let cursor = parse.reader.total_length();
|
||||
Self::get_completion_suggestions_with_cursor(parse, cursor)
|
||||
}
|
||||
|
||||
pub fn get_completion_suggestions_with_cursor(
|
||||
parse: ParseResults<S>,
|
||||
cursor: usize,
|
||||
) -> Suggestions {
|
||||
let context = parse.context;
|
||||
|
||||
let node_before_cursor = context.find_suggestion_context(cursor);
|
||||
let parent = node_before_cursor.parent;
|
||||
let start = usize::min(node_before_cursor.start_pos, cursor);
|
||||
|
||||
let full_input = parse.reader.string();
|
||||
let truncated_input = full_input[..cursor].to_string();
|
||||
let truncated_input_lowercase = truncated_input.to_lowercase();
|
||||
|
||||
let mut all_suggestions = Vec::new();
|
||||
for node in parent.read().children.values() {
|
||||
let suggestions = node.read().list_suggestions(
|
||||
context.build(&truncated_input),
|
||||
SuggestionsBuilder::new_with_lowercase(
|
||||
&truncated_input,
|
||||
&truncated_input_lowercase,
|
||||
start,
|
||||
),
|
||||
);
|
||||
all_suggestions.push(suggestions);
|
||||
}
|
||||
|
||||
Suggestions::merge(full_input, &all_suggestions)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for CommandDispatcher<S> {
|
||||
|
|
|
@ -2,7 +2,7 @@ use parking_lot::RwLock;
|
|||
|
||||
use super::{
|
||||
command_context::CommandContext, parsed_command_node::ParsedCommandNode,
|
||||
string_range::StringRange, ParsedArgument,
|
||||
string_range::StringRange, suggestion_context::SuggestionContext, ParsedArgument,
|
||||
};
|
||||
use crate::{
|
||||
command_dispatcher::CommandDispatcher,
|
||||
|
@ -99,6 +99,43 @@ impl<'a, S> CommandContextBuilder<'a, S> {
|
|||
input: input.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_suggestion_context(&self, cursor: usize) -> SuggestionContext<S> {
|
||||
if self.range.start() > cursor {
|
||||
panic!("Can't find node before cursor");
|
||||
}
|
||||
|
||||
if self.range.end() < cursor {
|
||||
if let Some(child) = &self.child {
|
||||
child.find_suggestion_context(cursor)
|
||||
} else if let Some(last) = self.nodes.last() {
|
||||
SuggestionContext {
|
||||
parent: Arc::clone(&last.node),
|
||||
start_pos: last.range.end() + 1,
|
||||
}
|
||||
} else {
|
||||
SuggestionContext {
|
||||
parent: Arc::clone(&self.root),
|
||||
start_pos: self.range.start(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut prev = &self.root;
|
||||
for node in &self.nodes {
|
||||
if node.range.start() <= cursor && cursor <= node.range.end() {
|
||||
return SuggestionContext {
|
||||
parent: Arc::clone(prev),
|
||||
start_pos: node.range.start(),
|
||||
};
|
||||
}
|
||||
prev = &node.node;
|
||||
}
|
||||
SuggestionContext {
|
||||
parent: Arc::clone(prev),
|
||||
start_pos: self.range.start(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Debug for CommandContextBuilder<'_, S> {
|
||||
|
|
|
@ -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;
|
||||
|
|
11
azalea-brigadier/src/context/suggestion_context.rs
Normal file
11
azalea-brigadier/src/context/suggestion_context.rs
Normal file
|
@ -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,
|
||||
}
|
|
@ -20,13 +20,10 @@ pub use suggestions_builder::SuggestionsBuilder;
|
|||
/// The `M` generic is the type of the tooltip, so for example a `String` or
|
||||
/// just `()` if you don't care about it.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Suggestion<M = ()>
|
||||
where
|
||||
M: Clone,
|
||||
{
|
||||
pub struct Suggestion {
|
||||
pub range: StringRange,
|
||||
value: SuggestionValue,
|
||||
pub tooltip: Option<M>,
|
||||
pub tooltip: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
|
@ -35,18 +32,16 @@ pub enum SuggestionValue {
|
|||
Text(String),
|
||||
}
|
||||
|
||||
impl Suggestion<()> {
|
||||
pub fn new(range: StringRange, text: &str) -> Suggestion<()> {
|
||||
impl Suggestion {
|
||||
pub fn new(range: StringRange, text: &str) -> Suggestion {
|
||||
Suggestion {
|
||||
range,
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
tooltip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Clone> Suggestion<M> {
|
||||
pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: M) -> Self {
|
||||
pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: String) -> Self {
|
||||
Self {
|
||||
range,
|
||||
value: SuggestionValue::Text(text.to_string()),
|
||||
|
@ -71,7 +66,7 @@ impl<M: Clone> Suggestion<M> {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn expand(&self, command: &str, range: StringRange) -> Suggestion<M> {
|
||||
pub fn expand(&self, command: &str, range: StringRange) -> Suggestion {
|
||||
if range == self.range {
|
||||
return self.clone();
|
||||
}
|
||||
|
@ -140,10 +135,13 @@ impl PartialOrd for SuggestionValue {
|
|||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl McBufWritable for Suggestion<FormattedText> {
|
||||
impl McBufWritable for Suggestion {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
self.value.to_string().write_into(buf)?;
|
||||
self.tooltip.write_into(buf)?;
|
||||
self.tooltip
|
||||
.clone()
|
||||
.map(FormattedText::from)
|
||||
.write_into(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,21 +12,18 @@ use azalea_chat::FormattedText;
|
|||
use std::io::{Cursor, Write};
|
||||
use std::{collections::HashSet, hash::Hash};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Suggestions<M>
|
||||
where
|
||||
M: Clone + PartialEq + Hash,
|
||||
{
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct Suggestions {
|
||||
range: StringRange,
|
||||
suggestions: Vec<Suggestion<M>>,
|
||||
suggestions: Vec<Suggestion>,
|
||||
}
|
||||
|
||||
impl<M: Clone + Eq + Hash> Suggestions<M> {
|
||||
pub fn new(range: StringRange, suggestions: Vec<Suggestion<M>>) -> Self {
|
||||
impl Suggestions {
|
||||
pub fn new(range: StringRange, suggestions: Vec<Suggestion>) -> Self {
|
||||
Self { range, suggestions }
|
||||
}
|
||||
|
||||
pub fn merge(command: &str, input: &[Suggestions<M>]) -> Self {
|
||||
pub fn merge(command: &str, input: &[Suggestions]) -> Self {
|
||||
if input.is_empty() {
|
||||
return Suggestions::default();
|
||||
} else if input.len() == 1 {
|
||||
|
@ -41,7 +38,7 @@ impl<M: Clone + Eq + Hash> Suggestions<M> {
|
|||
Suggestions::create(command, &texts)
|
||||
}
|
||||
|
||||
pub fn create(command: &str, suggestions: &HashSet<Suggestion<M>>) -> Self {
|
||||
pub fn create(command: &str, suggestions: &HashSet<Suggestion>) -> Self {
|
||||
if suggestions.is_empty() {
|
||||
return Suggestions::default();
|
||||
};
|
||||
|
@ -70,7 +67,7 @@ impl<M: Clone + Eq + Hash> Suggestions<M> {
|
|||
self.suggestions.is_empty()
|
||||
}
|
||||
|
||||
pub fn list(&self) -> &[Suggestion<M>] {
|
||||
pub fn list(&self) -> &[Suggestion] {
|
||||
&self.suggestions
|
||||
}
|
||||
|
||||
|
@ -79,19 +76,8 @@ impl<M: Clone + Eq + Hash> Suggestions<M> {
|
|||
}
|
||||
}
|
||||
|
||||
// this can't be derived because that'd require the generic to have `Default`
|
||||
// too even if it's not actually necessary
|
||||
impl<M: Clone + Hash + Eq> Default for Suggestions<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
range: StringRange::default(),
|
||||
suggestions: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl McBufReadable for Suggestions<FormattedText> {
|
||||
impl McBufReadable for Suggestions {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
#[derive(McBuf)]
|
||||
struct StandaloneSuggestion {
|
||||
|
@ -109,7 +95,7 @@ impl McBufReadable for Suggestions<FormattedText> {
|
|||
.into_iter()
|
||||
.map(|s| Suggestion {
|
||||
value: SuggestionValue::Text(s.text),
|
||||
tooltip: s.tooltip,
|
||||
tooltip: s.tooltip.map(|t| t.to_string()),
|
||||
range,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -120,7 +106,7 @@ impl McBufReadable for Suggestions<FormattedText> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
impl McBufWritable for Suggestions<FormattedText> {
|
||||
impl McBufWritable for Suggestions {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
(self.range.start() as u32).var_write_into(buf)?;
|
||||
(self.range.length() as u32).var_write_into(buf)?;
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::context::StringRange;
|
||||
|
||||
use super::{Suggestion, SuggestionValue, Suggestions};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct SuggestionsBuilder<M = ()>
|
||||
where
|
||||
M: Clone + Eq + Hash,
|
||||
{
|
||||
pub struct SuggestionsBuilder {
|
||||
input: String,
|
||||
input_lowercase: String,
|
||||
start: usize,
|
||||
remaining: String,
|
||||
remaining_lowercase: String,
|
||||
result: HashSet<Suggestion<M>>,
|
||||
result: HashSet<Suggestion>,
|
||||
}
|
||||
|
||||
impl SuggestionsBuilder<()> {
|
||||
impl SuggestionsBuilder {
|
||||
pub fn new(input: &str, start: usize) -> Self {
|
||||
Self::new_with_lowercase(input, input.to_lowercase().as_str(), start)
|
||||
}
|
||||
|
@ -35,7 +31,7 @@ impl SuggestionsBuilder<()> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
||||
impl SuggestionsBuilder {
|
||||
pub fn input(&self) -> &str {
|
||||
&self.input
|
||||
}
|
||||
|
@ -52,7 +48,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
|||
&self.remaining_lowercase
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Suggestions<M> {
|
||||
pub fn build(&self) -> Suggestions {
|
||||
Suggestions::create(&self.input, &self.result)
|
||||
}
|
||||
|
||||
|
@ -68,7 +64,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn suggest_with_tooltip(mut self, text: &str, tooltip: M) -> Self {
|
||||
pub fn suggest_with_tooltip(mut self, text: &str, tooltip: String) -> Self {
|
||||
if text == self.remaining {
|
||||
return self;
|
||||
}
|
||||
|
@ -89,7 +85,7 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn suggest_integer_with_tooltip(mut self, value: i32, tooltip: M) -> Self {
|
||||
pub fn suggest_integer_with_tooltip(mut self, value: i32, tooltip: String) -> Self {
|
||||
self.result.insert(Suggestion {
|
||||
range: StringRange::between(self.start, self.input.len()),
|
||||
value: SuggestionValue::Integer(value),
|
||||
|
@ -99,16 +95,16 @@ impl<M: Clone + Eq + Hash> SuggestionsBuilder<M> {
|
|||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add(mut self, other: SuggestionsBuilder<M>) -> Self {
|
||||
pub fn add(mut self, other: SuggestionsBuilder) -> Self {
|
||||
self.result.extend(other.result);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create_offset(&self, start: usize) -> SuggestionsBuilder<()> {
|
||||
pub fn create_offset(&self, start: usize) -> SuggestionsBuilder {
|
||||
SuggestionsBuilder::new_with_lowercase(&self.input, &self.input_lowercase, start)
|
||||
}
|
||||
|
||||
pub fn restart(&self) -> SuggestionsBuilder<()> {
|
||||
pub fn restart(&self) -> SuggestionsBuilder {
|
||||
self.create_offset(self.start)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||
modifier::RedirectModifier,
|
||||
string_reader::StringReader,
|
||||
suggestion::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
|
@ -209,6 +210,29 @@ impl<S> CommandNode<S> {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn list_suggestions(
|
||||
&self,
|
||||
// context is here because that's how it is in mojang's brigadier, but we haven't
|
||||
// implemented custom suggestions yet so this is unused rn
|
||||
_context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
match &self.value {
|
||||
ArgumentBuilderType::Literal(literal) => {
|
||||
if literal
|
||||
.value
|
||||
.to_lowercase()
|
||||
.starts_with(builder.remaining_lowercase())
|
||||
{
|
||||
builder.suggest(&literal.value).build()
|
||||
} else {
|
||||
Suggestions::default()
|
||||
}
|
||||
}
|
||||
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Debug for CommandNode<S> {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use azalea_brigadier::{prelude::*, tree::CommandNode};
|
||||
use azalea_brigadier::{prelude::*, string_reader::StringReader, tree::CommandNode};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
fn setup() -> CommandDispatcher<()> {
|
||||
|
@ -141,3 +141,54 @@ fn test_smart_usage_root() {
|
|||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_usage_h() {
|
||||
let subject = setup();
|
||||
let results = subject.get_smart_usage(&get(&subject, "h").read(), &());
|
||||
|
||||
let actual = results
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let expected = vec![
|
||||
(get(&subject, "h 1"), "[1] i"),
|
||||
(get(&subject, "h 2"), "[2] i ii"),
|
||||
(get(&subject, "h 3"), "[3]"),
|
||||
];
|
||||
|
||||
let expected = expected
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v.to_owned()))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_usage_offset_h() {
|
||||
let subject = setup();
|
||||
let mut offset_h = StringReader::from("/|/|/h");
|
||||
offset_h.cursor = 5;
|
||||
|
||||
let results = subject.get_smart_usage(&get(&subject, "h").read(), &());
|
||||
|
||||
let actual = results
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let expected = vec![
|
||||
(get(&subject, "h 1"), "[1] i"),
|
||||
(get(&subject, "h 2"), "[2] i ii"),
|
||||
(get(&subject, "h 3"), "[3]"),
|
||||
];
|
||||
|
||||
let expected = expected
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.read().name().to_owned(), v.to_owned()))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
|
|
@ -1 +1,446 @@
|
|||
use azalea_brigadier::{
|
||||
context::StringRange, prelude::*, string_reader::StringReader, suggestion::Suggestion,
|
||||
};
|
||||
|
||||
fn test_suggestions(
|
||||
subject: &CommandDispatcher<()>,
|
||||
contents: &str,
|
||||
cursor: usize,
|
||||
range: StringRange,
|
||||
suggestions: Vec<&str>,
|
||||
) {
|
||||
let result = CommandDispatcher::get_completion_suggestions_with_cursor(
|
||||
subject.parse(contents.into(), ()),
|
||||
cursor,
|
||||
);
|
||||
assert_eq!(result.range(), range);
|
||||
|
||||
let mut expected = Vec::new();
|
||||
for suggestion in suggestions {
|
||||
expected.push(Suggestion::new(range, suggestion));
|
||||
}
|
||||
|
||||
assert_eq!(result.list(), expected);
|
||||
}
|
||||
|
||||
fn input_with_offset(input: &str, offset: usize) -> StringReader {
|
||||
let mut result = StringReader::from(input);
|
||||
result.cursor = offset;
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_root_commands() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(literal("foo"));
|
||||
subject.register(literal("bar"));
|
||||
subject.register(literal("baz"));
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(subject.parse("".into(), ()));
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(0));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::at(0), "bar"),
|
||||
Suggestion::new(StringRange::at(0), "baz"),
|
||||
Suggestion::new(StringRange::at(0), "foo")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_root_commands_with_input_offset() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(literal("foo"));
|
||||
subject.register(literal("bar"));
|
||||
subject.register(literal("baz"));
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(
|
||||
subject.parse(input_with_offset("OOO", 3), ()),
|
||||
);
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(3));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::at(3), "bar"),
|
||||
Suggestion::new(StringRange::at(3), "baz"),
|
||||
Suggestion::new(StringRange::at(3), "foo")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_root_commands_partial() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(literal("foo"));
|
||||
subject.register(literal("bar"));
|
||||
subject.register(literal("baz"));
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(subject.parse("b".into(), ()));
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(0, 1));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(0, 1), "bar"),
|
||||
Suggestion::new(StringRange::between(0, 1), "baz")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_root_commands_partial_with_input_offset() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(literal("foo"));
|
||||
subject.register(literal("bar"));
|
||||
subject.register(literal("baz"));
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(
|
||||
subject.parse(input_with_offset("Zb", 1), ()),
|
||||
);
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(1, 2));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(1, 2), "bar"),
|
||||
Suggestion::new(StringRange::between(1, 2), "baz")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_sub_commands() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(
|
||||
literal("parent")
|
||||
.then(literal("foo"))
|
||||
.then(literal("bar"))
|
||||
.then(literal("baz")),
|
||||
);
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(subject.parse("parent ".into(), ()));
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(7));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::at(7), "bar"),
|
||||
Suggestion::new(StringRange::at(7), "baz"),
|
||||
Suggestion::new(StringRange::at(7), "foo")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_moving_cursor_sub_commands() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(
|
||||
literal("parent_one")
|
||||
.then(literal("faz"))
|
||||
.then(literal("fbz"))
|
||||
.then(literal("gaz")),
|
||||
);
|
||||
|
||||
subject.register(literal("parent_two"));
|
||||
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
0,
|
||||
StringRange::at(0),
|
||||
vec!["parent_one", "parent_two"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
1,
|
||||
StringRange::between(0, 1),
|
||||
vec!["parent_one", "parent_two"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
7,
|
||||
StringRange::between(0, 7),
|
||||
vec!["parent_one", "parent_two"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
8,
|
||||
StringRange::between(0, 8),
|
||||
vec!["parent_one"],
|
||||
);
|
||||
test_suggestions(&subject, "parent_one faz ", 10, StringRange::at(0), vec![]);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
11,
|
||||
StringRange::at(11),
|
||||
vec!["faz", "fbz", "gaz"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
12,
|
||||
StringRange::between(11, 12),
|
||||
vec!["faz", "fbz"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"parent_one faz ",
|
||||
13,
|
||||
StringRange::between(11, 13),
|
||||
vec!["faz"],
|
||||
);
|
||||
test_suggestions(&subject, "parent_one faz ", 14, StringRange::at(0), vec![]);
|
||||
test_suggestions(&subject, "parent_one faz ", 15, StringRange::at(0), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_sub_commands_partial() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(
|
||||
literal("parent")
|
||||
.then(literal("foo"))
|
||||
.then(literal("bar"))
|
||||
.then(literal("baz")),
|
||||
);
|
||||
|
||||
let parse = subject.parse("parent b".into(), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(7, 8));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(7, 8), "bar"),
|
||||
Suggestion::new(StringRange::between(7, 8), "baz")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_sub_commands_partial_with_input_offset() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
subject.register(
|
||||
literal("parent")
|
||||
.then(literal("foo"))
|
||||
.then(literal("bar"))
|
||||
.then(literal("baz")),
|
||||
);
|
||||
|
||||
let parse = subject.parse(input_with_offset("junk parent b", 5), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(12, 13));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::between(12, 13), "bar"),
|
||||
Suggestion::new(StringRange::between(12, 13), "baz")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_redirect() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let actual = subject.register(literal("actual").then(literal("sub")));
|
||||
subject.register(literal("redirect").redirect(actual));
|
||||
|
||||
let parse = subject.parse("redirect ".into(), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(9));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::at(9), "sub")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_redirect_partial() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let actual = subject.register(literal("actual").then(literal("sub")));
|
||||
subject.register(literal("redirect").redirect(actual));
|
||||
|
||||
let parse = subject.parse("redirect s".into(), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(9, 10));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::between(9, 10), "sub")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_moving_cursor_redirect() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let actual_one = subject.register(
|
||||
literal("actual_one")
|
||||
.then(literal("faz"))
|
||||
.then(literal("fbz"))
|
||||
.then(literal("gaz")),
|
||||
);
|
||||
|
||||
subject.register(literal("actual_two"));
|
||||
|
||||
subject.register(literal("redirect_one").redirect(actual_one.clone()));
|
||||
subject.register(literal("redirect_two").redirect(actual_one));
|
||||
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
0,
|
||||
StringRange::at(0),
|
||||
vec!["actual_one", "actual_two", "redirect_one", "redirect_two"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
9,
|
||||
StringRange::between(0, 9),
|
||||
vec!["redirect_one", "redirect_two"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
10,
|
||||
StringRange::between(0, 10),
|
||||
vec!["redirect_one"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
12,
|
||||
StringRange::at(0),
|
||||
vec![],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
13,
|
||||
StringRange::at(13),
|
||||
vec!["faz", "fbz", "gaz"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
14,
|
||||
StringRange::between(13, 14),
|
||||
vec!["faz", "fbz"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
15,
|
||||
StringRange::between(13, 15),
|
||||
vec!["faz"],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
16,
|
||||
StringRange::at(0),
|
||||
vec![],
|
||||
);
|
||||
test_suggestions(
|
||||
&subject,
|
||||
"redirect_one faz ",
|
||||
17,
|
||||
StringRange::at(0),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_redirect_partial_with_input_offset() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let actual = subject.register(literal("actual").then(literal("sub")));
|
||||
subject.register(literal("redirect").redirect(actual));
|
||||
|
||||
let parse = subject.parse(input_with_offset("/redirect s", 1), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::between(10, 11));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::between(10, 11), "sub")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_redirect_lots() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let loop_ = subject.register(literal("redirect"));
|
||||
subject.register(
|
||||
literal("redirect").then(literal("loop").then(argument("loop", integer()).redirect(loop_))),
|
||||
);
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(
|
||||
subject.parse("redirect loop 1 loop 02 loop 003 ".into(), ()),
|
||||
);
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(33));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![Suggestion::new(StringRange::at(33), "loop")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_execute_simulation() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let execute = subject.register(literal("execute"));
|
||||
subject.register(
|
||||
literal("execute")
|
||||
.then(literal("as").then(argument("name", word()).redirect(execute.clone())))
|
||||
.then(literal("store").then(argument("name", word()).redirect(execute)))
|
||||
.then(literal("run").executes(|_| 0)),
|
||||
);
|
||||
|
||||
let parse = subject.parse("execute as Dinnerbone as".into(), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_completion_suggestions_execute_simulation_partial() {
|
||||
let mut subject = CommandDispatcher::<()>::new();
|
||||
let execute = subject.register(literal("execute"));
|
||||
subject.register(
|
||||
literal("execute")
|
||||
.then(
|
||||
literal("as")
|
||||
.then(literal("bar").redirect(execute.clone()))
|
||||
.then(literal("baz").redirect(execute.clone())),
|
||||
)
|
||||
.then(literal("store").then(argument("name", word()).redirect(execute)))
|
||||
.then(literal("run").executes(|_| 0)),
|
||||
);
|
||||
|
||||
let parse = subject.parse("execute as bar as ".into(), ());
|
||||
|
||||
let result = CommandDispatcher::get_completion_suggestions(parse);
|
||||
|
||||
assert_eq!(result.range(), StringRange::at(18));
|
||||
assert_eq!(
|
||||
result.list(),
|
||||
vec![
|
||||
Suggestion::new(StringRange::at(18), "bar"),
|
||||
Suggestion::new(StringRange::at(18), "baz")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -7,7 +7,7 @@ use azalea_brigadier::{
|
|||
|
||||
#[test]
|
||||
fn merge_empty() {
|
||||
let merged = Suggestions::<()>::merge("foo b", &[]);
|
||||
let merged = Suggestions::merge("foo b", &[]);
|
||||
assert!(merged.is_empty());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue