1use std::path::{Path, PathBuf};
2use std::fs::metadata;
3use std::borrow::Cow;
4use walkdir::{DirEntry, WalkDir, Error as WalkDirError};
5use ansi_term::Colour;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum FileType {
9 File,
10 Dir,
11}
12
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct Entry {
16 pub path: PathBuf,
17 pub file_type: FileType,
18 pub key: Option<usize>
19}
20
21pub fn alphabetize_paths_vec(paths_vec: Vec<PathBuf>) -> Vec<PathBuf> {
23 let mut strings_vec: Vec<String> = vec![];
24 let alphabetized_paths_vec: Vec<PathBuf> = {
25 let mut _alphabetized_paths_vec: Vec<PathBuf> = vec![];
26 for path in paths_vec.iter() {
27 let path = path.clone();
28 let path_string = path.into_os_string().into_string().unwrap();
29 strings_vec.push(path_string);
30 }
31
32 strings_vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
33
34 for string in strings_vec.iter() {
35 _alphabetized_paths_vec.push(PathBuf::from(string));
36 }
37 _alphabetized_paths_vec
38 };
39
40 alphabetized_paths_vec
41}
42
43pub fn alphabetize_entry(a: &Entry, b: &Entry) -> std::cmp::Ordering {
44 let paths_vec: Vec<PathBuf> = vec![a.path.clone(), b.path.clone()];
45 let paths_vec = alphabetize_paths_vec(paths_vec.clone());
46
47 if &a.path == &b.path {
48 std::cmp::Ordering::Equal
49 } else if paths_vec.iter().nth(0).unwrap() == &b.path {
50 std::cmp::Ordering::Greater
51 } else {
52 std::cmp::Ordering::Less
53 }
54}
55
56
57#[cfg(test)]
58mod test_entries_sort {
59 use super::*;
60 #[test]
61 fn sort_entries() {
62 let a = Entry {
63 path: PathBuf::from("/a"),
64 file_type: FileType::File,
65 key: None,
66 };
67
68 let b = Entry {
69 path: PathBuf::from("/B"),
70 file_type: FileType::File,
71 key: None,
72 };
73
74
75 let c = Entry {
76 path: PathBuf::from("/c"),
77 file_type: FileType::File,
78 key: None,
79 };
80
81 let mut entries = vec![b.clone(), a.clone(), c.clone()];
82
83 entries.sort_by(|a, b| alphabetize_entry(a, b));
84
85 assert_eq!(
86 entries,
87 vec![a, b, c]
88 )
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Default)]
93pub struct List {
94 pub files: Vec<Entry>,
95 pub parent_path: PathBuf,
96 pub path_history: Vec<PathBuf>,
97 pub filter: Option<Vec<usize>>
98}
99
100impl List {
101 pub fn new<P: AsRef<Path>>(path: P) -> Self {
102 let mut list: List = Default::default();
103 list.parent_path = path.as_ref().to_path_buf();
104 list.path_history.push(list.parent_path.clone());
105
106 list
107 }
108
109 pub fn update<P: AsRef<Path>>(mut self, path: P) -> Self {
111 let old_path_history = self.path_history;
112 let old_parent_path = self.parent_path;
113 let p = path.as_ref().to_str().unwrap();
114 let np: String = basename(p, '/').into_owned();
115 let basename = Path::new(&np);
116 let list: List = Default::default();
117 self = list;
118 self.path_history = old_path_history;
119 self.parent_path = old_parent_path.join(basename);
120 self.path_history.push(self.parent_path.clone());
121
122 self
123 }
124
125 pub fn list_skip_hidden(mut self) -> Result<Self, std::io::Error> {
126 let list: List = Default::default();
127 let walker = WalkDir::new(&self.parent_path).max_depth(1).into_iter();
128 for entry in walker.filter_entry(|e| !list.clone().skip(e)) {
129 self = list_maker(entry, self)?;
130 }
131
132 Ok(self)
133 }
134
135 pub fn list_include_hidden(mut self) -> Result<Self, std::io::Error> {
136 let mut _list: List = Default::default();
137 for entry in WalkDir::new(&self.parent_path).max_depth(1) {
138 self = list_maker(entry, self)?;
139 }
140
141 Ok(self)
142 }
143
144 fn replace_shortest_path(mut self, pathbuf: PathBuf) -> Self {
145 let path = pathbuf.into_boxed_path();
146 let depth_from_root_dir = path.iter().count();
147
148 let parent_dir_count = self.parent_path.iter().count();
149 if depth_from_root_dir < parent_dir_count {
150 self.parent_path = path.to_path_buf();
151 }
152
153 self
154 }
155
156 pub fn order_and_sort_list(&mut self, sort: bool, filter: bool) -> Vec<Entry> {
157 let mut all_files = self.files.clone();
158 let previous_path = self.path_history.iter().last().unwrap();
159 if sort {
160 all_files.sort_by(|a, b| alphabetize_entry(a, b));
161 }
162 all_files.insert(
163 0,
164 Entry {
165 path: previous_path.to_path_buf(),
166 file_type: FileType::Dir,
167 key: None
168 }
169 );
170
171 let mut n = 0;
173 let mut final_all_files: Vec<Entry> = vec![];
174 for mut x in all_files.into_iter() {
175 x.key = Some(n);
176 final_all_files.push(x.clone());
177 n += 1;
178 }
179
180 if filter {
185
186 fn filter_thing(x: &Entry, filter: &Option<Vec<usize>>) -> bool {
187 if let Some(fl) = filter {
188 if let Some(key) = x.key {
189 fl.iter().find(|n| &key == *n).is_some()
190 } else {
191 true
192 }
193 } else {
194 true
195 }
196 }
197
198 let find_in_range = final_all_files.clone().into_iter().filter(|x|
199 filter_thing(x, &self.filter)
201 );
202
203 let files: Vec<Entry> = find_in_range.collect();
204
205 let entry_test = Entry {
206 path: PathBuf::from("entry_test"),
207 file_type: FileType::File,
208 key: Some(3)
209
210 };
211
212 if files != vec![entry_test] {
213 } else {
216 }
219 final_all_files = files;
220 } else {
221 }
222
223 final_all_files
224 }
225
226 pub fn get_file_by_key(&self, key: usize, sort: bool) -> Option<PathBuf> {
227 let all_files = order_and_sort_list(&self, sort);
228 let all_files = all_files.iter();
229
230 for entry in all_files.clone() {
231 let n = entry.key.unwrap();
234 if n == key {
236 let path = entry.path.to_path_buf();
237 return self.clone().full_entry_path(path);
238 }
239 }
240
241 return None
242 }
243
244 fn skip(self, entry: &DirEntry) -> bool {
246
247 entry.file_name()
251 .to_str()
252 .map(|s| s.starts_with("."))
253 .unwrap_or(false)
254 }
255
256 fn full_entry_path(self, path: PathBuf) -> Option<PathBuf> {
257 let p = self.parent_path;
258 Some(p.join(path.as_path()))
259 }
260}
261
262fn basename<'a>(path: &'a str, sep: char) -> Cow<'a, str> {
263 let mut pieces = path.rsplit(sep);
264 match pieces.next() {
265 Some(p) => p.into(),
266 None => path.into(),
267 }
268}
269
270fn file_or_dir_name(path: &PathBuf) -> Option<PathBuf> {
271 let path = path.as_path();
272 let path = path.file_name();
273
274 if let Some(p) = path {
275 Some(Path::new(&p).to_path_buf())
276 } else {
277 None
278 }
279}
280
281fn list_maker(entry: Result<DirEntry, WalkDirError>, mut list: List) -> Result<List, std::io::Error> {
282 match entry {
283 Ok(entry) => {
284 let entry = entry.path();
285 let parent_file_name = file_or_dir_name(&list.parent_path);
286
287 match metadata(entry) {
288 Ok(md) => {
289 let path = entry.to_path_buf();
290 let short_path = file_or_dir_name(&path);
291 if md.is_file() {
292 list = list.replace_shortest_path(path);
293 if let Some(p) = short_path {
294
295 if Some(p.clone()) != parent_file_name {
296 list.files.push(
297 Entry {
298 path: p,
299 file_type: FileType::File,
300 key: None
301 }
302 );
303 }
304 }
305 } else if md.is_dir() {
306 list = list.replace_shortest_path(path);
307 if let Some(p) = short_path {
308 if Some(p.clone()) != parent_file_name {
309 list.files.push(
310 Entry {
311 path: p,
312 file_type: FileType::Dir,
313 key: None
314 }
315 );
316 }
317 }
318 }
319 },
320 Err(_) => ()
321 }
322 },
323 Err(_) => ()
324 }
325
326 Ok(list)
327}
328
329pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
330 metadata(path).unwrap().is_file()
331}
332
333pub fn is_dir<P: AsRef<Path>>(path: P) -> bool {
334 metadata(path).unwrap().is_dir()
335}
336
337pub fn key_entries(entries: Vec<Entry>) -> Vec<String> {
345 let mut entries_keyed: Vec<String> = vec![];
346 for entry in entries.clone() {
347 let n = entry.key.unwrap();
348 let entry = match entry.file_type {
349 FileType::File => {
350 let entry = entry.path.to_str().unwrap();
351 let entry = format!(r#"{} [{}]"#, entry, n);
352 Colour::White.bold().paint(entry).to_string()
353 },
354 FileType::Dir => {
355 if n == 0 {
356 let _path = entry.path.clone();
357 let _path = _path.as_path();
358 let os_str = _path.iter().last().unwrap();
359 let entry_str = os_str.to_str().unwrap();
360 if entry_str != "/" {
361 let entry_string = format!("../{}", entry_str);
362 Colour::Blue.bold().paint(entry_string).to_string()
363 } else {
364 "/".to_string()
365 }
366 } else {
367 let entry_str = entry.path.to_str().unwrap();
368 let entry = format!(r#"{} [{}]"#, entry_str, n);
369 Colour::Blue.bold().paint(entry).to_string()
370 }
371 },
372 };
373 if entry != "/".to_string() {
374 entries_keyed.push(entry);
375 }
376 }
377
378 entries_keyed
379}
380
381pub fn order_and_sort_list(list: &List, sort: bool) -> Vec<Entry> {
382 let mut all_files = list.files.clone();
383 let previous_path = list.path_history.iter().last().unwrap();
384 if sort {
385 all_files.sort_by(|a, b| alphabetize_entry(a, b));
386 }
388 all_files.insert(
389 0,
390 Entry {
391 path: previous_path.to_path_buf(),
392 file_type: FileType::Dir,
393 key: None
394 }
395 );
396
397 let mut n = 0;
403 let mut final_all_files: Vec<Entry> = vec![];
404 for mut x in all_files.into_iter() {
405 x.key = Some(n);
406 final_all_files.push(x.clone());
407 n += 1;
408 }
409
410 final_all_files
413}
414
415pub fn print_list_with_keys(list: List) -> Result<(), std::io::Error> {
416 let all_files = order_and_sort_list(&list, true);
417 let mut n = 0;
418 for entry in all_files {
419 println!("{} [{}]", entry.path.display(), n);
420 n += 1;
421 }
422
423 Ok(())
424}
425
426pub mod fuzzy_score {
427 use super::Entry;
428 use fuzzy_matcher;
429 use fuzzy_matcher::FuzzyMatcher;
430 use fuzzy_matcher::skim::SkimMatcherV2;
431
432 #[derive(Debug, Clone, PartialEq)]
433 pub enum Score {
434 Files((Entry, Option<(i64, Vec<usize>)>)),
435 }
436
437 impl Score {
438 pub fn score(&self) -> (Entry, Option<(i64, Vec<usize>)>) {
439 match self.clone() {
440 Score::Files(score) => score,
441 }
442 }
443 }
444
445 #[derive(Debug, Clone, PartialEq)]
446 pub struct Scores {
447 pub files: Vec<Score>,
448 }
449
450 pub fn score(compare_to: &str, guess: &str) -> Option<(i64, Vec<usize>)> {
451 let matcher = SkimMatcherV2::default();
452 matcher.fuzzy_indices(compare_to, guess)
453 }
454
455 pub fn order(a: &Score, b: &Score) -> std::cmp::Ordering {
456 let a_score = a.score().0.path;
457 let b_score = b.score().0.path;
458
459 if a_score == b_score {
460 std::cmp::Ordering::Equal
461 } else if a_score > b_score {
462 std::cmp::Ordering::Less
463 } else {
464 std::cmp::Ordering::Greater
465 }
466 }
467}
468
469#[cfg(test)]
470mod fuzzy_tests {
471 use std::path::PathBuf;
472 use super::*;
473 #[test]
474 #[ignore]fn score() {
476 let res = super::fuzzy_score::score("abc", "abx");
477 assert_eq!(res, None);
478 let (score, indices) = super::fuzzy_score::score("axbycz", "xyz").unwrap();
479 assert_eq!(indices, [1, 3, 5]);
480 assert_eq!(score, 39);
481 let (score, indices) = super::fuzzy_score::score("axbycz", "abc").unwrap();
482 assert_eq!(indices, [0, 2, 4]);
483 assert_eq!(score, 55);
484 let (score, indices) = super::fuzzy_score::score("unignore_play_test", "uigp").unwrap();
485 assert_eq!(indices, [0, 2, 3, 9]);
486 assert_eq!(score, 78);
487 }
488
489 #[test]
490 #[ignore]fn sort_score() {
492 let guess = "xyz";
493 let file_a = "xayb";
494 let file_b = "xyazabc";
495 let file_c = "xyza";
496 let file_d = "afd";
497
498 let res_a = super::fuzzy_score::score(file_a, guess);
499 let res_b = super::fuzzy_score::score(file_b, guess);
500 let res_c = super::fuzzy_score::score(file_c, guess);
501 let res_d = super::fuzzy_score::score(file_d, guess);
502
503 let mut scores = super::fuzzy_score::Scores {
504 files: vec![
505 super::fuzzy_score::Score::Files(
506 (
507 Entry {
508 path: PathBuf::from(file_a),
509 file_type: FileType::File,
510 key: None
511 },
512 res_a.clone()
513 )
514 ),
515 super::fuzzy_score::Score::Files(
516 (
517 Entry {
518 path: PathBuf::from(file_b),
519 file_type: FileType::File,
520 key: None
521 },
522 res_b.clone()
523 )
524 ),
525 super::fuzzy_score::Score::Files(
526 (
527 Entry {
528 path: PathBuf::from(file_c),
529 file_type: FileType::File,
530 key: None
531 },
532 res_c.clone()
533 )
534 ),
535 super::fuzzy_score::Score::Files(
536 (
537 Entry {
538 path: PathBuf::from(file_d),
539 file_type: FileType::File,
540 key: None
541 },
542 res_d.clone()
543 )
544 ),
545 ],
546 };
547
548 let pre_sort = scores.clone();
549 scores.files.sort_by(|a, b| fuzzy_score::order(a, b));
550 let post_sort = scores.clone();
551 assert_ne!(
552 pre_sort,
553 post_sort
554 );
555 assert_ne!(
558 pre_sort.files,
559 post_sort.files
560 );
561
562 assert_eq!(
563 scores.files.iter().count(),
564 4
565 );
566
567 assert_eq!(
568 scores.files.iter().nth(0).unwrap().score().0.path,
569 PathBuf::from("xyza")
570 );
571
572 assert_eq!(
573 scores.files.iter().nth(1).unwrap().score().0.path,
574 PathBuf::from("xyazabc")
575 );
576
577 assert_eq!(
578 scores.files.iter().nth(2).unwrap().score().0.path,
579 PathBuf::from("xayb")
580 )
581 }
582}