summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2023-12-24 10:46:06 +0100
committerSebastian Thiel <sebastian.thiel@icloud.com>2023-12-24 10:46:06 +0100
commit1c4d6a77c9f439782446d5d5f791fe9e809de0e7 (patch)
tree2ab45e65481b661b2059337fb4d6204a019a801e
parentb23e13431dad1ed9efc6728f4c9ee8ab2254a42c (diff)
parent2e1858ca519fd2a6fbf4839a23abcf17588dcc32 (diff)
Merge branch 'glob-review'
-rw-r--r--Cargo.lock117
-rw-r--r--Cargo.toml8
-rw-r--r--src/interactive/app/common.rs61
-rw-r--r--src/interactive/app/eventloop.rs330
-rw-r--r--src/interactive/app/handlers.rs241
-rw-r--r--src/interactive/app/mod.rs2
-rw-r--r--src/interactive/app/navigation.rs77
-rw-r--r--src/interactive/app/tests/journeys_readonly.rs36
-rw-r--r--src/interactive/app/tests/journeys_with_writes.rs9
-rw-r--r--src/interactive/app/tests/unit.rs12
-rw-r--r--src/interactive/app/tests/utils.rs19
-rw-r--r--src/interactive/app/tree_view.rs115
-rw-r--r--src/interactive/mod.rs12
-rw-r--r--src/interactive/widgets/entries.rs262
-rw-r--r--src/interactive/widgets/glob.rs216
-rw-r--r--src/interactive/widgets/help.rs5
-rw-r--r--src/interactive/widgets/main.rs86
-rw-r--r--src/interactive/widgets/mark.rs14
-rw-r--r--src/interactive/widgets/mod.rs2
-rw-r--r--src/traverse.rs3
20 files changed, 1226 insertions, 401 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 38fb543..6e7b3dd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -104,6 +104,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
+name = "bstr"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
name = "byte-unit"
version = "4.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -296,11 +307,14 @@ version = "2.23.0"
dependencies = [
"anyhow",
"atty",
+ "bstr",
"byte-unit",
"chrono",
"clap",
"crosstermion",
"filesize",
+ "gix-glob",
+ "gix-path",
"human_format",
"itertools 0.12.0",
"jwalk",
@@ -314,6 +328,7 @@ dependencies = [
"trash",
"tui-react",
"unicode-segmentation",
+ "unicode-width",
"wild",
]
@@ -330,6 +345,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
+name = "faster-hex"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "filesize"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -354,6 +378,58 @@ dependencies = [
]
[[package]]
+name = "gix-features"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d46a4a5c6bb5bebec9c0d18b65ada20e6517dbd7cf855b87dd4bbdce3a771b2"
+dependencies = [
+ "gix-hash",
+ "gix-trace",
+ "libc",
+]
+
+[[package]]
+name = "gix-glob"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db19298c5eeea2961e5b3bf190767a2d1f09b8802aeb5f258e42276350aff19"
+dependencies = [
+ "bitflags 2.4.1",
+ "bstr",
+ "gix-features",
+ "gix-path",
+]
+
+[[package]]
+name = "gix-hash"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f8cf8c2266f63e582b7eb206799b63aa5fa68ee510ad349f637dfe2d0653de0"
+dependencies = [
+ "faster-hex",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-path"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d86d6fac2fabe07b67b7835f46d07571f68b11aa1aaecae94fe722ea4ef305e1"
+dependencies = [
+ "bstr",
+ "gix-trace",
+ "home",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-trace"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b686a35799b53a9825575ca3f06481d0a053a409c4d97ffcf5ddd67a8760b497"
+
+[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -391,6 +467,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "human_format"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -521,6 +606,12 @@ dependencies = [
]
[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -732,6 +823,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+
+[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -851,6 +948,26 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 8df207e..1df408e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ default = ["tui-crossplatform", "trash-move"]
tui-unix = ["crosstermion/tui-react-termion", "tui-shared"]
tui-crossplatform = ["crosstermion/tui-react-crossterm", "tui-shared"]
-tui-shared = ["tui", "tui-react", "open", "unicode-segmentation"]
+tui-shared = ["tui", "tui-react", "open", "unicode-segmentation", "unicode-width"]
trash-move = ["trash"]
[dependencies]
@@ -32,6 +32,7 @@ chrono = { version = "0.4.31", default-features = false, features = ["std"] }
# 'tui' related
unicode-segmentation = { version = "1.3.0", optional = true }
+unicode-width = { version = "0.1.5", optional = true }
crosstermion = { version = "0.12.0", default-features = false, optional = true }
tui = { package = "ratatui", version = "0.24.0", optional = true, default-features = false }
tui-react = { version = "0.21.0", optional = true }
@@ -40,6 +41,9 @@ wild = "2.0.4"
owo-colors = "3.5.0"
human_format = "1.0.3"
once_cell = "1.19"
+gix-glob = "0.14.1"
+gix-path = "0.10.1"
+bstr = "1.8.0"
[[bin]]
name="dua"
@@ -53,7 +57,7 @@ panic = 'abort'
incremental = false
overflow-checks = false
lto = "fat"
-codegen-units = 1
+#codegen-units = 1
build-override = { opt-level = 3 }
[dev-dependencies]
diff --git a/src/interactive/app/common.rs b/src/interactive/app/common.rs
index 6b455a1..3b79a9a 100644
--- a/src/interactive/app/common.rs
+++ b/src/interactive/app/common.rs
@@ -1,8 +1,9 @@
use crate::interactive::path_of;
-use dua::traverse::{EntryData, Tree, TreeIndex};
+use dua::traverse::{Tree, TreeIndex};
use itertools::Itertools;
use petgraph::Direction;
-use std::cmp::Ordering;
+use std::time::SystemTime;
+use std::{cmp::Ordering, path::PathBuf};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq, Eq)]
@@ -47,37 +48,61 @@ impl SortMode {
pub struct EntryDataBundle {
pub index: TreeIndex,
- pub data: EntryData,
+ pub name: PathBuf,
+ pub size: u128,
+ pub mtime: SystemTime,
+ pub entry_count: Option<u64>,
pub is_dir: bool,
pub exists: bool,
}
-pub fn sorted_entries(tree: &Tree, node_idx: TreeIndex, sorting: SortMode) -> Vec<EntryDataBundle> {
+/// Note that with `glob_root` present, we will not obtain metadata anymore as we might be seeing
+/// a lot of entries. That way, displaying 250k entries is no problem.
+pub fn sorted_entries(
+ tree: &Tree,
+ node_idx: TreeIndex,
+ sorting: SortMode,
+ glob_root: Option<TreeIndex>,
+) -> Vec<EntryDataBundle> {
use SortMode::*;
fn cmp_count(l: &EntryDataBundle, r: &EntryDataBundle) -> Ordering {
- l.data
- .entry_count
- .cmp(&r.data.entry_count)
- .then_with(|| l.data.name.cmp(&r.data.name))
+ l.entry_count
+ .cmp(&r.entry_count)
+ .then_with(|| l.name.cmp(&r.name))
}
tree.neighbors_directed(node_idx, Direction::Outgoing)
.filter_map(|idx| {
- tree.node_weight(idx).map(|w| {
- let p = path_of(tree, idx);
- let pm = p.symlink_metadata();
+ tree.node_weight(idx).map(|entry| {
+ let use_glob_path = glob_root.map_or(false, |glob_root| glob_root == node_idx);
+ let (path, exists, is_dir) = {
+ let path = path_of(tree, idx, glob_root);
+ if glob_root.is_some() {
+ (path, true, entry.is_dir)
+ } else {
+ let meta = path.symlink_metadata();
+ (path, meta.is_ok(), meta.ok().map_or(false, |m| m.is_dir()))
+ }
+ };
EntryDataBundle {
index: idx,
- data: w.clone(),
- exists: pm.is_ok(),
- is_dir: pm.ok().map_or(false, |m| m.is_dir()),
+ name: if use_glob_path {
+ path
+ } else {
+ entry.name.clone()
+ },
+ size: entry.size,
+ mtime: entry.mtime,
+ entry_count: entry.entry_count,
+ exists,
+ is_dir,
}
})
})
.sorted_by(|l, r| match sorting {
- SizeDescending => r.data.size.cmp(&l.data.size),
- SizeAscending => l.data.size.cmp(&r.data.size),
- MTimeAscending => l.data.mtime.cmp(&r.data.mtime),
- MTimeDescending => r.data.mtime.cmp(&l.data.mtime),
+ SizeDescending => r.size.cmp(&l.size),
+ SizeAscending => l.size.cmp(&r.size),
+ MTimeAscending => l.mtime.cmp(&r.mtime),
+ MTimeDescending => r.mtime.cmp(&l.mtime),
CountAscending => cmp_count(l, r),
CountDescending => cmp_count(l, r).reverse(),
})
diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs
index e0773bd..00b37e5 100644
--- a/src/interactive/app/eventloop.rs
+++ b/src/interactive/app/eventloop.rs
@@ -1,36 +1,46 @@
use crate::interactive::{
+ app::navigation::Navigation,
sorted_entries,
- widgets::{MainWindow, MainWindowProps},
+ widgets::{glob_search, MainWindow, MainWindowProps},
ByteVisualization, CursorDirection, CursorMode, DisplayOptions, EntryDataBundle, MarkEntryMode,
SortMode,
};
use anyhow::Result;
use crosstermion::input::{input_channel, Event, Key};
use dua::{
- traverse::{Traversal, TreeIndex},
+ traverse::{EntryData, Traversal},
WalkOptions, WalkResult,
};
-use std::{collections::BTreeMap, path::PathBuf};
+use std::path::PathBuf;
use tui::backend::Backend;
use tui_react::Terminal;
-#[derive(Default, Copy, Clone)]
+use super::tree_view::TreeView;
+
+#[derive(Default, Copy, Clone, PartialEq)]
pub enum FocussedPane {
#[default]
Main,
Help,
Mark,
+ Glob,
+}
+
+#[derive(Default)]
+pub struct Cursor {
+ pub show: bool,
+ pub x: u16,
+ pub y: u16,
}
#[derive(Default)]
pub struct AppState {
- pub root: TreeIndex,
- pub selected: Option<TreeIndex>,
+ pub navigation: Navigation,
+ pub glob_navigation: Option<Navigation>,
pub entries: Vec<EntryDataBundle>,
pub sorting: SortMode,
pub message: Option<String>,
pub focussed: FocussedPane,
- pub bookmarks: BTreeMap<TreeIndex, TreeIndex>,
pub is_scanning: bool,
}
@@ -40,10 +50,20 @@ pub enum ProcessingResult {
}
impl AppState {
+ pub fn navigation_mut(&mut self) -> &mut Navigation {
+ self.glob_navigation
+ .as_mut()
+ .unwrap_or(&mut self.navigation)
+ }
+
+ pub fn navigation(&self) -> &Navigation {
+ self.glob_navigation.as_ref().unwrap_or(&self.navigation)
+ }
+
pub fn draw<B>(
&mut self,
window: &mut MainWindow,
- traversal: &Traversal,
+ tree_view: &TreeView<'_>,
display: DisplayOptions,
terminal: &mut Terminal<B>,
) -> Result<()>
@@ -51,11 +71,26 @@ impl AppState {
B: Backend,
{
let props = MainWindowProps {
- traversal,
+ current_path: tree_view.current_path(self.navigation().view_root),
+ entries_traversed: tree_view.traversal.entries_traversed,
+ total_bytes: tree_view.traversal.total_bytes,
+ start: tree_view.traversal.start,
+ elapsed: tree_view.traversal.elapsed,
display,
state: self,
};
- draw_window(window, props, terminal)
+
+ let mut cursor = Cursor::default();
+ let result = draw_window(window, props, terminal, &mut cursor);
+
+ if cursor.show {
+ _ = terminal.show_cursor();
+ _ = terminal.set_cursor(cursor.x, cursor.y);
+ } else {
+ _ = terminal.hide_cursor();
+ }
+
+ result
}
pub fn process_events<B>(
@@ -72,7 +107,11 @@ impl AppState {
use crosstermion::input::Key::*;
use FocussedPane::*;
- self.draw(window, traversal, *display, terminal)?;
+ {
+ let tree_view = self.tree_view(traversal);
+ self.draw(window, &tree_view, *display, terminal)?;
+ }
+
for event in events {
let key = match event {
Event::Key(key) => key,
@@ -80,99 +119,215 @@ impl AppState {
};
self.reset_message();
+
+ let glob_focussed = self.focussed == Glob;
+ let mut tree_view = self.tree_view(traversal);
+ let mut handled = true;
match key {
- Char('?') => self.toggle_help_pane(window),
+ Esc => {
+ if let Some(value) = self.handle_quit(&mut tree_view, window) {
+ return value;
+ }
+ }
Char('\t') => {
self.cycle_focus(window);
}
- Ctrl('c') => {
+ Char('/') if !glob_focussed => {
+ self.toggle_glob_search(window);
+ }
+ Char('?') if !glob_focussed => self.toggle_help_pane(window),
+ Ctrl('c') if !glob_focussed => {
return Ok(ProcessingResult::ExitRequested(WalkResult {
- num_errors: traversal.io_errors,
+ num_errors: tree_view.traversal.io_errors,
}))
}
- Char('q') | Esc => match self.focussed {
- Main => {
- return Ok(ProcessingResult::ExitRequested(WalkResult {
- num_errors: traversal.io_errors,
- }))
- }
- Mark => self.focussed = Main,
- Help => {
- self.focussed = Main;
- window.help_pane = None
+ Char('q') if !glob_focussed => {
+ if let Some(value) = self.handle_quit(&mut tree_view, window) {
+ return value;
}
- },
- _ => {}
+ }
+ _ => {
+ handled = false;
+ }
}
- match self.focussed {
- Mark => self.dispatch_to_mark_pane(key, window, traversal, *display, terminal),
- Help => {
- window
- .help_pane
- .as_mut()
- .expect("help pane")
- .process_events(key);
- }
- Main => match key {
- Char('O') => self.open_that(traversal),
- Char(' ') => self.mark_entry(
- CursorMode::KeepPosition,
- MarkEntryMode::Toggle,
- window,
- traversal,
- ),
- Char('d') => self.mark_entry(
- CursorMode::Advance,
- MarkEntryMode::Toggle,
- window,
- traversal,
- ),
- Char('x') => self.mark_entry(
- CursorMode::Advance,
- MarkEntryMode::MarkForDeletion,
- window,
- traversal,
- ),
- Char('a') => self.mark_all_entries(MarkEntryMode::Toggle, window, traversal),
- Char('u') | Char('h') | Backspace | Left => {
- self.exit_node_with_traversal(traversal)
+ if !handled {
+ match self.focussed {
+ Mark => {
+ self.dispatch_to_mark_pane(key, window, &mut tree_view, *display, terminal)
}
- Char('o') | Char('l') | Char('\n') | Right => {
- self.enter_node_with_traversal(traversal)
+ Help => {
+ window
+ .help_pane
+ .as_mut()
+ .expect("help pane")
+ .process_events(key);
}
- Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop),
- Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom),
- 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('m') => self.cycle_mtime_sorting(traversal),
- Char('c') => self.cycle_count_sorting(traversal),
- Char('g') => display.byte_vis.cycle(),
- _ => {}
- },
- };
- self.draw(window, traversal, *display, terminal)?;
+ Glob => {
+ let glob_pane = window.glob_pane.as_mut().expect("glob pane");
+ match key {
+ Char('\n') => {
+ self.search_glob_pattern(&mut tree_view, &glob_pane.input)
+ }
+ _ => glob_pane.process_events(key),
+ }
+ }
+ Main => match key {
+ Char('O') => self.open_that(&tree_view),
+ Char(' ') => self.mark_entry(
+ CursorMode::KeepPosition,
+ MarkEntryMode::Toggle,
+ window,
+ &tree_view,
+ ),
+ Char('d') => self.mark_entry(
+ CursorMode::Advance,
+ MarkEntryMode::Toggle,
+ window,
+ &tree_view,
+ ),
+ Char('x') => self.mark_entry(
+ CursorMode::Advance,
+ MarkEntryMode::MarkForDeletion,
+ window,
+ &tree_view,
+ ),
+ Char('a') => {
+ self.mark_all_entries(MarkEntryMode::Toggle, window, &tree_view)
+ }
+ Char('u') | Char('h') | Backspace | Left => {
+ self.exit_node_with_traversal(&tree_view)
+ }
+ Char('o') | Char('l') | Char('\n') | Right => {
+ self.enter_node_with_traversal(&tree_view)
+ }
+ Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop),
+ Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom),
+ 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(&tree_view),
+ Char('m') => self.cycle_mtime_sorting(&tree_view),
+ Char('c') => self.cycle_count_sorting(&tree_view),
+ Char('g') => display.byte_vis.cycle(),
+ _ => {}
+ },
+ };
+ }
+ self.draw(window, &tree_view, *display, terminal)?;
}
Ok(ProcessingResult::Finished(WalkResult {
num_errors: traversal.io_errors,
}))
}
+
+ fn tree_view<'a>(&mut self, traversal: &'a mut Traversal) -> TreeView<'a> {
+ TreeView {
+ traversal,
+ glob_tree_root: self.glob_navigation.as_ref().map(|n| n.tree_root),
+ }
+ }
+
+ fn search_glob_pattern(&mut self, tree_view: &mut TreeView, glob_pattern: &str) {
+ use FocussedPane::*;
+ match glob_search(tree_view.tree(), self.navigation.view_root, glob_pattern) {
+ Ok(matches) if matches.is_empty() => {
+ self.message = Some("No match found".into());
+ }
+ Ok(matches) => {
+ if let Some(glob_source) = &self.glob_navigation {
+ tree_view.tree_mut().remove_node(glob_source.tree_root);
+ }
+
+ let tree_root = tree_view.tree_mut().add_node(EntryData::default());
+ let glob_source = Navigation {
+ tree_root,
+ view_root: tree_root,
+ selected: Some(tree_root),
+ ..Default::default()
+ };
+ self.glob_navigation = Some(glob_source);
+
+ for idx in matches {
+ tree_view.tree_mut().add_edge(tree_root, idx, ());
+ }
+
+ let glob_tree_view = TreeView {
+ traversal: tree_view.traversal,
+ glob_tree_root: Some(tree_root),
+ };
+ let new_entries = glob_tree_view.sorted_entries(tree_root, self.sorting);
+
+ let new_entries = self
+ .navigation_mut()
+ .selected
+ .map(|previously_selected| (previously_selected, new_entries));
+
+ self.enter_node(new_entries);
+ self.focussed = Main;
+ }
+ Err(err) => self.message = Some(err.to_string()),
+ }
+ }
+
+ fn handle_quit(
+ &mut self,
+ tree_view: &mut TreeView<'_>,
+ window: &mut MainWindow,
+ ) -> Option<std::result::Result<ProcessingResult, anyhow::Error>> {
+ use FocussedPane::*;
+ match self.focussed {
+ Main => {
+ if self.glob_navigation.is_some() {
+ self.handle_glob_quit(tree_view, window);
+ } else {
+ return Some(Ok(ProcessingResult::ExitRequested(WalkResult {
+ num_errors: tree_view.traversal.io_errors,
+ })));
+ }
+ }
+ Mark => self.focussed = Main,
+ Help => {
+ self.focussed = Main;
+ window.help_pane = None
+ }
+ Glob => {
+ self.handle_glob_quit(tree_view, window);
+ }
+ }
+ None
+ }
+
+ fn handle_glob_quit(&mut self, tree_view: &mut TreeView<'_>, window: &mut MainWindow) {
+ use FocussedPane::*;
+ self.focussed = Main;
+ if let Some(glob_source) = &self.glob_navigation {
+ tree_view.tree_mut().remove_node(glob_source.tree_root);
+ }
+ self.glob_navigation = None;
+ window.glob_pane = None;
+
+ tree_view.glob_tree_root.take();
+ self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting);
+ }
}
pub fn draw_window<B>(
window: &mut MainWindow,
props: MainWindowProps,
terminal: &mut Terminal<B>,
+ cursor: &mut Cursor,
) -> Result<()>
where
B: Backend,
{
let area = terminal.pre_render()?;
- window.render(props, area, terminal.current_buffer_mut());
+ window.render(props, area, terminal, cursor);
terminal.post_render()?;
+
Ok(())
}
@@ -202,6 +357,7 @@ impl TerminalApp {
)
.ok();
}
+
pub fn process_events<B>(
&mut self,
terminal: &mut Terminal<B>,
@@ -258,11 +414,16 @@ impl TerminalApp {
let mut received_events = false;
let traversal = Traversal::from_walk(options, input_paths, |traversal| {
if !received_events {
- state.root = traversal.root_index;
+ state.navigation_mut().view_root = traversal.root_index;
}
- state.entries = sorted_entries(&traversal.tree, state.root, state.sorting);
+ state.entries = sorted_entries(
+ &traversal.tree,
+ state.navigation().view_root,
+ state.sorting,
+ state.glob_root(),
+ );
if !received_events {
- state.selected = state.entries.first().map(|b| b.index);
+ state.navigation_mut().selected = state.entries.first().map(|b| b.index);
}
state.reset_message(); // force "scanning" to appear
@@ -279,6 +440,7 @@ impl TerminalApp {
ProcessingResult::ExitRequested(_) => true,
ProcessingResult::Finished(_) => false,
};
+
Ok(should_exit)
})?;
@@ -289,10 +451,16 @@ impl TerminalApp {
state.is_scanning = false;
if !received_events {
- state.root = traversal.root_index;
+ state.navigation_mut().view_root = traversal.root_index;
}
- state.entries = sorted_entries(&traversal.tree, state.root, state.sorting);
- state.selected = state
+ state.entries = sorted_entries(
+ &traversal.tree,
+ state.navigation().view_root,
+ state.sorting,
+ state.glob_root(),
+ );
+ state.navigation_mut().selected = state
+ .navigation()
.selected
.filter(|_| received_events)
.or_else(|| state.entries.first().map(|b| b.index));
diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs
index d3761e9..6e12c3d 100644
--- a/src/interactive/app/handlers.rs
+++ b/src/interactive/app/handlers.rs
@@ -1,13 +1,11 @@
use crate::interactive::{
+ app::tree_view::TreeView,
app::FocussedPane::*,
- path_of, sorted_entries,
- widgets::{HelpPane, MainWindow, MarkMode, MarkPane},
+ widgets::{GlobPane, HelpPane, MainWindow, MarkMode, MarkPane},
AppState, DisplayOptions, EntryDataBundle,
};
use crosstermion::input::Key;
-use dua::traverse::{Traversal, TreeIndex};
-use itertools::Itertools;
-use petgraph::{visit::Bfs, Direction};
+use dua::traverse::TreeIndex;
use std::{fs, io, path::PathBuf};
use tui::backend::Backend;
use tui_react::Terminal;
@@ -48,29 +46,27 @@ impl CursorDirection {
}
impl AppState {
- pub fn open_that(&self, traversal: &Traversal) {
- if let Some(idx) = self.selected {
- open::that(path_of(&traversal.tree, idx)).ok();
+ pub fn open_that(&self, tree_view: &TreeView<'_>) {
+ if let Some(idx) = self.navigation().selected {
+ open::that(tree_view.path_of(idx)).ok();
}
}
- pub fn exit_node_with_traversal(&mut self, traversal: &Traversal) {
- let entries = self.entries_for_exit_node(traversal);
+ pub fn exit_node_with_traversal(&mut self, tree_view: &TreeView<'_>) {
+ let entries = self.entries_for_exit_node(tree_view);
self.exit_node(entries);
}
fn entries_for_exit_node(
&self,
- traversal: &Traversal,
+ tree_view: &TreeView<'_>,
) -> Option<(TreeIndex, Vec<EntryDataBundle>)> {
- traversal
- .tree
- .neighbors_directed(self.root, Direction::Incoming)
- .next()
+ tree_view
+ .view_parent_of(self.navigation().view_root)
.map(|parent_idx| {
(
parent_idx,
- sorted_entries(&traversal.tree, parent_idx, self.sorting),
+ tree_view.sorted_entries(parent_idx, self.sorting),
)
})
}
@@ -78,13 +74,8 @@ impl AppState {
pub fn exit_node(&mut self, entries: Option<(TreeIndex, Vec<EntryDataBundle>)>) {
match entries {
Some((parent_idx, entries)) => {
- self.root = parent_idx;
+ self.navigation_mut().exit_node(parent_idx, &entries);
self.entries = entries;
- self.selected = self
- .bookmarks
- .get(&parent_idx)
- .copied()
- .or_else(|| self.entries.first().map(|b| b.index));
}
None => self.message = Some("Top level reached".into()),
}
@@ -92,38 +83,30 @@ impl AppState {