use super::*;
#[cfg(feature = "runnable")]
use colored::*;
#[cfg(feature = "runnable")]
pub use linefeed::{Completer, Completion, Interface, Prompter, ReadResult, Terminal};
impl<'r, R> Commander<R> {
#[cfg(feature = "runnable")]
pub fn run_with_completion<
C: 'static + Completer<linefeed::DefaultTerminal>,
F: Fn(&Self) -> C,
>(
mut self,
completer_fn: F,
) {
let interface = Interface::new("commander").expect("failed to start interface");
let mut exit = false;
while !exit {
interface
.set_prompt(&format!("{}=> ", self.path().bright_cyan()))
.expect("failed to set prompt");
let completer = completer_fn(&self);
interface.set_completer(Arc::new(completer));
if let Ok(ReadResult::Input(s)) = interface.read_line() {
if let LineResult::Exit = self.parse_line(&s, true, &mut std::io::stdout()) {
exit = true
}
interface.add_history_unique(s);
}
}
}
}
#[derive(Debug, PartialEq)]
pub struct ActionMatch {
pub info: CompletionInfo,
pub qualified_path: String,
}
#[derive(Debug, PartialEq)]
pub struct CompletionInfo {
pub completestr: String,
pub itemtype: ItemType,
pub help_msg: CmdStr,
}
pub fn create_tree_completion_items<R>(cmdr: &Commander<R>) -> Vec<CompletionInfo> {
cmdr.structure(false)
.into_iter()
.filter_map(|info| {
let StructureInfo {
path,
itemtype,
help_msg,
} = info;
let completestr =
path.split('.')
.filter(|x| !x.is_empty())
.fold(String::new(), |mut s, x| {
if !s.is_empty() {
s.push(' ');
}
s.push_str(x);
s
});
if completestr.is_empty() {
None
} else {
Some(CompletionInfo {
completestr,
itemtype,
help_msg,
})
}
})
.collect()
}
pub fn create_action_completion_items<R>(cmdr: &Commander<R>) -> Vec<ActionMatch> {
let cpath = cmdr.path();
let rname = cmdr.root_name();
let starter = if cpath == rname {
""
} else {
&cpath[rname.len() + 1..]
};
cmdr.structure(true)
.into_iter()
.filter(|x| x.path.contains("..") && x.path.starts_with(starter))
.filter_map(|x| {
let StructureInfo {
path,
itemtype,
help_msg,
} = x;
let qualified_path = path.clone();
let completestr = path[starter.len()..]
.split('.')
.filter(|x| !x.is_empty())
.fold(String::new(), |mut s, x| {
s.push_str(x);
s.push(' ');
s
});
if completestr.is_empty() {
None
} else {
let info = CompletionInfo {
completestr,
itemtype,
help_msg,
};
Some(ActionMatch {
info,
qualified_path,
})
}
})
.collect()
}
pub fn tree_completions<'l: 'i, 'i, 'a: 'i, I>(
line: &'l str,
items: I,
) -> impl Iterator<Item = (&'i str, &'i CompletionInfo)>
where
I: Iterator<Item = &'i CompletionInfo>,
{
items
.filter(move |x| x.completestr.starts_with(line))
.map(move |x| {
let word_idx = word_break_start(line, &[' ']);
let word = &x.completestr[word_idx..];
(word, x)
})
}
pub fn word_break_start(s: &str, word_break_ch: &[char]) -> usize {
let mut start = s.len();
for (idx, ch) in s.char_indices().rev() {
if word_break_ch.contains(&ch) {
break;
}
start = idx;
}
start
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_tree_completion_items_test() {
let mut cmder = Builder::default_config("cmdtree-example")
.begin_class("class1", "")
.begin_class("inner-class1", "")
.add_action("name", "print class name", |_, _| ())
.end_class()
.end_class()
.begin_class("print", "")
.add_action("echo", "", |_, _| ())
.add_action("countdown", "", |_, _| ())
.into_commander()
.unwrap();
let v: Vec<_> = {
let v: Vec<_> = create_tree_completion_items(&cmder)
.into_iter()
.map(|x| x.completestr)
.collect();
v
};
assert_eq!(
v,
vec_str(vec![
"class1",
"class1 inner-class1",
"class1 inner-class1 name",
"print",
"print countdown",
"print echo"
])
);
cmder.parse_line("class1", true, &mut std::io::sink());
let v: Vec<_> = create_tree_completion_items(&cmder)
.into_iter()
.map(|x| x.completestr)
.collect();
assert_eq!(v, vec_str(vec!["inner-class1", "inner-class1 name",]));
}
#[test]
fn create_action_completion_items_test() {
let mut cmder = Builder::default_config("eg")
.begin_class("one", "")
.begin_class("two", "")
.add_action("three", "", |_, _| ())
.end_class()
.end_class()
.begin_class("hello", "")
.end_class()
.into_commander()
.unwrap();
let v: Vec<_> = create_action_completion_items(&cmder)
.into_iter()
.map(|x| (x.info.completestr, x.qualified_path))
.collect();
assert_eq!(
v,
vec![("one two three ".to_string(), "one.two..three".to_string(),)]
);
cmder.parse_line("one", true, &mut std::io::sink());
let v: Vec<_> = create_action_completion_items(&cmder)
.into_iter()
.map(|x| (x.info.completestr, x.qualified_path))
.collect();
assert_eq!(
v,
vec![("two three ".to_string(), "one.two..three".to_string(),)]
);
}
#[test]
fn tree_completions_test() {
let mut cmder = Builder::default_config("cmdtree-example")
.begin_class("class1", "")
.begin_class("inner-class1", "")
.add_action("name", "", |_, _| ())
.end_class()
.end_class()
.begin_class("print", "")
.add_action("echo", "", |_, _| ())
.add_action("countdown", "", |_, _| ())
.end_class()
.add_action("clone", "", |_, _| ())
.into_commander()
.unwrap();
let v = create_tree_completion_items(&cmder);
let completions = tree_completions("", v.iter())
.map(|x| x.0)
.collect::<Vec<_>>();
assert_eq!(
completions,
vec![
"clone",
"class1",
"class1 inner-class1",
"class1 inner-class1 name",
"print",
"print countdown",
"print echo",
]
);
let completions = tree_completions("cl", v.iter())
.map(|x| x.0)
.collect::<Vec<_>>();
assert_eq!(
completions,
vec![
"clone",
"class1",
"class1 inner-class1",
"class1 inner-class1 name",
]
);
let completions = tree_completions("class1 ", v.iter())
.map(|x| x.0)
.collect::<Vec<_>>();
assert_eq!(completions, vec!["inner-class1", "inner-class1 name",]);
cmder.parse_line("class1", true, &mut std::io::sink());
let v = create_tree_completion_items(&cmder);
let completions = tree_completions("inn", v.iter())
.map(|x| x.0)
.collect::<Vec<_>>();
assert_eq!(completions, vec!["inner-class1", "inner-class1 name",]);
}
fn vec_str(v: Vec<&str>) -> Vec<String> {
v.into_iter().map(|x| x.to_string()).collect()
}
}