irongit

feat: bulk move/delete operations in focus list

huncholanehuncholaneauthored
parent c3974dfcommit 3b4230f98fc3d5cd026007e61f104aa04248e202Browse files

1 file changed, +65 -1

+65-1src/views/browser.rs
@@ -55,6 +55,8 @@ pub struct BrowserView {
5555 focused: Vec<Vec<PathSegment>>,
5656 pub focus_mode: bool,
5757 focus_dirty: bool,
58+ // Bulk selection in focus root (indices into self.focused)
59+ bulk_selected: std::collections::BTreeSet<usize>,
5860 }
5961
6062 impl BrowserView {
@@ -75,6 +77,7 @@ impl BrowserView {
7577 focused: Vec::new(),
7678 focus_mode: false,
7779 focus_dirty: false,
80+ bulk_selected: std::collections::BTreeSet::new(),
7881 }
7982 }
8083
@@ -432,6 +435,61 @@ impl BrowserView {
432435 self.focus_mode = !self.focus_mode;
433436 self.path.clear();
434437 self.selection = 0;
438+ self.bulk_selected.clear();
439+ }
440+
441+ // Bulk operations (focus root only)
442+ if in_focus_root {
443+ // Space: toggle bulk select on current item, move down
444+ if ui.input(|i| i.key_pressed(egui::Key::Space)) {
445+ if !self.bulk_selected.remove(&self.selection) {
446+ self.bulk_selected.insert(self.selection);
447+ }
448+ if self.selection + 1 < self.focused.len() {
449+ self.selection += 1;
450+ }
451+ }
452+ // Escape: clear bulk selection
453+ if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
454+ self.bulk_selected.clear();
455+ }
456+ // 'p': move selected items below current position
457+ if ui.input(|i| i.key_pressed(egui::Key::P) && !i.modifiers.ctrl) && !self.bulk_selected.is_empty() {
458+ // Collect selected items (in reverse order to preserve indices during removal)
459+ let mut selected_items: Vec<Vec<PathSegment>> = self.bulk_selected.iter().rev()
460+ .filter_map(|&idx| {
461+ if idx < self.focused.len() { Some(self.focused[idx].clone()) } else { None }
462+ })
463+ .collect();
464+ selected_items.reverse();
465+ // Remove selected from their current positions (reverse order)
466+ for &idx in self.bulk_selected.iter().rev() {
467+ if idx < self.focused.len() {
468+ self.focused.remove(idx);
469+ }
470+ }
471+ // Adjust insertion point (current selection may have shifted)
472+ let insert_at = (self.selection + 1).min(self.focused.len());
473+ // Insert after current position
474+ for (i, item) in selected_items.into_iter().enumerate() {
475+ self.focused.insert(insert_at + i, item);
476+ }
477+ self.bulk_selected.clear();
478+ self.focus_dirty = true;
479+ }
480+ // 'd': delete selected items from focus list
481+ if ui.input(|i| i.key_pressed(egui::Key::D)) && !self.bulk_selected.is_empty() {
482+ for &idx in self.bulk_selected.iter().rev() {
483+ if idx < self.focused.len() {
484+ self.focused.remove(idx);
485+ }
486+ }
487+ self.bulk_selected.clear();
488+ if self.selection >= self.focused.len() && !self.focused.is_empty() {
489+ self.selection = self.focused.len() - 1;
490+ }
491+ self.focus_dirty = true;
492+ }
435493 }
436494
437495 // Copy selected value: c or Ctrl+C
@@ -803,6 +861,7 @@ impl BrowserView {
803861 let current_path = self.path.clone();
804862 let focused = self.focused.clone();
805863 let is_focus_root = self.focus_mode && self.path.is_empty();
864+ let bulk_selected = self.bulk_selected.clone();
806865
807866 // Map original selection index → filtered list position
808867 let filtered_pos = entries.iter().position(|(orig, _)| *orig == self.selection)
@@ -818,7 +877,12 @@ impl BrowserView {
818877 let (orig_idx, entry) = &entries[i];
819878 let (is_hovered, row_id) = crate::widgets::prev_frame_hover(ui.ctx(), ui.id(), i);
820879
821- let bg = if is_selected {
880+ let is_bulk = is_focus_root && bulk_selected.contains(orig_idx);
881+ let bg = if is_bulk {
882+ egui::Color32::from_rgba_unmultiplied(
883+ CatppuccinMocha::MAUVE.r(), CatppuccinMocha::MAUVE.g(), CatppuccinMocha::MAUVE.b(), 30,
884+ )
885+ } else if is_selected {
822886 CatppuccinMocha::SURFACE0
823887 } else if is_hovered {
824888 egui::Color32::from_rgba_unmultiplied(