Skip to content

Commit

Permalink
feat: Press r or R for refresh (Byron#96)
Browse files Browse the repository at this point in the history
Lower-case `r` will refresh the currently selected entry, while upper-case `R`
will refresh the entire displayed directory, and all entries in it.

Further, what was called `item` is now called `entry` across the
user-interface.
  • Loading branch information
Byron committed Jan 17, 2024
2 parents 1a54d95 + 18a725d commit bed351e
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 155 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ Via `pacman` on your ArchLinux system.
sudo pacman -S dua-cli
```

#### NixOS
https://search.nixos.org/packages?channel=23.11&show=dua&from=0&size=50&sort=relevance&type=packages&query=dua

Nix-shell (temporary)

```
nix-shell -p dua
```

NixOS configuration

```
environment.systemPackages = [
pkgs.dua
];
```

#### NetBSD
Via `pkgin` on your NetBSD system.

Expand Down
2 changes: 1 addition & 1 deletion src/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn aggregate(
continue;
}
};
for entry in walk_options.iter_from_path(path.as_ref(), device_id) {
for entry in walk_options.iter_from_path(path.as_ref(), device_id, false) {
stats.entries_traversed += 1;
progress.throttled(|| {
if let Some(err) = err.as_mut() {
Expand Down
3 changes: 2 additions & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,12 @@ pub struct WalkOptions {
type WalkDir = jwalk::WalkDirGeneric<((), Option<Result<std::fs::Metadata, jwalk::Error>>)>;

impl WalkOptions {
pub fn iter_from_path(&self, root: &Path, root_device_id: u64) -> WalkDir {
pub fn iter_from_path(&self, root: &Path, root_device_id: u64, skip_root: bool) -> WalkDir {
let ignore_dirs = self.ignore_dirs.clone();
let cwd = std::env::current_dir().unwrap_or_else(|_| root.to_owned());
WalkDir::new(root)
.follow_links(false)
.min_depth(if skip_root { 1 } else { 0 })
.sort(match self.sorting {
TraversalSorting::None => false,
TraversalSorting::AlphabeticalByFileName => true,
Expand Down
189 changes: 154 additions & 35 deletions src/interactive/app/eventloop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::interactive::state::FilesystemScan;
use crate::interactive::{
app::navigation::Navigation,
sorted_entries,
state::FocussedPane,
widgets::{glob_search, MainWindow, MainWindowProps},
CursorDirection, CursorMode, DisplayOptions, MarkEntryMode,
Expand All @@ -10,8 +10,8 @@ use crossbeam::channel::Receiver;
use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crosstermion::input::Event;
use dua::{
traverse::{BackgroundTraversal, EntryData, Traversal},
WalkOptions, WalkResult,
traverse::{BackgroundTraversal, EntryData, Traversal, TreeIndex},
WalkResult,
};
use std::path::PathBuf;
use tui::backend::Backend;
Expand Down Expand Up @@ -43,10 +43,10 @@ impl AppState {
{
let props = MainWindowProps {
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,
entries_traversed: self.stats.entries_traversed,
total_bytes: tree_view.total_size(),
start: self.stats.start,
elapsed: self.stats.elapsed,
display,
state: self,
};
Expand All @@ -64,19 +64,27 @@ impl AppState {
result
}

pub fn traverse(
&mut self,
traversal: &Traversal,
walk_options: &WalkOptions,
input: Vec<PathBuf>,
) -> Result<()> {
let background_traversal =
BackgroundTraversal::start(traversal.root_index, walk_options, input)?;
pub fn traverse(&mut self, traversal: &Traversal, input: Vec<PathBuf>) -> Result<()> {
let traverasal = BackgroundTraversal::start(
traversal.root_index,
&self.walk_options,
input,
false,
true,
)?;
self.navigation_mut().view_root = traversal.root_index;
self.active_traversal = Some(background_traversal);
self.scan = Some(FilesystemScan {
active_traversal: traverasal,
previous_selection: None,
});
Ok(())
}

fn recompute_sizes_recursively(&mut self, traversal: &mut Traversal, node_index: TreeIndex) {
let mut tree_view = self.tree_view(traversal);
tree_view.recompute_sizes_recursively(node_index);
}

fn refresh_screen<B>(
&mut self,
window: &mut MainWindow,
Expand Down Expand Up @@ -126,11 +134,15 @@ impl AppState {
where
B: Backend,
{
if let Some(active_traversal) = &mut self.active_traversal {
if let Some(FilesystemScan {
active_traversal,
previous_selection,
}) = self.scan.as_mut()
{
crossbeam::select! {
recv(events) -> event => {
let Ok(event) = event else {
return Ok(Some(WalkResult { num_errors: 0 }));
return Ok(Some(WalkResult { num_errors: self.stats.io_errors }));
};
let res = self.process_terminal_event(
window,
Expand All @@ -148,17 +160,23 @@ impl AppState {
};

if let Some(is_finished) = active_traversal.integrate_traversal_event(traversal, event) {
self.stats = active_traversal.stats;
let previous_selection = previous_selection.clone();
if is_finished {
self.active_traversal = None;
let root_index = active_traversal.root_idx;
self.recompute_sizes_recursively(traversal, root_index);
self.scan = None;
}
self.update_state(traversal);
self.update_state_during_traversal(traversal, previous_selection.as_ref(), is_finished);
self.refresh_screen(window, traversal, display, terminal)?;
};
}
}
} else {
let Ok(event) = events.recv() else {
return Ok(Some(WalkResult { num_errors: 0 }));
return Ok(Some(WalkResult {
num_errors: self.stats.io_errors,
}));
};
let result =
self.process_terminal_event(window, traversal, display, terminal, event)?;
Expand All @@ -169,15 +187,28 @@ impl AppState {
Ok(None)
}

fn update_state(&mut self, traversal: &Traversal) {
self.entries = sorted_entries(
&traversal.tree,
self.navigation().view_root,
self.sorting,
self.glob_root(),
);
fn update_state_during_traversal(
&mut self,
traversal: &mut Traversal,
previous_selection: Option<&(PathBuf, usize)>,
is_finished: bool,
) {
let tree_view = self.tree_view(traversal);
self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting);

if !self.received_events {
self.navigation_mut().selected = self.entries.first().map(|b| b.index);
let previously_selected_entry =
previous_selection.and_then(|(selected_name, selected_idx)| {
self.entries
.iter()
.find(|e| e.name == *selected_name)
.or_else(|| self.entries.get(*selected_idx))
});
if let Some(selected_entry) = previously_selected_entry {
self.navigation_mut().selected = Some(selected_entry.index);
} else if is_finished {
self.navigation_mut().selected = self.entries.first().map(|b| b.index);
}
}
self.reset_message(); // force "scanning" to appear
}
Expand Down Expand Up @@ -222,12 +253,16 @@ impl AppState {
self.cycle_focus(window);
}
Char('/') if !glob_focussed => {
self.toggle_glob_search(window);
if self.scan.is_some() {
self.message = Some("glob search disabled during traversal".into());
} else {
self.toggle_glob_search(window);
}
}
Char('?') if !glob_focussed => self.toggle_help_pane(window),
Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) && !glob_focussed => {
return Ok(Some(WalkResult {
num_errors: tree_view.traversal.io_errors,
num_errors: self.stats.io_errors,
}))
}
Char('q') if !glob_focussed => {
Expand Down Expand Up @@ -275,6 +310,8 @@ impl AppState {
Char('o') | Char('l') | Enter | Right => {
self.enter_node_with_traversal(&tree_view)
}
Char('r') => self.refresh(&mut tree_view, window, Refresh::Selected)?,
Char('R') => self.refresh(&mut tree_view, window, Refresh::AllInView)?,
Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop),
Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom),
PageUp => self.change_entry_selection(CursorDirection::PageUp),
Expand Down Expand Up @@ -311,6 +348,81 @@ impl AppState {
Ok(None)
}

fn refresh(
&mut self,
tree: &mut TreeView<'_>,
window: &mut MainWindow,
what: Refresh,
) -> anyhow::Result<()> {
// If another traversal is already running do not do anything.
if self.scan.is_some() {
self.message = Some("Traversal already running".into());
return Ok(());
}

let previous_selection = self.navigation().selected.and_then(|sel_index| {
tree.tree().node_weight(sel_index).map(|w| {
(
w.name.clone(),
self.entries
.iter()
.enumerate()
.find_map(|(idx, e)| (e.index == sel_index).then_some(idx))
.expect("selected item is always in entries"),
)
})
});

// If we are displaying the root of the glob search results then cancel the search.
if let Some(glob_tree_root) = tree.glob_tree_root {
if glob_tree_root == self.navigation().view_root {
self.quit_glob_mode(tree, window)
}
}

let (remove_root_node, skip_root, index, parent_index) = match what {
Refresh::Selected => {
let Some(selected) = self.navigation().selected else {
return Ok(());
};
let parent_index = tree
.fs_parent_of(selected)
.expect("there is always a parent to a selection");
(true, false, selected, parent_index)
}
Refresh::AllInView => (
false,
true,
self.navigation().view_root,
self.navigation().view_root,
),
};

let mut path = tree.path_of(index);
if path.to_str() == Some("") {
path = PathBuf::from(".");
}
tree.remove_entries(index, remove_root_node);
tree.recompute_sizes_recursively(parent_index);

self.entries = tree.sorted_entries(self.navigation().view_root, self.sorting);
self.navigation_mut().selected = self.entries.first().map(|e| e.index);

self.scan = Some(FilesystemScan {
active_traversal: BackgroundTraversal::start(
parent_index,
&self.walk_options,
vec![path],
skip_root,
false,
)?,
previous_selection,
});

self.received_events = false;
Ok(())
}

fn tree_view<'a>(&mut self, traversal: &'a mut Traversal) -> TreeView<'a> {
TreeView {
traversal,
Expand Down Expand Up @@ -369,10 +481,10 @@ impl AppState {
match self.focussed {
Main => {
if self.glob_navigation.is_some() {
self.handle_glob_quit(tree_view, window);
self.quit_glob_mode(tree_view, window);
} else {
return Some(Ok(WalkResult {
num_errors: tree_view.traversal.io_errors,
num_errors: self.stats.io_errors,
}));
}
}
Expand All @@ -382,13 +494,13 @@ impl AppState {
window.help_pane = None
}
Glob => {
self.handle_glob_quit(tree_view, window);
self.quit_glob_mode(tree_view, window);
}
}
None
}

fn handle_glob_quit(&mut self, tree_view: &mut TreeView<'_>, window: &mut MainWindow) {
fn quit_glob_mode(&mut self, tree_view: &mut TreeView<'_>, window: &mut MainWindow) {
use FocussedPane::*;
self.focussed = Main;
if let Some(glob_source) = &self.glob_navigation {
Expand All @@ -402,6 +514,13 @@ impl AppState {
}
}

enum Refresh {
/// Refresh the directory currently in view
AllInView,
/// Refresh only the selected item
Selected,
}

pub fn draw_window<B>(
window: &mut MainWindow,
props: MainWindowProps<'_>,
Expand Down
7 changes: 4 additions & 3 deletions src/interactive/app/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl AppState {
}

pub fn reset_message(&mut self) {
if self.active_traversal.is_some() {
if self.scan.is_some() {
self.message = Some("-> scanning <-".into());
} else {
self.message = None;
Expand Down Expand Up @@ -312,7 +312,8 @@ impl AppState {
let parent_idx = tree_view
.fs_parent_of(index)
.expect("us being unable to delete the root index");
let entries_deleted = tree_view.remove_entries(index);
let entries_deleted =
tree_view.remove_entries(index, true /* remove node at `index` */);

if !tree_view.exists(self.navigation().view_root) {
self.go_to_root(tree_view);
Expand All @@ -334,7 +335,7 @@ impl AppState {
entries_deleted
}

fn go_to_root(&mut self, tree_view: &TreeView<'_>) {
pub fn go_to_root(&mut self, tree_view: &TreeView<'_>) {
let root = self.navigation().tree_root;
let entries = tree_view.sorted_entries(root, self.sorting);
self.navigation_mut().exit_node(root, &entries);
Expand Down
Loading

0 comments on commit bed351e

Please sign in to comment.