use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use crate::app::mode::Mode;
use crate::app::state::AppState;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct MarkSet {
paths: BTreeSet<PathBuf>,
}
impl MarkSet {
pub fn toggle(&mut self, path: &Path) -> bool {
if self.paths.remove(path) {
false
} else {
self.paths.insert(path.to_owned());
true
}
}
pub fn extend(&mut self, paths: impl IntoIterator<Item = PathBuf>) {
self.paths.extend(paths);
}
pub fn contains(&self, path: &Path) -> bool {
self.paths.contains(path)
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn clear(&mut self) -> usize {
let count = self.paths.len();
self.paths.clear();
count
}
pub fn remove(&mut self, path: &Path) {
self.paths.remove(path);
}
pub fn paths(&self) -> impl Iterator<Item = &PathBuf> {
self.paths.iter()
}
}
pub fn visual_bounds(state: &AppState) -> Option<(usize, usize)> {
let Mode::Visual { anchor } = &state.mode else {
return None;
};
let anchor_row = state
.filtered_entries()
.position(|(_, e)| &e.path == anchor)
.unwrap_or(state.selected);
Some((
anchor_row.min(state.selected),
anchor_row.max(state.selected),
))
}
pub fn visual_paths(state: &AppState) -> Vec<PathBuf> {
match visual_bounds(state) {
Some((first, last)) => state
.filtered_entries()
.skip(first)
.take(last - first + 1)
.map(|(_, e)| e.path.clone())
.collect(),
None => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toggling_twice_unmarks() {
let mut marks = MarkSet::default();
let path = Path::new("a.txt");
assert!(marks.toggle(path));
assert!(marks.contains(path));
assert!(!marks.toggle(path));
assert!(marks.is_empty());
}
}