use std::path::{Path, PathBuf};
use cdt_tree::Tree;
use crate::search::{Done, Kind, Search};
pub(crate) enum Mode {
Tree,
Find(String),
Grep(String),
}
pub(crate) struct App {
pub(crate) tree: Tree,
pub(crate) hits: Vec<PathBuf>,
pub(crate) mode: Mode,
pub(crate) sel: usize,
pub(crate) msg: String,
pub(crate) search: Search,
}
impl App {
pub(crate) fn new(root: PathBuf) -> Self {
App {
tree: Tree::new(root, false),
hits: Vec::new(),
mode: Mode::Tree,
sel: 0,
msg: String::new(),
search: Search::spawn(),
}
}
pub(crate) fn len(&self) -> usize {
match self.mode {
Mode::Tree => self.tree.len(),
_ => self.hits.len(),
}
}
pub(crate) fn selected(&self) -> Option<&Path> {
match self.mode {
Mode::Tree => self.tree.get(self.sel).map(|e| e.path.as_path()),
_ => self.hits.get(self.sel).map(PathBuf::as_path),
}
}
pub(crate) fn target(&self) -> PathBuf {
match self.selected() {
Some(p) if p.is_dir() => p.to_path_buf(),
Some(p) => p.parent().unwrap_or(&self.tree.root).to_path_buf(),
None => self.tree.root.clone(),
}
}
pub(crate) fn move_by(&mut self, d: isize) {
let n = self.len();
if n > 0 {
self.sel = (self.sel as isize + d).rem_euclid(n as isize) as usize;
}
}
pub(crate) fn back_to_tree(&mut self) {
self.mode = Mode::Tree;
self.hits.clear();
self.sel = 0;
self.msg.clear();
self.search.cancel();
}
pub(crate) fn collapse_or_up(&mut self) {
match self.tree.get(self.sel) {
Some(e) if e.is_dir && e.open => self.tree.toggle(self.sel),
Some(_) => match self.tree.parent_of(self.sel) {
Some(p) => self.sel = p,
None => {
if self.tree.up() {
self.sel = 0;
}
}
},
None => {
if self.tree.up() {
self.sel = 0;
}
}
}
}
pub(crate) fn toggle_hidden(&mut self) {
self.tree.hidden = !self.tree.hidden;
self.tree.reload();
self.sel = 0;
}
pub(crate) fn request_search(&mut self, q: &str) {
self.msg.clear();
if q.is_empty() {
self.hits.clear();
self.sel = 0;
self.search.cancel();
return;
}
let kind = match self.mode {
Mode::Grep(_) => Kind::Content,
_ => Kind::Names,
};
self.search.request(
kind,
self.tree.root.clone(),
q.to_string(),
self.tree.hidden,
);
}
pub(crate) fn apply(&mut self, d: Done) {
self.hits = d.hits;
self.sel = 0;
if let Some(e) = d.err {
self.msg = e;
}
}
#[cfg(test)]
pub(crate) fn settle(&mut self) {
if let Some(d) = self.search.wait(std::time::Duration::from_secs(10)) {
self.apply(d);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn app() -> App {
App::new(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
}
#[test]
fn move_by_wraps_and_tolerates_an_empty_list() {
let mut app = app();
let n = app.len();
assert!(n > 0);
app.move_by(-1);
assert_eq!(app.sel, n - 1);
app.move_by(1);
assert_eq!(app.sel, 0);
app.mode = Mode::Find(String::new()); app.move_by(1);
assert_eq!(app.sel, 0);
}
#[test]
fn target_of_a_file_is_its_directory() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut app = app();
app.mode = Mode::Find(String::new());
app.hits = vec![root.join("src/lib.rs")];
assert_eq!(app.target(), root.join("src"));
}
#[test]
fn target_of_a_directory_is_that_directory() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut app = app();
app.mode = Mode::Find(String::new());
app.hits = vec![root.join("src")];
assert_eq!(app.target(), root.join("src"));
}
#[test]
fn target_falls_back_to_the_root_when_nothing_is_selected() {
let mut app = app();
app.mode = Mode::Find(String::new()); assert_eq!(app.target(), app.tree.root);
}
}