From b4c9ec1de2ca5896453d3d05e51c2a6eb1cf396d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Mon, 4 Mar 2024 11:27:30 +0800 Subject: [PATCH] fix: nested conflict detection exception when performing cross-level searches (#769) --- yazi-config/preset/yazi.toml | 5 +- yazi-core/src/tab/commands/escape.rs | 5 +- yazi-core/src/tab/commands/select_all.rs | 18 +++-- yazi-core/src/tab/selected.rs | 99 +++++++++++------------- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index e9d46403..48a3e059 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -27,12 +27,13 @@ ueberzug_offset = [ 0, 0, 0, 0 ] [opener] edit = [ { exec = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, - { exec = 'code "%*"', orphan = true, for = "windows" }, + { exec = 'code "%*"', orphan = true, desc = "code", for = "windows" }, + { exec = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" }, ] open = [ { exec = 'xdg-open "$@"', desc = "Open", for = "linux" }, { exec = 'open "$@"', desc = "Open", for = "macos" }, - { exec = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" } + { exec = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, ] reveal = [ { exec = 'open -R "$1"', desc = "Reveal", for = "macos" }, diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index bd76c4a2..f730e79c 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -108,9 +108,10 @@ impl Tab { let urls: Vec<_> = indices.into_iter().filter_map(|i| self.current.files.get(i)).map(|f| &f.url).collect(); + let same = !self.current.cwd.is_search(); if !select { - self.selected.remove_many(&urls); - } else if self.selected.add_many(&urls) != urls.len() { + self.selected.remove_many(&urls, same); + } else if self.selected.add_many(&urls, same) != urls.len() { AppProxy::warn( "Escape visual mode", "Some files cannot be selected, due to path nesting conflict.", diff --git a/yazi-core/src/tab/commands/select_all.rs b/yazi-core/src/tab/commands/select_all.rs index 5182e42a..9fa7d47a 100644 --- a/yazi-core/src/tab/commands/select_all.rs +++ b/yazi-core/src/tab/commands/select_all.rs @@ -24,15 +24,21 @@ impl From> for Opt { impl Tab { pub fn select_all(&mut self, opt: impl Into) { + let state = opt.into().state; + if state == Some(false) { + return render!(self.selected.clear()); + } + let iter = self.current.files.iter().map(|f| &f.url); - let (removal, addition): (Vec<_>, Vec<_>) = match opt.into().state { - Some(true) => (vec![], iter.collect()), - Some(false) => (iter.collect(), vec![]), - None => iter.partition(|&u| self.selected.contains(u)), + let (removal, addition): (Vec<_>, Vec<_>) = if state == Some(true) { + (vec![], iter.collect()) + } else { + iter.partition(|&u| self.selected.contains(u)) }; - render!(self.selected.remove_many(&removal) > 0); - let added = self.selected.add_many(&addition); + let same = !self.current.cwd.is_search(); + render!(self.selected.remove_many(&removal, same) > 0); + let added = self.selected.add_many(&addition, same); render!(added > 0); if added != addition.len() { diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 5dbfcbcc..275616de 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -16,46 +16,23 @@ impl Deref for Selected { impl Selected { #[inline] - pub fn add(&mut self, url: &Url) -> bool { self.add_many(&[url]) == 1 } + pub fn add(&mut self, url: &Url) -> bool { self.add_same(&[url]) == 1 } - /// Adds a list of URLs to the user structure. - /// - /// This method attempts to add a slice of `Url` references to the internal - /// structure, ensuring that all URLs have the same parent directory. For - /// example, URLs such as `/a/b/c`, `/a/b/d`, `/a/b/e`, and `/a/b/f` are - /// acceptable, while `/a/b/c` and `/a/e/f` would not be, due to differing - /// parent directories. - /// - /// The addition will fail under the following conditions: - /// - Any of the URLs already exists within the `inner` collection. - /// - The parent directory of the URLs already exists as a key in the - /// `parents` map. - /// - /// When the provided list of URLs is empty, the method will return `true` as - /// there are no URLs to process, which is considered a successful operation. - /// - /// # Arguments - /// - /// * `urls` - A slice of references to `Url` objects that are to be added. - /// All URLs should have the same parent path. - /// - /// # Returns - /// - /// Return the number of URLs that did not conflict, - /// even if they were already present in the structure and were not added. - /// - /// # Examples - /// - /// ``` - /// # use yazi_core::tab::Selected; - /// # use yazi_shared::fs::Url; - /// let mut s = Selected::default(); - /// - /// let url1 = Url::from("/a/b/c"); - /// let url2 = Url::from("/a/b/d"); - /// assert_eq!(2, s.add_many(&[&url1, &url2])); - /// ``` - pub fn add_many(&mut self, urls: &[&Url]) -> usize { + pub fn add_many(&mut self, urls: &[&Url], same: bool) -> usize { + if same { + return self.add_same(urls); + } + + let mut grouped: HashMap<_, Vec<_>> = Default::default(); + for &u in urls { + if let Some(p) = u.parent_url() { + grouped.entry(p).or_default().push(u); + } + } + grouped.into_values().map(|v| self.add_same(&v)).sum() + } + + fn add_same(&mut self, urls: &[&Url]) -> usize { // If it has appeared as a parent let urls: Vec<_> = urls.iter().filter(|&&u| !self.parents.contains_key(u)).collect(); if urls.is_empty() { @@ -84,9 +61,23 @@ impl Selected { } #[inline] - pub fn remove(&mut self, url: &Url) -> bool { self.remove_many(&[url]) == 1 } + pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 } - pub fn remove_many(&mut self, urls: &[&Url]) -> usize { + pub fn remove_many(&mut self, urls: &[&Url], same: bool) -> usize { + if same { + return self.remove_same(urls); + } + + let mut grouped: HashMap<_, Vec<_>> = Default::default(); + for &u in urls { + if let Some(p) = u.parent_url() { + grouped.entry(p).or_default().push(u); + } + } + grouped.into_values().map(|v| self.remove_same(&v)).sum() + } + + fn remove_same(&mut self, urls: &[&Url]) -> usize { let count = urls.iter().map(|&u| self.inner.remove(u)).filter(|&b| b).count(); if count == 0 { return 0; @@ -106,9 +97,11 @@ impl Selected { count } - pub fn clear(&mut self) { + pub fn clear(&mut self) -> bool { + let b = !self.inner.is_empty(); self.inner.clear(); self.parents.clear(); + b } } @@ -160,7 +153,7 @@ mod tests { assert_eq!( 3, - s.add_many(&[ + s.add_same(&[ &Url::from("/parent/child1"), &Url::from("/parent/child2"), &Url::from("/parent/child3") @@ -173,7 +166,7 @@ mod tests { let mut s = Selected::default(); s.add(&Url::from("/parent")); - assert_eq!(0, s.add_many(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); + assert_eq!(0, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); } #[test] @@ -181,14 +174,14 @@ mod tests { let mut s = Selected::default(); s.add(&Url::from("/parent/child1")); - assert_eq!(2, s.add_many(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); + assert_eq!(2, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); } #[test] fn insert_many_empty_urls_list() { let mut s = Selected::default(); - assert_eq!(0, s.add_many(&[])); + assert_eq!(0, s.add_same(&[])); } #[test] @@ -198,7 +191,7 @@ mod tests { s.add(&Url::from("/parent/child")); assert_eq!( 0, - s.add_many(&[&Url::from("/parent/child/child1"), &Url::from("/parent/child/child2")]) + s.add_same(&[&Url::from("/parent/child/child1"), &Url::from("/parent/child/child2")]) ); } #[test] @@ -206,7 +199,7 @@ mod tests { let mut s = Selected::default(); s.add(&Url::from("/a")); - assert_eq!(0, s.add_many(&[&Url::from("/a/b")])); + assert_eq!(0, s.add_same(&[&Url::from("/a/b")])); } #[test] @@ -214,15 +207,15 @@ mod tests { let mut s = Selected::default(); s.add(&Url::from("/a/b")); - assert_eq!(0, s.add_many(&[&Url::from("/a")])); - assert_eq!(1, s.add_many(&[&Url::from("/b"), &Url::from("/a")])); + assert_eq!(0, s.add_same(&[&Url::from("/a")])); + assert_eq!(1, s.add_same(&[&Url::from("/b"), &Url::from("/a")])); } #[test] fn insert_many_sibling_directories_success() { let mut s = Selected::default(); - assert_eq!(2, s.add_many(&[&Url::from("/a/b"), &Url::from("/a/c")])); + assert_eq!(2, s.add_same(&[&Url::from("/a/b"), &Url::from("/a/c")])); } #[test] @@ -230,7 +223,7 @@ mod tests { let mut s = Selected::default(); s.add(&Url::from("/a/b")); - assert_eq!(0, s.add_many(&[&Url::from("/a/b/c")])); + assert_eq!(0, s.add_same(&[&Url::from("/a/b/c")])); } #[test] @@ -240,7 +233,7 @@ mod tests { let child1 = Url::from("/parent/child1"); let child2 = Url::from("/parent/child2"); let child3 = Url::from("/parent/child3"); - assert_eq!(3, s.add_many(&[&child1, &child2, &child3])); + assert_eq!(3, s.add_same(&[&child1, &child2, &child3])); assert!(s.remove(&child1)); assert_eq!(s.inner.len(), 2);