diff options
-rw-r--r-- | src/interactive/app/eventloop.rs | 125 | ||||
-rw-r--r-- | src/interactive/app/handlers.rs | 305 | ||||
-rw-r--r-- | src/traverse.rs | 4 |
3 files changed, 306 insertions, 128 deletions
diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index c81960c..7dbe9f3 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -37,7 +37,113 @@ pub struct AppState { pub bookmarks: BTreeMap<TreeIndex, TreeIndex>, } -impl AppState {} +impl AppState { + pub fn draw<B>( + &mut self, + window: &mut MainWindow, + traversal: &Traversal, + display: DisplayOptions, + terminal: &mut Terminal<B>, + ) -> Result<(), Error> + where + B: Backend, + { + let props = MainWindowProps { + traversal: &traversal, + display, + state: &self, + }; + draw_window(window, props, terminal) + } + + pub fn process_events<B>( + &mut self, + window: &mut MainWindow, + traversal: &mut Traversal, + mut display: DisplayOptions, + mut terminal: Terminal<B>, + keys: impl Iterator<Item = Result<Key, io::Error>>, + ) -> Result<WalkResult, Error> + where + B: Backend, + { + use termion::event::Key::*; + use FocussedPane::*; + + fn exit_now<B: Backend>(terminal: Terminal<B>) -> ! { + drop(terminal); + io::stdout().flush().ok(); + // Exit 'quickly' to avoid having to wait for all memory to be freed by us. + // Let the OS do it - we have nothing to lose, literally. + std::process::exit(0); + } + + self.draw(window, traversal, display, &mut 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') => exit_now(terminal), + Char('q') | Esc => match self.focussed { + Main => exit_now(terminal), + 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, &mut 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, &mut terminal)?; + } + Ok(WalkResult { + num_errors: traversal.io_errors, + }) + } +} + +pub fn draw_window<B>( + window: &mut MainWindow, + props: MainWindowProps, + terminal: &mut Terminal<B>, +) -> 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 { @@ -48,19 +154,6 @@ pub struct TerminalApp { } impl TerminalApp { - pub fn draw_window<B>( - window: &mut MainWindow, - props: MainWindowProps, - terminal: &mut Terminal<B>, - ) -> Result<(), Error> - where - B: Backend, - { - let area = terminal.pre_render()?; - window.render(props, area, terminal.current_buffer_mut()); - terminal.post_render()?; - Ok(()) - } pub fn draw<B>(&mut self, terminal: &mut Terminal<B>) -> Result<(), Error> where B: Backend, @@ -70,7 +163,7 @@ impl TerminalApp { display: self.display, state: &self.state, }; - Self::draw_window(&mut self.window, props, terminal) + draw_window(&mut self.window, props, terminal) } pub fn process_events<B>( &mut self, @@ -165,7 +258,7 @@ impl TerminalApp { display: display_options, state: &state, }; - Self::draw_window(&mut window, props, terminal) + draw_window(&mut window, props, terminal) })?; let sorting = Default::default(); diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index 103db4f..8c0df23 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -1,9 +1,10 @@ +use crate::interactive::widgets::MainWindow; use crate::interactive::{ app::{FocussedPane::*, TerminalApp}, path_of, sorted_entries, widgets::MarkMode, widgets::{HelpPane, MarkPane}, - AppState, EntryDataBundle, + AppState, DisplayOptions, EntryDataBundle, }; use dua::traverse::{Traversal, TreeIndex}; use itertools::Itertools; @@ -39,6 +40,11 @@ impl AppState { } } + pub fn exit_node_with_traversal(&mut self, traversal: &Traversal) { + let entries = self.entries_for_exit_node(traversal); + self.exit_node(entries); + } + fn entries_for_exit_node( &self, traversal: &Traversal, @@ -82,6 +88,11 @@ impl AppState { }) } + pub fn enter_node_with_traversal(&mut self, traversal: &Traversal) { + let new_entries = self.entries_for_enter_node(traversal); + self.enter_node(new_entries) + } + pub fn enter_node(&mut self, entries_at_selected: Option<(TreeIndex, Vec<EntryDataBundle>)>) { if let Some((previously_selected, new_entries)) = entries_at_selected { match new_entries.get( @@ -130,6 +141,175 @@ impl AppState { self.sorting.toggle_size(); self.entries = sorted_entries(&traversal.tree, self.root, self.sorting); } + + pub fn reset_message(&mut self) { + self.message = None; + } + + pub fn toggle_help_pane(&mut self, window: &mut MainWindow) { + self.focussed = match self.focussed { + Main | Mark => { + window.help_pane = Some(HelpPane::default()); + Help + } + Help => { + window.help_pane = None; + Main + } + } + } + pub fn cycle_focus(&mut self, window: &mut MainWindow) { + if let Some(p) = window.mark_pane.as_mut() { + p.set_focus(false) + }; + self.focussed = match (self.focussed, &window.help_pane, &mut window.mark_pane) { + (Main, Some(_), _) => Help, + (Help, _, Some(ref mut pane)) => { + pane.set_focus(true); + Mark + } + (Help, _, None) => Main, + (Mark, _, _) => Main, + (Main, None, None) => Main, + (Main, None, Some(ref mut pane)) => { + pane.set_focus(true); + Mark + } + }; + } + + pub fn dispatch_to_mark_pane<B>( + &mut self, + key: Key, + window: &mut MainWindow, + traversal: &mut Traversal, + display: DisplayOptions, + terminal: &mut Terminal<B>, + ) where + B: Backend, + { + let res = window.mark_pane.take().and_then(|p| p.key(key)); + window.mark_pane = match res { + Some((pane, mode)) => match mode { + Some(MarkMode::Delete) => { + self.message = Some("Deleting entries...".to_string()); + let mut entries_deleted = 0; + let res = pane.iterate_deletable_items(|mut pane, entry_to_delete| { + window.mark_pane = Some(pane); + self.draw(window, traversal, display, terminal).ok(); + pane = window.mark_pane.take().expect("option to be filled"); + match self.delete_entry(entry_to_delete, traversal) { + Ok(ed) => { + entries_deleted += ed; + self.message = + Some(format!("Deleted {} entries...", entries_deleted)); + Ok(pane) + } + Err(c) => Err((pane, c)), + } + }); + self.message = None; + res + } + None => Some(pane), + }, + None => None, + }; + if window.mark_pane.is_none() { + self.focussed = Main; + } + } + + pub fn delete_entry( + &mut self, + index: TreeIndex, + traversal: &mut Traversal, + ) -> Result<usize, usize> { + let mut entries_deleted = 0; + if let Some(_entry) = traversal.tree.node_weight(index) { + let path_to_delete = path_of(&traversal.tree, index); + delete_directory_recursively(path_to_delete)?; + let parent_idx = traversal + .tree + .neighbors_directed(index, Direction::Incoming) + .next() + .expect("us being unable to delete the root index"); + let mut bfs = Bfs::new(&traversal.tree, index); + while let Some(nx) = bfs.next(&traversal.tree) { + traversal.tree.remove_node(nx); + traversal.entries_traversed -= 1; + entries_deleted += 1; + } + self.entries = sorted_entries(&traversal.tree, self.root, self.sorting); + if traversal.tree.node_weight(self.root).is_none() { + self.set_root(traversal.root_index, traversal); + } + if self + .selected + .and_then(|selected| self.entries.iter().find(|e| e.index == selected)) + .is_none() + { + self.selected = self.entries.get(0).map(|e| e.index); + } + self.recompute_sizes_recursively(parent_idx, traversal); + } + Ok(entries_deleted) + } + + fn set_root(&mut self, root: TreeIndex, traversal: &Traversal) { + self.root = root; + self.entries = sorted_entries(&traversal.tree, root, self.sorting); + } + + fn recompute_sizes_recursively(&mut self, mut index: TreeIndex, traversal: &mut Traversal) { + loop { + traversal + .tree + .node_weight_mut(index) + .expect("valid index") + .size = traversal + .tree + .neighbors_directed(index, Direction::Outgoing) + .filter_map(|idx| traversal.tree.node_weight(idx).map(|w| w.size)) + .sum(); + match traversal + .tree + .neighbors_directed(index, Direction::Incoming) + .next() + { + None => break, + Some(parent) => index = parent, + } + } + traversal.total_bytes = traversal + .tree + .node_weight(traversal.root_index) + .map(|w| w.size); + } + + pub fn mark_entry( + &mut self, + advance_cursor: bool, + window: &mut MainWindow, + traversal: &Traversal, + ) { + if let Some(index) = self.selected { + let is_dir = self + .entries + .iter() + .find(|e| e.index == index) + .unwrap() + .is_dir; + if let Some(pane) = window.mark_pane.take() { + window.mark_pane = pane.toggle_index(index, &traversal.tree, is_dir); + } else { + window.mark_pane = MarkPane::default().toggle_index(index, &traversal.tree, is_dir) + } + }; + if advance_cursor { + self.change_entry_selection(CursorDirection::Down) + } + } } impl TerminalApp { @@ -171,7 +351,7 @@ impl TerminalApp { } pub fn reset_message(&mut self) { - self.state.message = None; + self.state.reset_message() } pub fn open_that(&self) { @@ -197,129 +377,34 @@ impl TerminalApp { } pub fn mark_entry(&mut self, advance_cursor: bool) { - if let Some(index) = self.state.selected { - let is_dir = self - .state - .entries - .iter() - .find(|e| e.index == index) - .unwrap() - .is_dir; - if let Some(pane) = self.window.mark_pane.take() { - self.window.mark_pane = pane.toggle_index(index, &self.traversal.tree, is_dir); - } else { - self.window.mark_pane = - MarkPane::default().toggle_index(index, &self.traversal.tree, is_dir) - } - }; - if advance_cursor { - self.change_entry_selection(CursorDirection::Down) - } + self.state + .mark_entry(advance_cursor, &mut self.window, &self.traversal) } fn set_root(&mut self, root: TreeIndex) { - self.state.root = root; - self.state.entries = sorted_entries(&self.traversal.tree, root, self.state.sorting); + self.state.set_root(root, &self.traversal); } pub fn delete_entry(&mut self, index: TreeIndex) -> Result<usize, usize> { - let mut entries_deleted = 0; - if let Some(_entry) = self.traversal.tree.node_weight(index) { - let path_to_delete = path_of(&self.traversal.tree, index); - delete_directory_recursively(path_to_delete)?; - let parent_idx = self - .traversal - .tree - .neighbors_directed(index, Direction::Incoming) - .next() - .expect("us being unable to delete the root index"); - let mut bfs = Bfs::new(&self.traversal.tree, index); - while let Some(nx) = bfs.next(&self.traversal.tree) { - self.traversal.tree.remove_node(nx); - self.traversal.entries_traversed -= 1; - entries_deleted += 1; - } - self.state.entries = - sorted_entries(&self.traversal.tree, self.state.root, self.state.sorting); - if self.traversal.tree.node_weight(self.state.root).is_none() { - self.set_root(self.traversal.root_index); - } - if self - .state - .selected - .and_then(|selected| self.state.entries.iter().find(|e| e.index == selected)) - .is_none() - { - self.state.selected = self.state.entries.get(0).map(|e| e.index); - } - self.recompute_sizes_recursively(parent_idx); - } - Ok(entries_deleted) + self.state.delete_entry(index, &mut self.traversal) } fn recompute_sizes_recursively(&mut self, mut index: TreeIndex) { - loop { - self.traversal - .tree - .node_weight_mut(index) - .expect("valid index") - .size = self - .traversal - .tree - .neighbors_directed(index, Direction::Outgoing) - .filter_map(|idx| self.traversal.tree.node_weight(idx).map(|w| w.size)) - .sum(); - match self - .traversal - .tree - .neighbors_directed(index, Direction::Incoming) - .next() - { - None => break, - Some(parent) => index = parent, - } - } - self.traversal.total_bytes = self - .traversal - .tree - .node_weight(self.traversal.root_index) - .map(|w| w.size); + self.state + .recompute_sizes_recursively(index, &mut self.traversal) } pub fn dispatch_to_mark_pane<B>(&mut self, key: Key, terminal: &mut Terminal<B>) where B: Backend, { - let res = self.window.mark_pane.take().and_then(|p| p.key(key)); - self.window.mark_pane = match res { - Some((pane, mode)) => match mode { - Some(MarkMode::Delete) => { - self.state.message = Some("Deleting entries...".to_string()); - let mut entries_deleted = 0; - let res = pane.iterate_deletable_items(|mut pane, entry_to_delete| { - self.window.mark_pane = Some(pane); - self.draw(terminal).ok(); - pane = self.window.mark_pane.take().expect("option to be filled"); - match self.delete_entry(entry_to_delete) { - Ok(ed) => { - entries_deleted += ed; - self.state.message = - Some(format!("Deleted {} entries...", entries_deleted)); - Ok(pane) - } - Err(c) => Err((pane, c)), - } - }); - self.state.message = None; - res - } - None => Some(pane), - }, - None => None, - }; - if self.window.mark_pane.is_none() { - self.state.focussed = Main; - } + self.state.dispatch_to_mark_pane( + key, + &mut self.window, + &mut self.traversal, + self.display, + terminal, + ); } } diff --git a/src/traverse.rs b/src/traverse.rs index ab4eff9..13e4761 100644 --- a/src/traverse.rs +++ b/src/traverse.rs @@ -37,7 +37,7 @@ impl Traversal { pub fn from_walk( mut walk_options: WalkOptions, input: Vec<PathBuf>, - mut update: impl FnMut(&Traversal) -> Result<(), Error>, + mut update: impl FnMut(&mut Traversal) -> Result<(), Error>, ) -> Result<Traversal, Error> { fn set_size_or_panic(tree: &mut Tree, node_idx: TreeIndex, current_size_at_depth: u64) { tree.node_weight_mut(node_idx) @@ -178,7 +178,7 @@ impl Traversal { last_seen_eid = eid; last_checked = now; - update(&t)?; + update(&mut t)?; } } } |