use std::{
env,
fs,
cmp::Ordering::Equal,
os::unix::fs::PermissionsExt,
};
#[derive(Debug)]
pub enum Completion {
None,
Partial(Vec<String>),
Complete(String),
}
impl Completion {
pub fn is_complete(&self) -> bool {
match *self {
Completion::None |
Completion::Partial(_) => false,
Completion::Complete(_) => true,
}
}
pub fn first(&self) -> String {
match *self {
Completion::None => "".to_owned(),
Completion::Partial(ref p) => {
match p.first() {
Some(t) => t.to_owned(),
None => "".to_owned(),
}
},
Completion::Complete(ref s) => s.to_owned(),
}
}
pub fn possibilities(&self) -> Vec<String> {
match *self {
Completion::None => vec![],
Completion::Partial(ref p) => p.clone(),
Completion::Complete(ref t) => vec![t.clone()],
}
}
}
pub fn complete(text: &str) -> Completion {
match executable_completions(text) {
c @ Completion::Partial(_) |
c @ Completion::Complete(_) => c,
Completion::None => path_complete(text),
}
}
pub fn executable_completions(text: &str) -> Completion {
match env::var_os("PATH") {
Some(paths) => {
let mut matches = vec![];
for dir in env::split_paths(&paths) {
if let Ok(executables) = fs::read_dir(dir) {
let paths = executables.filter_map(|e| {
match e { Ok(p) => Some(p.path()), _ => None }
});
for path in paths {
if let Some(filename) = path.file_name() {
let filename = filename.to_string_lossy();
if let Ok(metadata) = fs::metadata(&path) {
if (metadata.permissions()
.mode() & 0o111 != 0)
&& filename.starts_with(text)
{
matches.push(filename.into());
}
}
}
}
}
}
match matches.len() {
0 => Completion::None,
1 => Completion::Complete(matches.remove(0)),
_ => {
matches.sort_by(|a, b| {
match a.len().cmp(&b.len()) {
Equal => b.cmp(&a),
o => o
}
});
Completion::Partial(matches)
}
}
}
None => panic!("PATH is undefined"),
}
}
pub fn path_complete(text: &str) -> Completion {
match text {
"/hom" => Completion::Complete("/home/".into()),
"/usr/b" => Completion::Complete("/usr/bin/".into()),
"ls /hom" => Completion::Complete("ls /home/".into()),
_ => Completion::None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lexicographical_order() {
assert_eq!("cargo", complete("car").first());
}
#[test]
fn paths() {
assert_eq!("/home/", complete("/hom").first());
assert_eq!("/usr/bin/", complete("/usr/b").first());
assert_eq!("ls /home/", complete("ls /hom").first());
}
}