azalea_brigadier/suggestion/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
mod suggestions;
mod suggestions_builder;

#[cfg(feature = "azalea-buf")]
use std::io::Write;
use std::{
    fmt::{self, Display},
    hash::Hash,
};

#[cfg(feature = "azalea-buf")]
use azalea_buf::McBufWritable;
#[cfg(feature = "azalea-buf")]
use azalea_chat::FormattedText;
pub use suggestions::Suggestions;
pub use suggestions_builder::SuggestionsBuilder;

use crate::context::StringRange;

/// A suggestion given to the user for what they might want to type next.
///
/// 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 {
    pub range: StringRange,
    value: SuggestionValue,
    pub tooltip: Option<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,
        }
    }

    pub fn new_with_tooltip(range: StringRange, text: &str, tooltip: String) -> 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(&text);
        if self.range.end() < input.len() {
            result.push_str(&input[self.range.end()..]);
        }

        result
    }

    pub fn expand(&self, command: &str, range: StringRange) -> Suggestion {
        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.value.to_string());
        if range.end() > self.range.end() {
            result.push_str(&command[self.range.end()..range.end()]);
        }
        Suggestion {
            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 {
    fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
        self.value.to_string().write_into(buf)?;
        self.tooltip
            .clone()
            .map(FormattedText::from)
            .write_into(buf)?;
        Ok(())
    }
}