From 50438ef584d5f2ade0a0501ebca151c99893580f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 5 Jun 2019 08:13:16 +0530 Subject: move sorted_entries closer to where it is used --- src/common.rs | 39 ---- src/interactive/app.rs | 375 ------------------------------------ src/interactive/app/common.rs | 40 ++++ src/interactive/app/eventloop.rs | 379 +++++++++++++++++++++++++++++++++++++ src/interactive/app/mod.rs | 5 + src/interactive/widgets/entries.rs | 9 +- 6 files changed, 427 insertions(+), 420 deletions(-) delete mode 100644 src/interactive/app.rs create mode 100644 src/interactive/app/common.rs create mode 100644 src/interactive/app/eventloop.rs create mode 100644 src/interactive/app/mod.rs (limited to 'src') diff --git a/src/common.rs b/src/common.rs index 2e25e9e..f9fda92 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,32 +1,8 @@ use crate::traverse::{EntryData, Tree, TreeIndex}; use byte_unit::{n_gb_bytes, n_gib_bytes, n_mb_bytes, n_mib_bytes, ByteUnit}; -use itertools::Itertools; use jwalk::WalkDir; -use petgraph::Direction; use std::{fmt, path::Path, path::PathBuf}; -#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] -pub enum SortMode { - SizeDescending, - SizeAscending, -} - -impl SortMode { - pub fn toggle_size(&mut self) { - use SortMode::*; - *self = match self { - SizeAscending => SizeDescending, - SizeDescending => SizeAscending, - } - } -} - -impl Default for SortMode { - fn default() -> Self { - SortMode::SizeDescending - } -} - pub fn path_of(tree: &Tree, mut node_idx: TreeIndex) -> PathBuf { const THE_ROOT: usize = 1; let mut entries = Vec::new(); @@ -55,21 +31,6 @@ pub(crate) fn get_size_or_panic(tree: &Tree, node_idx: TreeIndex) -> u64 { get_entry_or_panic(tree, node_idx).size } -pub fn sorted_entries( - tree: &Tree, - node_idx: TreeIndex, - sorting: SortMode, -) -> Vec<(TreeIndex, &EntryData)> { - use SortMode::*; - tree.neighbors_directed(node_idx, Direction::Outgoing) - .filter_map(|idx| tree.node_weight(idx).map(|w| (idx, w))) - .sorted_by(|(_, l), (_, r)| match sorting { - SizeDescending => r.size.cmp(&l.size), - SizeAscending => l.size.cmp(&r.size), - }) - .collect() -} - /// Specifies a way to format bytes #[derive(Clone, Copy)] pub enum ByteFormat { diff --git a/src/interactive/app.rs b/src/interactive/app.rs deleted file mode 100644 index b939074..0000000 --- a/src/interactive/app.rs +++ /dev/null @@ -1,375 +0,0 @@ -use crate::{ - interactive::widgets::{DrawState, HelpPaneState, MainWindow}, - ByteFormat, -}; -use dua::{ - path_of, sorted_entries, - traverse::{Traversal, TreeIndex}, - SortMode, WalkOptions, WalkResult, -}; -use failure::Error; -use itertools::Itertools; -use petgraph::Direction; -use std::{fmt, io, path::PathBuf}; -use termion::input::{Keys, TermReadEventsAndRaw}; -use tui::{backend::Backend, widgets::Widget, Terminal}; - -#[derive(Clone, Copy)] -pub enum ByteVisualization { - Percentage, - Bar, - LongBar, - PercentageAndBar, -} - -pub struct DisplayByteVisualization { - format: ByteVisualization, - percentage: f32, -} - -impl Default for ByteVisualization { - fn default() -> Self { - ByteVisualization::PercentageAndBar - } -} - -impl ByteVisualization { - pub fn cycle(&mut self) { - use ByteVisualization::*; - *self = match self { - Bar => LongBar, - LongBar => PercentageAndBar, - PercentageAndBar => Percentage, - Percentage => Bar, - } - } - pub fn display(&self, percentage: f32) -> DisplayByteVisualization { - DisplayByteVisualization { - format: *self, - percentage, - } - } -} - -impl fmt::Display for DisplayByteVisualization { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - use ByteVisualization::*; - let Self { format, percentage } = self; - - const BAR_SIZE: usize = 10; - match format { - Percentage => Self::make_percentage(f, percentage), - PercentageAndBar => { - Self::make_percentage(f, percentage)?; - f.write_str(" ")?; - Self::make_bar(f, percentage, BAR_SIZE) - } - Bar => Self::make_bar(f, percentage, BAR_SIZE), - LongBar => Self::make_bar(f, percentage, 20), - } - } -} - -impl DisplayByteVisualization { - fn make_bar(f: &mut fmt::Formatter, percentage: &f32, length: usize) -> Result<(), fmt::Error> { - let block_length = (length as f32 * percentage).round() as usize; - for _ in 0..block_length { - f.write_str(tui::symbols::block::FULL)?; - } - for _ in 0..length - block_length { - f.write_str(" ")?; - } - Ok(()) - } - fn make_percentage(f: &mut fmt::Formatter, percentage: &f32) -> Result<(), fmt::Error> { - write!(f, " {:>5.02}% ", percentage * 100.0) - } -} - -/// Options to configure how we display things -#[derive(Clone, Copy)] -pub struct DisplayOptions { - pub byte_format: ByteFormat, - pub byte_vis: ByteVisualization, -} - -impl From for DisplayOptions { - fn from(WalkOptions { byte_format, .. }: WalkOptions) -> Self { - DisplayOptions { - byte_format, - byte_vis: ByteVisualization::default(), - } - } -} - -#[derive(Copy, Clone)] -pub enum FocussedPane { - Main, - Help, -} - -impl Default for FocussedPane { - fn default() -> Self { - FocussedPane::Main - } -} - -#[derive(Default)] -pub struct AppState { - pub root: TreeIndex, - pub selected: Option, - pub sorting: SortMode, - pub message: Option, - pub help_pane: Option, - pub focussed: FocussedPane, -} - -/// 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 draw_state: DrawState, -} - -enum CursorDirection { - PageDown, - Down, - Up, - PageUp, -} - -impl TerminalApp { - fn draw(&mut self, terminal: &mut Terminal) -> Result<(), Error> - where - B: Backend, - { - let Self { - traversal, - display, - state, - ref mut draw_state, - } = self; - - terminal.draw(|mut f| { - let full_screen = f.size(); - MainWindow { - traversal, - display: *display, - state: &state, - draw_state, - } - .render(&mut f, full_screen) - })?; - - Ok(()) - } - pub fn process_events( - &mut self, - terminal: &mut Terminal, - keys: Keys, - ) -> Result - where - B: Backend, - R: io::Read + TermReadEventsAndRaw, - { - use termion::event::Key::{Char, Ctrl}; - use FocussedPane::*; - - self.draw(terminal)?; - for key in keys.filter_map(Result::ok) { - self.update_message(); - match key { - Char('?') => self.toggle_help_pane(), - Char('\t') => { - self.cycle_focus(); - } - Ctrl('c') => break, - Char('q') => match self.state.focussed { - Main => break, - Help => { - self.state.focussed = Main; - self.state.help_pane = None - } - }, - _ => {} - } - - match self.state.focussed { - FocussedPane::Help => match key { - Ctrl('u') => self.scroll_help(CursorDirection::PageUp), - Char('k') => self.scroll_help(CursorDirection::Up), - Char('j') => self.scroll_help(CursorDirection::Down), - Ctrl('d') => self.scroll_help(CursorDirection::PageDown), - _ => {} - }, - FocussedPane::Main => match key { - Char('O') => self.open_that(), - Char('u') => self.exit_node(), - Char('o') => self.enter_node(), - Ctrl('u') => self.change_entry_selection(CursorDirection::PageUp), - Char('k') => self.change_entry_selection(CursorDirection::Up), - Char('j') => self.change_entry_selection(CursorDirection::Down), - Ctrl('d') => self.change_entry_selection(CursorDirection::PageDown), - Char('s') => self.state.sorting.toggle_size(), - Char('g') => self.display.byte_vis.cycle(), - _ => {} - }, - }; - self.draw(terminal)?; - } - Ok(WalkResult { - num_errors: self.traversal.io_errors, - }) - } - - fn cycle_focus(&mut self) { - use FocussedPane::*; - self.state.focussed = match (self.state.focussed, self.state.help_pane) { - (Main, Some(_)) => Help, - (Help, _) => Main, - _ => Main, - }; - } - - fn toggle_help_pane(&mut self) { - use FocussedPane::*; - self.state.focussed = match self.state.focussed { - Main => { - self.state.help_pane = Some(HelpPaneState::default()); - Help - } - Help => { - self.state.help_pane = None; - Main - } - } - } - - fn update_message(&mut self) { - self.state.message = None; - } - - fn exit_node(&mut self) { - match self - .traversal - .tree - .neighbors_directed(self.state.root, Direction::Incoming) - .next() - { - Some(parent_idx) => { - self.state.root = parent_idx; - self.state.selected = - sorted_entries(&self.traversal.tree, parent_idx, self.state.sorting) - .get(0) - .map(|(idx, _)| *idx); - } - None => self.state.message = Some("Top level reached".into()), - } - } - - fn open_that(&mut self) { - match self.state.selected { - Some(ref idx) => { - open::that(path_of(&self.traversal.tree, *idx)).ok(); - } - None => {} - } - } - - fn enter_node(&mut self) { - if let Some(new_root) = self.state.selected { - let entries = sorted_entries(&self.traversal.tree, new_root, self.state.sorting); - match entries.get(0) { - Some((next_selection, _)) => { - self.state.root = new_root; - self.state.selected = Some(*next_selection); - } - None => self.state.message = Some("Entry is a file or an empty directory".into()), - } - } - } - - fn scroll_help(&mut self, direction: CursorDirection) { - use CursorDirection::*; - let scroll = self.draw_state.help_scroll; - self.draw_state.help_scroll = match direction { - Down => scroll.saturating_add(1), - Up => scroll.saturating_sub(1), - PageDown => scroll.saturating_add(10), - PageUp => scroll.saturating_sub(10), - }; - } - - fn change_entry_selection(&mut self, direction: CursorDirection) { - let entries = sorted_entries(&self.traversal.tree, self.state.root, self.state.sorting); - let next_selected_pos = match self.state.selected { - Some(ref selected) => entries - .iter() - .find_position(|(idx, _)| *idx == *selected) - .map(|(idx, _)| match direction { - CursorDirection::PageDown => idx.saturating_add(10), - CursorDirection::Down => idx.saturating_add(1), - CursorDirection::Up => idx.saturating_sub(1), - CursorDirection::PageUp => idx.saturating_sub(10), - }) - .unwrap_or(0), - None => 0, - }; - self.state.selected = entries - .get(next_selected_pos) - .or(entries.last()) - .map(|(idx, _)| *idx) - .or(self.state.selected) - } - - pub fn initialize( - terminal: &mut Terminal, - options: WalkOptions, - input: Vec, - ) -> Result - where - B: Backend, - { - terminal.hide_cursor()?; - let mut display_options: DisplayOptions = options.clone().into(); - display_options.byte_vis = ByteVisualization::Bar; - let traversal = Traversal::from_walk(options, input, move |traversal| { - terminal.draw(|mut f| { - let full_screen = f.size(); - let state = AppState { - root: traversal.root_index, - sorting: Default::default(), - message: Some("-> scanning <-".into()), - ..Default::default() - }; - MainWindow { - traversal, - display: display_options, - state: &state, - draw_state: &mut Default::default(), - } - .render(&mut f, full_screen) - })?; - Ok(()) - })?; - - let sorting = Default::default(); - let root = traversal.root_index; - let selected = sorted_entries(&traversal.tree, root, sorting) - .get(0) - .map(|(idx, _)| *idx); - display_options.byte_vis = ByteVisualization::PercentageAndBar; - Ok(TerminalApp { - state: AppState { - root, - sorting, - selected, - ..Default::default() - }, - display: display_options, - traversal, - draw_state: Default::default(), - }) - } -} diff --git a/src/interactive/app/common.rs b/src/interactive/app/common.rs new file mode 100644 index 0000000..10d9f11 --- /dev/null +++ b/src/interactive/app/common.rs @@ -0,0 +1,40 @@ +use dua::traverse::{EntryData, Tree, TreeIndex}; +use itertools::Itertools; +use petgraph::Direction; + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] +pub enum SortMode { + SizeDescending, + SizeAscending, +} + +impl SortMode { + pub fn toggle_size(&mut self) { + use SortMode::*; + *self = match self { + SizeAscending => SizeDescending, + SizeDescending => SizeAscending, + } + } +} + +impl Default for SortMode { + fn default() -> Self { + SortMode::SizeDescending + } +} + +pub fn sorted_entries( + tree: &Tree, + node_idx: TreeIndex, + sorting: SortMode, +) -> Vec<(TreeIndex, &EntryData)> { + use SortMode::*; + tree.neighbors_directed(node_idx, Direction::Outgoing) + .filter_map(|idx| tree.node_weight(idx).map(|w| (idx, w))) + .sorted_by(|(_, l), (_, r)| match sorting { + SizeDescending => r.size.cmp(&l.size), + SizeAscending => l.size.cmp(&r.size), + }) + .collect() +} diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs new file mode 100644 index 0000000..c05ca02 --- /dev/null +++ b/src/interactive/app/eventloop.rs @@ -0,0 +1,379 @@ +use crate::{ + interactive::{ + sorted_entries, + widgets::{DrawState, HelpPaneState, MainWindow}, + SortMode, + }, + ByteFormat, +}; +use dua::{ + path_of, + traverse::{Traversal, TreeIndex}, + WalkOptions, WalkResult, +}; +use failure::Error; +use itertools::Itertools; +use petgraph::Direction; +use std::{fmt, io, path::PathBuf}; +use termion::input::{Keys, TermReadEventsAndRaw}; +use tui::{backend::Backend, widgets::Widget, Terminal}; + +#[derive(Clone, Copy)] +pub enum ByteVisualization { + Percentage, + Bar, + LongBar, + PercentageAndBar, +} + +pub struct DisplayByteVisualization { + format: ByteVisualization, + percentage: f32, +} + +impl Default for ByteVisualization { + fn default() -> Self { + ByteVisualization::PercentageAndBar + } +} + +impl ByteVisualization { + pub fn cycle(&mut self) { + use ByteVisualization::*; + *self = match self { + Bar => LongBar, + LongBar => PercentageAndBar, + PercentageAndBar => Percentage, + Percentage => Bar, + } + } + pub fn display(&self, percentage: f32) -> DisplayByteVisualization { + DisplayByteVisualization { + format: *self, + percentage, + } + } +} + +impl fmt::Display for DisplayByteVisualization { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use ByteVisualization::*; + let Self { format, percentage } = self; + + const BAR_SIZE: usize = 10; + match format { + Percentage => Self::make_percentage(f, percentage), + PercentageAndBar => { + Self::make_percentage(f, percentage)?; + f.write_str(" ")?; + Self::make_bar(f, percentage, BAR_SIZE) + } + Bar => Self::make_bar(f, percentage, BAR_SIZE), + LongBar => Self::make_bar(f, percentage, 20), + } + } +} + +impl DisplayByteVisualization { + fn make_bar(f: &mut fmt::Formatter, percentage: &f32, length: usize) -> Result<(), fmt::Error> { + let block_length = (length as f32 * percentage).round() as usize; + for _ in 0..block_length { + f.write_str(tui::symbols::block::FULL)?; + } + for _ in 0..length - block_length { + f.write_str(" ")?; + } + Ok(()) + } + fn make_percentage(f: &mut fmt::Formatter, percentage: &f32) -> Result<(), fmt::Error> { + write!(f, " {:>5.02}% ", percentage * 100.0) + } +} + +/// Options to configure how we display things +#[derive(Clone, Copy)] +pub struct DisplayOptions { + pub byte_format: ByteFormat, + pub byte_vis: ByteVisualization, +} + +impl From for DisplayOptions { + fn from(WalkOptions { byte_format, .. }: WalkOptions) -> Self { + DisplayOptions { + byte_format, + byte_vis: ByteVisualization::default(), + } + } +} + +#[derive(Copy, Clone)] +pub enum FocussedPane { + Main, + Help, +} + +impl Default for FocussedPane { + fn default() -> Self { + FocussedPane::Main + } +} + +#[derive(Default)] +pub struct AppState { + pub root: TreeIndex, + pub selected: Option, + pub sorting: SortMode, + pub message: Option, + pub help_pane: Option, + pub focussed: FocussedPane, +} + +/// 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 draw_state: DrawState, +} + +enum CursorDirection { + PageDown, + Down, + Up, + PageUp, +} + +impl TerminalApp { + fn draw(&mut self, terminal: &mut Terminal) -> Result<(), Error> + where + B: Backend, + { + let Self { + traversal, + display, + state, + ref mut draw_state, + } = self; + + terminal.draw(|mut f| { + let full_screen = f.size(); + MainWindow { + traversal, + display: *display, + state: &state, + draw_state, + } + .render(&mut f, full_screen) + })?; + + Ok(()) + } + pub fn process_events( + &mut self, + terminal: &mut Terminal, + keys: Keys, + ) -> Result + where + B: Backend, + R: io::Read + TermReadEventsAndRaw, + { + use termion::event::Key::{Char, Ctrl}; + use FocussedPane::*; + + self.draw(terminal)?; + for key in keys.filter_map(Result::ok) { + self.update_message(); + match key { + Char('?') => self.toggle_help_pane(), + Char('\t') => { + self.cycle_focus(); + } + Ctrl('c') => break, + Char('q') => match self.state.focussed { + Main => break, + Help => { + self.state.focussed = Main; + self.state.help_pane = None + } + }, + _ => {} + } + + match self.state.focussed { + FocussedPane::Help => match key { + Ctrl('u') => self.scroll_help(CursorDirection::PageUp), + Char('k') => self.scroll_help(CursorDirection::Up), + Char('j') => self.scroll_help(CursorDirection::Down), + Ctrl('d') => self.scroll_help(CursorDirection::PageDown), + _ => {} + }, + FocussedPane::Main => match key { + Char('O') => self.open_that(), + Char('u') => self.exit_node(), + Char('o') => self.enter_node(), + Ctrl('u') => self.change_entry_selection(CursorDirection::PageUp), + Char('k') => self.change_entry_selection(CursorDirection::Up), + Char('j') => self.change_entry_selection(CursorDirection::Down), + Ctrl('d') => self.change_entry_selection(CursorDirection::PageDown), + Char('s') => self.state.sorting.toggle_size(), + Char('g') => self.display.byte_vis.cycle(), + _ => {} + }, + }; + self.draw(terminal)?; + } + Ok(WalkResult { + num_errors: self.traversal.io_errors, + }) + } + + fn cycle_focus(&mut self) { + use FocussedPane::*; + self.state.focussed = match (self.state.focussed, self.state.help_pane) { + (Main, Some(_)) => Help, + (Help, _) => Main, + _ => Main, + }; + } + + fn toggle_help_pane(&mut self) { + use FocussedPane::*; + self.state.focussed = match self.state.focussed { + Main => { + self.state.help_pane = Some(HelpPaneState::default()); + Help + } + Help => { + self.state.help_pane = None; + Main + } + } + } + + fn update_message(&mut self) { + self.state.message = None; + } + + fn exit_node(&mut self) { + match self + .traversal + .tree + .neighbors_directed(self.state.root, Direction::Incoming) + .next() + { + Some(parent_idx) => { + self.state.root = parent_idx; + self.state.selected = + sorted_entries(&self.traversal.tree, parent_idx, self.state.sorting) + .get(0) + .map(|(idx, _)| *idx); + } + None => self.state.message = Some("Top level reached".into()), + } + } + + fn open_that(&mut self) { + match self.state.selected { + Some(ref idx) => { + open::that(path_of(&self.traversal.tree, *idx)).ok(); + } + None => {} + } + } + + fn enter_node(&mut self) { + if let Some(new_root) = self.state.selected { + let entries = sorted_entries(&self.traversal.tree, new_root, self.state.sorting); + match entries.get(0) { + Some((next_selection, _)) => { + self.state.root = new_root; + self.state.selected = Some(*next_selection); + } + None => self.state.message = Some("Entry is a file or an empty directory".into()), + } + } + } + + fn scroll_help(&mut self, direction: CursorDirection) { + use CursorDirection::*; + let scroll = self.draw_state.help_scroll; + self.draw_state.help_scroll = match direction { + Down => scroll.saturating_add(1), + Up => scroll.saturating_sub(1), + PageDown => scroll.saturating_add(10), + PageUp => scroll.saturating_sub(10), + }; + } + + fn change_entry_selection(&mut self, direction: CursorDirection) { + let entries = sorted_entries(&self.traversal.tree, self.state.root, self.state.sorting); + let next_selected_pos = match self.state.selected { + Some(ref selected) => entries + .iter() + .find_position(|(idx, _)| *idx == *selected) + .map(|(idx, _)| match direction { + CursorDirection::PageDown => idx.saturating_add(10), + CursorDirection::Down => idx.saturating_add(1), + CursorDirection::Up => idx.saturating_sub(1), + CursorDirection::PageUp => idx.saturating_sub(10), + }) + .unwrap_or(0), + None => 0, + }; + self.state.selected = entries + .get(next_selected_pos) + .or(entries.last()) + .map(|(idx, _)| *idx) + .or(self.state.selected) + } + + pub fn initialize( + terminal: &mut Terminal, + options: WalkOptions, + input: Vec, + ) -> Result + where + B: Backend, + { + terminal.hide_cursor()?; + let mut display_options: DisplayOptions = options.clone().into(); + display_options.byte_vis = ByteVisualization::Bar; + let traversal = Traversal::from_walk(options, input, move |traversal| { + terminal.draw(|mut f| { + let full_screen = f.size(); + let state = AppState { + root: traversal.root_index, + sorting: Default::default(), + message: Some("-> scanning <-".into()), + ..Default::default() + }; + MainWindow { + traversal, + display: display_options, + state: &state, + draw_state: &mut Default::default(), + } + .render(&mut f, full_screen) + })?; + Ok(()) + })?; + + let sorting = Default::default(); + let root = traversal.root_index; + let selected = sorted_entries(&traversal.tree, root, sorting) + .get(0) + .map(|(idx, _)| *idx); + display_options.byte_vis = ByteVisualization::PercentageAndBar; + Ok(TerminalApp { + state: AppState { + root, + sorting, + selected, + ..Default::default() + }, + display: display_options, + traversal, + draw_state: Default::default(), + }) + } +} diff --git a/src/interactive/app/mod.rs b/src/interactive/app/mod.rs new file mode 100644 index 0000000..912120e --- /dev/null +++ b/src/interactive/app/mod.rs @@ -0,0 +1,5 @@ +mod common; +mod eventloop; + +pub use common::*; +pub use eventloop::*; diff --git a/src/interactive/widgets/entries.rs b/src/interactive/widgets/entries.rs index 25c7194..d420f71 100644 --- a/src/interactive/widgets/entries.rs +++ b/src/interactive/widgets/entries.rs @@ -1,12 +1,9 @@ use crate::interactive::{ - widgets::{fill_background_to_right, List, ListState}, - DisplayOptions, -}; -use dua::{ sorted_entries, - traverse::{Tree, TreeIndex}, - SortMode, + widgets::{fill_background_to_right, List, ListState}, + DisplayOptions, SortMode, }; +use dua::traverse::{Tree, TreeIndex}; use itertools::Itertools; use std::path::Path; use tui::{ -- cgit v1.2.3