1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use std::path::PathBuf;

pub trait Completer {
    fn completions(&self, start: &str) -> Vec<String>;
}

pub struct BasicCompleter {
    prefixes: Vec<String>,
}

impl BasicCompleter {
    pub fn new<T: Into<String>>(prefixes: Vec<T>) -> BasicCompleter {
        BasicCompleter { prefixes: prefixes.into_iter().map(|s| s.into()).collect() }
    }
}

impl Completer for BasicCompleter {
    fn completions(&self, start: &str) -> Vec<String> {
        self.prefixes.iter().filter(|s| s.starts_with(start)).cloned().collect()
    }
}

pub struct FilenameCompleter {
    working_dir: Option<PathBuf>,
}

impl FilenameCompleter {
    pub fn new<T: Into<PathBuf>>(working_dir: Option<T>) -> Self {
        FilenameCompleter { working_dir: working_dir.map(|p| p.into()) }
    }
}

impl Completer for FilenameCompleter {
    fn completions(&self, mut start: &str) -> Vec<String> {
        // XXX: this function is really bad, TODO rewrite

        let start_owned;
        if start.starts_with("\"") || start.starts_with("'") {
            start = &start[1..];
            if start.len() >= 1 {
                start = &start[..start.len() - 1];
            }
            start_owned = start.into();
        } else {
            start_owned = start.replace("\\ ", " ");
        }

        let full_path;
        let start_path = PathBuf::from(&start_owned[..]);

        if let Some(ref wd) = self.working_dir {
            let mut fp = PathBuf::from(wd);
            fp.push(start_owned.clone());
            full_path = fp;
        } else {
            full_path = PathBuf::from(start_owned.clone());
        }

        let p;
        let start_name;
        let completing_dir;
        match full_path.parent() {
            // XXX non-unix separaor
            Some(parent) if start != "" && !start_owned.ends_with("/") &&
                            !full_path.ends_with("..") => {
                p = PathBuf::from(parent);
                start_name = full_path.file_name().unwrap().to_string_lossy().into_owned();
                completing_dir = false;
            }
            _ => {
                p = full_path.clone();
                start_name = "".into();
                completing_dir = start == "" || start.ends_with("/") || full_path.ends_with("..");
            }
        }


        let read_dir = match p.read_dir() {
            Ok(x) => x,
            Err(_) => return vec![],
        };

        let mut matches = vec![];
        for dir in read_dir {
            let dir = match dir {
                Ok(x) => x,
                Err(_) => continue,
            };
            let file_name = dir.file_name();
            let file_name = file_name.to_string_lossy();

            if start_name == "" || file_name.starts_with(&*start_name) {
                let mut a = start_path.clone();
                if !a.is_absolute() {
                    a = PathBuf::new();
                } else if !completing_dir && !a.pop() {
                    return vec![];
                }
                a.push(dir.file_name());
                let mut s = a.to_string_lossy().into_owned();
                if dir.path().is_dir() {
                    s = s + "/";
                }

                let mut b = PathBuf::from(start_owned.clone());
                if !completing_dir {
                    b.pop();
                }
                b.push(s);

                matches.push(b.to_string_lossy().to_owned().replace(" ", "\\ "));
            }
        }

        matches
    }
}