1use std::borrow::Cow::{self, Borrowed, Owned};
4use std::fs::read_dir;
5use std::path::{is_separator, MAIN_SEPARATOR};
6
7use crate::prompter::Prompter;
8use crate::terminal::Terminal;
9
10#[derive(Clone, Debug)]
12pub struct Completion {
13 pub completion: String,
15 pub display: Option<String>,
17 pub suffix: Suffix,
19}
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
23pub enum Suffix {
24 Default,
26 None,
28 Some(char),
30}
31
32impl Completion {
33 pub fn simple(s: String) -> Completion {
36 Completion{
37 completion: s,
38 display: None,
39 suffix: Suffix::default(),
40 }
41 }
42
43 pub fn completion(&self, def_suffix: Option<char>) -> Cow<str> {
46 let mut s = Borrowed(&self.completion[..]);
47
48 if let Some(suffix) = self.suffix.with_default(def_suffix) {
49 s.to_mut().push(suffix);
50 }
51
52 s
53 }
54
55 pub fn display(&self) -> Cow<str> {
57 let mut s = Borrowed(self.display_str());
58
59 if let Suffix::Some(suffix) = self.suffix {
60 s.to_mut().push(suffix);
61 }
62
63 s
64 }
65
66 pub fn display_chars(&self) -> usize {
68 let n = self.display_str().chars().count();
69 n + if self.suffix.is_some() { 1 } else { 0 }
70 }
71
72 fn display_str(&self) -> &str {
73 match self.display {
74 Some(ref dis) => dis,
75 None => &self.completion
76 }
77 }
78}
79
80impl Suffix {
81 pub fn is_default(&self) -> bool {
83 match *self {
84 Suffix::Default => true,
85 _ => false
86 }
87 }
88
89 pub fn is_some(&self) -> bool {
91 match *self {
92 Suffix::Some(_) => true,
93 _ => false
94 }
95 }
96
97 pub fn is_none(&self) -> bool {
99 match *self {
100 Suffix::None => true,
101 _ => false
102 }
103 }
104
105 pub fn with_default(self, default: Option<char>) -> Option<char> {
107 match self {
108 Suffix::None => None,
109 Suffix::Some(ch) => Some(ch),
110 Suffix::Default => default
111 }
112 }
113}
114
115impl Default for Suffix {
116 fn default() -> Suffix {
117 Suffix::Default
118 }
119}
120
121pub trait Completer<Term: Terminal>: Send + Sync {
123 fn complete(&self, word: &str, prompter: &Prompter<Term>,
125 start: usize, end: usize) -> Option<Vec<Completion>>;
126
127 fn word_start(&self, line: &str, end: usize, prompter: &Prompter<Term>) -> usize {
132 word_break_start(&line[..end], prompter.word_break_chars())
133 }
134
135 fn quote<'a>(&self, word: &'a str) -> Cow<'a, str> { Borrowed(word) }
139
140 fn unquote<'a>(&self, word: &'a str) -> Cow<'a, str> { Borrowed(word) }
144}
145
146pub struct DummyCompleter;
150
151impl<Term: Terminal> Completer<Term> for DummyCompleter {
152 fn complete(&self, _word: &str, _reader: &Prompter<Term>,
153 _start: usize, _end: usize) -> Option<Vec<Completion>> { None }
154}
155
156pub struct PathCompleter;
158
159impl<Term: Terminal> Completer<Term> for PathCompleter {
160 fn complete(&self, word: &str, _reader: &Prompter<Term>, _start: usize, _end: usize)
161 -> Option<Vec<Completion>> {
162 Some(complete_path(word))
163 }
164
165 fn word_start(&self, line: &str, end: usize, _reader: &Prompter<Term>) -> usize {
166 escaped_word_start(&line[..end])
167 }
168
169 fn quote<'a>(&self, word: &'a str) -> Cow<'a, str> {
170 escape(word)
171 }
172
173 fn unquote<'a>(&self, word: &'a str) -> Cow<'a, str> {
174 unescape(word)
175 }
176}
177
178pub fn complete_path(path: &str) -> Vec<Completion> {
180 let (base_dir, fname) = split_path(path);
181 let mut res = Vec::new();
182
183 let lookup_dir = base_dir.unwrap_or(".");
184
185 if let Ok(list) = read_dir(lookup_dir) {
186 for ent in list {
187 if let Ok(ent) = ent {
188 let ent_name = ent.file_name();
189
190 if let Ok(path) = ent_name.into_string() {
192 if path.starts_with(fname) {
193 let (name, display) = if let Some(dir) = base_dir {
194 (format!("{}{}{}", dir, MAIN_SEPARATOR, path),
195 Some(path))
196 } else {
197 (path, None)
198 };
199
200 let is_dir = ent.metadata().ok()
201 .map_or(false, |m| m.is_dir());
202
203 let suffix = if is_dir {
204 Suffix::Some(MAIN_SEPARATOR)
205 } else {
206 Suffix::Default
207 };
208
209 res.push(Completion{
210 completion: name,
211 display: display,
212 suffix: suffix,
213 });
214 }
215 }
216 }
217 }
218 }
219
220 res.sort_by(|a, b| a.display_str().cmp(b.display_str()));
221 res
222}
223
224pub fn word_break_start(s: &str, word_break: &str) -> usize {
226 let mut start = s.len();
227
228 for (idx, ch) in s.char_indices().rev() {
229 if word_break.contains(ch) {
230 break;
231 }
232 start = idx;
233 }
234
235 start
236}
237
238pub fn escaped_word_start(s: &str) -> usize {
241 let mut chars = s.char_indices().rev();
242 let mut start = s.len();
243
244 while let Some((idx, ch)) = chars.next() {
245 if needs_escape(ch) {
246 let n = {
247 let mut n = 0;
248
249 loop {
250 let mut clone = chars.clone();
251
252 let ch = match clone.next() {
253 Some((_, ch)) => ch,
254 None => break
255 };
256
257 if ch == '\\' {
258 chars = clone;
259 n += 1;
260 } else {
261 break;
262 }
263 }
264
265 n
266 };
267
268 if n % 2 == 0 {
269 break;
270 }
271 }
272
273 start = idx;
274 }
275
276 start
277}
278
279pub fn escape(s: &str) -> Cow<str> {
281 let n = s.chars().filter(|&ch| needs_escape(ch)).count();
282
283 if n == 0 {
284 Borrowed(s)
285 } else {
286 let mut res = String::with_capacity(s.len() + n);
287
288 for ch in s.chars() {
289 if needs_escape(ch) {
290 res.push('\\');
291 }
292 res.push(ch);
293 }
294
295 Owned(res)
296 }
297}
298
299pub fn unescape(s: &str) -> Cow<str> {
301 if s.contains('\\') {
302 let mut res = String::with_capacity(s.len());
303 let mut chars = s.chars();
304
305 while let Some(ch) = chars.next() {
306 if ch == '\\' {
307 if let Some(ch) = chars.next() {
308 res.push(ch);
309 }
310 } else {
311 res.push(ch);
312 }
313 }
314
315 Owned(res)
316 } else {
317 Borrowed(s)
318 }
319}
320
321fn needs_escape(ch: char) -> bool {
322 match ch {
323 ' ' | '\t' | '\n' | '\\' => true,
324 _ => false
325 }
326}
327
328fn split_path(path: &str) -> (Option<&str>, &str) {
329 match path.rfind(is_separator) {
330 Some(pos) => (Some(&path[..pos]), &path[pos + 1..]),
331 None => (None, path)
332 }
333}