diff options
-rw-r--r-- | src/interactive/app/eventloop.rs | 8 | ||||
-rw-r--r-- | src/interactive/app/handlers.rs | 33 | ||||
-rw-r--r-- | src/interactive/app_test.rs | 28 | ||||
-rw-r--r-- | src/interactive/widgets/entries.rs | 8 | ||||
-rw-r--r-- | src/interactive/widgets/footer.rs | 6 | ||||
-rw-r--r-- | src/interactive/widgets/main.rs | 47 | ||||
-rw-r--r-- | src/interactive/widgets/mark.rs | 63 |
7 files changed, 104 insertions, 89 deletions
diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index 6227448..adf0955 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -40,7 +40,6 @@ pub struct AppState { pub sorting: SortMode, pub message: Option<String>, pub focussed: FocussedPane, - pub marked: EntryMarkMap, } /// State and methods representing the interactive disk usage analyser for the terminal @@ -110,12 +109,7 @@ impl TerminalApp { } match self.state.focussed { - FocussedPane::Mark => self - .window - .mark_pane - .as_mut() - .expect("mark pane") - .key(key, &self.state.marked), + FocussedPane::Mark => self.window.mark_pane.as_mut().expect("mark pane").key(key), FocussedPane::Help => { self.window.help_pane.as_mut().expect("help pane").key(key); } diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index 11becc1..850ce12 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -1,5 +1,5 @@ use crate::interactive::{ - app::{EntryMark, FocussedPane, TerminalApp}, + app::{FocussedPane, TerminalApp}, sorted_entries, widgets::{HelpPane, MarkPane}, }; @@ -125,29 +125,18 @@ impl TerminalApp { } pub fn mark_entry(&mut self, advance_cursor: bool) { - if let Some(index) = self.state.selected { - // TODO: consider using the Entry::Occupied/Vacant API to remove things - if self.state.marked.get(&index).is_some() { - self.state.marked.remove(&index); - } else { - if let Some(e) = self.state.entries.iter().find(|e| e.index == index) { - self.state.marked.insert( - index, - EntryMark { - size: e.data.size, - path: path_of(&self.traversal.tree, index), - }, - ); - } - } - if self.state.marked.is_empty() { - self.window.mark_pane = None; - } else { - self.window.mark_pane = Some(MarkPane::new(&self.state.marked)); + match (self.state.selected, self.window.mark_pane.take()) { + (Some(index), Some(pane)) => { + self.window.mark_pane = pane.toggle_index(index, &self.traversal.tree); } - if advance_cursor { - self.change_entry_selection(CursorDirection::Down) + (Some(index), None) => { + self.window.mark_pane = + MarkPane::default().toggle_index(index, &self.traversal.tree) } + _ => {} + }; + if advance_cursor { + self.change_entry_selection(CursorDirection::Down) } } } diff --git a/src/interactive/app_test.rs b/src/interactive/app_test.rs index fb413e6..6215c85 100644 --- a/src/interactive/app_test.rs +++ b/src/interactive/app_test.rs @@ -224,9 +224,15 @@ fn simple_user_journey() -> Result<(), Error> { let previously_selected_index = *app.state.selected.as_ref().unwrap(); app.process_events(&mut terminal, b"d".keys())?; { - assert_eq!(1, app.state.marked.len(), "it marks only a single node",); + assert_eq!( + Some(1), + app.window.mark_pane.as_ref().map(|p| p.marked().len()), + "it marks only a single node", + ); assert!( - app.state.marked.contains_key(&previously_selected_index), + app.window.mark_pane.as_ref().map_or(false, |p| p + .marked() + .contains_key(&previously_selected_index)), "it marks the selected node" ); assert_eq!( @@ -241,8 +247,8 @@ fn simple_user_journey() -> Result<(), Error> { app.process_events(&mut terminal, b"d".keys())?; assert_eq!( - 2, - app.state.marked.len(), + Some(2), + app.window.mark_pane.as_ref().map(|p| p.marked().len()), "it marks the currently selected, second node", ); @@ -258,13 +264,15 @@ fn simple_user_journey() -> Result<(), Error> { app.process_events(&mut terminal, b"d".keys())?; assert_eq!( - 1, - app.state.marked.len(), + Some(1), + app.window.mark_pane.as_ref().map(|p| p.marked().len()), "it toggled the previous selected entry off", ); assert!( - app.state.marked.contains_key(&previously_selected_index), + app.window.mark_pane.as_ref().map_or(false, |p| p + .marked() + .contains_key(&previously_selected_index)), "it leaves the first selected entry marked" ); } @@ -272,7 +280,11 @@ fn simple_user_journey() -> Result<(), Error> { { app.process_events(&mut terminal, b"k ".keys())?; - assert_eq!(0, app.state.marked.len(), "it toggles the item off",); + assert_eq!( + Some(0), + app.window.mark_pane.as_ref().map(|p| p.marked().len()), + "it toggles the item off", + ); assert_eq!( node_by_index(&app, previously_selected_index), diff --git a/src/interactive/widgets/entries.rs b/src/interactive/widgets/entries.rs index 29affa5..bef91cd 100644 --- a/src/interactive/widgets/entries.rs +++ b/src/interactive/widgets/entries.rs @@ -19,7 +19,7 @@ pub struct EntriesProps<'a> { pub display: DisplayOptions, pub selected: Option<TreeIndex>, pub entries: &'a [EntryDataBundle], - pub marked: &'a EntryMarkMap, + pub marked: Option<&'a EntryMarkMap>, pub border_style: Style, pub is_focussed: bool, } @@ -152,7 +152,11 @@ impl Entries { ) .into(), Style { - fg: match (!is_dir, exists, marked.contains_key(node_idx)) { + fg: match ( + !is_dir, + exists, + marked.map(|m| m.contains_key(node_idx)).unwrap_or(false), + ) { (true, true, false) if !is_selected => Color::DarkGray, (true, true, false) => style.fg, (false, true, false) => style.fg, diff --git a/src/interactive/widgets/footer.rs b/src/interactive/widgets/footer.rs index 56d6657..f961412 100644 --- a/src/interactive/widgets/footer.rs +++ b/src/interactive/widgets/footer.rs @@ -17,7 +17,7 @@ pub struct FooterProps<'a> { pub total_bytes: Option<u64>, pub entries_traversed: u64, pub format: ByteFormat, - pub marked: &'a EntryMarkMap, + pub marked: Option<&'a EntryMarkMap>, pub message: Option<String>, } @@ -45,7 +45,7 @@ impl Footer { ) .into(), )), - match marked.is_empty() { + marked.and_then(|marked| match marked.is_empty() { true => None, false => Some(Text::Styled( format!( @@ -60,7 +60,7 @@ impl Footer { modifier: Modifier::BOLD | Modifier::RAPID_BLINK, }, )), - }, + }), message.as_ref().map(|m| { Text::Styled( m.into(), diff --git a/src/interactive/widgets/main.rs b/src/interactive/widgets/main.rs index 6eab313..a5640a1 100644 --- a/src/interactive/widgets/main.rs +++ b/src/interactive/widgets/main.rs @@ -92,28 +92,34 @@ impl MainWindow { Mark => (grey, grey, white), }; - let bg_color = match (state.marked.is_empty(), state.focussed) { - (false, FocussedPane::Mark) => Color::LightRed, - (false, _) => COLOR_MARKED_LIGHT, - (true, _) => Color::White, + let bg_color = { + let marked = mark_pane.as_ref().map(|(_, p)| p.marked()); + match (marked.map(|m| m.is_empty()), state.focussed) { + (Some(false), FocussedPane::Mark) => Color::LightRed, + (Some(false), _) | (None, _) => COLOR_MARKED_LIGHT, + (_, _) => Color::White, + } }; Header.render(bg_color, header_area, buf); - let props = EntriesProps { - tree: &tree, - root: state.root, - display: *display, - entries: &state.entries, - marked: &state.marked, - selected: state.selected, - border_style: entries_style, - is_focussed: if let Main = state.focussed { - true - } else { - false - }, - }; - self.entries_pane.render(props, entries_area, buf); + { + let marked = mark_pane.as_ref().map(|(_, p)| p.marked()); + let props = EntriesProps { + tree: &tree, + root: state.root, + display: *display, + entries: &state.entries, + marked, + selected: state.selected, + border_style: entries_style, + is_focussed: if let Main = state.focussed { + true + } else { + false + }, + }; + self.entries_pane.render(props, entries_area, buf); + } if let Some((help_area, pane)) = help_pane { let props = HelpPaneProps { @@ -124,7 +130,6 @@ impl MainWindow { if let Some((mark_area, pane)) = mark_pane { let props = MarkPaneProps { border_style: mark_style, - marked: &state.marked, }; pane.render(props, mark_area, buf); } @@ -133,7 +138,7 @@ impl MainWindow { FooterProps { total_bytes: *total_bytes, entries_traversed: *entries_traversed, - marked: &state.marked, + marked: self.mark_pane.as_ref().map(|p| p.marked()), format: display.byte_format, message: state.message.clone(), }, diff --git a/src/interactive/widgets/mark.rs b/src/interactive/widgets/mark.rs index 42650aa..d600b34 100644 --- a/src/interactive/widgets/mark.rs +++ b/src/interactive/widgets/mark.rs @@ -1,5 +1,6 @@ -use crate::interactive::{widgets::COLOR_MARKED_LIGHT, CursorDirection, EntryMarkMap}; -use dua::traverse::TreeIndex; +use crate::interactive::{widgets::COLOR_MARKED_LIGHT, CursorDirection, EntryMark, EntryMarkMap}; +use dua::path_of; +use dua::traverse::{Tree, TreeIndex}; use itertools::Itertools; use std::borrow::Borrow; use termion::{event::Key, event::Key::*}; @@ -13,47 +14,57 @@ use tui::{ }; use tui_react::{List, ListProps}; +#[derive(Default)] pub struct MarkPane { - list: List, selected: Option<TreeIndex>, + marked: EntryMarkMap, + list: List, } -pub struct MarkPaneProps<'a> { +pub struct MarkPaneProps { pub border_style: Style, - pub marked: &'a EntryMarkMap, } impl MarkPane { - pub fn new(_marked: &EntryMarkMap) -> MarkPane { - MarkPane { - list: Default::default(), - selected: None, + pub fn toggle_index(mut self, index: TreeIndex, tree: &Tree) -> Option<Self> { + if self.marked.get(&index).is_some() { + self.marked.remove(&index); + } else { + if let Some(e) = tree.node_weight(index) { + self.marked.insert( + index, + EntryMark { + size: e.size, + path: path_of(tree, index), + }, + ); + } + } + if self.marked.is_empty() { + None + } else { + Some(self) } } - - pub fn key(&mut self, key: Key, marked: &EntryMarkMap) { + pub fn marked(&self) -> &EntryMarkMap { + &self.marked + } + pub fn key(&mut self, key: Key) { match key { - Ctrl('u') | PageUp => self.change_selection(CursorDirection::PageUp, marked), - Char('k') | Up => self.change_selection(CursorDirection::Up, marked), - Char('j') | Down => self.change_selection(CursorDirection::Down, marked), - Ctrl('d') | PageDown => self.change_selection(CursorDirection::PageDown, marked), + Ctrl('u') | PageUp => self.change_selection(CursorDirection::PageUp), + Char('k') | Up => self.change_selection(CursorDirection::Up), + Char('j') | Down => self.change_selection(CursorDirection::Down), + Ctrl('d') | PageDown => self.change_selection(CursorDirection::PageDown), _ => {} }; } - fn change_selection(&mut self, _direction: CursorDirection, _marked: &EntryMarkMap) {} + fn change_selection(&mut self, _direction: CursorDirection) {} - pub fn render<'a>( - &mut self, - props: impl Borrow<MarkPaneProps<'a>>, - area: Rect, - buf: &mut Buffer, - ) { - let MarkPaneProps { - border_style, - marked, - } = props.borrow(); + pub fn render(&mut self, props: impl Borrow<MarkPaneProps>, area: Rect, buf: &mut Buffer) { + let MarkPaneProps { border_style } = props.borrow(); + let marked: &_ = &self.marked; let block = Block::default() .title("Marked Entries") .border_style(*border_style) |