use anyhow::Result;
use crossterm::event::{KeyEvent, MouseEvent};
use ratatui::Frame;
use std::path::PathBuf;
use crate::bookmarks::Bookmarks;
use crate::config::Config;
use crate::disks::Disks;
use crate::event_handler::EventHandler;
use crate::navigation::Navigation;
use crate::search::Search;
use crate::ui::UI;
pub struct App {
nav: Navigation,
search: Search,
ui: UI,
event_handler: EventHandler,
config: Config,
pub bookmarks: Bookmarks,
pub disks: Disks,
needs_redraw: bool,
}
impl App {
pub fn new(start_path: PathBuf) -> Result<Self> {
let config = Config::load()?;
let nav = Navigation::new(
start_path,
false,
config.behavior.show_hidden,
config.behavior.follow_symlinks,
)?;
let search = Search::new();
let ui = UI::new();
let event_handler = EventHandler::new();
let bookmarks = Bookmarks::new()?;
let disks = Disks::new();
Ok(App {
nav,
search,
ui,
event_handler,
config,
bookmarks,
disks,
needs_redraw: true,
})
}
pub fn handle_key(&mut self, key: KeyEvent) -> Result<Option<PathBuf>> {
let result = self.event_handler.handle_key(
key,
&mut self.nav,
&mut self.search,
&mut self.bookmarks,
&mut self.disks,
&self.ui,
&self.config,
);
self.mark_dirty();
result
}
pub fn handle_mouse(&mut self, mouse: MouseEvent) -> Result<()> {
let result = self.event_handler.handle_mouse(
mouse,
&mut self.nav,
&mut self.search,
&mut self.bookmarks,
&mut self.disks,
&mut self.ui,
&self.config,
);
self.mark_dirty();
result
}
pub fn render(&mut self, frame: &mut Frame) {
self.ui.render_compact(
frame,
&self.nav,
&self.search,
&self.bookmarks,
&self.disks,
&self.config,
);
}
pub fn poll_search(&mut self) -> bool {
let updated = self.search.poll_results();
if updated {
self.mark_dirty();
}
updated
}
pub fn mark_dirty(&mut self) {
self.needs_redraw = true;
}
pub fn clear_dirty(&mut self) {
self.needs_redraw = false;
}
pub fn needs_redraw(&self) -> bool {
self.needs_redraw
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[test]
fn test_bookmark_create_enters_creation_mode() {
let temp_dir = std::env::temp_dir().join("bmrk_test_bm_create");
std::fs::create_dir_all(&temp_dir).unwrap();
let mut app = App::new(temp_dir.clone()).unwrap();
let key_m = KeyEvent::new(KeyCode::Char('m'), KeyModifiers::NONE);
let result = app.handle_key(key_m).unwrap();
assert!(result.is_some(), "pressing 'm' should not exit the app");
assert!(app.bookmarks.is_creating);
let key_esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
let _ = app.handle_key(key_esc);
assert!(!app.bookmarks.is_creating);
std::fs::remove_dir_all(&temp_dir).ok();
}
#[test]
fn test_search_mode_activation() {
let temp_dir = std::env::temp_dir().join("bmrk_test_search");
std::fs::create_dir_all(&temp_dir).unwrap();
let mut app = App::new(temp_dir.clone()).unwrap();
let key_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::NONE);
let result = app.handle_key(key_slash).unwrap();
assert!(result.is_some(), "entering search should not exit the app");
assert!(app.search.mode, "search mode should be active");
let key_r = KeyEvent::new(KeyCode::Char('r'), KeyModifiers::NONE);
let _ = app.handle_key(key_r);
let key_enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
let result = app.handle_key(key_enter).unwrap();
assert!(result.is_some(), "search execution should not exit the app");
assert!(app.search.show_results, "search results should be shown");
std::fs::remove_dir_all(&temp_dir).ok();
}
#[test]
fn test_bookmark_select_enters_selection_mode() {
let temp_dir = std::env::temp_dir().join("bmrk_test_bm_select");
std::fs::create_dir_all(&temp_dir).unwrap();
let mut app = App::new(temp_dir.clone()).unwrap();
let key_tick = KeyEvent::new(KeyCode::Char('\''), KeyModifiers::NONE);
let result = app.handle_key(key_tick).unwrap();
assert!(
result.is_some(),
"bookmark selection should not exit the app"
);
assert!(app.bookmarks.is_selecting);
let key_esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
let _ = app.handle_key(key_esc);
assert!(!app.bookmarks.is_selecting);
std::fs::remove_dir_all(&temp_dir).ok();
}
#[test]
fn test_esc_exits_app() {
let temp_dir = std::env::temp_dir().join("bmrk_test_esc");
std::fs::create_dir_all(&temp_dir).unwrap();
let mut app = App::new(temp_dir.clone()).unwrap();
let key_esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
let result = app.handle_key(key_esc).unwrap();
assert!(result.is_none(), "Esc in normal mode should signal exit");
std::fs::remove_dir_all(&temp_dir).ok();
}
}