liner/
complete.rs

1use super::event::Event;
2use std::io::Write;
3use std::path::PathBuf;
4
5pub trait Completer {
6    fn completions(&mut self, start: &str) -> Vec<String>;
7    fn on_event<W: Write>(&mut self, _event: Event<W>) {}
8}
9
10pub struct BasicCompleter {
11    prefixes: Vec<String>,
12}
13
14impl BasicCompleter {
15    pub fn new<T: Into<String>>(prefixes: Vec<T>) -> BasicCompleter {
16        BasicCompleter {
17            prefixes: prefixes.into_iter().map(|s| s.into()).collect(),
18        }
19    }
20}
21
22impl Completer for BasicCompleter {
23    fn completions(&mut self, start: &str) -> Vec<String> {
24        self.prefixes
25            .iter()
26            .filter(|s| s.starts_with(start))
27            .cloned()
28            .collect()
29    }
30}
31
32pub struct FilenameCompleter {
33    working_dir: Option<PathBuf>,
34    case_sensitive: bool,
35}
36
37impl FilenameCompleter {
38    pub fn new<T: Into<PathBuf>>(working_dir: Option<T>) -> Self {
39        FilenameCompleter {
40            working_dir: working_dir.map(|p| p.into()),
41            case_sensitive: true,
42        }
43    }
44
45    pub fn with_case_sensitivity<T: Into<PathBuf>>(
46        working_dir: Option<T>,
47        case_sensitive: bool,
48    ) -> Self {
49        FilenameCompleter {
50            working_dir: working_dir.map(|p| p.into()),
51            case_sensitive,
52        }
53    }
54}
55
56impl Completer for FilenameCompleter {
57    fn completions(&mut self, mut start: &str) -> Vec<String> {
58        // XXX: this function is really bad, TODO rewrite
59
60        let start_owned: String = if start.starts_with('\"') || start.starts_with('\'') {
61            start = &start[1..];
62            if !start.is_empty() {
63                start = &start[..start.len() - 1];
64            }
65            start.into()
66        } else {
67            start.replace(r"\ ", " ")
68        };
69
70        let start_path = PathBuf::from(start_owned.as_str());
71
72        let full_path = match self.working_dir {
73            Some(ref wd) => {
74                let mut fp = PathBuf::from(wd);
75                fp.push(start_owned.as_str());
76                fp
77            }
78            None => PathBuf::from(start_owned.as_str()),
79        };
80
81        let p;
82        let start_name;
83        let completing_dir;
84        match full_path.parent() {
85            // XXX non-unix separaor
86            Some(parent)
87                if !start.is_empty()
88                    && !start_owned.ends_with('/')
89                    && !full_path.ends_with("..") =>
90            {
91                p = parent;
92                start_name = if self.case_sensitive {
93                    full_path.file_name().unwrap().to_string_lossy()
94                } else {
95                    let sn = full_path.file_name().unwrap().to_string_lossy();
96                    sn.to_lowercase().into()
97                };
98                completing_dir = false;
99            }
100            _ => {
101                p = full_path.as_path();
102                start_name = "".into();
103                completing_dir =
104                    start.is_empty() || start.ends_with('/') || full_path.ends_with("..");
105            }
106        }
107
108        let read_dir = match p.read_dir() {
109            Ok(x) => x,
110            Err(_) => return vec![],
111        };
112
113        let mut matches = vec![];
114        for dir in read_dir {
115            let dir = match dir {
116                Ok(x) => x,
117                Err(_) => continue,
118            };
119            let file_name = dir.file_name();
120            let file_name = if self.case_sensitive {
121                file_name.to_string_lossy().to_string()
122            } else {
123                file_name.to_string_lossy().to_lowercase()
124            };
125
126            if start_name.is_empty() || file_name.starts_with(&*start_name) {
127                let mut a = start_path.clone();
128                if !a.is_absolute() {
129                    a = PathBuf::new();
130                } else if !completing_dir && !a.pop() {
131                    return vec![];
132                }
133
134                a.push(dir.file_name());
135                let mut s = a.to_string_lossy();
136                if dir.path().is_dir() {
137                    let mut string = s.into_owned();
138                    string.push('/');
139                    s = string.into();
140                }
141
142                let mut b = PathBuf::from(&start_owned);
143                if !completing_dir {
144                    b.pop();
145                }
146                b.push(s.as_ref());
147
148                matches.push(b.to_string_lossy().replace(" ", r"\ "));
149            }
150        }
151
152        matches
153    }
154}