1extern crate skim;
2use skim::prelude::*;
3use std::io::{Cursor, Error};
4
5use log::info;
6use std::future::Future;
7
8#[derive(Debug, PartialEq)]
9pub enum Navigation {
10 _Unknown,
11 Running,
12 OutOf,
13 Finished,
14}
15
16pub struct Navi;
17
18impl Navi {
19 pub async fn run<'a, Fut>(base_url: &str, handler: impl Fn(String) -> Fut) -> Option<String>
20 where
21 Fut: Future<Output = Result<Vec<String>, Error>>,
22 {
23 let options = SkimOptionsBuilder::default()
24 .height(Some("50%"))
25 .multi(false)
26 .bind(vec!["/:accept", "Enter:accept", "Esc:abort", "Tab:accept"])
27 .build()
28 .unwrap();
29 let mut subpath = "/".to_string();
30 loop {
31 let path = base_url.to_string() + &subpath;
32 let items = handler(path).await.unwrap().join("\n");
33
34 let item_reader = SkimItemReader::default();
35 let items = item_reader.of_bufread(Cursor::new(items));
36 let selected_items = Skim::run_with(&options, Some(items))
37 .map(|out| match out.final_key {
38 Key::Char('/') => out
39 .selected_items
40 .iter()
41 .map(|i| Self::navigate_into(&i.text()))
42 .collect(),
43 Key::Tab => out
44 .selected_items
45 .iter()
46 .map(|i| Self::navigate_into(&i.text()))
47 .collect(),
48 Key::Enter => out
49 .selected_items
50 .iter()
51 .map(|i| Self::navigate_enter(&i.text()))
52 .collect(),
53 _ => Vec::new(),
54 })
55 .unwrap();
56
57 if selected_items.len() == 0 {
58 return None;
59 }
60 let item = &selected_items[0];
61
62 if item.0 == Navigation::Finished {
63 return Some(item.1.to_string());
64 }
65
66 if item.0 == Navigation::OutOf {
67 subpath = Self::up(&subpath).to_string();
68 } else {
69 subpath = "/".to_string() + &item.1;
70 }
71 }
72 }
73
74 fn up(path: &str) -> &str {
75 if path.matches('/').count() > 0 {
76 return &path[..path.rfind('/').unwrap()];
77 }
78 path
79 }
80
81 fn navigate_outof(item: &str) -> (Navigation, String) {
82 info!("{}", item);
83 (Navigation::OutOf, item.to_string())
84 }
85
86 fn navigate_into(item: &str) -> (Navigation, String) {
87 info!("/{}", item);
88 (Navigation::Running, item.to_string())
89 }
90
91 fn navigate_enter(item: &str) -> (Navigation, String) {
92 if item == ".." {
93 return Self::navigate_outof(item);
94 }
95 info!("Navigation finished: {}", item);
96 (Navigation::Finished, item.to_string())
97 }
98}