reedline/completion/default.rs
1use crate::{Completer, Span, Suggestion};
2use std::{
3 collections::{BTreeMap, BTreeSet},
4 str::Chars,
5 sync::Arc,
6};
7
8/// A default completer that can detect keywords
9///
10/// # Example
11///
12/// ```rust
13/// use reedline::{DefaultCompleter, Reedline};
14///
15/// let commands = vec![
16/// "test".into(),
17/// "hello world".into(),
18/// "hello world reedline".into(),
19/// "this is the reedline crate".into(),
20/// ];
21/// let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
22///
23/// let mut line_editor = Reedline::create().with_completer(completer);
24/// ```
25#[derive(Debug, Clone)]
26pub struct DefaultCompleter {
27 root: CompletionNode,
28 min_word_len: usize,
29}
30
31impl Default for DefaultCompleter {
32 fn default() -> Self {
33 let inclusions = Arc::new(BTreeSet::new());
34 Self {
35 root: CompletionNode::new(inclusions),
36 min_word_len: 2,
37 }
38 }
39}
40impl Completer for DefaultCompleter {
41 /// Returns a vector of completions and the position in which they must be replaced;
42 /// based on the provided input.
43 ///
44 /// # Arguments
45 ///
46 /// * `line` The line to complete
47 /// * `pos` The cursor position
48 ///
49 /// # Example
50 /// ```
51 /// use reedline::{DefaultCompleter,Completer,Span,Suggestion};
52 ///
53 /// let mut completions = DefaultCompleter::default();
54 /// completions.insert(vec!["batman","robin","batmobile","batcave","robber"].iter().map(|s| s.to_string()).collect());
55 /// assert_eq!(
56 /// completions.complete("bat",3),
57 /// vec![
58 /// Suggestion {value: "batcave".into(), description: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
59 /// Suggestion {value: "batman".into(), description: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
60 /// Suggestion {value: "batmobile".into(), description: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
61 /// ]);
62 ///
63 /// assert_eq!(
64 /// completions.complete("to the bat",10),
65 /// vec![
66 /// Suggestion {value: "batcave".into(), description: None, extra: None, span: Span { start: 7, end: 10 }, append_whitespace: false},
67 /// Suggestion {value: "batman".into(), description: None, extra: None, span: Span { start: 7, end: 10 }, append_whitespace: false},
68 /// Suggestion {value: "batmobile".into(), description: None, extra: None, span: Span { start: 7, end: 10 }, append_whitespace: false},
69 /// ]);
70 /// ```
71 fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
72 let mut span_line_whitespaces = 0;
73 let mut completions = vec![];
74 if !line.is_empty() {
75 let mut split = line[0..pos].split(' ').rev();
76 let mut span_line: String = String::new();
77 for _ in 0..split.clone().count() {
78 if let Some(s) = split.next() {
79 if s.is_empty() {
80 span_line_whitespaces += 1;
81 continue;
82 }
83 if span_line.is_empty() {
84 span_line = s.to_string();
85 } else {
86 span_line = format!("{s} {span_line}");
87 }
88 if let Some(mut extensions) = self.root.complete(span_line.chars()) {
89 extensions.sort();
90 completions.extend(
91 extensions
92 .iter()
93 .map(|ext| {
94 let span = Span::new(
95 pos - span_line.len() - span_line_whitespaces,
96 pos,
97 );
98
99 Suggestion {
100 value: format!("{span_line}{ext}"),
101 description: None,
102 extra: None,
103 span,
104 append_whitespace: false,
105 }
106 })
107 .filter(|t| t.value.len() > (t.span.end - t.span.start))
108 .collect::<Vec<Suggestion>>(),
109 );
110 }
111 }
112 }
113 }
114 completions.dedup();
115 completions
116 }
117}
118impl DefaultCompleter {
119 /// Construct the default completer with a list of commands/keywords to highlight
120 pub fn new(external_commands: Vec<String>) -> Self {
121 let mut dc = DefaultCompleter::default();
122 dc.insert(external_commands);
123 dc
124 }
125
126 /// Construct the default completer with a list of commands/keywords to highlight, given a minimum word length
127 pub fn new_with_wordlen(external_commands: Vec<String>, min_word_len: usize) -> Self {
128 let mut dc = DefaultCompleter::default().set_min_word_len(min_word_len);
129 dc.insert(external_commands);
130 dc
131 }
132
133 /// Insert `external_commands` list in the object root
134 ///
135 /// # Arguments
136 ///
137 /// * `line` A vector of `String` containing the external commands
138 ///
139 /// # Example
140 /// ```
141 /// use reedline::{DefaultCompleter,Completer};
142 ///
143 /// let mut completions = DefaultCompleter::default();
144 ///
145 /// // Insert multiple words
146 /// completions.insert(vec!["a","line","with","many","words"].iter().map(|s| s.to_string()).collect());
147 ///
148 /// // The above line is equal to the following:
149 /// completions.insert(vec!["a","line","with"].iter().map(|s| s.to_string()).collect());
150 /// completions.insert(vec!["many","words"].iter().map(|s| s.to_string()).collect());
151 /// ```
152 pub fn insert(&mut self, words: Vec<String>) {
153 for word in words {
154 if word.len() >= self.min_word_len {
155 self.root.insert(word.chars());
156 }
157 }
158 }
159
160 /// Create a new `DefaultCompleter` with provided non alphabet characters whitelisted.
161 /// The default `DefaultCompleter` will only parse alphabet characters (a-z, A-Z). Use this to
162 /// introduce additional accepted special characters.
163 ///
164 /// # Arguments
165 ///
166 /// * `incl` An array slice with allowed characters
167 ///
168 /// # Example
169 /// ```
170 /// use reedline::{DefaultCompleter,Completer,Span,Suggestion};
171 ///
172 /// let mut completions = DefaultCompleter::default();
173 /// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
174 /// assert_eq!(
175 /// completions.complete("te",2),
176 /// vec![Suggestion {value: "test".into(), description: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false}]);
177 ///
178 /// let mut completions = DefaultCompleter::with_inclusions(&['-', '_']);
179 /// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
180 /// assert_eq!(
181 /// completions.complete("te",2),
182 /// vec![
183 /// Suggestion {value: "test-hyphen".into(), description: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
184 /// Suggestion {value: "test_underscore".into(), description: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
185 /// ]);
186 /// ```
187 pub fn with_inclusions(incl: &[char]) -> Self {
188 let mut set = BTreeSet::new();
189 set.extend(incl.iter());
190 let inclusions = Arc::new(set);
191 Self {
192 root: CompletionNode::new(inclusions),
193 ..Self::default()
194 }
195 }
196
197 /// Clears all the data from the tree
198 /// # Example
199 /// ```
200 /// use reedline::{DefaultCompleter,Completer};
201 ///
202 /// let mut completions = DefaultCompleter::default();
203 /// completions.insert(vec!["batman","robin","batmobile","batcave","robber"].iter().map(|s| s.to_string()).collect());
204 /// assert_eq!(completions.word_count(), 5);
205 /// assert_eq!(completions.size(), 24);
206 /// completions.clear();
207 /// assert_eq!(completions.size(), 1);
208 /// assert_eq!(completions.word_count(), 0);
209 /// ```
210 pub fn clear(&mut self) {
211 self.root.clear();
212 }
213
214 /// Returns a count of how many words that exist in the tree
215 /// # Example
216 /// ```
217 /// use reedline::{DefaultCompleter,Completer};
218 ///
219 /// let mut completions = DefaultCompleter::default();
220 /// completions.insert(vec!["batman","robin","batmobile","batcave","robber"].iter().map(|s| s.to_string()).collect());
221 /// assert_eq!(completions.word_count(), 5);
222 /// ```
223 pub fn word_count(&self) -> u32 {
224 self.root.word_count()
225 }
226
227 /// Returns the size of the tree, the amount of nodes, not words
228 /// # Example
229 /// ```
230 /// use reedline::{DefaultCompleter,Completer};
231 ///
232 /// let mut completions = DefaultCompleter::default();
233 /// completions.insert(vec!["batman","robin","batmobile","batcave","robber"].iter().map(|s| s.to_string()).collect());
234 /// assert_eq!(completions.size(), 24);
235 /// ```
236 pub fn size(&self) -> u32 {
237 self.root.subnode_count()
238 }
239
240 /// Returns the minimum word length to complete. This allows you
241 /// to pass full sentences to `insert()` and not worry about
242 /// pruning out small words like "a" or "to", because they will be
243 /// ignored.
244 /// # Example
245 /// ```
246 /// use reedline::{DefaultCompleter,Completer};
247 ///
248 /// let mut completions = DefaultCompleter::default().set_min_word_len(4);
249 /// completions.insert(vec!["one","two","three","four","five"].iter().map(|s| s.to_string()).collect());
250 /// assert_eq!(completions.word_count(), 3);
251 ///
252 /// let mut completions = DefaultCompleter::default().set_min_word_len(1);
253 /// completions.insert(vec!["one","two","three","four","five"].iter().map(|s| s.to_string()).collect());
254 /// assert_eq!(completions.word_count(), 5);
255 /// ```
256 pub fn min_word_len(&self) -> usize {
257 self.min_word_len
258 }
259
260 /// Sets the minimum word length to complete on. Smaller words are
261 /// ignored. This only affects future calls to `insert()` -
262 /// changing this won't start completing on smaller words that
263 /// were added in the past, nor will it exclude larger words
264 /// already inserted into the completion tree.
265 #[must_use]
266 pub fn set_min_word_len(mut self, len: usize) -> Self {
267 self.min_word_len = len;
268 self
269 }
270}
271
272#[derive(Debug, Clone)]
273struct CompletionNode {
274 subnodes: BTreeMap<char, CompletionNode>,
275 leaf: bool,
276 inclusions: Arc<BTreeSet<char>>,
277}
278
279impl CompletionNode {
280 fn new(incl: Arc<BTreeSet<char>>) -> Self {
281 Self {
282 subnodes: BTreeMap::new(),
283 leaf: false,
284 inclusions: incl,
285 }
286 }
287
288 fn clear(&mut self) {
289 self.subnodes.clear();
290 }
291
292 fn word_count(&self) -> u32 {
293 let mut count = self.subnodes.values().map(CompletionNode::word_count).sum();
294 if self.leaf {
295 count += 1;
296 }
297 count
298 }
299
300 fn subnode_count(&self) -> u32 {
301 self.subnodes
302 .values()
303 .map(CompletionNode::subnode_count)
304 .sum::<u32>()
305 + 1
306 }
307
308 fn insert(&mut self, mut iter: Chars) {
309 if let Some(c) = iter.next() {
310 if self.inclusions.contains(&c) || c.is_alphanumeric() || c.is_whitespace() {
311 let inclusions = self.inclusions.clone();
312 let subnode = self
313 .subnodes
314 .entry(c)
315 .or_insert_with(|| CompletionNode::new(inclusions));
316 subnode.insert(iter);
317 } else {
318 self.leaf = true;
319 }
320 } else {
321 self.leaf = true;
322 }
323 }
324
325 fn complete(&self, mut iter: Chars) -> Option<Vec<String>> {
326 if let Some(c) = iter.next() {
327 if let Some(subnode) = self.subnodes.get(&c) {
328 subnode.complete(iter)
329 } else {
330 None
331 }
332 } else {
333 Some(self.collect(""))
334 }
335 }
336
337 fn collect(&self, partial: &str) -> Vec<String> {
338 let mut completions = vec![];
339 if self.leaf {
340 completions.push(partial.to_string());
341 }
342
343 if !self.subnodes.is_empty() {
344 for (c, node) in &self.subnodes {
345 let mut partial = partial.to_string();
346 partial.push(*c);
347 completions.append(&mut node.collect(&partial));
348 }
349 }
350 completions
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 #[test]
357 fn default_completer_with_non_ansi() {
358 use super::*;
359
360 let mut completions = DefaultCompleter::default();
361 completions.insert(
362 ["nushell", "null", "number"]
363 .iter()
364 .map(|s| s.to_string())
365 .collect(),
366 );
367
368 assert_eq!(
369 completions.complete("n", 3),
370 [
371 Suggestion {
372 value: "null".into(),
373 description: None,
374 extra: None,
375 span: Span { start: 0, end: 3 },
376 append_whitespace: false,
377 },
378 Suggestion {
379 value: "number".into(),
380 description: None,
381 extra: None,
382 span: Span { start: 0, end: 3 },
383 append_whitespace: false,
384 },
385 Suggestion {
386 value: "nushell".into(),
387 description: None,
388 extra: None,
389 span: Span { start: 0, end: 3 },
390 append_whitespace: false,
391 },
392 ]
393 );
394 }
395}