azalea_brigadier/suggestion/
mod.rs

1mod suggestion_provider;
2mod suggestions;
3mod suggestions_builder;
4
5#[cfg(feature = "azalea-buf")]
6use std::io::{self, Write};
7use std::{
8    cmp::Ordering,
9    fmt::{self, Display},
10    hash::Hash,
11};
12
13#[cfg(feature = "azalea-buf")]
14use azalea_buf::AzaleaWrite;
15#[cfg(feature = "azalea-buf")]
16use azalea_chat::FormattedText;
17pub use suggestion_provider::SuggestionProvider;
18pub use suggestions::Suggestions;
19pub use suggestions_builder::SuggestionsBuilder;
20
21use crate::context::StringRange;
22
23/// A suggestion given to the user for what they might want to type next.
24///
25/// The `M` generic is the type of the tooltip, so for example a `String` or
26/// just `()` if you don't care about it.
27#[derive(Debug, Clone, Hash, Eq, PartialEq)]
28pub struct Suggestion {
29    pub range: StringRange,
30    value: SuggestionValue,
31    pub tooltip: Option<String>,
32}
33
34#[derive(Debug, Clone, Hash, Eq, PartialEq)]
35pub enum SuggestionValue {
36    Integer(i32),
37    Text(String),
38}
39
40impl Suggestion {
41    pub fn new(range: StringRange, text: &str) -> Suggestion {
42        Suggestion {
43            range,
44            value: SuggestionValue::Text(text.to_string()),
45            tooltip: None,
46        }
47    }
48
49    pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: String) -> Self {
50        Self {
51            range,
52            value: SuggestionValue::Text(text.to_string()),
53            tooltip: Some(tooltip),
54        }
55    }
56
57    pub fn apply(&self, input: &str) -> String {
58        let text = self.value.to_string();
59        if self.range.start() == 0 && self.range.end() == input.len() {
60            return text;
61        }
62        let mut result = String::with_capacity(text.len());
63        if self.range.start() > 0 {
64            result.push_str(&input[0..self.range.start()]);
65        }
66        result.push_str(&text);
67        if self.range.end() < input.len() {
68            result.push_str(&input[self.range.end()..]);
69        }
70
71        result
72    }
73
74    pub fn expand(&self, command: &str, range: StringRange) -> Suggestion {
75        if range == self.range {
76            return self.clone();
77        }
78        let mut result = String::new();
79        if range.start() < self.range.start() {
80            result.push_str(&command[range.start()..self.range.start()]);
81        }
82        result.push_str(&self.value.to_string());
83        if range.end() > self.range.end() {
84            result.push_str(&command[self.range.end()..range.end()]);
85        }
86        Suggestion {
87            range,
88            value: SuggestionValue::Text(result),
89            tooltip: self.tooltip.clone(),
90        }
91    }
92
93    pub fn text(&self) -> String {
94        self.value.to_string()
95    }
96}
97
98impl SuggestionValue {
99    pub fn cmp_ignore_case(&self, other: &Self) -> Ordering {
100        match (self, other) {
101            (SuggestionValue::Text(a), SuggestionValue::Text(b)) => {
102                a.to_lowercase().cmp(&b.to_lowercase())
103            }
104            (SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
105            _ => {
106                let a = self.to_string();
107                let b = other.to_string();
108                a.cmp(&b)
109            }
110        }
111    }
112}
113
114impl Display for SuggestionValue {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            SuggestionValue::Text(text) => write!(f, "{text}"),
118            SuggestionValue::Integer(value) => write!(f, "{value}"),
119        }
120    }
121}
122
123impl Ord for SuggestionValue {
124    fn cmp(&self, other: &Self) -> Ordering {
125        match (self, other) {
126            (SuggestionValue::Text(a), SuggestionValue::Text(b)) => a.cmp(b),
127            (SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
128            _ => {
129                let a = self.to_string();
130                let b = other.to_string();
131                a.cmp(&b)
132            }
133        }
134    }
135}
136impl PartialOrd for SuggestionValue {
137    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138        Some(self.cmp(other))
139    }
140}
141
142#[cfg(feature = "azalea-buf")]
143impl AzaleaWrite for Suggestion {
144    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
145        self.value.to_string().azalea_write(buf)?;
146        self.tooltip
147            .clone()
148            .map(FormattedText::from)
149            .azalea_write(buf)?;
150        Ok(())
151    }
152}