ls_key/
mod.rs

1pub mod list;
2pub mod terminal;
3pub mod fixtures;
4
5use list::Entry;
6
7use std::env;
8use std::path::{Path, PathBuf};
9use std::fs::{create_dir_all, metadata, OpenOptions};
10use list::List;
11use fixtures::command_assistors;
12use termion::input::TermRead;
13use termion::event::Key;
14use termion::raw::{IntoRawMode, RawTerminal};
15use std::io::{ Write, stdout, stdin, StdoutLock};
16use termion::screen::AlternateScreen;
17use easy_hasher::easy_hasher::*;
18
19pub mod app {
20    use super::*;
21
22    pub fn run<P: AsRef<Path>>(path: P, all: bool, test: bool, fzf_hook_path: Option<PathBuf>, fzc_hook_path: Option<PathBuf>, fzd_hook_path: Option<PathBuf>) -> LsKey {
23        if test {
24            let mut path = path.as_ref().to_path_buf();
25            create_dir_all(&path).expect("Failed to create directories.");
26            path.push(".lsk_test_output");
27            std::fs::File::create(&path).expect("failed to create lsk output file");
28        }
29        let path = path.as_ref();
30        let mut ls_key = LsKey::new(path, all, test, fzf_hook_path.clone(), fzc_hook_path.clone(), fzd_hook_path.clone());
31        ls_key.update_file_display(false);
32        ls_key.run_cmd();
33        let mut list = ls_key.list.clone();
34
35        while ls_key.is_fuzzed {
36            ls_key.list = list;
37            let display = ls_key.display.clone();
38            if let Some(fuzzy_list) = ls_key.fuzzy_list.clone() {
39                let _list = ls_key.list;
40                ls_key = LsKey::new(path, all, test, fzf_hook_path.clone(), fzc_hook_path.clone(), fzd_hook_path.clone());
41                ls_key.list = fuzzy_list.clone();
42                ls_key.display = display;
43            } else if !ls_key.halt {
44                let _list = ls_key.list;
45                ls_key = LsKey::new(path, all, test, fzf_hook_path.clone(), fzc_hook_path.clone(), fzd_hook_path.clone());
46                ls_key.list = _list;
47                ls_key.display = display;
48            }
49            ls_key.update_file_display(false);
50            ls_key.run_cmd();
51            list = ls_key.list.clone();
52        }
53
54        ls_key
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Default)]
59pub struct LsKey {
60    pub list: List,
61    pub all: bool,
62    pub input: Input,
63    pub fuzzy_list: Option<List>,
64    pub pre_fuzz_list: Option<List>,
65    pub display: Option<(PathBuf, String)>,
66    pub halt: bool,
67    pub is_fuzzed: bool,
68    pub test: bool,
69    pub input_vec: Vec<String>,
70    pub output_vec: Vec<String>,
71    pub fzf_hook_path: Option<PathBuf>,
72    pub fzc_hook_path: Option<PathBuf>,
73    pub fzd_hook_path: Option<PathBuf>,
74    pub default_editor: String,
75    pub default_opener: String,
76}
77
78impl LsKey {
79    pub fn new<P: AsRef<Path>>(path: P, all: bool, test: bool, fzf_hook_path: Option<PathBuf>, fzc_hook_path: Option<PathBuf>, fzd_hook_path: Option<PathBuf>) -> Self {
80        let mut ls_key: LsKey = Default::default();
81        let list = if all {
82           list::List::new(path)
83               .list_include_hidden()
84               .unwrap()
85        } else {
86           list::List::new(path)
87               .list_skip_hidden()
88               .unwrap()
89        };
90
91        match env::var("EDITOR") {
92            Ok(editor) => {
93                 ls_key.default_editor = editor
94            },
95            Err(_) => {
96                ls_key.default_editor = "nano".to_string()
97            }
98        };
99
100        match env::var("LSK_FILE_OPENER") {
101            Ok(editor) => {
102                 ls_key.default_opener = editor
103            },
104            Err(_) => {
105                ls_key.default_opener = "xdg-open".to_string()
106            }
107        };
108
109        ls_key.list = list;
110        ls_key.all = all;
111        ls_key.halt = true;
112        ls_key.is_fuzzed = false;
113        ls_key.test = test;
114        ls_key.fzf_hook_path = fzf_hook_path;
115        ls_key.fzc_hook_path = fzc_hook_path;
116        ls_key.fzd_hook_path = fzd_hook_path;
117
118        ls_key
119    }
120
121    fn fuzzy_score(&mut self, mut input: String) -> list::fuzzy_score::Scores {
122        let files = &self.list.files;
123
124        let mut input_vec_str: Vec<&str> = input.split(" ").collect();
125
126        if input_vec_str.iter().count() > 1{
127            input_vec_str.pop();
128            input = input_vec_str.into_iter().collect();
129        }
130        let score_list = |entry: Entry| {
131            (
132             entry.clone(),
133             list::fuzzy_score::score(entry.path.to_str().unwrap(), &input)
134            )
135        };
136
137        let files_score: Vec<list::fuzzy_score::Score> =
138           files.iter()
139               .map(|file| list::fuzzy_score::Score::Files(score_list(
140                                Entry {
141                                    path: file.path.to_path_buf(),
142                                    file_type: file.file_type.clone(),
143                                    key: file.key
144                                }
145                           )
146                       )
147                )
148               .collect();
149
150        let files = files_score;
151
152        list::fuzzy_score::Scores {
153            files,
154        }
155    }
156
157    fn fuzzy_rank(&mut self, mut scores: list::fuzzy_score::Scores) -> list::fuzzy_score::Scores {
158        scores.files.sort_by(|a, b| list::fuzzy_score::order(a, b));
159
160        scores
161    }
162
163    // Filter out no-scores.
164    fn fuzzy_filter(&mut self, scores: list::fuzzy_score::Scores) -> list::fuzzy_score::Scores {
165         let mut files_vec: Vec<list::fuzzy_score::Score> = vec![];
166         for score in scores.files.iter() {
167             let path = score.score().0;
168             let score = score.score().1;
169
170             let thing = (path, score.clone());
171
172             if score.is_some() {
173                  files_vec.push(list::fuzzy_score::Score::Files(thing));
174             }
175         }
176
177         list::fuzzy_score::Scores {
178             files: files_vec,
179         }
180    }
181
182    pub fn fuzzy_update(&mut self, input: String) -> Self {
183        let scores = self.fuzzy_score(input);
184        let scores = self.fuzzy_rank(scores);
185        let scores = self.fuzzy_filter(scores);
186        let list = self.scores_to_list(scores);
187        self.update_file_display(false);
188        self.fuzzy_list = Some(list);
189
190        self.clone()
191    }
192
193
194    pub fn scores_to_list(&mut self, scores: list::fuzzy_score::Scores) -> list::List {
195        let files_list: Vec<Entry> = scores.files.iter().map(|score|
196            Entry {
197                path: score.score().0.path,
198                file_type: score.score().0.file_type,
199                key: score.score().0.key
200            }
201        ).collect();
202
203        self.list.files = files_list;
204
205        self.list.clone()
206    }
207
208    pub fn update(&mut self, list: List) {
209            let list = if self.all {
210                   list
211                   .list_include_hidden()
212                   .unwrap()
213            } else {
214                   list
215                   .list_skip_hidden()
216                   .unwrap()
217            };
218
219            self.list = list;
220    }
221
222   pub fn update_file_display(&mut self, mut filter: bool) {
223            let mut go = true;
224            let entries_count = self.list.files.iter().count();
225            let mut start = 0;
226            let mut end = entries_count;
227            if let Some(ls) = &self.list.filter {
228                start = ls.clone().into_iter().nth(0).unwrap();
229                end = *ls.into_iter().last().unwrap();
230            } else {
231            }
232            while go {
233                let entries = self.list.order_and_sort_list(true, filter);
234                let mut entries_keyed: Vec<String> = list::key_entries(entries.clone());
235                if end  < entries_count {
236                    let last = format!("[{}...{}]", end, entries_count);
237                    entries_keyed.push(last);
238                }
239                let res = terminal::input_n_display::grid(entries_keyed.clone());
240                if let Some(r) = res {
241                    let grid = r.0;
242                    let width = r.1;
243                    let height = r.2;
244                    let display: terminal::input_n_display::Display;
245                    let _display = grid.fit_into_width(width);
246                    let pad: usize;
247                    if _display.is_some() && !self.test {
248                         display = _display.unwrap(); // Safe to unwrap.
249                         pad = 4;
250                    } else {
251                         display = grid.fit_into_columns(1);
252                         pad = 5;
253                    }
254                    let grid_row_count = display.row_count();
255                    if (grid_row_count + pad) > height {
256                        //panic!("Can't fit list into screen.");
257                        //
258
259                        let mut filter_vec: Vec<usize> = vec![];
260
261                        let diff = (grid_row_count + pad).checked_sub(height).unwrap_or(1);
262
263                        end = end.checked_sub(diff).unwrap_or(end);
264                        let range = start..end;
265
266                        range.into_iter().for_each(|i|
267                            filter_vec.push(i)
268                        );
269
270                        self.list.filter = Some(filter_vec);
271                        filter = true;
272
273
274                    } else {
275                       go = false;
276                    }
277
278                    self.display = Some((self.list.parent_path.clone(), display.to_string()));
279                } else {
280                    go = false;
281                }
282            }
283    }
284
285    fn return_file_by_key_mode(&mut self, input: Input, is_fuzzed: bool) {
286        let get_file = |key_string: String| {
287             let key: usize = key_string.parse().unwrap();
288             self.list.get_file_by_key(key, !is_fuzzed).unwrap()
289        };
290
291        let mut n = 0;
292        let mut format_cmd = |key: PathBuf| {
293                    n +=1;
294                    let file_string = get_file(key.to_str().unwrap().to_string()).to_str().unwrap().to_string();
295                    let file_string = file_string.replace(" ", r"\ ");
296                   format!(r#"{}={}"#, n, file_string)
297        };
298
299        if let Some (r) = input.args {
300            let _output_vec: Vec<std::process::Output> =
301                r.iter()
302                    .map(|key|
303                         format_cmd(PathBuf::from(key))
304                    ).map(|statement|
305                        format!(r#""$(printf '{} \n ')""#, statement)
306                    ).map(|cmd|
307                        terminal::parent_shell::type_text(
308                            cmd,
309                            0
310                        )
311                    ).collect();
312        } else {
313            ()
314        }
315    }
316
317    pub fn filter_mode(&mut self, list: List) {
318        let input_string: String = self.input.display.iter().collect();
319        let mut input_vec_str: Vec<&str> = input_string.split("-").collect();
320        let mut key_vec: Vec<usize> = vec![];
321
322        // Does it end in "-"?
323        let last = input_vec_str.iter().last();
324        let mut open_range = false;
325        if let Some(l) = last {
326            if l == &"" {
327                open_range = true;
328           }
329        }
330
331        // Only want to deal with integers if it's an open range.
332        if open_range {
333           input_vec_str.pop();
334        }
335
336        // Make sure it'sall integers.
337        for i in input_vec_str.into_iter() {
338            let key: Result<usize, std::num::ParseIntError> = i.parse();
339            if key.is_ok() {
340                key_vec.push(key.unwrap());
341            }
342
343        }
344
345        let end: usize;
346        let start = key_vec.clone().into_iter().nth(0).unwrap();
347
348        if open_range {
349            end =  list.files.iter().count() + 1;
350        } else {
351            end = key_vec.clone().into_iter().nth(1).unwrap() + 1;
352        }
353
354        let range = start..end;
355
356        let mut filter_vec: Vec<usize> = vec![];
357
358        range.into_iter().for_each(|i|
359            filter_vec.push(i)
360        );
361
362        self.list.filter = Some(filter_vec);
363        self.update_file_display(true);
364        self.run_cmd()
365    }
366
367    pub fn key_mode(&mut self, list: List, input: Input, is_fuzzed: bool) {
368        let key: usize = input.cmd.unwrap().parse().unwrap();
369        match key {
370            0 => {
371                 self.list.parent_path.pop();
372                 let file_pathbuf = self.list.parent_path.clone();
373                 self.list.parent_path.pop();
374                 let list = self.list.clone().update(file_pathbuf);
375                 self.update(list);
376                 self.halt = false;
377                 let halt = self.list.filter.is_some();
378                 self.update_file_display(halt);
379                 if !halt {
380                     self.run_cmd();
381                 }
382            },
383            _ => {
384                  let file_pathbuf = list.get_file_by_key(key, !is_fuzzed).unwrap();
385                  if metadata(file_pathbuf.clone()).unwrap().is_dir() {
386                      let list = self.list.clone().update(file_pathbuf);
387                      self.update(list);
388                      self.halt = false;
389                      let halt = self.list.filter.is_some();
390                      self.update_file_display(halt);
391                      if !halt {
392                          self.run_cmd();
393                      }
394                  } else {
395                      let file_path =
396                          file_pathbuf
397                          .to_str().unwrap()
398                          .to_string();
399                      terminal::shell::spawn(self.default_editor.clone(), vec![file_path]);
400                      self.halt = true;
401                      self.update_file_display(self.halt);
402                      self.run_cmd();
403                  }
404            }
405        }
406    }
407
408    fn cmd_mode(&mut self, input: Input) {
409         let args = input.args;
410         if let Some(a) = args {
411             let args = a;
412             // Unwrap is safe because is_key is not None and there are args.
413             let cmd = input.cmd.unwrap();
414             let list_parent_path = self.list.parent_path.clone();
415             let mut path_cache = command_assistors::PathCache::new(
416                 list_parent_path.as_path()
417             );
418             path_cache.switch();
419             match cmd.as_str() {
420                 //"fzf" => {
421                 //    //Split cmd ('fzf')
422                 //    let split: Vec<&str> = input.as_read.split("fzf").collect();
423                 //    let cmd = split.iter().last().unwrap();
424                 //    let cmd = format!(r#"fzf {}"#, cmd);
425                 //    let output = terminal::shell::cmd(cmd.clone());
426                 //    let file_path = output.unwrap();
427                 //    terminal::shell::spawn("vim".to_string(), vec![file_path]);
428                 //},
429                 //"fzc" => {
430                 //    //let split: Vec<&str> = input.as_read.split("fzc").collect();
431                 //    let fzc_pathbuf = self.fzc_hook_path.as_ref().expect("fzc fail: no fzc hook path specified");
432                 //    let fzc_path_string = fzc_pathbuf.clone().into_os_string().into_string().unwrap();
433                 //    let output = terminal::shell::output("sh".to_string(), vec![fzc_path_string]).expect("fail to get output from fzc hook script");
434                 //    let cmd = String::from_utf8_lossy(&output.stdout).to_string();
435                 //    let mut input = Input::new();
436                 //    let input = input.parse(cmd);
437                 //    assert!(input.args.is_some());
438                 //    self.cmd_mode(input);
439                 //},
440                 _ => {
441                      terminal::shell::spawn(cmd.to_string(), args);
442                 }
443             }
444             path_cache.switch_back();
445         } else {
446             let as_read = input.as_read.as_str();
447             match as_read {
448                 "q" => (),
449                 "fzf" => {
450                     let mut path_cache = command_assistors::PathCache::new(
451                         self.list.parent_path.as_path()
452                     );
453                     path_cache.switch();
454                     let fzf_pathbuf = self.fzf_hook_path.as_ref().expect("fzf fail: no fzf hook path specified");
455                     let fzf_path_string = fzf_pathbuf.clone().into_os_string().into_string().unwrap();
456                     let file_path = terminal::shell::cmd(fzf_path_string).unwrap();
457                     //assert!(input.args.is_some());
458                     //let cmd_res = terminal::shell::cmd(cmd_path).unwrap();
459                     terminal::shell::spawn(self.default_editor.clone(), vec![file_path]);
460                     path_cache.switch_back();
461                 },
462                 "fzc" => {
463                     let mut path_cache = command_assistors::PathCache::new(
464                         self.list.parent_path.as_path()
465                     );
466                     path_cache.switch();
467                     let fzc_pathbuf = self.fzc_hook_path.as_ref().expect("fzc fail: no fzc hook path specified");
468                     let fzc_path_string = fzc_pathbuf.clone().into_os_string().into_string().unwrap();
469                     let cmd = terminal::shell::cmd(fzc_path_string).unwrap();
470                     let input = Input::new();
471                     let input = input.parse(cmd);
472                     //assert!(input.args.is_some());
473                     //let cmd_res = terminal::shell::cmd(cmd_path).unwrap();
474                     path_cache.switch_back();
475                     self.cmd_mode(input);
476                 },
477                 "fzd" => {
478                     let list_parent_path = self.list.parent_path.clone();
479                     let mut path_cache = command_assistors::PathCache::new(
480                         list_parent_path.as_path()
481                     );
482                     path_cache.switch();
483                     let fzd_pathbuf = self.fzd_hook_path.as_ref().expect("fzd fail: no fzd hook path specified");
484                     let fzd_path_string = fzd_pathbuf.clone().into_os_string().into_string().unwrap();
485                     let dir = terminal::shell::cmd(fzd_path_string).unwrap();
486                     let mut dir_pathbuf = PathBuf::from(dir);
487                     let mut pathbuf_vec: Vec<PathBuf> = vec![];
488                     if metadata(dir_pathbuf.clone()).unwrap().is_dir() {
489                         loop {
490                             if dir_pathbuf != PathBuf::from("") {
491                                 if dir_pathbuf != PathBuf::from("/") {
492                                     pathbuf_vec.push(dir_pathbuf.clone());
493                                 } else {
494                                     break
495                                 }
496                             } else {
497                                 break
498                             }
499                             dir_pathbuf.pop();
500                         }
501                         for dir_pathbuf in pathbuf_vec.iter().rev() {
502                             let list = self.list.clone().update(dir_pathbuf);
503                             self.update(list);
504                         }
505                         self.halt = false;
506                         let halt = self.list.filter.is_some();
507                         self.update_file_display(halt);
508                     }
509                     path_cache.switch_back();
510                 },
511                 "zsh" => {
512                     let mut path_cache = command_assistors::PathCache::new(
513                         self.list.parent_path.as_path()
514                     );
515                     path_cache.switch();
516                     //Split cmd ('zsh')
517                     //let split: Vec<&str> = input.as_read.split("zsh").collect();
518                     //let cmd = split.iter().last().unwrap();
519                     //let cmd = format!(r#"zsh {}"#, cmd);
520                     terminal::shell::spawn("zsh".to_string(), vec![]);
521                     path_cache.switch_back();
522                 },
523                 _ => {
524                     let mut path_cache = command_assistors::PathCache::new(
525                         self.list.parent_path.as_path()
526                     );
527                     path_cache.switch();
528                     let _output = terminal::shell::cmd(as_read.to_string()).unwrap();
529                     path_cache.switch_back();
530                 }
531             }
532        }
533    }
534
535    fn open_file_by_key_mode(&mut self, input: Input, is_fuzzed: bool) {
536        let get_file = |key_string: String| {
537             let key: usize = key_string.parse().unwrap();
538             self.list.get_file_by_key(key, !is_fuzzed).unwrap()
539        };
540
541        //let mut n = 0;
542        //let mut format_cmd = |key: PathBuf| {
543        //            n +=1;
544        //            let file_string = get_file(key.to_str().unwrap().to_string()).to_str().unwrap().to_string();
545        //            let file_string = file_string.replace(" ", r"\ ");
546        //           format!(r#"{}={}"#, n, file_string)
547        //};
548
549        if let Some (r) = input.args {
550            let _output_vec: Vec<String> =
551                r.iter()
552                    .map(|key|
553                         get_file(key.to_string())
554                    ).map(|file|
555                        terminal::shell::cmd(format!("{:?} {:?}", self.default_opener, file)).unwrap()
556                    ).collect();
557        } else {
558            ()
559        }
560
561        self.halt = false;
562        let halt = self.list.filter.is_some();
563        self.update_file_display(halt);
564        if !halt {
565            self.run_cmd();
566        }
567    }
568
569    fn key_related_mode(&mut self, input: Result<Option<String>, std::io::Error>, is_fuzzed: bool) {
570        match input {
571            Ok(t) =>  {
572                if let Some(i) = t {
573                    let input = Input::new();
574                    let input = input.parse(i);
575                    // Safe to unwrap.
576                    //
577                    match input.clone().cmd_type.unwrap() {
578                        CmdType::SingleKey => {
579                            self.key_mode(self.list.clone(), input, is_fuzzed);
580                        },
581                        CmdType::OpenKeys => {
582                            self.open_file_by_key_mode(input, is_fuzzed);
583                        },
584                        CmdType::MultipleKeys => {
585                            /*
586                                * get_file_by_key for each key
587                                * let text_vec = vec![r#"printf '1=file1; 2=file2;...'; \n "#]
588                                * then type_text_spawn(text_vec);
589                            */
590                            self.return_file_by_key_mode(input, is_fuzzed);
591                        },
592                        CmdType::FilterKeys => {
593                            self.filter_mode(self.list.clone());
594                        },
595                        _ => ()
596                    }
597                } else {
598                    ()
599                }
600            },
601            Err(_) => ()
602        }
603    }
604
605    // If you want to return the output of a commands, see fzf example below.
606    // The commmand 'vim', without any args, is a special case handled by
607    // ls-key. If the non-built in command doesn't return output and enters
608    // into a child process (e.g. vim), then shell::cmd cannot be used, to my
609    // understanding.
610    fn run_cmd(&mut self) {
611        //If the is a fuzzy re-entry, we must reset is_fuzzed and halt to default.
612        let mut execute = false;
613        while !execute {
614           let (input,_execute) = self.read_process_chars();
615            execute = _execute;
616           if execute && !self.input.full_backspace {
617               self.key_related_mode(Ok(input), self.is_fuzzed);
618           } else if !execute && self.input.full_backspace {
619           } else {
620               break
621           }
622           self.input.full_backspace = false;
623        }
624    }
625
626    fn test_data_update(&mut self, input: Option<String>) {
627        if self.test == true {
628            if input.is_some() {
629                let hash = sha256(&input.clone().unwrap());
630                let original_dir = self.clone().list.path_history.into_iter().nth(0);
631                if original_dir.is_some() {
632                    let mut original_dir = original_dir.unwrap();
633                    //file.write_all(stuff.as_bytes()).unwrap();
634                    original_dir.push(".lsk_test_output");
635                    let mut file = OpenOptions::new()
636                       .write(true)
637                       .append(true)
638                       .open(original_dir.clone().into_os_string().into_string().unwrap())
639                       .unwrap();
640
641                   if let Err(e) = writeln!(file, "{}", hash.to_hex_string()) {
642                       eprintln!("Couldn't write to file: {}", e);
643                   }
644                }
645            }
646            if self.display.is_some() {
647                let hash = sha256(&self.display.clone().unwrap().1);
648                let original_dir = self.clone().list.path_history.into_iter().nth(0);
649
650                if original_dir.is_some() {
651                    let mut original_dir = original_dir.unwrap();
652                    //file.write_all(stuff.as_bytes()).unwrap();
653                    original_dir.push(".lsk_test_output");
654                    let mut file = OpenOptions::new()
655                       .write(true)
656                       .append(true)
657                       .open(original_dir.clone().into_os_string().into_string().unwrap())
658                       .unwrap();
659
660                   if let Err(e) = writeln!(file, "{}", hash.to_hex_string()) {
661                       eprintln!("Couldn't write to file: {}", e);
662                   }
663                }
664            }
665        }
666    }
667
668    fn read_process_chars(&mut self) -> (Option<String>, bool) {
669        self.input = Input::new();
670        let stdin = stdin();
671        let stdout = stdout();
672        let stdout = stdout.lock().into_raw_mode().unwrap();
673        let mut screen: AlternateScreen<RawTerminal<StdoutLock>> = AlternateScreen::from(stdout);
674        let stdin = stdin.lock();
675        let mut result: Option<String> =  None;
676        let mut is_fuzzed = false;
677        let orig_ls_key = self.clone();
678
679        clear_display(&mut screen);
680
681        let input_string: String = self.input.display.iter().collect();
682        self.test_data_update(Some(input_string));
683        display_files(self.clone(), b"", &mut screen, (0, 3));
684
685        for c in stdin.keys() {
686            self.input.full_backspace;
687            clear_display(&mut screen);
688            let c = c.unwrap();
689
690            self.input.match_event(c);
691            let mut input_string: String = self.input.display.iter().collect();
692            let input = self.input.clone();
693            let first = input.display.iter().nth(0);
694            let input = self.input.clone();
695            let last = input.display.iter().last();
696            let mut _input = self.input.clone();
697
698            let place = (0, 1);
699            if let Some(_) = first {
700
701                if self.input.unwiddle {
702                    self.fuzzy_list = self.pre_fuzz_list.clone();
703                    if let Some(x) = self.pre_fuzz_list.clone() {
704                        self.list = x;
705                    }
706                    if self.input.full_backspace {
707                       *self = orig_ls_key.clone();
708                       is_fuzzed = false;
709                    }
710                }
711                self.test_data_update(Some(input_string.clone()));
712                display_input(input_string.clone(), &mut screen, place);
713
714                let some_mode = self.mode_parse(input_string.clone());
715
716                if let Some(mode) = some_mode {
717                    match mode {
718                        Mode::Cmd(_) => {
719                             if &last == &Some(&'\n') {
720                                 self.cmd_read();
721
722                                 //Clear the command from the lsk console after executing.
723                                 input_string = "".to_string();
724                                 self.input.display = input_string.chars().collect();
725                                 clear_display(&mut screen);
726                             }
727                        },
728                        Mode::Work => {
729                             if &last == &Some(&'\n') {
730                                 let path = self.list.parent_path.clone();
731                                 let path = path.to_str().unwrap();
732                                 let cmd = format!(r#""$(printf 'cd {} \n ')""#, path).to_string();
733                                 terminal::parent_shell::type_text(cmd, 0);
734                                 self.is_fuzzed = false;
735                                 break
736                            } else {
737
738                            }
739                        },
740                        Mode::Fuzzy(fuzzy_mode_input) => {
741                            if !is_fuzzed {
742                                self.pre_fuzz_list = Some(self.list.clone());
743                            }
744                            let _show = self.display.clone();
745                            let some_keys = parse_keys(fuzzy_mode_input.as_str());
746
747                            if let Some(keys) = some_keys {
748                                input_string = keys;
749                                self.input.display = input_string.chars().collect();
750                            } else {
751
752                                if self.input.unwiddle {
753                                    self.fuzzy_list = self.pre_fuzz_list.clone();
754                                    if let Some(x) = self.pre_fuzz_list.clone() {
755                                        self.list = x;
756                                    }
757                                }
758
759                                if self.input.display.iter().last() != Some(&'\n') {
760                                    self.fuzzy_update(fuzzy_mode_input);
761                                }
762                            }
763
764                            is_fuzzed = true;
765                        }
766                    }
767                }
768            }
769
770            //if self.input.unwiddle {
771            //    self.fuzzy_list = self.pre_fuzz_list.clone();
772            //    if let Some(x) = self.pre_fuzz_list.clone() {
773            //        self.list = x;
774            //    }
775            //    if self.input.full_backspace {
776            //       *self = orig_ls_key.clone();
777            //       is_fuzzed = false;
778            //    }
779            //}
780
781
782            if self.input.full_backspace {
783               *self = orig_ls_key.clone();
784               is_fuzzed = false;
785            }
786            self.test_data_update(Some(input_string.clone()));
787            display_files(self.clone(), b"", &mut screen, (0, 3));
788
789            if self.input.display.iter().last() == Some(&'\n') {
790                self.input.display.pop();
791                let input_string: String = self.input.display.iter().collect();
792                result = Some(input_string);
793                self.is_fuzzed = is_fuzzed;
794                if self.is_fuzzed {
795                }
796                break
797            }
798        }
799
800        write!(screen, "{}", termion::cursor::Show).unwrap();
801
802        (result, self.input.execute)
803    }
804
805    pub fn mode_parse(&mut self, mut input: String) -> Option<Mode> {
806        let len = input.len();
807        let mode = if len >= 2 {
808             let mode = input.as_str();
809             if mode == "w\n" {
810                 Some(Mode::Work)
811             } else {
812                  let mode: String = input.drain(..2).collect();
813                  let mode = mode.as_str();
814                  match mode {
815                      "s " => Some(Mode::Fuzzy(input.clone())),
816                      "c " => Some(Mode::Cmd(input.clone())),
817                      _ => None
818                  }
819             }
820        } else {
821            None
822        };
823
824        mode
825    }
826
827    fn cmd_read(&mut self) -> String {
828         self.input.display.pop();
829         let input_string: String = self.input.display.iter().collect();
830         let cmd_mode = self.mode_parse(input_string.clone()).unwrap(); //safe
831
832         match cmd_mode {
833             Mode::Cmd(cmd_mode_input) => {
834                 let input = Input::new();
835                 let input = input.parse(cmd_mode_input);
836
837                 match input.clone().cmd_type.unwrap() {
838                     CmdType::Cmd => {
839                         self.cmd_mode(input);
840                     },
841                     _ => {}
842                 }
843                 //break
844             }
845             _ => { }
846         }
847
848        input_string
849    }
850}
851
852
853fn clear_display(screen: &mut AlternateScreen<RawTerminal<StdoutLock>>) {
854    write!(
855        screen,
856        "{}",
857        termion::clear::All
858    ).unwrap();
859    screen.flush().unwrap();
860}
861
862fn display_input(input_string: String, screen: &mut AlternateScreen<RawTerminal<StdoutLock>>, position: (u16, u16)) {
863    write!(screen,
864        "{}{}{}{}", format!("{}", input_string.as_str()
865        ),
866       termion::clear::AfterCursor,
867       termion::cursor::Goto(position.0, position.1 + 1),
868       termion::cursor::Hide,
869    ).unwrap();
870    screen.flush().unwrap();
871}
872
873fn display_files(ls_key: LsKey, some_stuff: &[u8], screen: &mut AlternateScreen<RawTerminal<StdoutLock>>, position: (u16, u16)) {
874     let show = ls_key.clone().display;
875     if let Some(x) = show {
876         if x.0 == ls_key.list.parent_path {
877              //into_raw_mode requires carriage returns.
878              let display = str::replace(x.1.as_str(), "\n", "\n\r");
879              write!(
880                  screen,
881                  "{}{}{}\n", format!("{}", std::str::from_utf8(&some_stuff).unwrap()),
882                  termion::cursor::Goto(position.0, position.1),
883                  termion::cursor::Hide,
884
885              ).unwrap();
886              screen.flush().unwrap();
887
888              write!(screen,
889                  "{}{}{}{}", format!("{}", display.as_str()
890                  ),
891                 termion::clear::AfterCursor,
892                 termion::cursor::Goto(position.0, position.1 + 1),
893                 termion::cursor::Hide,
894              ).unwrap();
895              screen.flush().unwrap();
896
897              write!(
898                  screen,
899                  "{}",
900                  termion::cursor::Goto(0, 3),
901              ).unwrap();
902              screen.flush().unwrap();
903         }
904     }
905}
906
907#[derive(Debug, Clone, PartialEq)]
908pub enum CmdType {
909    SingleKey,
910    MultipleKeys,
911    OpenKeys,
912    FilterKeys,
913    Cmd,
914}
915#[derive(Debug, Clone, PartialEq, Default)]
916pub struct Input {
917    pub cmd: Option<String>,
918    pub args: Option<Vec<String>>,
919    pub as_read: String,
920    pub cmd_type: Option<CmdType>,
921    pub display: Vec<char>,
922    pub execute: bool,
923    pub unwiddle: bool, //i.e. backspacing
924    pub full_backspace: bool,
925}
926
927
928
929impl Input {
930    pub fn new() -> Self {
931        let mut input: Input = Default::default();
932        input.execute = true;
933        input.unwiddle = false;
934        input.full_backspace = false;
935
936        input
937    }
938
939    pub fn match_event(&mut self, c: termion::event::Key) {
940            self.unwiddle = false;
941            match c {
942                Key::Char(c) => {
943                    match c {
944                        _ => {
945                            self.display.push(c);
946                        }
947                    }
948                }
949                Key::Alt(c) => println!("^{}", c),
950                Key::Ctrl(c) => println!("*{}", c),
951                Key::Esc => println!("ESC"),
952                Key::Left => println!("←"),
953                Key::Right => println!("→"),
954                Key::Up => println!("↑"),
955                Key::Down => println!("↓"),
956                Key::Backspace => {
957                    self.unwiddle = true;
958                    if self.display.pop().is_some() {
959                        let count = self.display.iter().count();
960                        if count  == 0 {
961                            self.execute = false;
962                            self.full_backspace = true;
963                        }
964                    }
965
966                },
967                _ => {}
968            }
969    }
970
971    fn defang_args(&self, args: Vec<String>) -> Option<Vec<String>> {
972        let count = args.clone().iter().count();
973        let empty_item = args.clone().iter().any(|x| *x == "".to_string());
974        let is_valid = if empty_item && count <= 1 {
975            false
976        } else {
977            true
978        };
979
980        if is_valid && count != 0 {
981            Some(args)
982        } else {
983            None
984        }
985    }
986
987    pub fn parse(mut self, input: String) -> Self {
988        let (cmd, args) = self.parse_cmd(input.clone());
989        let is_key = if args == None {
990            let key: Result<usize, std::num::ParseIntError> = cmd.clone().unwrap().parse();
991            match key {
992                Ok(_) => Some(true),
993                Err(_) => Some(false)
994            }
995        } else {
996            Some(false)
997        };
998
999        let is_filter = {
1000            let mut res = false;
1001            let mut input_vec_str: Vec<&str> = input.split("-").collect();
1002
1003            // Make sure it's more than one item (it's range of values).
1004            if input_vec_str.iter().count() > 1 {
1005                res = true;
1006            }
1007
1008            if res {
1009                // Does it end in "-"?
1010                let last = input_vec_str.iter().last();
1011                let mut open_range = false;
1012                if let Some(l) = last {
1013                    if l == &"" {
1014                        open_range = true;
1015                   }
1016                }
1017
1018                // Only want to deal with integers if it's an open range.
1019                if open_range {
1020                   input_vec_str.pop();
1021                }
1022
1023                // Make sure it'sall integers.
1024                for i in input_vec_str.into_iter() {
1025                    let key: Result<usize, std::num::ParseIntError> = i.parse();
1026                    if !key.is_ok() {
1027                        res = false;
1028                        break;
1029                    } else {
1030                        res = true;
1031                    }
1032                }
1033            } // if res
1034
1035            res
1036        };
1037
1038
1039        let are_all_keys = if let Some(c) = cmd.clone() {
1040             if c == "r".to_string() {
1041                 let _args = args.clone();
1042                 if let Some(a) = _args {
1043                      self.are_all_keys(a)
1044                 }
1045                 else {
1046                     false
1047                 }
1048             } else {
1049                 false
1050             }
1051        } else {
1052            false
1053        };
1054
1055        let are_all_keys_open = if let Some(c) = cmd.clone() {
1056             if c == "o".to_string() {
1057                 let _args = args.clone();
1058                 if let Some(a) = _args {
1059                      self.are_all_keys(a)
1060                 }
1061                 else {
1062                     false
1063                 }
1064             } else {
1065                 false
1066             }
1067        } else {
1068            false
1069        };
1070
1071        let cmd_type = if are_all_keys {
1072            CmdType::MultipleKeys
1073        } else if are_all_keys_open {
1074            CmdType::OpenKeys
1075        } else if is_filter {
1076            CmdType::FilterKeys
1077        } else if let Some(k) = is_key {
1078            if k {
1079                CmdType::SingleKey
1080            } else {
1081                CmdType::Cmd
1082            }
1083        } else if is_filter {
1084            CmdType::FilterKeys
1085        } else {
1086            CmdType::Cmd
1087        };
1088
1089            //if cmd_type == CmdType::FilterKeys {
1090            //    let few_ms = std::time::Duration::from_millis(1000);
1091            //    std::thread::sleep(few_ms);
1092            //}
1093
1094        self.cmd = cmd;
1095        self.args = args;
1096        self.as_read = input;
1097        self.cmd_type = Some(cmd_type);
1098
1099        self
1100    }
1101
1102    fn parse_cmd(&self, input: String) -> (Option<String>, Option<Vec<String>>) {
1103        let mut input: Vec<String> = input.clone().split(" ").map(|s| s.to_string()).collect();
1104        let cmd = input.remove(0);
1105
1106        let args = self.defang_args(input);
1107        (Some(cmd), args)
1108     }
1109
1110     fn are_all_keys(&self, input: Vec<String>) -> bool {
1111        let is_num = |x: &str| {
1112            let res: Result<usize, std::num::ParseIntError> = x.parse();
1113            match res {
1114                Ok(_) => true,
1115                Err(_) => false
1116            }
1117        };
1118        let is_all_nums = !input.iter().any(|x| !is_num(x.as_str()));
1119
1120        is_all_nums
1121     }
1122
1123     //fn is_key(&self, input: &Vec<String>) -> bool {
1124     //   if input.iter().count() == 1 {
1125     //       let key: Result<usize, std::num::ParseIntError> = input.iter().next().unwrap().parse();
1126     //       match key {
1127     //           Ok(_) => true,
1128     //           Err(_) => false
1129     //       }
1130     //   } else {
1131     //       false
1132     //   }
1133     //}
1134}
1135
1136#[derive(Debug, Clone, PartialEq)]
1137pub enum Mode {
1138    Fuzzy(String),
1139    Cmd(String),
1140    Work,
1141}
1142
1143fn parse_keys(input: &str) -> Option<String> {
1144    let x = input;
1145    let mut y: Vec<&str> = x.split(" ").collect();
1146    let mut count = y.iter().count();
1147
1148    let some = if count > 1 {
1149        y.remove(0);
1150
1151        let mut n = 0;
1152        let single_key: bool;
1153        if count == 2 {
1154            single_key = true;
1155        } else {
1156            single_key = false;
1157        }
1158        loop {
1159            y.insert(n, " ");
1160            count = count - 1;
1161            n = n + 2;
1162            if count == 1 {
1163                break
1164            }
1165        }
1166
1167        if single_key {
1168           y.remove(0);
1169        }
1170
1171        let z: String = y.into_iter().collect();
1172
1173        if z == "".to_string() {
1174            None
1175        } else {
1176            Some(z)
1177        }
1178
1179    } else {
1180        None
1181    };
1182
1183    some
1184}
1185
1186#[cfg(test)]
1187mod app_test {
1188    use std::fs::metadata;
1189    use std::path::{Path, PathBuf};
1190    use std::process::Command;
1191    use fixtures::{Fixture, command_assistors};
1192    use termion::terminal_size;
1193    use super::{Input, LsKey, CmdType, Mode};
1194    use super::*;
1195
1196    macro_rules! test {
1197        (
1198            $test_mode_bool: expr, // If false, tests are screen-size sensitive.
1199            $list_all_bool: expr, // lk -a would be true
1200            $name:ident,
1201            $test_file_path :expr, // We write text to a file so we know it's it when it's opened in test.
1202            $delay: expr, // Delay in each character typed.
1203            $input1: expr,
1204            $input2: expr,
1205            $input3: expr,
1206            $input4: expr,
1207            $input5: expr,
1208            $input6: expr,
1209            $input7: expr,
1210            $sub_path: expr, //We test all of this in a specific path. This'll create a sub-dir under that test-path.
1211            $intent: expr, //Explain what will happen in the test so tester can visually verify.
1212            $file_hash: expr,
1213            $test_macro: ident //We want to ignore the tests when we want and run when we want.
1214        ) => {
1215
1216            #[test]
1217            #[$test_macro]
1218            fn $name() {
1219                let path = format!("/tmp/lsk_tests/{}/", $sub_path);
1220
1221                let mut fixture = Fixture::new()
1222                    .add_dirpath(path.clone())
1223                    .add_dirpath(format!("{}basilides/", path.clone()))
1224                    .add_dirpath(format!("{}cyrinus/", path.clone()))
1225                    .add_dirpath(format!("{}nabor/", path.clone()))
1226                    .add_dirpath(format!("{}nazarius/", path.clone()))
1227                    .add_dirpath(format!("{}primus/", path.clone()))
1228                    .add_dirpath(format!("{}felician/", path.clone()))
1229                    .add_dirpath(format!("{}marcelinus/", path.clone()))
1230                    .add_dirpath(format!("{}isidore/", path.clone()))
1231                    .add_dirpath(format!("{}margaret/", path.clone()))
1232                    .add_dirpath(format!("{}angela/", path.clone()))
1233                    .add_dirpath(format!("{}francis/", path.clone()))
1234                    .add_dirpath(format!("{}gregory/", path.clone()))
1235                    .add_dirpath(format!("{}joseph/", path.clone()))
1236                    .add_dirpath(format!("{}anne/", path.clone()))
1237                    .add_dirpath(format!("{}joachim/", path.clone()))
1238                    .add_dirpath(format!("{}faustina/", path.clone()))
1239                    .add_dirpath(format!("{}john/", path.clone()))
1240                    .add_dirpath(format!("{}peter/", path.clone()))
1241                    .add_dirpath(format!("{}cecilia/", path.clone()))
1242                    .add_dirpath(format!("{}rita/", path.clone()))
1243                    .add_dirpath(format!("{}magdelene/", path.clone()))
1244                    .add_dirpath(format!("{}expeditus/", path.clone()))
1245                    .add_dirpath(format!("{}sebastian/", path.clone()))
1246                    .add_dirpath(format!("{}gabriel/", path.clone()))
1247                    .add_dirpath(format!("{}michael/", path.clone()))
1248                    .add_dirpath(format!("{}jude/", path.clone()))
1249                    .add_dirpath(format!("{}anthony/", path.clone()))
1250                    .add_dirpath(format!("{}nicholaus/", path.clone()))
1251                    .add_dirpath(format!("{}teresa/", path.clone()))
1252                    .build();
1253
1254                let path_path = Path::new(path.clone().as_str()).to_path_buf();
1255
1256                let mut sample_files_files = PathBuf::from(".fixtures");
1257                sample_files_files.push("sample_files");
1258
1259                let mut path_test = path_path.clone();
1260                path_test.push("sample_files");
1261
1262                let md = metadata(path_test.clone());
1263                let test_path_string = path_test.clone().into_os_string().into_string().unwrap();
1264
1265                if !md.is_ok() {
1266                    fixture.build();
1267                    Command::new("cp")
1268                        .arg("-r".to_string())
1269                        .arg(sample_files_files.clone().into_os_string().into_string().unwrap())
1270                        .arg(test_path_string.clone())
1271                        .output()
1272                        .expect("failed to execute lsk process");
1273                }
1274
1275                let mut path_cache = command_assistors::PathCache::new(&path_test);
1276                // Changing directories.
1277                path_cache.switch();
1278
1279                println!("");
1280                let text_vec = vec![
1281                     format!(r#""{}""#, $input1),
1282                     format!(r#""{}""#, $input2),
1283                     format!(r#""{}""#, $input3),
1284                     format!(r#""{}""#, $input4),
1285                     format!(r#""{}""#, $input5),
1286                     format!(r#""{}""#, $input6),
1287                     format!(r#""{}""#, $input7),
1288                ];
1289
1290                //println!("\n\n\nNew case intent:\n{}", $intent);
1291                //let few_ms = std::time::Duration::from_millis(5000);
1292                //std::thread::sleep(few_ms);
1293
1294
1295                if !$test_mode_bool {
1296                    let term_size = terminal_size().unwrap();
1297                    let term_width = term_size.0;
1298                    let term_height = term_size.1;
1299
1300                    assert_eq!(term_width, 31);
1301                    assert_eq!(term_height, 15);
1302                }
1303
1304                let spawn = super::terminal::parent_shell::type_text_spawn(text_vec, $delay);
1305                let fzf = PathBuf::from("/home/me/.fzf.sh");
1306                let fzc = PathBuf::from("/home/me/.fzc.sh");
1307                let fzd = PathBuf::from("/home/me/.fzd.sh");
1308                let _ls_key = super::app::run(test_path_string.clone(), $list_all_bool, true, Some(fzf), Some(fzc), Some(fzd));
1309                spawn.join().expect("failed to spawn thread");
1310
1311                let mut test_output_path = path_path.clone();
1312                test_output_path.push("sample_files");
1313                test_output_path.push(".lsk_test_output");
1314                let test_output_path_string = test_output_path.clone().into_os_string().into_string().unwrap();
1315                let output_mv_to_path_string = path_path.clone().into_os_string().into_string().unwrap();
1316
1317                Command::new("mv")
1318                    .arg(test_output_path_string)
1319                    .arg(output_mv_to_path_string.clone())
1320                    .output()
1321                    .expect("failed to execute lsk process");
1322
1323                let few_ms = std::time::Duration::from_millis(100);
1324                std::thread::sleep(few_ms);
1325
1326                let mut output_mv_to_path = path_path.clone();
1327                output_mv_to_path.push(".lsk_test_output");
1328                let output_mv_to_path_string = output_mv_to_path.clone().into_os_string().into_string().unwrap();
1329
1330                println!("\npath:\n{}", output_mv_to_path_string.clone());
1331
1332                let file256 = file_sha256(output_mv_to_path_string.clone());
1333
1334                match file256 {
1335                    Ok(h) => {
1336                        assert_eq!(
1337                            h.to_hex_string(),
1338                            $file_hash.to_string()
1339                        )
1340                    },
1341                    Err(..) => assert!(false)
1342                }
1343
1344                path_cache.switch_back();
1345
1346                std::fs::remove_file(output_mv_to_path_string).unwrap();
1347            }
1348        };
1349    }
1350
1351    test!(
1352          true, // test_mode_bool
1353          false, //list_all_bool
1354          macro_fzc_enter_file,
1355          "Makefile",
1356          100,               //$delay in milleseconds
1357          "$(printf 'c fzc\r')", //$input1
1358          "$(printf 'vimread\r')",//$input2
1359          "$(printf ':q\r')", //$input3
1360          "$(printf 'q\r')", //$input4
1361          "",                //$input5
1362          "",                //$input6
1363          "",                //$input7
1364          "macro_fzc_enter_file",
1365          ">Run lsk\n>Open file using vim with fzc hook\n>Quite vim\n>Quite lsk",
1366          "64e1b450ebdc532da6ebd5b11df4347d67b48405aa166b05800180e1a1136bf2",
1367          ignore/*macro_use*/
1368    );
1369
1370    test!(
1371          false, // test_mode_bool
1372          false, //list_all_bool
1373          macro_term_size_enter_file,
1374          "Makefile",
1375          100,               //$delay in milleseconds
1376          "$(printf '5\r')", //$input1
1377          "$(printf ':q\r')",//$input2
1378          "$(printf 'q\r')", //$input3
1379          "",                //$input4
1380          "",                //$input5
1381          "",                //$input6
1382          "",                //$input7
1383          "macro_enter_file",
1384          ">Run lsk\n>Open file by key (2)\n>Quite vim\n>Quite lsk",
1385         "dab8010a2c67e6986aa511180b41132058be77ad72a9c9c5fc076e371dccf584",
1386         ignore/*host_term_size_dependent*/
1387    );
1388
1389    test!(
1390          true, // test_mode_bool
1391          false, //list_all_bool
1392          macro_enter_file,
1393          "Makefile",
1394          100,               //$delay in milleseconds
1395          "$(printf '5\r')", //$input1
1396          "$(printf ':q\r')",//$input2
1397          "$(printf 'q\r')", //$input3
1398          "",                //$input4
1399          "",                //$input5
1400          "",                //$input6
1401          "",                //$input7
1402          "macro_enter_file",
1403          ">Run lsk\n>Open file by key (2)\n>Quite vim\n>Quite lsk",
1404          "99150c8a4c5960ee34cfbbb8393a65a9fe1c544394c1d02bf6a0a5cf0ad9b6a9",
1405          ignore/*macro_use*/
1406    );
1407
1408    test!(
1409          true, // test_mode_bool
1410          true, //list_all_bool
1411          macro_enter_file_list_all,
1412          ".eternal",
1413          100,               //$delay in milleseconds
1414          "$(printf '2\r')", //$input1
1415          "$(printf ':q\r')",//$input2
1416          "$(printf 'q\r')", //$input3
1417          "",                //$input4
1418          "",                //$input5
1419          "",                //$input6
1420          "",                //$input7
1421          "macro_enter_file_list_all",
1422          ">Run lsk\n>Open hidden file by key (2)\n>Quite vim\n>Quite lsk",
1423          "e539e2c5a37d677c59fd71ca1739ae398ed467fc9dd506ec2512533f5d070ae4",
1424          ignore/*macro_use*/
1425    );
1426
1427    test!(
1428          true, // test_mode_bool
1429          false,
1430          macro_fuzzy_enter_file,
1431          "intercession",
1432          100,               //$delay in milleseconds
1433          "$(printf 's boo\r')",
1434          "$(printf '4\r')",
1435          "$(printf ':q\r')",
1436          "$(printf 'q\r')",
1437          "",
1438          "",
1439          "",
1440          "macro_fuzzy_enter_file",
1441          ">Run lsk\n>Fuzzy widdle\n>Open file by key (1)\n>Quite vim\n>Quite lsk",
1442          "d7e51ab94b12bcb1773b2a92d0b4dec550c38f845add73850347a8bd03cc8f49",
1443          ignore/*macro_use*/
1444    );
1445
1446    test!(
1447          true, // test_mode_bool
1448          false,
1449          macro_fuzzy_enter_dir,
1450          "a-file",
1451          100,               //inrease 200 => 500 ms to see better.
1452          "$(printf 's ins\r')",
1453          "$(printf '4\r')",
1454          "$(printf 'q\r')",
1455          "",
1456          "",
1457          "",
1458          "",
1459          "macro_fuzzy_enter_dir",
1460          ">Run lsk\n>Fuzzy widdle\n>Open dir by key (1)\n>Quite vim\n>Quite lsk",
1461          "9127fec0401b363161da1931c49795066cb21a98fd025be9816655019be48b2f",
1462          ignore/*macro_use*/
1463    );
1464
1465    test!(
1466          true, // test_mode_bool
1467          false,
1468          macro_fuzzy_enter_dir_go_back_then_repeat,
1469          "a-file",
1470          100,               //inrease 200 => 500 ms to see better.
1471          "$(printf 's do\r')",
1472          "$(printf '2\r')",
1473          "$(printf '0\r')",
1474          "$(printf 's do\r')",
1475          "$(printf '2\r')",
1476          "$(printf 'q\r')",
1477          "",
1478          "macro_fuzzy_enter_dir",
1479          ">Run lsk\n>Fuzzy widdle\n>Open dir by key (1)\n>Go back (0) and repeat\n>Quite vim\n>Quite lsk",
1480          "27a1709e1aaa369079a83d411e082c22bccca5987b008d71865e091864264e9f",
1481          ignore/*macro_use*/
1482    );
1483
1484    test!(
1485          true, // test_mode_bool
1486          false,
1487          macro_go_back_fuzzy_enter_back_into_dir,
1488          "a-file",
1489          100,               //inrease 200 => 500 ms to see better.
1490          "$(printf '0\r')",
1491          "$(printf 's sa\r')",
1492          "$(printf '2\r')",
1493          "$(printf 'q\r')",
1494          "",
1495          "",
1496          "",
1497          "macro_go_back_fuzzy_enter_back_into_dir",
1498          ">Run lsk\n>Go back (0)\n>Fuzzy widdle\n>Open back into original dir by key (2)\n>\n>Quite lsk",
1499          "399976731f562f608d1d4c04afc1fa4f8f3530acf8683c39917d8771afebe5d1",
1500          ignore/*macro_use*/
1501    );
1502
1503    test!(
1504          true, // test_mode_bool
1505          false,
1506          macro_walk_in_park,
1507          "a-file",
1508          100,               //inrease 200 => 500 ms to see better.
1509          "$(printf '24\r')",
1510          "$(printf '1\r')",
1511          "$(printf 's con\r')",
1512          "$(printf '2\r')",
1513          "$(printf ':q\r')",
1514          "$(printf 'q\r')",
1515          "",
1516          "macro_walk_in_park",
1517          ">Run lsk\n>Go back (0)\n>Fuzzy widdle\n>Open back into original dir by key (2)\n>\n>Quite lsk",
1518          "668f2e7980c6260f572e837375b1e4a2beea20d3683b5293f0b968f00a4e68b0",
1519          ignore/*macro_use*/
1520    );
1521
1522     test!(
1523           true, // test_mode_bool
1524           false, //list_all_bool
1525           macro_fuzzy_backspace,
1526           "Makefile",
1527           100,
1528           "s itf",
1529           "BackSpace",
1530           "BackSpace",
1531           "BackSpace",
1532           "BackSpace",
1533           "BackSpace",
1534           "q\r",
1535           "macro_fuzzy_backspace",
1536           ">Run lsk\n>OFuzzy widdle (2)\n>Backspace fully (bad behavior)\n>Quite lsk",
1537           "ee86f4888a18c074a2df32a467ee94027ea5f437d0fede8125e3526e07ee8147",
1538           ignore/*macro_use*/
1539     );
1540
1541     test!(
1542           true, // test_mode_bool
1543           false, //list_all_bool
1544           macro_bad_fuzzy_backspace_enter,
1545           "Makefile",
1546           100,
1547           "s itf",
1548           "BackSpace",
1549           "BackSpace",
1550           "",
1551           "",
1552           "\r",
1553           "q\r",
1554           "macro_bad_fuzzy_backspace_enter",
1555           ">Run lsk\n>OFuzzy widdle (2)\n>Backspace partially (bad behavior)\n>Quite lsk",
1556           "83f5005ed89aca8834f69b3e064afca283c3ec6f90000e6e902473e1a582d11c",
1557           ignore/*macro_use*/
1558     );
1559
1560     test!(
1561           true, // test_mode_bool
1562           false, //list_all_bool
1563           macro_file_range,
1564           "Makefile",
1565           100,
1566           "20-25\r",
1567           "24\r",
1568           "1\r",
1569           "11\r",
1570           "",
1571           ":q\r",
1572           "q\r",
1573           "macro_file_range",
1574           ">Run lsk\n>List range 20-25\n>Enter rust dir\nEnter redox dir\n>Open filesystem.toml\n>Quite Vim\n>Quite lsk",
1575           "f5f1e7f641b5f348080ca2f86c0dffa8530cfab308cd9ec61d4cb9b8fa4cf3b7",
1576           ignore/*macro_use*/
1577     );
1578
1579     test!(
1580           true, // test_mode_bool
1581           true, //list_all_bool
1582           macro_list_all_fuzzy_file_range,
1583           "Makefile",
1584           100,
1585           "s m\r",
1586           "1-10\r",
1587           "10\r",
1588           "9\r",
1589           ":q\r",
1590           "0\r",
1591           "q\r",
1592           "macro_list_all_all_file_range",
1593           ">Run lsk\n>List all\n>Fuzzy search 'm'\n>List range 1-10\n>Enter mk dir\n>Open qemu.mk\n>Quite Vim\n>Go back/up a dir level\n>Quite lsk",
1594           "0eea81e276ad3766422bf859f4d9d3e76b84ef11e9d5233fe1aa206f8115e4f8",
1595           ignore/*macro_use*/
1596     );
1597
1598     test!(
1599           true, // test_mode_bool
1600           true, //list_all_bool
1601           macro_list_all_fuzzy_undo_open_range,
1602           "Makefile",
1603           100,
1604           "s i\r",
1605           "5-17\n",
1606           "7-\r",
1607           "17\r",
1608           ":q\r",
1609           "1-\n",
1610           "q\r",
1611           "macro_list_all_fuzzy_undo_open_range",
1612           ">Run lsk\n>List all\n>Fuzzy search 'i'\n>List range 5 - 17.\n>List range 7 open-ended\n>Open last one, key 17\n>Quite Vim\n>List entire range, 1-\n>Quite lsk",
1613           "83796531204cd54871410774f6858b0e79b2e291eb7c0a77b06a1314bca8ebd7",
1614           ignore/*macro_use*/
1615     );
1616
1617     test!(
1618           true, // test_mode_bool
1619           true, //list_all_bool
1620           macro_list_all_fuzzy_dir,
1621           "Makefile",
1622           100,
1623           "s i\r",
1624           "c fzd\r",
1625           "redoxgitl\r",
1626           "1\r",
1627           "1\r",
1628           ":q\r",
1629           "q\r",
1630           "macro_list_all_fuzzy_dir",
1631           ">Run lsk\n>List all\n>Fuzzy search 'i'\n>List range 5 - 17.\n>List range 7 open-ended\n>Open rust-toolchain fie  with command vim\n>Quite Vim\n>List entire range, 1-\n>Quite lsk",
1632           "20fc38f75c6739dbce70e86974cc2b7f58f8212279c47f05fe29f639ab1977f7",
1633           ignore/*macro_use*/
1634     );
1635
1636     // It's good if this test is broken. For some reason, and bin/<file> doesn't show up when
1637     // running fzd.
1638     test!(
1639           true, // test_mode_bool
1640           true, //list_all_bool
1641           macro_bad_list_all_fuzzy_dir,
1642           "Makefile",
1643           100,
1644           "s i\r",
1645           "7-\n",
1646           "c fzd\r",
1647           "bin\r",
1648           "1\r",
1649           ":q\r",
1650           "q\r",
1651           "macro_list_all_fuzzy_dir",
1652           ">Run lsk\n>List all\n>Fuzzy search 'i'\n>List range 5 - 17.\n>List range 7 open-ended\n>Open bind dir fzd command, but main.rs doesn't show.\n>Quite lsk",
1653           "9d2896a092c094c6073fe65408d38aa5d672dccf3f34597174774d8098194075",
1654           ignore/*macro_use*/
1655     );
1656
1657
1658    #[test]
1659    #[ignore]//docker
1660    fn parse() {
1661        let input = Input::new();
1662        let input = input.parse("vim Cargo.toml".to_string());
1663
1664        assert_eq!(
1665           Some(CmdType::Cmd),
1666           input.cmd_type
1667        );
1668
1669        assert_eq!(
1670           Some("vim".to_string()),
1671           input.cmd
1672        );
1673
1674        assert_eq!(
1675           Some(vec!["Cargo.toml".to_string()]),
1676           input.args
1677        );
1678    }
1679
1680    #[test]
1681    #[ignore]//docker
1682    fn parse_long() {
1683        let input = Input::new();
1684        let input = input.parse("git clone https://github.com/7db9a/ls-key --depth 1".to_string());
1685
1686        assert_eq!(
1687           Some(CmdType::Cmd),
1688           input.cmd_type
1689        );
1690
1691        //vec!["clone".to_string(), "https://github.com/7db9a/ls-key".to_string(), "--depth".to_string(), "1".to_string()]
1692
1693        assert_eq!(
1694           Some("git".to_string()),
1695           input.cmd
1696        );
1697
1698        assert_eq!(
1699           Some(vec![
1700                "clone".to_string(),
1701                "https://github.com/7db9a/ls-key".to_string(),
1702                "--depth".to_string(),
1703                "1".to_string()
1704           ]),
1705           input.args
1706        );
1707    }
1708
1709    #[test]
1710    #[ignore]//docker
1711    fn parse_single_cmd() {
1712        let input = Input::new();
1713        let input = input.parse("vim".to_string());
1714
1715        assert_eq!(
1716           Some(CmdType::Cmd),
1717           input.cmd_type
1718        );
1719
1720        assert_eq!(
1721           Some("vim".to_string()),
1722           input.cmd
1723        );
1724
1725        assert_eq!(
1726           None,
1727           input.args
1728        );
1729    }
1730
1731    #[test]
1732    #[ignore]//docker
1733    fn parse_key() {
1734        let input = Input::new();
1735        let input = input.parse("33".to_string());
1736
1737        assert_eq!(
1738           Some(CmdType::SingleKey),
1739           input.cmd_type
1740        );
1741
1742        assert_eq!(
1743           Some("33".to_string()),
1744           input.cmd
1745        );
1746
1747        assert_eq!(
1748           None,
1749           input.args
1750        );
1751    }
1752
1753    #[test]
1754    #[ignore]//docker
1755    fn parse_bad() {
1756        let input = Input::new();
1757        let input = input.parse(" vim Cargo.toml".to_string());
1758
1759        assert_eq!(
1760           Some(CmdType::Cmd),
1761           input.cmd_type
1762        );
1763
1764        assert_eq!(
1765           Some("".to_string()),
1766           input.cmd
1767        );
1768
1769        assert_eq!(
1770           None,
1771           input.args
1772        );
1773    }
1774
1775    #[test]
1776    #[ignore]//docker
1777    fn parse_cmd() {
1778        let input = Input::new();
1779        let (cmd, args) = input.parse_cmd("vim Cargo.toml".to_string());
1780
1781        assert_eq!(
1782            cmd,
1783            Some("vim".to_string())
1784        );
1785
1786        assert_eq!(
1787            args,
1788            Some(vec!["Cargo.toml".to_string()])
1789        )
1790    }
1791
1792    //#[test]
1793    //fn shell_spawn_vim() {
1794    //    super::terminal::shell::spawn("vim".to_string(), vec!["-c".to_string(), "vsplit README.md".to_string(), "dev.sh".to_string()]);
1795    //}
1796
1797    //#[test]
1798    //fn shell_pipe_cmd() {
1799    //    super::terminal::shell::cmd(r#"du -ah . | sort -hr | head -n 10"#.to_string());
1800    //}
1801
1802    //#[test]
1803    //fn shell_cat_cmd() {
1804    //    super::terminal::shell::cmd("cat Cargo.toml".to_string());
1805    //}
1806
1807    //#[test]
1808    //fn shell_cat() {
1809    //    super::terminal::shell::spawn("cat".to_string(), vec!["Cargo.toml".to_string()]);
1810    //}
1811
1812     #[test]
1813     #[ignore]//docker
1814     fn test_mode_parse() {
1815        let mut ls_key = LsKey::new("/tmp", false, false, None, None, None);
1816        let input_single = "s something".to_string();
1817        let some_fuzzy_search_single = ls_key.mode_parse(input_single.clone());
1818
1819        let input_multi = "s something and more".to_string();
1820        let some_fuzzy_search_multi = ls_key.mode_parse(input_multi.clone());
1821
1822        let input_invalid = "sd".to_string();
1823        let some_fuzzy_search_invalid = ls_key.mode_parse(input_invalid.clone());
1824
1825        let input_lack_more = "s".to_string();
1826        let some_fuzzy_search_lack_more = ls_key.mode_parse(input_lack_more.clone());
1827
1828        let input_wrong = "d something".to_string();
1829        let some_fuzzy_search_wrong = ls_key.mode_parse(input_wrong.clone());
1830
1831        assert_eq!(
1832            some_fuzzy_search_invalid,
1833            None
1834        );
1835
1836        assert_eq!(
1837            some_fuzzy_search_lack_more,
1838            None
1839        );
1840
1841        assert_eq!(
1842            some_fuzzy_search_single,
1843            Some(Mode::Fuzzy("something".to_string()))
1844        );
1845
1846        assert_eq!(
1847            some_fuzzy_search_multi,
1848            Some(Mode::Fuzzy("something and more".to_string()))
1849
1850        );
1851
1852        assert_eq!(
1853            some_fuzzy_search_wrong,
1854            None
1855        );
1856     }
1857
1858     #[test]
1859     #[ignore]//docker
1860     fn test_bad_mode_parse() {
1861        let mut ls_key = LsKey::new("/tmp", false, false, None, None, None);
1862
1863        let input_lack = "s ".to_string();
1864        let some_fuzzy_search_lack = ls_key.mode_parse(input_lack.clone());
1865
1866        assert_eq!(
1867            some_fuzzy_search_lack,
1868            None
1869        );
1870     }
1871}