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 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 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}