rs_complete/
lib.rs

1//! rs-completion is a library to use when you want to implement tab-completion (or similar)
2//! in your project.
3//!
4//! rs-completion is mainly built for memory efficiency. Completions are stored in binary trees
5//! where each node holds a number of characters. The characters in turn link to new nodes. Similar
6//! words will thusly share memory.
7//!
8//! ## Visual example
9//!
10//! ```text
11//! //                          'c' - 'a' - 'v' - 'e'
12//! //                         /
13//! //    root - 'b' - 'a' - 't' - 'm' - 'a' - 'n'
14//! //                               \
15//! //                                'o' - 'b' - 'i' - 'l' - 'e'
16//! ```
17//!
18//!
19//! This means that a worst case scenario you could have 25^25 nodes in memory where 25 is the size
20//! of your alphabet. But this would mean that you are holding every thinkable combination of
21//! characters in memory with no regards for consonant or verb rules. If this is what you need then
22//! you don't need a library for it.
23//!
24//! I can't argue if this solution is fast or efficient. It has worked to solve the problem
25//! I intended to solve when I created the library. If you have ideas for extensions or
26//! improvements I'm happy to see them.
27//!
28//! ## Example
29//! ```
30//! extern crate rs_complete;
31//! use rs_complete::CompletionTree;
32//!
33//! let mut completions = CompletionTree::default();
34//!
35//! completions.insert("large bunch of words that bungalow we want to be bundesliga able to complete");
36//! assert_eq!(
37//!     completions.complete("bun"),
38//!     Some(vec!["bunch", "bundesliga", "bungalow"].iter().map(|s| s.to_string()).collect()));
39//! ```
40
41#[allow(dead_code)]
42mod completion_tree;
43
44pub use completion_tree::CompletionTree;
45pub use completion_tree::WordSeparator;
46
47#[cfg(test)]
48mod tests {
49    use crate::{completion_tree::CompletionTree, WordSeparator};
50
51    #[test]
52    fn test_completion() {
53        let mut tree = CompletionTree::default();
54        tree.insert("wording");
55        let completions = tree.complete("wo").unwrap_or(vec![]);
56        let mut iter = completions.iter();
57        assert_eq!(iter.next(), Some(&"wording".to_string()));
58    }
59
60    #[test]
61    fn test_multi_completion() {
62        let mut tree = CompletionTree::default();
63        tree.insert("wording");
64        tree.insert("wollybugger");
65        tree.insert("workerbee");
66        tree.insert("worldleader");
67        tree.insert("batman");
68        tree.insert("robin");
69        let completions = tree.complete("wo").unwrap();
70        assert!(completions.contains(&"workerbee".to_string()));
71        assert!(completions.contains(&"wollybugger".to_string()));
72        assert!(completions.contains(&"wording".to_string()));
73        assert!(completions.contains(&"worldleader".to_string()));
74        assert!(!completions.contains(&"batman".to_string()));
75        assert!(!completions.contains(&"robin".to_string()));
76    }
77
78    #[test]
79    fn test_multi_insert() {
80        let mut tree = CompletionTree::default();
81        tree.insert("wollybugger workerbee worldleader batman robin wording");
82        assert_eq!(tree.word_count(), 6);
83        let completions = tree.complete("wo").unwrap();
84        assert!(completions.contains(&"workerbee".to_string()));
85        assert!(completions.contains(&"wollybugger".to_string()));
86        assert!(completions.contains(&"wording".to_string()));
87        assert!(completions.contains(&"worldleader".to_string()));
88        assert!(!completions.contains(&"batman".to_string()));
89        assert!(!completions.contains(&"robin".to_string()));
90    }
91
92    #[test]
93    fn test_multi_insert_custom_sep_1() {
94        let mut tree = CompletionTree::default();
95        tree.separator(WordSeparator::Separator("&"));
96        tree.insert("wollybugger&workerbee&worldleader&batman&robin&wording");
97        assert_eq!(tree.word_count(), 6);
98        let completions = tree.complete("wo").unwrap();
99        assert!(completions.contains(&"workerbee".to_string()));
100        assert!(completions.contains(&"wollybugger".to_string()));
101        assert!(completions.contains(&"wording".to_string()));
102        assert!(completions.contains(&"worldleader".to_string()));
103        assert!(!completions.contains(&"batman".to_string()));
104        assert!(!completions.contains(&"robin".to_string()));
105    }
106
107    #[test]
108    fn test_multi_insert_custom_sep_2() {
109        let mut tree = CompletionTree::default();
110        tree.separator(WordSeparator::Separator("slaad"));
111        tree.insert("wollybuggerslaadworkerbeeslaadworldleaderslaadbatmanslaadrobinslaadwording");
112        assert_eq!(tree.word_count(), 6);
113        let completions = tree.complete("wo").unwrap();
114        assert!(completions.contains(&"workerbee".to_string()));
115        assert!(completions.contains(&"wollybugger".to_string()));
116        assert!(completions.contains(&"wording".to_string()));
117        assert!(completions.contains(&"worldleader".to_string()));
118        assert!(!completions.contains(&"batman".to_string()));
119        assert!(!completions.contains(&"robin".to_string()));
120    }
121
122    #[test]
123    fn test_substring_matches() {
124        let mut tree = CompletionTree::default();
125        tree.insert("dumpster dumpsterfire");
126        let completions = tree.complete("dum").unwrap();
127        assert!(completions.contains(&"dumpster".to_string()));
128        assert!(completions.contains(&"dumpsterfire".to_string()));
129    }
130
131    #[test]
132    fn test_dont_include_specials() {
133        let mut tree = CompletionTree::default();
134        tree.insert("dumpster\x1b[34m dumpsterfire{}");
135        let completions = tree.complete("dum").unwrap();
136        assert!(completions.contains(&"dumpster".to_string()));
137        assert!(completions.contains(&"dumpsterfire".to_string()));
138    }
139
140    #[test]
141    fn test_without_inclusions() {
142        let mut tree = CompletionTree::default();
143        tree.insert("/dumpster /dumpsterfire");
144        assert!(tree.complete("/dum").is_none());
145    }
146
147    #[test]
148    fn test_with_inclusions() {
149        let mut tree = CompletionTree::with_inclusions(&['/', '_']);
150        tree.insert("/dumpster /dumpster_fire");
151        let completions = tree.complete("/dum").unwrap();
152        assert!(completions.contains(&"/dumpster".to_string()));
153        assert!(completions.contains(&"/dumpster_fire".to_string()));
154    }
155
156    #[test]
157    fn test_complete_empty_line() {
158        let mut tree = CompletionTree::default();
159        assert_eq!(tree.complete(""), None);
160        tree.insert("test testersson testington");
161        assert_eq!(tree.complete("barf"), None);
162        assert_eq!(tree.complete(""), None);
163    }
164
165    #[test]
166    fn test_clear() {
167        let mut completions = CompletionTree::default();
168        completions.insert("batman robin batmobile batcave robber");
169        assert_eq!(completions.word_count(), 5);
170        assert_eq!(completions.size(), 24);
171        completions.clear();
172        assert_eq!(completions.size(), 1);
173        assert_eq!(completions.word_count(), 0);
174    }
175
176    #[test]
177    fn test_word_count() {
178        let mut completions = CompletionTree::default();
179        completions.insert("batman robin batmobile batcave robber");
180        assert_eq!(completions.word_count(), 5);
181    }
182
183    #[test]
184    fn test_size() {
185        let mut completions = CompletionTree::default();
186        assert_eq!(completions.size(), 1);
187        completions.insert("batman robin batmobile batcave robber");
188        assert_eq!(completions.size(), 24);
189    }
190
191    #[test]
192    fn test_min_word_len() {
193        let mut completions = CompletionTree::default();
194        completions.set_min_word_len(4);
195        completions.insert("one two three four five");
196        assert_eq!(completions.min_word_len(), 4);
197        assert_eq!(completions.word_count(), 3);
198
199        let mut completions = CompletionTree::default();
200        completions.set_min_word_len(1);
201        completions.insert("one two three four five");
202        assert_eq!(completions.min_word_len(), 1);
203        assert_eq!(completions.word_count(), 5);
204    }
205}