1use std::collections::BTreeSet;
14use std::path::{Path, PathBuf};
15
16use crate::app::mode::Mode;
17use crate::app::state::AppState;
18
19#[derive(Debug, Default, Clone, PartialEq, Eq)]
21pub struct MarkSet {
22 paths: BTreeSet<PathBuf>,
23}
24
25impl MarkSet {
26 pub fn toggle(&mut self, path: &Path) -> bool {
29 if self.paths.remove(path) {
30 false
31 } else {
32 self.paths.insert(path.to_owned());
33 true
34 }
35 }
36
37 pub fn extend(&mut self, paths: impl IntoIterator<Item = PathBuf>) {
39 self.paths.extend(paths);
40 }
41
42 pub fn contains(&self, path: &Path) -> bool {
44 self.paths.contains(path)
45 }
46
47 pub fn len(&self) -> usize {
49 self.paths.len()
50 }
51
52 pub fn is_empty(&self) -> bool {
54 self.paths.is_empty()
55 }
56
57 pub fn clear(&mut self) -> usize {
59 let count = self.paths.len();
60 self.paths.clear();
61 count
62 }
63
64 pub fn remove(&mut self, path: &Path) {
66 self.paths.remove(path);
67 }
68
69 pub fn paths(&self) -> impl Iterator<Item = &PathBuf> {
71 self.paths.iter()
72 }
73}
74
75pub fn visual_bounds(state: &AppState) -> Option<(usize, usize)> {
82 let Mode::Visual { anchor } = &state.mode else {
83 return None;
84 };
85 let anchor_row = state
86 .filtered_entries()
87 .position(|(_, e)| &e.path == anchor)
88 .unwrap_or(state.selected);
89 Some((
90 anchor_row.min(state.selected),
91 anchor_row.max(state.selected),
92 ))
93}
94
95pub fn visual_paths(state: &AppState) -> Vec<PathBuf> {
97 match visual_bounds(state) {
98 Some((first, last)) => state
99 .filtered_entries()
100 .skip(first)
101 .take(last - first + 1)
102 .map(|(_, e)| e.path.clone())
103 .collect(),
104 None => Vec::new(),
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn toggling_twice_unmarks() {
114 let mut marks = MarkSet::default();
115 let path = Path::new("a.txt");
116 assert!(marks.toggle(path));
117 assert!(marks.contains(path));
118 assert!(!marks.toggle(path));
119 assert!(marks.is_empty());
120 }
121}