use core::prelude::*;
use alloc::rc::Rc;
use alloc::boxed::Box;
use collections::string::*;
use collections::Vec;
use collections::slice::SliceConcatExt;
use utils::*;
#[derive(Clone)]
pub enum AutocompleteOption {
Hint { hint: String },
FullCommand { line: String}
}
pub trait CliTerminal {
fn output_line(&mut self, line: &str);
}
pub trait CliCommand {
fn execute(&mut self, cli: &mut CliTerminal, line: &str);
fn is_match(&self, line: &str) -> bool;
fn autocomplete(&self, line_start: &str) -> Option<Vec<AutocompleteOption>>;
}
pub fn cli_execute(line: &str, cmds: &mut [Box<CliCommand + 'static>], cli: &mut CliTerminal) {
let mut line_start = line.trim();
if line_start.len() == 0 { return; }
for ref mut cmd in cmds.iter_mut() {
if !cmd.is_match(line_start) {
continue;
}
cmd.execute(cli, line_start);
return;
}
if line_start.ends_with("?") {
line_start = line_start.trim_right_matches("?").trim();
} else {
cli.output_line("Unrecognized command.");
}
let fl = collect_options(line_start, cmds);
if fl.len() > 0 {
let mut hints = fl.iter().filter_map(|c| {
match c {
&AutocompleteOption::Hint {hint: ref hint} => { Some(hint.clone()) }
_ => { None }
}
}).collect::<Vec<String>>();
if hints.len() > 0 {
hints.sort_by(|a, b| { a.cmp(&b) });
cli.output_line(format!("Related commands: {}", hints.connect(", ")).as_str());
}
}
}
#[derive(Debug, Clone)]
pub enum AutocompleteResult {
None,
SingleMatch { line: AutocompleteLine },
MultipleMatches { lines: Vec<AutocompleteLine> }
}
#[derive(Debug, Clone)]
pub struct AutocompleteLine {
pub full_new_line: String,
pub additional_part: String
}
fn collect_options(line: &str, cmds: &mut [Box<CliCommand + 'static>]) -> Vec<AutocompleteOption> {
let mut ret = Vec::new();
for cmd in cmds.iter() {
let options = cmd.autocomplete(line);
if let Some(options) = options {
for option in options.iter() {
ret.push(option.clone());
}
}
}
ret
}
pub fn cli_try_autocomplete(line: &str, cmds: &mut [Box<CliCommand + 'static>]) -> AutocompleteResult {
for ref mut cmd in cmds.iter_mut() {
if !cmd.is_match(line) {
continue;
}
return AutocompleteResult::None;
}
let fl = collect_options(line, cmds);
let mut matches = Vec::new();
for opt in fl.iter() {
match opt {
&AutocompleteOption::FullCommand { line: ref line } => {
matches.push(line.clone());
}
_ => {}
}
}
match matches.len() {
0 => AutocompleteResult::None,
1 => {
let ref m = matches[0];
let c = m.chars().skip(line.len()).collect();
let l = AutocompleteLine { full_new_line: m.clone(), additional_part: c };
AutocompleteResult::SingleMatch { line: l }
}
_ => {
let mut lines = Vec::new();
for m in matches.iter() {
let c = m.chars().skip(line.len()).collect();
let l = AutocompleteLine { full_new_line: m.clone(), additional_part: c };
lines.push(l);
}
lines.sort_by(|a, b| { a.full_new_line.cmp(&b.full_new_line) });
let lcp = {
let mut strings = Vec::new();
for m in lines.iter() {
strings.push(m.full_new_line.as_str());
}
let lcp = longest_common_prefix(strings.as_slice());
if let Some(lcp) = lcp {
if lcp.len() == line.len() {
None
} else {
Some(lcp)
}
} else {
None
}
};
if let Some(lcp) = lcp {
AutocompleteResult::SingleMatch {
line: AutocompleteLine {
full_new_line: lcp.clone(),
additional_part: lcp.chars().skip(line.len()).collect()
}
}
} else {
AutocompleteResult::MultipleMatches { lines: lines }
}
}
}
}