Add scope-based selection and credits

- Selection keys (+/-/*) now work on current scope (parent dataset)
- Add author credits: Florian Schermer, DATAZONE GmbH
- Update help text and README with scope documentation
This commit is contained in:
kidev 2026-01-31 00:15:26 +01:00
parent 1bf3235f9e
commit 194b81bbcf
4 changed files with 76 additions and 21 deletions

View File

@ -91,11 +91,11 @@ zfsdu
| **Tree** | | | **Tree** | |
| e | Expand all | | e | Expand all |
| c | Collapse all | | c | Collapse all |
| **Selection** | | | **Selection (current scope)** | |
| Space | Mark/unmark snapshot | | Space | Mark/unmark snapshot |
| + | Select all visible snapshots | | + | Select all in scope |
| - | Unselect all visible snapshots | | - | Unselect all in scope |
| * | Invert selection | | * | Invert selection in scope |
| **Search & Filter** | | | **Search & Filter** | |
| / | Search (n=next, N=prev) | | / | Search (n=next, N=prev) |
| f | Filter visible items | | f | Filter visible items |
@ -141,6 +141,11 @@ search_match = "lightred"
- Terminal with UTF-8 support - Terminal with UTF-8 support
- Root privileges (for snapshot deletion) - Root privileges (for snapshot deletion)
## Author
**Florian Schermer**
[DATAZONE GmbH](https://datazone.de)
## License ## License
CDDL (Common Development and Distribution License) - same as OpenZFS. CDDL (Common Development and Distribution License) - same as OpenZFS.

View File

@ -561,15 +561,28 @@ impl App {
self.search_matches.contains(&real_index) self.search_matches.contains(&real_index)
} }
// Select/Unselect Group (like MC) // Select/Unselect Group (like MC) - works in current scope
pub fn select_group(&mut self) { pub fn select_group(&mut self) {
let current_parent = if let Some(real_idx) = self.nth_visible(self.selected) {
self.tree_items.get(real_idx).map(|ti| ti.parent_path.clone())
} else {
None
};
let mut count = 0; let mut count = 0;
for ti in &self.tree_items { for ti in &self.tree_items {
if ti.visible && ti.item.is_snapshot() { if ti.visible && ti.item.is_snapshot() {
let name = ti.item.full_name().to_string(); let in_scope = match &current_parent {
if !self.marked.contains(&name) { Some(parent) => &ti.parent_path == parent,
self.marked.push(name); None => true,
count += 1; };
if in_scope {
let name = ti.item.full_name().to_string();
if !self.marked.contains(&name) {
self.marked.push(name);
count += 1;
}
} }
} }
} }
@ -577,9 +590,23 @@ impl App {
} }
pub fn unselect_group(&mut self) { pub fn unselect_group(&mut self) {
let current_parent = if let Some(real_idx) = self.nth_visible(self.selected) {
self.tree_items.get(real_idx).map(|ti| ti.parent_path.clone())
} else {
None
};
let visible_names: Vec<String> = self.tree_items let visible_names: Vec<String> = self.tree_items
.iter() .iter()
.filter(|ti| ti.visible && ti.item.is_snapshot()) .filter(|ti| {
if !ti.visible || !ti.item.is_snapshot() {
return false;
}
match &current_parent {
Some(parent) => &ti.parent_path == parent,
None => true,
}
})
.map(|ti| ti.item.full_name().to_string()) .map(|ti| ti.item.full_name().to_string())
.collect(); .collect();
@ -590,17 +617,34 @@ impl App {
} }
pub fn invert_selection(&mut self) { pub fn invert_selection(&mut self) {
// Get current scope (parent dataset of selected item)
let current_parent = if let Some(real_idx) = self.nth_visible(self.selected) {
self.tree_items.get(real_idx).map(|ti| ti.parent_path.clone())
} else {
None
};
let mut count = 0;
for ti in &self.tree_items { for ti in &self.tree_items {
// Only invert in current scope (same parent)
if ti.visible && ti.item.is_snapshot() { if ti.visible && ti.item.is_snapshot() {
let name = ti.item.full_name().to_string(); let in_scope = match &current_parent {
if let Some(pos) = self.marked.iter().position(|x| x == &name) { Some(parent) => &ti.parent_path == parent,
self.marked.remove(pos); None => true, // If no parent, work on all visible
} else { };
self.marked.push(name);
if in_scope {
let name = ti.item.full_name().to_string();
if let Some(pos) = self.marked.iter().position(|x| x == &name) {
self.marked.remove(pos);
} else {
self.marked.push(name);
count += 1;
}
} }
} }
} }
self.message = Some(format!("{} snapshot(s) marked", self.marked.len())); self.message = Some(format!("Inverted {} in scope", count));
} }
// Expand all datasets // Expand all datasets

View File

@ -75,7 +75,7 @@ fn print_help() {
println!(" ↑/k, ↓/j Navigate up/down"); println!(" ↑/k, ↓/j Navigate up/down");
println!(" Enter/l Expand/collapse dataset"); println!(" Enter/l Expand/collapse dataset");
println!(" Space Mark/unmark snapshot"); println!(" Space Mark/unmark snapshot");
println!(" +/-/* Select/unselect/invert"); println!(" +/-/* Select/unselect/invert (in scope)");
println!(" s Toggle sort (Size/Date/Name)"); println!(" s Toggle sort (Size/Date/Name)");
println!(" / Search"); println!(" / Search");
println!(" f Filter"); println!(" f Filter");
@ -88,6 +88,9 @@ fn print_help() {
println!(); println!();
println!("CONFIG:"); println!("CONFIG:");
println!(" ~/.config/zfsdu/config.toml"); println!(" ~/.config/zfsdu/config.toml");
println!();
println!("(c) Florian Schermer - DATAZONE GmbH");
println!("https://datazone.de");
} }
fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> { fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {

View File

@ -407,11 +407,11 @@ fn draw_help(f: &mut Frame, app: &App) {
" e Expand all", " e Expand all",
" c Collapse all", " c Collapse all",
"", "",
" Selection:", " Selection (current scope):",
" Space Mark/unmark snapshot", " Space Mark/unmark snapshot",
" + Select all visible snapshots", " + Select all in scope",
" - Unselect all visible snapshots", " - Unselect all in scope",
" * Invert selection", " * Invert selection in scope",
"", "",
" Search & Filter:", " Search & Filter:",
" / Search (n=next, N=prev)", " / Search (n=next, N=prev)",
@ -431,6 +431,9 @@ fn draw_help(f: &mut Frame, app: &App) {
"", "",
" Config: ~/.config/zfsdu/config.toml", " Config: ~/.config/zfsdu/config.toml",
"", "",
" (c) Florian Schermer - DATAZONE GmbH",
" https://datazone.de",
"",
" Press any key to close", " Press any key to close",
]; ];