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, &current_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}