rm_lisa/input/autocomplete.rs
1//! An 'autocomplete' provider, providing 'tab completion' for input providers
2//! that support it.
3//!
4//! Although lisa doesn't provide any auto input providers itself it provides
5//! the common trait that all folks can use. You should be your own
6//! autocomplete solution that feels right to you.
7//!
8//! However, your autocomplete provider should be fast as if provided it can
9//! delay user input from rendering which may cause a suboptimal user
10//! experience.
11
12/// Something that can provide auto-complete results.
13///
14/// Lisa has built this interface in such a way to make it easy to write
15/// relatively stateless, if not fully ZST auto complete providers. The
16/// current input provider should store the 'auto-complete' state, along with
17/// determining when to call the autocomplete provider.
18///
19/// It should also be important to note that not all input mechanisms may
20/// support an autocomplete system (e.g. if you accept HTTP API calls in,
21/// you may not need autocomplete at all).
22///
23/// Please consult your input providers documentation for more information
24/// about how it does, or does not use autocomplete.
25///
26/// Your autocomplete provider should be fast as if provided it can
27/// delay user input from rendering which may cause a suboptimal user
28/// experience.
29pub trait AutocompleteProvider: Send + Sync {
30 /// Whether or not this autocomplete provider can actually provide
31 /// autocompletes.
32 #[must_use]
33 fn can_autocomplete(&self) -> bool;
34
35 /// Get the characters to display after a users current input as a
36 /// suggestion.
37 ///
38 /// This should return only the _net new_ characters to add to
39 /// `current_input` to make a full command. While this value should be cached
40 /// by the display provider, it is dependent on which display provider you
41 /// are using. Even in a cached world though this function should return
42 /// _fast_ as it will impact the display rendering time when called.
43 #[must_use]
44 fn get_displayable_suggestion(&self, current_input: String) -> Option<String>;
45
46 /// Get the characters to add to an input when a user hits 'tab'.
47 ///
48 /// This should return the _net new_ characters to add to `input` to complete
49 /// the completion.
50 ///
51 /// The 'last completion index' is just a counter that goes up to keep track
52 /// of which particular completion to complete too. If there isn't another
53 /// completion for the index, you should wrap around and return the next
54 /// completion.
55 #[must_use]
56 fn get_tab_completion(
57 &self,
58 last_completion_index: Option<usize>,
59 input: &str,
60 ) -> Option<String>;
61}
62
63#[cfg(test)]
64pub mod test_helpers {
65 use super::*;
66
67 /// A manual list of suggestions.
68 ///
69 /// Used to do very basic autocomplete suggestions in tests.
70 pub struct AutocompleteSuggestionList(pub Vec<String>);
71
72 impl AutocompleteProvider for AutocompleteSuggestionList {
73 fn can_autocomplete(&self) -> bool {
74 true
75 }
76
77 fn get_displayable_suggestion(&self, current_input: String) -> Option<String> {
78 self.get_tab_completion(None, ¤t_input)
79 }
80
81 fn get_tab_completion(
82 &self,
83 last_completion_index: Option<usize>,
84 input: &str,
85 ) -> Option<String> {
86 // We never have conflicting suggestions, so we always have only one completion. You should
87 // probably NOT do this unless all your commands truly are unique from the very first character.
88 let to_ignore_count = last_completion_index.map(|val| val + 1).unwrap_or_default();
89 let mut ignored = 0_usize;
90 for suggestion in &self.0 {
91 if input.len() >= suggestion.len() {
92 continue;
93 }
94
95 if suggestion.starts_with(input) {
96 if ignored >= to_ignore_count {
97 return Some(suggestion[input.len()..].to_owned());
98 } else {
99 ignored += 1;
100 }
101 }
102 }
103
104 None
105 }
106 }
107}