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]
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" },

View file

@ -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.",

View file

@ -24,15 +24,21 @@ impl From<Option<bool>> for Opt {
impl Tab {
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 (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() {

View file

@ -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);