1use super::*;
6#[cfg(feature = "runnable")]
7use colored::*;
8#[cfg(feature = "runnable")]
9pub use linefeed::{Completer, Completion, Interface, Prompter, ReadResult, Terminal};
10
11impl<'r, R> Commander<R> {
12 #[cfg(feature = "runnable")]
17 pub fn run_with_completion<
18 C: 'static + Completer<linefeed::DefaultTerminal>,
19 F: Fn(&Self) -> C,
20 >(
21 mut self,
22 completer_fn: F,
23 ) {
24 let interface = Interface::new("commander").expect("failed to start interface");
25 let mut exit = false;
26
27 while !exit {
28 interface
29 .set_prompt(&format!("{}=> ", self.path().bright_cyan()))
30 .expect("failed to set prompt");
31
32 let completer = completer_fn(&self);
33 interface.set_completer(Arc::new(completer));
34
35 if let Ok(ReadResult::Input(s)) = interface.read_line() {
36 if let LineResult::Exit = self.parse_line(&s, true, &mut std::io::stdout()) {
37 exit = true
38 }
39 interface.add_history_unique(s);
40 }
41 }
42 }
43}
44
45#[derive(Debug, PartialEq)]
47pub struct ActionMatch {
48 pub info: CompletionInfo,
54 pub qualified_path: String,
59}
60
61#[derive(Debug, PartialEq)]
63pub struct CompletionInfo {
64 pub completestr: String,
66 pub itemtype: ItemType,
68 pub help_msg: CmdStr,
70}
71
72pub fn create_tree_completion_items<R>(cmdr: &Commander<R>) -> Vec<CompletionInfo> {
100 cmdr.structure(false)
101 .into_iter()
102 .filter_map(|info| {
103 let StructureInfo {
104 path,
105 itemtype,
106 help_msg,
107 } = info;
108
109 let completestr =
110 path.split('.')
111 .filter(|x| !x.is_empty())
112 .fold(String::new(), |mut s, x| {
113 if !s.is_empty() {
114 s.push(' ');
115 }
116 s.push_str(x);
117 s
118 });
119
120 if completestr.is_empty() {
121 None
122 } else {
123 Some(CompletionInfo {
124 completestr,
125 itemtype,
126 help_msg,
127 })
128 }
129 })
130 .collect()
131}
132
133pub fn create_action_completion_items<R>(cmdr: &Commander<R>) -> Vec<ActionMatch> {
136 let cpath = cmdr.path();
137 let rname = cmdr.root_name();
138
139 let starter = if cpath == rname {
140 "" } else {
142 &cpath[rname.len() + 1..] };
144
145 cmdr.structure(true)
146 .into_iter()
147 .filter(|x| x.path.contains("..") && x.path.starts_with(starter))
148 .filter_map(|x| {
149 let StructureInfo {
150 path,
151 itemtype,
152 help_msg,
153 } = x;
154
155 let qualified_path = path.clone();
156
157 let completestr = path[starter.len()..]
158 .split('.')
159 .filter(|x| !x.is_empty())
160 .fold(String::new(), |mut s, x| {
161 s.push_str(x);
162 s.push(' ');
163 s
164 });
165
166 if completestr.is_empty() {
167 None
168 } else {
169 let info = CompletionInfo {
170 completestr,
171 itemtype,
172 help_msg,
173 };
174
175 Some(ActionMatch {
176 info,
177 qualified_path,
178 })
179 }
180 })
181 .collect()
182}
183
184pub fn tree_completions<'l: 'i, 'i, 'a: 'i, I>(
196 line: &'l str,
197 items: I,
198) -> impl Iterator<Item = (&'i str, &'i CompletionInfo)>
199where
200 I: Iterator<Item = &'i CompletionInfo>,
201{
202 items
203 .filter(move |x| x.completestr.starts_with(line))
204 .map(move |x| {
205 let word_idx = word_break_start(line, &[' ']);
208 let word = &x.completestr[word_idx..];
209 (word, x)
210 })
211}
212
213pub fn word_break_start(s: &str, word_break_ch: &[char]) -> usize {
215 let mut start = s.len();
216
217 for (idx, ch) in s.char_indices().rev() {
218 if word_break_ch.contains(&ch) {
219 break;
220 }
221 start = idx;
222 }
223
224 start
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn create_tree_completion_items_test() {
233 let mut cmder = Builder::default_config("cmdtree-example")
234 .begin_class("class1", "") .begin_class("inner-class1", "") .add_action("name", "print class name", |_, _| ())
237 .end_class()
238 .end_class()
239 .begin_class("print", "")
240 .add_action("echo", "", |_, _| ())
241 .add_action("countdown", "", |_, _| ())
242 .into_commander()
243 .unwrap();
244
245 let v: Vec<_> = {
246 let v: Vec<_> = create_tree_completion_items(&cmder)
247 .into_iter()
248 .map(|x| x.completestr)
249 .collect();
250 v
251 };
252 assert_eq!(
253 v,
254 vec_str(vec![
255 "class1",
256 "class1 inner-class1",
257 "class1 inner-class1 name",
258 "print",
259 "print countdown",
260 "print echo"
261 ])
262 );
263
264 cmder.parse_line("class1", true, &mut std::io::sink());
265
266 let v: Vec<_> = create_tree_completion_items(&cmder)
267 .into_iter()
268 .map(|x| x.completestr)
269 .collect();
270 assert_eq!(v, vec_str(vec!["inner-class1", "inner-class1 name",]));
271 }
272
273 #[test]
274 fn create_action_completion_items_test() {
275 let mut cmder = Builder::default_config("eg")
276 .begin_class("one", "") .begin_class("two", "")
278 .add_action("three", "", |_, _| ())
279 .end_class()
280 .end_class()
281 .begin_class("hello", "")
282 .end_class()
283 .into_commander()
284 .unwrap();
285
286 let v: Vec<_> = create_action_completion_items(&cmder)
287 .into_iter()
288 .map(|x| (x.info.completestr, x.qualified_path))
289 .collect();
290 assert_eq!(
291 v,
292 vec![("one two three ".to_string(), "one.two..three".to_string(),)]
293 );
294
295 cmder.parse_line("one", true, &mut std::io::sink());
296
297 let v: Vec<_> = create_action_completion_items(&cmder)
298 .into_iter()
299 .map(|x| (x.info.completestr, x.qualified_path))
300 .collect();
301 assert_eq!(
302 v,
303 vec![("two three ".to_string(), "one.two..three".to_string(),)]
304 );
305 }
306
307 #[test]
308 fn tree_completions_test() {
309 let mut cmder = Builder::default_config("cmdtree-example")
310 .begin_class("class1", "")
311 .begin_class("inner-class1", "")
312 .add_action("name", "", |_, _| ())
313 .end_class()
314 .end_class()
315 .begin_class("print", "")
316 .add_action("echo", "", |_, _| ())
317 .add_action("countdown", "", |_, _| ())
318 .end_class()
319 .add_action("clone", "", |_, _| ())
320 .into_commander()
321 .unwrap();
322
323 let v = create_tree_completion_items(&cmder);
324 let completions = tree_completions("", v.iter())
325 .map(|x| x.0)
326 .collect::<Vec<_>>();
327 assert_eq!(
328 completions,
329 vec![
330 "clone",
331 "class1",
332 "class1 inner-class1",
333 "class1 inner-class1 name",
334 "print",
335 "print countdown",
336 "print echo",
337 ]
338 );
339
340 let completions = tree_completions("cl", v.iter())
341 .map(|x| x.0)
342 .collect::<Vec<_>>();
343 assert_eq!(
344 completions,
345 vec![
346 "clone",
347 "class1",
348 "class1 inner-class1",
349 "class1 inner-class1 name",
350 ]
351 );
352
353 let completions = tree_completions("class1 ", v.iter())
354 .map(|x| x.0)
355 .collect::<Vec<_>>();
356 assert_eq!(completions, vec!["inner-class1", "inner-class1 name",]);
357
358 cmder.parse_line("class1", true, &mut std::io::sink());
359
360 let v = create_tree_completion_items(&cmder);
361 let completions = tree_completions("inn", v.iter())
362 .map(|x| x.0)
363 .collect::<Vec<_>>();
364 assert_eq!(completions, vec!["inner-class1", "inner-class1 name",]);
365 }
366
367 fn vec_str(v: Vec<&str>) -> Vec<String> {
368 v.into_iter().map(|x| x.to_string()).collect()
369 }
370}