use crate::interactive::{ sorted_entries, widgets::{MainWindow, MainWindowProps}, ByteVisualization, CursorDirection, DisplayOptions, EntryDataBundle, SortMode, }; use dua::{ traverse::{Traversal, TreeIndex}, WalkOptions, WalkResult, }; use failure::Error; use std::{collections::BTreeMap, io, path::PathBuf}; use termion::{event::Key, input::TermRead}; use tui::backend::Backend; use tui_react::Terminal; #[derive(Copy, Clone)] pub enum FocussedPane { Main, Help, Mark, } impl Default for FocussedPane { fn default() -> Self { FocussedPane::Main } } #[derive(Default)] pub struct AppState { pub root: TreeIndex, pub selected: Option, pub entries: Vec, pub sorting: SortMode, pub message: Option, pub focussed: FocussedPane, pub bookmarks: BTreeMap, } impl AppState { pub fn draw( &mut self, window: &mut MainWindow, traversal: &Traversal, display: DisplayOptions, terminal: &mut Terminal, ) -> Result<(), Error> where B: Backend, { let props = MainWindowProps { traversal: &traversal, display, state: &self, }; draw_window(window, props, terminal) } pub fn process_events( &mut self, window: &mut MainWindow, traversal: &mut Traversal, display: &mut DisplayOptions, terminal: &mut Terminal, keys: impl Iterator>, ) -> Result where B: Backend, { use termion::event::Key::*; use FocussedPane::*; self.draw(window, traversal, display.clone(), terminal)?; for key in keys.filter_map(Result::ok) { match key { Char('?') => self.toggle_help_pane(window), Char('\t') => { self.cycle_focus(window); } Ctrl('c') => break, Char('q') | Esc => match self.focussed { Main => break, Mark => self.focussed = Main, Help => { self.focussed = Main; window.help_pane = None } }, _ => {} } match self.focussed { FocussedPane::Mark => { self.dispatch_to_mark_pane(key, window, traversal, display.clone(), terminal) } FocussedPane::Help => { window.help_pane.as_mut().expect("help pane").key(key); } FocussedPane::Main => match key { Char('O') => self.open_that(traversal), Char(' ') => self.mark_entry(false, window, traversal), Char('d') => self.mark_entry(true, window, traversal), Char('u') | Char('h') | Backspace | Left => { self.exit_node_with_traversal(traversal) } Char('o') | Char('l') | Char('\n') | Right => { self.enter_node_with_traversal(traversal) } Ctrl('u') | PageUp => self.change_entry_selection(CursorDirection::PageUp), Char('k') | Up => self.change_entry_selection(CursorDirection::Up), Char('j') | Down => self.change_entry_selection(CursorDirection::Down), Ctrl('d') | PageDown => self.change_entry_selection(CursorDirection::PageDown), Char('s') => self.cycle_sorting(traversal), Char('g') => display.byte_vis.cycle(), _ => {} }, }; self.draw(window, traversal, display.clone(), terminal)?; } Ok(WalkResult { num_errors: traversal.io_errors, }) } } pub fn draw_window( window: &mut MainWindow, props: MainWindowProps, terminal: &mut Terminal, ) -> Result<(), Error> where B: Backend, { let area = terminal.pre_render()?; window.render(props, area, terminal.current_buffer_mut()); terminal.post_render()?; Ok(()) } /// State and methods representing the interactive disk usage analyser for the terminal pub struct TerminalApp { pub traversal: Traversal, pub display: DisplayOptions, pub state: AppState, pub window: MainWindow, } impl TerminalApp { pub fn process_events( &mut self, terminal: &mut Terminal, keys: impl Iterator>, ) -> Result where B: Backend, { self.state.process_events( &mut self.window, &mut self.traversal, &mut self.display, terminal, keys, ) } pub fn initialize( terminal: &mut Terminal, options: WalkOptions, input: Vec, mode: Interaction, ) -> Result where B: Backend, { terminal.hide_cursor()?; terminal.clear()?; let mut display: DisplayOptions = options.clone().into(); display.byte_vis = ByteVisualization::Bar; let mut window = MainWindow::default(); let (keys_tx, keys_rx) = crossbeam_channel::unbounded(); match mode { Interaction::None => drop(keys_tx), Interaction::Full => drop(std::thread::spawn(move || { let keys = std::io::stdin().keys(); for key in keys { if let Err(_) = keys_tx.try_send(key) { break; } } })), } let fetch_buffered_key_events = || { let mut keys = Vec::new(); while let Ok(key) = keys_rx.try_recv() { keys.push(key); } keys }; let mut state = None; let traversal = Traversal::from_walk(options, input, |traversal| { let s = match state.as_mut() { Some(s) => s, None => { state = Some({ let sorting = Default::default(); AppState { root: traversal.root_index, sorting, message: Some("-> scanning <-".into()), entries: sorted_entries(&traversal.tree, traversal.root_index, sorting), ..Default::default() } }); state.as_mut().expect("state to be present, we just set it") } }; s.process_events( &mut window, traversal, &mut display, terminal, fetch_buffered_key_events().into_iter(), )?; Ok(()) })?; display.byte_vis = ByteVisualization::PercentageAndBar; Ok(TerminalApp { state: { let mut s = state.unwrap_or_else(|| { let sorting = Default::default(); let root = traversal.root_index; let entries = sorted_entries(&traversal.tree, root, sorting); AppState { root, sorting, entries, ..Default::default() } }); s.reset_message(); s.entries = sorted_entries(&traversal.tree, s.root, s.sorting); s.selected = s.selected.or_else(|| s.entries.get(0).map(|b| b.index)); s }, display, traversal, window, }) } } pub enum Interaction { Full, #[allow(dead_code)] None, }