1use std::borrow::Cow::{self, Borrowed, Owned};
3use std::collections::BTreeSet;
4use std::fs;
5use std::path::{self, Path};
6
7use super::Result;
8use line_buffer::LineBuffer;
9
10pub trait Completer {
17 fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)>;
23 fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
25 let end = line.pos();
26 line.replace(start..end, elected)
27 }
28}
29
30impl Completer for () {
31 fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
32 Ok((0, Vec::with_capacity(0)))
33 }
34 fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str) {
35 unreachable!()
36 }
37}
38
39impl<'c, C: ?Sized + Completer> Completer for &'c C {
40 fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
41 (**self).complete(line, pos)
42 }
43 fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
44 (**self).update(line, start, elected)
45 }
46}
47macro_rules! box_completer {
48 ($($id: ident)*) => {
49 $(
50 impl<C: ?Sized + Completer> Completer for $id<C> {
51 fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
52 (**self).complete(line, pos)
53 }
54 fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
55 (**self).update(line, start, elected)
56 }
57 }
58 )*
59 }
60}
61
62use std::rc::Rc;
63use std::sync::Arc;
64box_completer! { Box Rc Arc }
65
66pub struct FilenameCompleter {
68 break_chars: BTreeSet<char>,
69 double_quotes_special_chars: BTreeSet<char>,
70}
71
72#[cfg(unix)]
74static DEFAULT_BREAK_CHARS: [char; 18] = [
75 ' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0',
76];
77#[cfg(unix)]
78static ESCAPE_CHAR: Option<char> = Some('\\');
79#[cfg(windows)]
81static DEFAULT_BREAK_CHARS: [char; 17] = [
82 ' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0',
83];
84#[cfg(windows)]
85static ESCAPE_CHAR: Option<char> = None;
86
87static DOUBLE_QUOTES_SPECIAL_CHARS: [char; 4] = ['"', '$', '\\', '`'];
90
91impl FilenameCompleter {
92 pub fn new() -> FilenameCompleter {
93 FilenameCompleter {
94 break_chars: DEFAULT_BREAK_CHARS.iter().cloned().collect(),
95 double_quotes_special_chars: DOUBLE_QUOTES_SPECIAL_CHARS.iter().cloned().collect(),
96 }
97 }
98}
99
100impl Default for FilenameCompleter {
101 fn default() -> FilenameCompleter {
102 FilenameCompleter::new()
103 }
104}
105
106impl Completer for FilenameCompleter {
107 fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
108 let (start, path, esc_char, break_chars) =
109 if let Some((idx, double_quote)) = find_unclosed_quote(&line[..pos]) {
110 let start = idx + 1;
111 if double_quote {
112 (
113 start,
114 unescape(&line[start..pos], ESCAPE_CHAR),
115 ESCAPE_CHAR,
116 &self.double_quotes_special_chars,
117 )
118 } else {
119 (start, Borrowed(&line[start..pos]), None, &self.break_chars)
120 }
121 } else {
122 let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars);
123 let path = unescape(path, ESCAPE_CHAR);
124 (start, path, ESCAPE_CHAR, &self.break_chars)
125 };
126 let matches = try!(filename_complete(&path, esc_char, break_chars));
127 Ok((start, matches))
128 }
129}
130
131pub fn unescape(input: &str, esc_char: Option<char>) -> Cow<str> {
133 if esc_char.is_none() {
134 return Borrowed(input);
135 }
136 let esc_char = esc_char.unwrap();
137 let n = input.chars().filter(|&c| c == esc_char).count();
138 if n == 0 {
139 return Borrowed(input);
140 }
141 let mut result = String::with_capacity(input.len() - n);
142 let mut chars = input.chars();
143 while let Some(ch) = chars.next() {
144 if ch == esc_char {
145 if let Some(ch) = chars.next() {
146 result.push(ch);
147 }
148 } else {
149 result.push(ch);
150 }
151 }
152 Owned(result)
153}
154
155pub fn escape(input: String, esc_char: Option<char>, break_chars: &BTreeSet<char>) -> String {
159 if esc_char.is_none() {
160 return input;
161 }
162 let esc_char = esc_char.unwrap();
163 let n = input.chars().filter(|c| break_chars.contains(c)).count();
164 if n == 0 {
165 return input;
166 }
167 let mut result = String::with_capacity(input.len() + n);
168
169 for c in input.chars() {
170 if break_chars.contains(&c) {
171 result.push(esc_char);
172 }
173 result.push(c);
174 }
175 result
176}
177
178fn filename_complete(
179 path: &str,
180 esc_char: Option<char>,
181 break_chars: &BTreeSet<char>,
182) -> Result<Vec<String>> {
183 use std::env::{current_dir, home_dir};
184
185 let sep = path::MAIN_SEPARATOR;
186 let (dir_name, file_name) = match path.rfind(sep) {
187 Some(idx) => path.split_at(idx + sep.len_utf8()),
188 None => ("", path),
189 };
190
191 let dir_path = Path::new(dir_name);
192 let dir = if dir_path.starts_with("~") {
193 if let Some(home) = home_dir() {
195 match dir_path.strip_prefix("~") {
196 Ok(rel_path) => home.join(rel_path),
197 _ => home,
198 }
199 } else {
200 dir_path.to_path_buf()
201 }
202 } else if dir_path.is_relative() {
203 if let Ok(cwd) = current_dir() {
205 cwd.join(dir_path)
206 } else {
207 dir_path.to_path_buf()
208 }
209 } else {
210 dir_path.to_path_buf()
211 };
212
213 let mut entries: Vec<String> = Vec::new();
214 for entry in try!(dir.read_dir()) {
215 let entry = try!(entry);
216 if let Some(s) = entry.file_name().to_str() {
217 if s.starts_with(file_name) {
218 let mut path = String::from(dir_name) + s;
219 if try!(fs::metadata(entry.path())).is_dir() {
220 path.push(sep);
221 }
222 entries.push(escape(path, esc_char, break_chars));
223 }
224 }
225 }
226 Ok(entries)
227}
228
229pub fn extract_word<'l>(
234 line: &'l str,
235 pos: usize,
236 esc_char: Option<char>,
237 break_chars: &BTreeSet<char>,
238) -> (usize, &'l str) {
239 let line = &line[..pos];
240 if line.is_empty() {
241 return (0, line);
242 }
243 let mut start = None;
244 for (i, c) in line.char_indices().rev() {
245 if esc_char.is_some() && start.is_some() {
246 if esc_char.unwrap() == c {
247 start = None;
249 continue;
250 } else {
251 break;
252 }
253 }
254 if break_chars.contains(&c) {
255 start = Some(i + c.len_utf8());
256 if esc_char.is_none() {
257 break;
258 } }
260 }
261
262 match start {
263 Some(start) => (start, &line[start..]),
264 None => (0, line),
265 }
266}
267
268pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> {
269 if candidates.is_empty() {
270 return None;
271 } else if candidates.len() == 1 {
272 return Some(&candidates[0]);
273 }
274 let mut longest_common_prefix = 0;
275 'o: loop {
276 for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) {
277 let b1 = c1.as_bytes();
278 let b2 = candidates[i + 1].as_bytes();
279 if b1.len() <= longest_common_prefix
280 || b2.len() <= longest_common_prefix
281 || b1[longest_common_prefix] != b2[longest_common_prefix]
282 {
283 break 'o;
284 }
285 }
286 longest_common_prefix += 1;
287 }
288 while !candidates[0].is_char_boundary(longest_common_prefix) {
289 longest_common_prefix -= 1;
290 }
291 if longest_common_prefix == 0 {
292 return None;
293 }
294 Some(&candidates[0][0..longest_common_prefix])
295}
296
297#[derive(PartialEq)]
298enum ScanMode {
299 DoubleQuote,
300 Escape,
301 EscapeInDoubleQuote,
302 Normal,
303 SingleQuote,
304}
305
306fn find_unclosed_quote(s: &str) -> Option<(usize, bool)> {
310 let char_indices = s.char_indices();
311 let mut mode = ScanMode::Normal;
312 let mut quote_index = 0;
313 for (index, char) in char_indices {
314 match mode {
315 ScanMode::DoubleQuote => {
316 if char == '"' {
317 mode = ScanMode::Normal;
318 } else if char == '\\' {
319 mode = ScanMode::EscapeInDoubleQuote;
320 }
321 }
322 ScanMode::Escape => {
323 mode = ScanMode::Normal;
324 }
325 ScanMode::EscapeInDoubleQuote => {
326 mode = ScanMode::DoubleQuote;
327 }
328 ScanMode::Normal => {
329 if char == '"' {
330 mode = ScanMode::DoubleQuote;
331 quote_index = index;
332 } else if char == '\\' && cfg!(not(windows)) {
333 mode = ScanMode::Escape;
334 } else if char == '\'' && cfg!(not(windows)) {
335 mode = ScanMode::SingleQuote;
336 quote_index = index;
337 }
338 }
339 ScanMode::SingleQuote => {
340 if char == '\'' {
341 mode = ScanMode::Normal;
342 } }
344 };
345 }
346 if ScanMode::DoubleQuote == mode || ScanMode::SingleQuote == mode {
347 return Some((quote_index, ScanMode::DoubleQuote == mode));
348 }
349 None
350}
351
352#[cfg(test)]
353mod tests {
354 use std::collections::BTreeSet;
355
356 #[test]
357 pub fn extract_word() {
358 let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
359 let line = "ls '/usr/local/b";
360 assert_eq!(
361 (4, "/usr/local/b"),
362 super::extract_word(line, line.len(), Some('\\'), &break_chars)
363 );
364 let line = "ls /User\\ Information";
365 assert_eq!(
366 (3, "/User\\ Information"),
367 super::extract_word(line, line.len(), Some('\\'), &break_chars)
368 );
369 }
370
371 #[test]
372 pub fn unescape() {
373 use std::borrow::Cow::{self, Borrowed, Owned};
374 let input = "/usr/local/b";
375 assert_eq!(Borrowed(input), super::unescape(input, Some('\\')));
376 let input = "/User\\ Information";
377 let result: Cow<str> = Owned(String::from("/User Information"));
378 assert_eq!(result, super::unescape(input, Some('\\')));
379 }
380
381 #[test]
382 pub fn escape() {
383 let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
384 let input = String::from("/usr/local/b");
385 assert_eq!(
386 input.clone(),
387 super::escape(input, Some('\\'), &break_chars)
388 );
389 let input = String::from("/User Information");
390 let result = String::from("/User\\ Information");
391 assert_eq!(result, super::escape(input, Some('\\'), &break_chars));
392 }
393
394 #[test]
395 pub fn longest_common_prefix() {
396 let mut candidates = vec![];
397 {
398 let lcp = super::longest_common_prefix(&candidates);
399 assert!(lcp.is_none());
400 }
401
402 let s = "User";
403 let c1 = String::from(s);
404 candidates.push(c1.clone());
405 {
406 let lcp = super::longest_common_prefix(&candidates);
407 assert_eq!(Some(s), lcp);
408 }
409
410 let c2 = String::from("Users");
411 candidates.push(c2.clone());
412 {
413 let lcp = super::longest_common_prefix(&candidates);
414 assert_eq!(Some(s), lcp);
415 }
416
417 let c3 = String::from("");
418 candidates.push(c3.clone());
419 {
420 let lcp = super::longest_common_prefix(&candidates);
421 assert!(lcp.is_none());
422 }
423
424 let candidates = vec![String::from("fée"), String::from("fête")];
425 let lcp = super::longest_common_prefix(&candidates);
426 assert_eq!(Some("f"), lcp);
427 }
428
429 #[test]
430 pub fn find_unclosed_quote() {
431 assert_eq!(None, super::find_unclosed_quote("ls /etc"));
432 assert_eq!(
433 Some((3, true)),
434 super::find_unclosed_quote("ls \"User Information")
435 );
436 assert_eq!(
437 None,
438 super::find_unclosed_quote("ls \"/User Information\" /etc")
439 );
440 }
441}