use hashbrown::HashSet;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default)]
pub struct Selection {
selected: HashSet<PathBuf>,
anchor: Option<usize>,
}
impl Selection {
pub fn new() -> Self {
Self::default()
}
pub fn toggle(&mut self, path: &Path) {
if self.selected.contains(path) {
self.selected.remove(path);
} else {
self.selected.insert(path.to_path_buf());
}
}
pub fn select(&mut self, path: &Path) {
self.selected.insert(path.to_path_buf());
}
pub fn deselect(&mut self, path: &Path) {
self.selected.remove(path);
}
pub fn clear(&mut self) {
self.selected.clear();
}
pub fn is_selected(&self, path: &Path) -> bool {
self.selected.contains(path)
}
pub fn count(&self) -> usize {
self.selected.len()
}
pub fn is_empty(&self) -> bool {
self.selected.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Path> {
self.selected.iter().map(|p| p.as_path())
}
pub fn paths(&self) -> Vec<&Path> {
self.iter().collect()
}
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&PathBuf) -> bool,
{
self.selected.retain(f);
}
pub fn select_all<I, P>(&mut self, paths: I)
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
for path in paths {
self.selected.insert(path.as_ref().to_path_buf());
}
}
pub fn anchor(&self) -> Option<usize> {
self.anchor
}
pub fn set_anchor(&mut self, index: usize) {
self.anchor = Some(index);
}
pub fn clear_anchor(&mut self) {
self.anchor = None;
}
pub fn has_anchor(&self) -> bool {
self.anchor.is_some()
}
pub fn select_range<F>(&mut self, from: usize, to: usize, get_path: F)
where
F: Fn(usize) -> Option<PathBuf>,
{
let (start, end) = if from <= to { (from, to) } else { (to, from) };
for i in start..=end {
if let Some(path) = get_path(i) {
self.selected.insert(path);
}
}
}
pub fn extend_to<F>(&mut self, from: usize, to: usize, get_path: F, _total: usize)
where
F: Fn(usize) -> Option<PathBuf>,
{
let anchor = self.anchor.unwrap_or(from);
if self.anchor.is_none() {
self.anchor = Some(from);
}
self.select_range(anchor, to, get_path);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_is_empty() {
let selection = Selection::new();
assert!(selection.is_empty());
assert_eq!(selection.count(), 0);
}
#[test]
fn test_select_and_deselect() {
let mut selection = Selection::new();
let path = Path::new("/test/file.txt");
selection.select(path);
assert!(selection.is_selected(path));
assert_eq!(selection.count(), 1);
selection.deselect(path);
assert!(!selection.is_selected(path));
assert!(selection.is_empty());
}
#[test]
fn test_toggle() {
let mut selection = Selection::new();
let path = Path::new("/test/file.txt");
selection.toggle(path);
assert!(selection.is_selected(path));
selection.toggle(path);
assert!(!selection.is_selected(path));
}
#[test]
fn test_clear() {
let mut selection = Selection::new();
selection.select(Path::new("/a"));
selection.select(Path::new("/b"));
selection.select(Path::new("/c"));
assert_eq!(selection.count(), 3);
selection.clear();
assert!(selection.is_empty());
}
#[test]
fn test_iter() {
let mut selection = Selection::new();
selection.select(Path::new("/a"));
selection.select(Path::new("/b"));
let paths: Vec<_> = selection.iter().collect();
assert_eq!(paths.len(), 2);
}
#[test]
fn test_select_all() {
let mut selection = Selection::new();
selection.select_all(["/a", "/b", "/c"]);
assert_eq!(selection.count(), 3);
}
#[test]
fn test_retain() {
let mut selection = Selection::new();
selection.select(Path::new("/keep/a"));
selection.select(Path::new("/remove/b"));
selection.select(Path::new("/keep/c"));
selection.retain(|p| p.starts_with("/keep"));
assert_eq!(selection.count(), 2);
assert!(selection.is_selected(Path::new("/keep/a")));
assert!(!selection.is_selected(Path::new("/remove/b")));
}
#[test]
fn test_anchor_lifecycle() {
let mut selection = Selection::new();
assert!(!selection.has_anchor());
assert_eq!(selection.anchor(), None);
selection.set_anchor(5);
assert!(selection.has_anchor());
assert_eq!(selection.anchor(), Some(5));
selection.clear_anchor();
assert!(!selection.has_anchor());
assert_eq!(selection.anchor(), None);
}
#[test]
fn test_select_range() {
let mut selection = Selection::new();
let paths = vec![
PathBuf::from("/a"),
PathBuf::from("/b"),
PathBuf::from("/c"),
PathBuf::from("/d"),
PathBuf::from("/e"),
];
selection.select_range(1, 3, |i| paths.get(i).cloned());
assert_eq!(selection.count(), 3);
assert!(!selection.is_selected(Path::new("/a")));
assert!(selection.is_selected(Path::new("/b")));
assert!(selection.is_selected(Path::new("/c")));
assert!(selection.is_selected(Path::new("/d")));
assert!(!selection.is_selected(Path::new("/e")));
}
#[test]
fn test_select_range_reversed() {
let mut selection = Selection::new();
let paths = vec![
PathBuf::from("/a"),
PathBuf::from("/b"),
PathBuf::from("/c"),
];
selection.select_range(2, 0, |i| paths.get(i).cloned());
assert_eq!(selection.count(), 3);
}
#[test]
fn test_extend_to_sets_anchor() {
let mut selection = Selection::new();
let paths = vec![
PathBuf::from("/a"),
PathBuf::from("/b"),
PathBuf::from("/c"),
];
selection.extend_to(0, 2, |i| paths.get(i).cloned(), paths.len());
assert_eq!(selection.anchor(), Some(0));
assert_eq!(selection.count(), 3);
}
#[test]
fn test_extend_to_uses_existing_anchor() {
let mut selection = Selection::new();
let paths = vec![
PathBuf::from("/a"),
PathBuf::from("/b"),
PathBuf::from("/c"),
PathBuf::from("/d"),
PathBuf::from("/e"),
];
selection.set_anchor(1);
selection.extend_to(2, 4, |i| paths.get(i).cloned(), paths.len());
assert_eq!(selection.anchor(), Some(1));
assert_eq!(selection.count(), 4); assert!(!selection.is_selected(Path::new("/a")));
assert!(selection.is_selected(Path::new("/b")));
assert!(selection.is_selected(Path::new("/e")));
}
}