summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2024-01-17 09:03:46 +0100
committerSebastian Thiel <sebastian.thiel@icloud.com>2024-01-17 10:14:35 +0100
commit18a725dc5af97841afd06dcd4c8469e1d7ea873c (patch)
tree808958e942afdb79d6105c93f5cdc045197c7a03
parentf1fc13ec8e2af583d0ce4eb541e260e9045c8cf2 (diff)
refactor
- show messages that indicate why sometimes key-presses are ignored - maintain previous selection in a clearer fashion - maintain seelction from glob-mode as well - switch title to `entry` as it's not only 'file's there, also directories. - also show how many entries there are visible
-rw-r--r--src/interactive/app/eventloop.rs76
-rw-r--r--src/interactive/app/handlers.rs5
-rw-r--r--src/interactive/app/state.rs10
-rw-r--r--src/interactive/app/terminal.rs2
-rw-r--r--src/interactive/app/tests/journeys_readonly.rs2
-rw-r--r--src/interactive/app/tree_view.rs6
-rw-r--r--src/interactive/widgets/entries.rs26
-rw-r--r--src/interactive/widgets/footer.rs2
-rw-r--r--src/interactive/widgets/help.rs38
9 files changed, 104 insertions, 63 deletions
diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs
index 2377cc0..e7c91a4 100644
--- a/src/interactive/app/eventloop.rs
+++ b/src/interactive/app/eventloop.rs
@@ -1,3 +1,4 @@
+use crate::interactive::state::FilesystemScan;
use crate::interactive::{
app::navigation::Navigation,
state::FocussedPane,
@@ -64,7 +65,7 @@ impl AppState {
}
pub fn traverse(&mut self, traversal: &Traversal, input: Vec<PathBuf>) -> Result<()> {
- let background_traversal = BackgroundTraversal::start(
+ let traverasal = BackgroundTraversal::start(
traversal.root_index,
&self.walk_options,
input,
@@ -72,7 +73,10 @@ impl AppState {
true,
)?;
self.navigation_mut().view_root = traversal.root_index;
- self.active_traversal = Some((background_traversal, None));
+ self.scan = Some(FilesystemScan {
+ active_traversal: traverasal,
+ previous_selection: None,
+ });
Ok(())
}
@@ -130,7 +134,11 @@ impl AppState {
where
B: Backend,
{
- if let Some((active_traversal, selected_name)) = &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 {
@@ -153,13 +161,13 @@ impl AppState {
if let Some(is_finished) = active_traversal.integrate_traversal_event(traversal, event) {
self.stats = active_traversal.stats;
- let selected_name = selected_name.clone();
+ let previous_selection = previous_selection.clone();
if is_finished {
let root_index = active_traversal.root_idx;
self.recompute_sizes_recursively(traversal, root_index);
- self.active_traversal = None;
+ self.scan = None;
}
- self.update_state_during_traversal(traversal, selected_name.as_ref(), is_finished);
+ self.update_state_during_traversal(traversal, previous_selection.as_ref(), is_finished);
self.refresh_screen(window, traversal, display, terminal)?;
};
}
@@ -182,16 +190,21 @@ impl AppState {
fn update_state_during_traversal(
&mut self,
traversal: &mut Traversal,
- selected_name: Option<&PathBuf>,
+ 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 {
- let selected_entry = selected_name
- .and_then(|selected_name| self.entries.iter().find(|e| e.name == *selected_name));
- if let Some(selected_entry) = selected_entry {
+ 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);
@@ -239,8 +252,12 @@ impl AppState {
Tab => {
self.cycle_focus(window);
}
- Char('/') if !glob_focussed && self.active_traversal.is_none() => {
- self.toggle_glob_search(window);
+ Char('/') if !glob_focussed => {
+ 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 => {
@@ -338,18 +355,32 @@ impl AppState {
what: Refresh,
) -> anyhow::Result<()> {
// If another traversal is already running do not do anything.
- if self.active_traversal.is_some() {
+ if self.scan.is_some() {
+ self.message = Some("Traversal already running".into());
return Ok(());
}
- // If we are displaing root of the glob search results then cancel the search.
+ 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_index, skip_root, index, parent_index) = match what {
+ let (remove_root_node, skip_root, index, parent_index) = match what {
Refresh::Selected => {
let Some(selected) = self.navigation().selected else {
return Ok(());
@@ -367,31 +398,26 @@ impl AppState {
),
};
- let selected_name = self
- .navigation()
- .selected
- .and_then(|w| tree.tree().node_weight(w).map(|w| w.name.clone()));
-
let mut path = tree.path_of(index);
if path.to_str() == Some("") {
path = PathBuf::from(".");
}
- tree.remove_entries(index, remove_index);
+ 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.active_traversal = Some((
- BackgroundTraversal::start(
+ self.scan = Some(FilesystemScan {
+ active_traversal: BackgroundTraversal::start(
parent_index,
&self.walk_options,
vec![path],
skip_root,
false,
)?,
- selected_name,
- ));
+ previous_selection,
+ });
self.received_events = false;
Ok(())
diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs
index 5601cf1..c320c59 100644
--- a/src/interactive/app/handlers.rs
+++ b/src/interactive/app/handlers.rs
@@ -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;
@@ -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, true);
+ 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);
diff --git a/src/interactive/app/state.rs b/src/interactive/app/state.rs
index 47f681b..aee7542 100644
--- a/src/interactive/app/state.rs
+++ b/src/interactive/app/state.rs
@@ -24,6 +24,12 @@ pub struct Cursor {
pub y: u16,
}
+pub(crate) struct FilesystemScan {
+ pub active_traversal: BackgroundTraversal,
+ /// The selected item prior to starting the traversal, if available, based on its name or index into [`AppState::entries`].
+ pub previous_selection: Option<(PathBuf, usize)>,
+}
+
pub struct AppState {
pub navigation: Navigation,
pub glob_navigation: Option<Navigation>,
@@ -33,7 +39,7 @@ pub struct AppState {
pub message: Option<String>,
pub focussed: FocussedPane,
pub received_events: bool,
- pub active_traversal: Option<(BackgroundTraversal, Option<PathBuf>)>,
+ pub scan: Option<FilesystemScan>,
pub stats: TraversalStats,
pub walk_options: WalkOptions,
}
@@ -49,7 +55,7 @@ impl AppState {
message: None,
focussed: Default::default(),
received_events: false,
- active_traversal: None,
+ scan: None,
stats: TraversalStats::default(),
walk_options,
}
diff --git a/src/interactive/app/terminal.rs b/src/interactive/app/terminal.rs
index d2ae955..f29834f 100644
--- a/src/interactive/app/terminal.rs
+++ b/src/interactive/app/terminal.rs
@@ -99,7 +99,7 @@ mod tests {
where
B: Backend,
{
- while self.state.active_traversal.is_some() {
+ while self.state.scan.is_some() {
if let Some(res) = self.state.process_event(
&mut self.window,
&mut self.traversal,
diff --git a/src/interactive/app/tests/journeys_readonly.rs b/src/interactive/app/tests/journeys_readonly.rs
index b6dc579..f307294 100644
--- a/src/interactive/app/tests/journeys_readonly.rs
+++ b/src/interactive/app/tests/journeys_readonly.rs
@@ -40,7 +40,7 @@ fn simple_user_journey_read_only() -> Result<()> {
);
assert!(
- app.state.active_traversal.is_none(),
+ app.state.scan.is_none(),
"it will not think it is still scanning as there is no traversal"
);
diff --git a/src/interactive/app/tree_view.rs b/src/interactive/app/tree_view.rs
index 612582b..8a80cf0 100644
--- a/src/interactive/app/tree_view.rs
+++ b/src/interactive/app/tree_view.rs
@@ -59,12 +59,12 @@ impl TreeView<'_> {
current_path(&self.traversal.tree, view_root, self.glob_tree_root)
}
- pub fn remove_entries(&mut self, index: TreeIndex, remove_index: bool) -> usize {
+ pub fn remove_entries(&mut self, root_index: TreeIndex, remove_root_node: bool) -> usize {
let mut entries_deleted = 0;
- let mut bfs = Bfs::new(self.tree(), index);
+ let mut bfs = Bfs::new(self.tree(), root_index);
while let Some(nx) = bfs.next(&self.tree()) {
- if nx == index && !remove_index {
+ if nx == root_index && !remove_root_node {
continue;
}
self.tree_mut().remove_node(nx);
diff --git a/src/interactive/widgets/entries.rs b/src/interactive/widgets/entries.rs
index 4005739..c4794f4 100644
--- a/src/interactive/widgets/entries.rs
+++ b/src/interactive/widgets/entries.rs
@@ -63,12 +63,18 @@ impl Entries {
let list = &mut self.list;
let total: u128 = entries.iter().map(|b| b.size).sum();
- let (item_count, item_size): (u64, u128) = entries
+ let (recursive_item_count, item_size): (u64, u128) = entries
.iter()
.map(|f| (f.entry_count.unwrap_or(1), f.size))
.reduce(|a, b| (a.0 + b.0, a.1 + b.1))
.unwrap_or_default();
- let title = title(current_path, item_count, *display, item_size);
+ let title = title(
+ current_path,
+ entries.len(),
+ recursive_item_count,
+ *display,
+ item_size,
+ );
let title_block = title_block(&title, *border_style);
let inner_area = title_block.inner(area);
let entry_in_view = entry_in_view(*selected, entries);
@@ -155,15 +161,17 @@ fn title_block(title: &str, border_style: Style) -> Block<'_> {
.borders(Borders::ALL)
}
-fn title(current_path: &str, item_count: u64, display: DisplayOptions, size: u128) -> String {
+fn title(
+ current_path: &str,
+ item_count: usize,
+ recursive_item_count: u64,
+ display: DisplayOptions,
+ size: u128,
+) -> String {
format!(
- " {} ({} file{}, {}) ",
+ " {} ({item_count} visible, {} total, {}) ",
current_path,
- COUNT.format(item_count as f64),
- match item_count {
- 1 => "",
- _ => "s",
- },
+ COUNT.format(recursive_item_count as f64),
display.byte_format.display(size)
)
}
diff --git a/src/interactive/widgets/footer.rs b/src/interactive/widgets/footer.rs
index 98251c7..80bd352 100644
--- a/src/interactive/widgets/footer.rs
+++ b/src/interactive/widgets/footer.rs
@@ -37,7 +37,7 @@ impl Footer {
let spans = vec![
Span::from(format!(
- "Sort mode: {} Total disk usage: {} Processed {} items {progress} ",
+ "Sort mode: {} Total disk usage: {} Processed {} entries {progress} ",
match sort_mode {
SortMode::SizeAscending => "size ascending",
SortMode::SizeDescending => "size descending",
diff --git a/src/interactive/widgets/help.rs b/src/interactive/widgets/help.rs
index d5107dd..8cf83f6 100644
--- a/src/interactive/widgets/help.rs
+++ b/src/interactive/widgets/help.rs
@@ -123,8 +123,8 @@ impl HelpPane {
}
title("Navigation");
{
- hotkey("j/<Down>", "Move down 1 item.", None);
- hotkey("k/<Up>", "Move up 1 item.", None);
+ hotkey("j/<Down>", "Move down 1 entry.", None);
+ hotkey("k/<Up>", "Move up 1 entry.", None);
hotkey("o/l/<Enter>", "Descent into the selected directory.", None);
hotkey("<Right>", "^", None);
hotkey(
@@ -133,9 +133,9 @@ impl HelpPane {
None,
);
hotkey("<Backspace>", "^", None);
- hotkey("Ctrl + d", "Move down 10 items.", None);
+ hotkey("Ctrl + d", "Move down 10 entries.", None);
hotkey("<Page Down>", "^", None);
- hotkey("Ctrl + u", "Move up 10 items.", None);
+ hotkey("Ctrl + u", "Move up 10 entries.", None);
hotkey("<Page Up>", "^", None);
hotkey("H/<Home>", "Move to the top of the list.", None);
hotkey("G/<End>", "Move to the bottom of the list.", None);
@@ -150,8 +150,8 @@ impl HelpPane {
None,
);
hotkey("M", "Show/hide modified time.", None);
- hotkey("c", "Toggle sort by items descending/ascending.", None);
- hotkey("C", "Show/hide item count.", None);
+ hotkey("c", "Toggle sort by entries descending/ascending.", None);
+ hotkey("C", "Show/hide entry count.", None);
hotkey(
"g/S",
"Cycle through percentage display and bar options.",
@@ -163,48 +163,48 @@ impl HelpPane {
{
hotkey(
"Shift + o",
- "Open the selected item with the associated program.",
+ "Open the selected entry with the associated program.",
None,
);
hotkey(
"d",
- "Toggle the currently selected item and move down.",
+ "Toggle the currently selected entry and move down.",
None,
);
hotkey(
"x",
- "Mark the currently selected item for deletion and move down.",
+ "Mark the currently selected entry for deletion and move down.",
None,
);
- hotkey("<Space>", "Toggle the currently selected item.", None);
- hotkey("a", "Toggle all items.", None);
+ hotkey("<Space>", "Toggle the currently selected entry.", None);
+ hotkey("a", "Toggle all entries.", None);
hotkey(
"/",
"Git-style glob search, case-insensitive.",
Some("Search starts from the current directory."),
);
- hotkey("r", "Refresh only the selected item.", None);
- hotkey("R", "Refresh all items in the current view.", None);
+ hotkey("r", "Refresh only the selected entry.", None);
+ hotkey("R", "Refresh all entries in the current view.", None);
spacer();
}
- title("Mark items pane");
+ title("Mark entries pane");
{
hotkey(
"x/d/<Space>",
- "Remove the selected item from the list.",
+ "Remove the selected entry from the list.",
None,
);
- hotkey("a", "Remove all items from the list.", None);
+ hotkey("a", "Remove all entries from the list.", None);
hotkey(
"Ctrl + r",
- "Permanently delete all marked items without prompt.",
+ "Permanently delete all marked entries without prompt.",
Some("This operation cannot be undone!"),
);
#[cfg(feature = "trash-move")]
hotkey(
"Ctrl + t",
- "Move all marked items to the trash bin.",
- Some("The items can be restored from the trash bin."),
+ "Move all marked entries to the trash bin.",
+ Some("The entries can be restored from the trash bin."),
);
spacer();
}