From 3e6547c267316c07af4137ab1a4a2bfeb8b8fbc5 Mon Sep 17 00:00:00 2001 From: evpeople Date: Sat, 17 Feb 2024 15:49:10 +0800 Subject: [PATCH] feat: Implemented conflict path detection using HashMap.#688 --- yazi-core/src/tab/mod.rs | 1 + yazi-core/src/tab/selected.rs | 120 ++++++++++++++++++++++++++++++++++ yazi-core/src/tab/tab.rs | 6 +- 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 yazi-core/src/tab/selected.rs diff --git a/yazi-core/src/tab/mod.rs b/yazi-core/src/tab/mod.rs index b002a2fa..59cf7ed9 100644 --- a/yazi-core/src/tab/mod.rs +++ b/yazi-core/src/tab/mod.rs @@ -4,6 +4,7 @@ mod config; mod finder; mod mode; mod preview; +mod selected; mod tab; pub use backstack::*; diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs new file mode 100644 index 00000000..a21771d4 --- /dev/null +++ b/yazi-core/src/tab/selected.rs @@ -0,0 +1,120 @@ +use std::{collections::{BTreeSet, HashMap}, path::PathBuf}; + +use yazi_shared::fs::Url; + +#[derive(Default)] +pub struct Selected { + inner: BTreeSet, + parents: HashMap, +} + +impl Selected { + pub fn new() -> Self { Selected { inner: BTreeSet::new(), parents: HashMap::new() } } + + pub fn insert(&mut self, url: Url) -> bool { + let url_buf = url.to_path_buf(); + + let mut current_path = url_buf.clone(); + while let Some(parent) = current_path.parent() { + if self.inner.contains(&Url::from(parent)) { + return false; + } + current_path = parent.to_path_buf(); + } + + if self.parents.contains_key(&url_buf) { + return false; + } + + let mut current_path = url_buf.clone(); + loop { + current_path = match current_path.parent() { + Some(parent) => parent.to_path_buf(), + None => break, + }; + let counter = self.parents.entry(current_path.clone()).or_insert(0); + *counter += 1; + } + self.inner.insert(url.clone()); + true + } + + pub fn remove(&mut self, url: &Url) -> bool { + if !self.inner.remove(&url) { + return false; + } + + let mut current_path = url.to_path_buf(); + loop { + current_path = match current_path.parent() { + Some(parent) => parent.to_path_buf(), + None => break, + }; + let counter = self.parents.entry(current_path.clone()).or_insert(0); + *counter -= 1; + if *counter == 0 { + self.parents.remove(¤t_path); + } + } + return true; + } + + pub fn is_empty(&self) -> bool { self.inner.is_empty() } + + pub fn clear(&mut self) { + self.parents.clear(); + self.inner.clear(); + } + + pub fn iter(&self) -> std::collections::btree_set::Iter { self.inner.iter() } +} +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn test_insert_non_conflicting() { + let mut selected = Selected::new(); + let url1 = Url::from(Path::new("/a/b")); + let url2 = Url::from(Path::new("/c/d")); + + assert!(selected.insert(url1), "Should successfully insert url1"); + assert!(selected.insert(url2), "Should successfully insert url2"); + assert_eq!(selected.inner.len(), 2, "There should be two URLs"); + } + + #[test] + fn test_insert_conflicting_parent() { + let mut selected = Selected::new(); + let parent_url = Url::from(Path::new("/a")); + let child_url = Url::from(Path::new("/a/b")); + + assert!(selected.insert(parent_url), "Should successfully insert parent_url"); + assert!(!selected.insert(child_url), "Should fail to insert child_url due to conflict"); + } + + #[test] + fn test_insert_conflicting_child() { + let mut selected = Selected::new(); + let child_url = Url::from(Path::new("/a/b/c")); + let parent_url = Url::from(Path::new("/a/b")); + let sibling_url = Url::from(Path::new("/a/b/d")); + + assert!(selected.insert(child_url), "Should successfully insert child_url"); + assert!(!selected.insert(parent_url), "Should fail to insert parent_url due to conflict"); + assert!(selected.insert(sibling_url), "Should successfully insert sibling_url"); + } + + #[test] + fn test_remove() { + let mut selected = Selected::new(); + let url = Url::from(Path::new("/a/b")); + + assert!(selected.insert(url.clone()), "Should successfully insert url"); + assert!(selected.remove(&url), "Should successfully remove url"); + assert!(selected.inner.is_empty(), "Inner set should be empty after removal"); + assert!(selected.parents.is_empty(), "Parents map should be empty after removal"); + } +} diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index f8f1678f..eef9ab47 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -1,11 +1,11 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use anyhow::Result; use tokio::task::JoinHandle; use yazi_shared::{fs::Url, render}; use super::{Backstack, Config, Finder, Mode, Preview}; -use crate::folder::{Folder, FolderStage}; +use crate::{folder::{Folder, FolderStage}, tab::selected::Selected}; pub struct Tab { pub mode: Mode, @@ -15,7 +15,7 @@ pub struct Tab { pub backstack: Backstack, pub history: BTreeMap, - pub selected: BTreeSet, + pub selected: Selected, pub preview: Preview, pub finder: Option,