azalea_brigadier/suggestion/
suggestions.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
#[cfg(feature = "azalea-buf")]
use std::io::{Cursor, Write};
use std::{collections::HashSet, hash::Hash};

#[cfg(feature = "azalea-buf")]
use azalea_buf::{
    BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
};
#[cfg(feature = "azalea-buf")]
use azalea_chat::FormattedText;

use super::Suggestion;
use crate::context::StringRange;
#[cfg(feature = "azalea-buf")]
use crate::suggestion::SuggestionValue;

#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct Suggestions {
    range: StringRange,
    suggestions: Vec<Suggestion>,
}

impl Suggestions {
    pub fn new(range: StringRange, suggestions: Vec<Suggestion>) -> Self {
        Self { range, suggestions }
    }

    pub fn merge(command: &str, input: &[Suggestions]) -> Self {
        if input.is_empty() {
            return Suggestions::default();
        } else if input.len() == 1 {
            return input[0].clone();
        };

        let mut texts = HashSet::new();
        for suggestions in input {
            texts.extend(suggestions.suggestions.clone());
        }

        Suggestions::create(command, &texts)
    }

    pub fn create(command: &str, suggestions: &HashSet<Suggestion>) -> Self {
        if suggestions.is_empty() {
            return Suggestions::default();
        };
        let mut start = usize::MAX;
        let mut end = usize::MIN;
        for suggestion in suggestions {
            start = suggestion.range.start().min(start);
            end = suggestion.range.end().max(end);
        }
        let range = StringRange::new(start, end);
        let mut texts = HashSet::new();
        for suggestion in suggestions {
            texts.insert(suggestion.expand(command, range));
        }
        let mut sorted = texts.into_iter().collect::<Vec<_>>();

        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] {
        &self.suggestions
    }

    pub fn range(&self) -> StringRange {
        self.range
    }
}

#[cfg(feature = "azalea-buf")]
impl McBufReadable for Suggestions {
    fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
        #[derive(McBuf)]
        struct StandaloneSuggestion {
            pub text: String,
            pub tooltip: Option<FormattedText>,
        }

        let start = u32::var_read_from(buf)? as usize;
        let length = u32::var_read_from(buf)? as usize;
        let range = StringRange::between(start, start + length);

        // the range of a Suggestion depends on the Suggestions containing it,
        // so we can't just `impl McBufReadable for Suggestion`
        let mut suggestions = Vec::<StandaloneSuggestion>::read_from(buf)?
            .into_iter()
            .map(|s| Suggestion {
                value: SuggestionValue::Text(s.text),
                tooltip: s.tooltip.map(|t| t.to_string()),
                range,
            })
            .collect::<Vec<_>>();
        suggestions.sort_by(|a, b| a.value.cmp(&b.value));

        Ok(Suggestions { range, suggestions })
    }
}

#[cfg(feature = "azalea-buf")]
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)?;
        self.suggestions.write_into(buf)?;
        Ok(())
    }
}