pub mod actions;
mod handlers;
mod nav;
mod parent;
mod preview;
pub use nav::NavState;
pub use parent::ParentState;
pub use preview::{PreviewData, PreviewState};
use crate::app::actions::ActionContext;
use crate::config::Config;
use crate::keymap::{Action, Keymap, SystemAction};
use crate::ui::overlays::OverlayStack;
use crate::worker::{WorkerResponse, WorkerTask, start_worker};
use crossbeam_channel::{Receiver, Sender, unbounded};
use crossterm::event::KeyEvent;
use std::sync::Arc;
use std::time::Instant;
pub enum KeypressResult {
Continue,
Consumed,
Quit,
OpenedEditor,
}
#[derive(Debug, Clone, Copy)]
pub struct LayoutMetrics {
pub parent_width: usize,
pub main_width: usize,
pub preview_width: usize,
pub preview_height: usize,
}
impl Default for LayoutMetrics {
fn default() -> Self {
Self {
parent_width: 20,
main_width: 40,
preview_width: 40,
preview_height: 50,
}
}
}
pub struct AppState<'a> {
config: &'a Config,
keymap: Keymap,
metrics: LayoutMetrics,
nav: NavState,
actions: ActionContext,
preview: PreviewState,
parent: ParentState,
worker_tx: Sender<WorkerTask>,
response_rx: Receiver<WorkerResponse>,
is_loading: bool,
notification_time: Option<Instant>,
overlays: OverlayStack,
}
impl<'a> AppState<'a> {
pub fn new(config: &'a Config) -> std::io::Result<Self> {
let (worker_tx, task_rx) = unbounded::<WorkerTask>();
let (res_tx, response_rx) = unbounded::<WorkerResponse>();
let current_dir = std::env::current_dir()?;
start_worker(task_rx, res_tx);
let mut app = Self {
config,
keymap: Keymap::from_config(config),
metrics: LayoutMetrics::default(),
nav: NavState::new(current_dir),
actions: ActionContext::default(),
preview: PreviewState::default(),
parent: ParentState::default(),
worker_tx,
response_rx,
is_loading: false,
notification_time: None,
overlays: OverlayStack::new(),
};
app.request_dir_load(None);
app.request_parent_content();
Ok(app)
}
pub fn config(&self) -> &Config {
self.config
}
pub fn metrics(&self) -> &LayoutMetrics {
&self.metrics
}
pub fn metrics_mut(&mut self) -> &mut LayoutMetrics {
&mut self.metrics
}
pub fn nav(&self) -> &NavState {
&self.nav
}
pub fn actions(&self) -> &ActionContext {
&self.actions
}
pub fn preview(&self) -> &PreviewState {
&self.preview
}
pub fn parent(&self) -> &ParentState {
&self.parent
}
pub fn notification_time(&self) -> &Option<Instant> {
&self.notification_time
}
pub fn overlays(&self) -> &OverlayStack {
&self.overlays
}
pub fn overlays_mut(&mut self) -> &mut OverlayStack {
&mut self.overlays
}
pub fn visible_selected(&self) -> Option<usize> {
if self.nav.entries().is_empty() {
None
} else {
Some(self.nav.selected_idx())
}
}
pub fn has_visible_entries(&self) -> bool {
!self.nav.entries().is_empty()
}
pub fn tick(&mut self) -> bool {
let mut changed = false;
if self.preview.should_trigger() {
self.request_preview();
changed = true;
}
let current_selection_path = self
.nav
.selected_entry()
.map(|entry| self.nav.current_dir().join(entry.name()));
while let Ok(response) = self.response_rx.try_recv() {
changed = true;
match response {
WorkerResponse::DirectoryLoaded {
path,
entries,
focus,
request_id,
} => {
if request_id == self.nav.request_id() && path == self.nav.current_dir() {
self.nav.update_from_worker(path, entries, focus);
self.is_loading = false;
self.request_preview();
self.request_parent_content();
self.refresh_show_info_if_open();
}
else if request_id == self.preview.request_id() {
if current_selection_path.as_ref() == Some(&path) {
self.preview.update_from_entries(entries, request_id);
let pos = current_selection_path
.as_ref()
.and_then(|p| self.nav.get_position().get(p))
.copied()
.unwrap_or(0);
self.preview.set_selected_idx(pos);
}
}
else if request_id == self.parent.request_id() {
let current_name = self
.nav
.current_dir()
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
self.parent
.update_from_entries(entries, ¤t_name, request_id);
}
}
WorkerResponse::PreviewLoaded { lines, request_id } => {
if request_id == self.preview.request_id() {
self.preview.update_content(lines, request_id);
}
}
WorkerResponse::OperationComplete {
message: _,
request_id: _,
need_reload,
focus,
} => {
if need_reload {
self.request_dir_load(focus);
self.request_parent_content();
}
}
WorkerResponse::Error(e) => {
self.preview.set_error(e);
}
}
}
changed
}
pub fn handle_keypress(&mut self, key: KeyEvent) -> KeypressResult {
if self.actions.is_input_mode() {
return self.handle_input_mode(key);
}
if let Some(action) = self.keymap.lookup(key) {
match action {
Action::System(SystemAction::Quit) => return KeypressResult::Quit,
Action::Nav(nav_act) => return self.handle_nav_action(nav_act),
Action::File(file_act) => return self.handle_file_action(file_act),
}
}
KeypressResult::Continue
}
pub fn request_dir_load(&mut self, focus: Option<std::ffi::OsString>) {
self.is_loading = true;
let request_id = self.nav.prepare_new_request();
let _ = self.worker_tx.send(WorkerTask::LoadDirectory {
path: self.nav.current_dir().to_path_buf(),
focus,
dirs_first: self.config.dirs_first(),
show_hidden: self.config.show_hidden(),
show_system: self.config.show_system(),
case_insensitive: self.config.case_insensitive(),
always_show: Arc::clone(self.config.always_show()),
pane_width: self.metrics.main_width,
request_id,
});
}
pub fn request_preview(&mut self) {
if let Some(entry) = self.nav.selected_shown_entry() {
let path = self.nav.current_dir().join(entry.name());
let req_id = self.preview.prepare_new_request(path.clone());
if entry.is_dir() {
let _ = self.worker_tx.send(WorkerTask::LoadDirectory {
path,
focus: None,
dirs_first: self.config.dirs_first(),
show_hidden: self.config.show_hidden(),
show_system: self.config.show_system(),
case_insensitive: self.config.case_insensitive(),
always_show: Arc::clone(self.config.always_show()),
pane_width: self.metrics.preview_width,
request_id: req_id,
});
} else {
let _ = self.worker_tx.send(WorkerTask::LoadPreview {
path,
max_lines: self.metrics.preview_height,
pane_width: self.metrics.preview_width,
request_id: req_id,
});
}
}
}
pub fn request_parent_content(&mut self) {
if let Some(parent_path) = self.nav.current_dir().parent() {
let parent_path_buf = parent_path.to_path_buf();
if self.parent.should_request(&parent_path_buf) {
let req_id = self.parent.prepare_new_request(parent_path_buf.clone());
let _ = self.worker_tx.send(WorkerTask::LoadDirectory {
path: parent_path_buf,
focus: None,
dirs_first: self.config.dirs_first(),
show_hidden: self.config.show_hidden(),
show_system: self.config.show_system(),
case_insensitive: self.config.case_insensitive(),
always_show: Arc::clone(self.config.always_show()),
pane_width: self.metrics.parent_width,
request_id: req_id,
});
}
} else {
self.parent.clear();
}
}
}