skim_navi/
lib.rs

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}