fix: nested conflict detection exception when performing cross-level searches (#769)

This commit is contained in:
三咲雅 · Misaki Masa 2024-03-04 11:27:30 +08:00 committed by GitHub
parent cc54205cc7
commit b4c9ec1de2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 63 deletions

View file

@ -27,12 +27,13 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
[opener] [opener]
edit = [ edit = [
{ exec = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, { 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 = [ open = [
{ exec = 'xdg-open "$@"', desc = "Open", for = "linux" }, { exec = 'xdg-open "$@"', desc = "Open", for = "linux" },
{ exec = 'open "$@"', desc = "Open", for = "macos" }, { 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 = [ reveal = [
{ exec = 'open -R "$1"', desc = "Reveal", for = "macos" }, { exec = 'open -R "$1"', desc = "Reveal", for = "macos" },

View file

@ -108,9 +108,10 @@ impl Tab {
let urls: Vec<_> = let urls: Vec<_> =
indices.into_iter().filter_map(|i| self.current.files.get(i)).map(|f| &f.url).collect(); 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 { if !select {
self.selected.remove_many(&urls); self.selected.remove_many(&urls, same);
} else if self.selected.add_many(&urls) != urls.len() { } else if self.selected.add_many(&urls, same) != urls.len() {
AppProxy::warn( AppProxy::warn(
"Escape visual mode", "Escape visual mode",
"Some files cannot be selected, due to path nesting conflict.", "Some files cannot be selected, due to path nesting conflict.",

View file

@ -24,15 +24,21 @@ impl From<Option<bool>> for Opt {
impl Tab { impl Tab {
pub fn select_all(&mut self, opt: impl Into<Opt>) { pub fn select_all(&mut self, opt: impl Into<Opt>) {
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 iter = self.current.files.iter().map(|f| &f.url);
let (removal, addition): (Vec<_>, Vec<_>) = match opt.into().state { let (removal, addition): (Vec<_>, Vec<_>) = if state == Some(true) {
Some(true) => (vec![], iter.collect()), (vec![], iter.collect())
Some(false) => (iter.collect(), vec![]), } else {
None => iter.partition(|&u| self.selected.contains(u)), iter.partition(|&u| self.selected.contains(u))
}; };
render!(self.selected.remove_many(&removal) > 0); let same = !self.current.cwd.is_search();
let added = self.selected.add_many(&addition); render!(self.selected.remove_many(&removal, same) > 0);
let added = self.selected.add_many(&addition, same);
render!(added > 0); render!(added > 0);
if added != addition.len() { if added != addition.len() {

View file

@ -16,46 +16,23 @@ impl Deref for Selected {
impl Selected { impl Selected {
#[inline] #[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. pub fn add_many(&mut self, urls: &[&Url], same: bool) -> usize {
/// if same {
/// This method attempts to add a slice of `Url` references to the internal return self.add_same(urls);
/// 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 let mut grouped: HashMap<_, Vec<_>> = Default::default();
/// parent directories. for &u in urls {
/// if let Some(p) = u.parent_url() {
/// The addition will fail under the following conditions: grouped.entry(p).or_default().push(u);
/// - 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. grouped.into_values().map(|v| self.add_same(&v)).sum()
/// }
/// 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. fn add_same(&mut self, urls: &[&Url]) -> usize {
///
/// # 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 {
// If it has appeared as a parent // If it has appeared as a parent
let urls: Vec<_> = urls.iter().filter(|&&u| !self.parents.contains_key(u)).collect(); let urls: Vec<_> = urls.iter().filter(|&&u| !self.parents.contains_key(u)).collect();
if urls.is_empty() { if urls.is_empty() {
@ -84,9 +61,23 @@ impl Selected {
} }
#[inline] #[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(); let count = urls.iter().map(|&u| self.inner.remove(u)).filter(|&b| b).count();
if count == 0 { if count == 0 {
return 0; return 0;
@ -106,9 +97,11 @@ impl Selected {
count count
} }
pub fn clear(&mut self) { pub fn clear(&mut self) -> bool {
let b = !self.inner.is_empty();
self.inner.clear(); self.inner.clear();
self.parents.clear(); self.parents.clear();
b
} }
} }
@ -160,7 +153,7 @@ mod tests {
assert_eq!( assert_eq!(
3, 3,
s.add_many(&[ s.add_same(&[
&Url::from("/parent/child1"), &Url::from("/parent/child1"),
&Url::from("/parent/child2"), &Url::from("/parent/child2"),
&Url::from("/parent/child3") &Url::from("/parent/child3")
@ -173,7 +166,7 @@ mod tests {
let mut s = Selected::default(); let mut s = Selected::default();
s.add(&Url::from("/parent")); 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] #[test]
@ -181,14 +174,14 @@ mod tests {
let mut s = Selected::default(); let mut s = Selected::default();
s.add(&Url::from("/parent/child1")); 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] #[test]
fn insert_many_empty_urls_list() { fn insert_many_empty_urls_list() {
let mut s = Selected::default(); let mut s = Selected::default();
assert_eq!(0, s.add_many(&[])); assert_eq!(0, s.add_same(&[]));
} }
#[test] #[test]
@ -198,7 +191,7 @@ mod tests {
s.add(&Url::from("/parent/child")); s.add(&Url::from("/parent/child"));
assert_eq!( assert_eq!(
0, 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] #[test]
@ -206,7 +199,7 @@ mod tests {
let mut s = Selected::default(); let mut s = Selected::default();
s.add(&Url::from("/a")); 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] #[test]
@ -214,15 +207,15 @@ mod tests {
let mut s = Selected::default(); let mut s = Selected::default();
s.add(&Url::from("/a/b")); s.add(&Url::from("/a/b"));
assert_eq!(0, s.add_many(&[&Url::from("/a")])); assert_eq!(0, s.add_same(&[&Url::from("/a")]));
assert_eq!(1, s.add_many(&[&Url::from("/b"), &Url::from("/a")])); assert_eq!(1, s.add_same(&[&Url::from("/b"), &Url::from("/a")]));
} }
#[test] #[test]
fn insert_many_sibling_directories_success() { fn insert_many_sibling_directories_success() {
let mut s = Selected::default(); 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] #[test]
@ -230,7 +223,7 @@ mod tests {
let mut s = Selected::default(); let mut s = Selected::default();
s.add(&Url::from("/a/b")); 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] #[test]
@ -240,7 +233,7 @@ mod tests {
let child1 = Url::from("/parent/child1"); let child1 = Url::from("/parent/child1");
let child2 = Url::from("/parent/child2"); let child2 = Url::from("/parent/child2");
let child3 = Url::from("/parent/child3"); 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!(s.remove(&child1));
assert_eq!(s.inner.len(), 2); assert_eq!(s.inner.len(), 2);