From 29284613a0b2e48553a431c0ddbdf81cb9837610 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: Sat, 25 Jan 2025 20:39:24 +0800 Subject: [PATCH] fix: ensure the selected file list is always ordered (#2244) --- Cargo.lock | 2 ++ Cargo.toml | 1 + yazi-config/Cargo.toml | 2 +- yazi-core/Cargo.toml | 1 + yazi-core/src/manager/commands/bulk_rename.rs | 2 +- yazi-core/src/manager/commands/open.rs | 4 +-- yazi-core/src/manager/commands/remove.rs | 2 +- yazi-core/src/manager/commands/yank.rs | 2 +- yazi-core/src/manager/manager.rs | 8 ++--- yazi-core/src/tab/commands/copy.rs | 2 +- yazi-core/src/tab/commands/shell.rs | 2 +- yazi-core/src/tab/selected.rs | 30 +++++++++++-------- yazi-core/src/tab/tab.rs | 17 +++-------- yazi-fm/Cargo.toml | 1 + yazi-fm/src/lives/selected.rs | 13 ++++---- 15 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c287d8e..62e1fc2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3408,6 +3408,7 @@ dependencies = [ "crossterm", "dirs", "futures", + "indexmap", "libc", "notify", "parking_lot", @@ -3473,6 +3474,7 @@ dependencies = [ "crossterm", "fdlimit", "futures", + "indexmap", "libc", "mlua", "ratatui", diff --git a/Cargo.toml b/Cargo.toml index eec51a37..73e0e93e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "6.0.0" futures = "0.3.31" globset = "0.4.15" +indexmap = { version = "2.7.1", features = [ "serde" ] } libc = "0.2.169" md-5 = "0.10.6" mlua = { version = "0.10.2", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] } diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index d90bbf74..326cefcc 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -18,7 +18,7 @@ anyhow = { workspace = true } bitflags = { workspace = true } crossterm = { workspace = true } globset = { workspace = true } -indexmap = { version = "2.7.1", features = [ "serde" ] } +indexmap = { workspace = true } ratatui = { workspace = true } regex = { workspace = true } serde = { workspace = true } diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index e99bd6dc..572160f1 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -27,6 +27,7 @@ bitflags = { workspace = true } crossterm = { workspace = true } dirs = { workspace = true } futures = { workspace = true } +indexmap = { workspace = true } notify = { version = "8.0.0", default-features = false, features = [ "macos_fsevent" ] } parking_lot = { workspace = true } ratatui = { workspace = true } diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index f96f9a00..0ddc75f9 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -17,7 +17,7 @@ impl Manager { return AppProxy::notify_warn("Bulk rename", "No text opener found"); }; - let old: Vec<_> = self.selected_or_hovered(true).collect(); + let old: Vec<_> = self.selected_or_hovered().collect(); let root = max_common_root(&old); let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index acdbdca7..aa6ad355 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -33,9 +33,7 @@ impl Manager { return; }; - let selected = - if opt.hovered { vec![&hovered] } else { self.selected_or_hovered(true).collect() }; - + let selected = if opt.hovered { vec![&hovered] } else { self.selected_or_hovered().collect() }; if Self::quit_with_selected(opt, &selected) { return; } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index a59c6ca6..e9d65e7b 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -32,7 +32,7 @@ impl Manager { opt.targets = if opt.hovered { self.hovered().map_or(vec![], |h| vec![h.url.clone()]) } else { - self.selected_or_hovered(true).cloned().collect() + self.selected_or_hovered().cloned().collect() }; if opt.targets.is_empty() { diff --git a/yazi-core/src/manager/commands/yank.rs b/yazi-core/src/manager/commands/yank.rs index 68d94725..f9dc2b01 100644 --- a/yazi-core/src/manager/commands/yank.rs +++ b/yazi-core/src/manager/commands/yank.rs @@ -18,7 +18,7 @@ impl Manager { return; } - self.yanked = Yanked::new(opt.cut, self.selected_or_hovered(false).cloned().collect()); + self.yanked = Yanked::new(opt.cut, self.selected_or_hovered().cloned().collect()); render!(self.yanked.catchup_revision(true)); self.active_mut().escape_select(); diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index 0bf62564..51c44eb0 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -77,12 +77,12 @@ impl Manager { pub fn hovered_folder(&self) -> Option<&Folder> { self.active().hovered_folder() } #[inline] - pub fn selected_or_hovered(&self, reorder: bool) -> Box + '_> { - self.tabs.active().selected_or_hovered(reorder) + pub fn selected_or_hovered(&self) -> Box + '_> { + self.tabs.active().selected_or_hovered() } #[inline] - pub fn hovered_and_selected(&self, reorder: bool) -> Box + '_> { - self.tabs.active().hovered_and_selected(reorder) + pub fn hovered_and_selected(&self) -> Box + '_> { + self.tabs.active().hovered_and_selected() } } diff --git a/yazi-core/src/tab/commands/copy.rs b/yazi-core/src/tab/commands/copy.rs index c687ea68..dc028354 100644 --- a/yazi-core/src/tab/commands/copy.rs +++ b/yazi-core/src/tab/commands/copy.rs @@ -27,7 +27,7 @@ impl Tab { } let mut s = OsString::new(); - let mut it = self.selected_or_hovered(true).peekable(); + let mut it = self.selected_or_hovered().peekable(); while let Some(u) = it.next() { s.push(match opt.type_.as_ref() { "path" => opt.separator.transform(u), diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-core/src/tab/commands/shell.rs index f62c7cff..8a706593 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-core/src/tab/commands/shell.rs @@ -53,7 +53,7 @@ impl Tab { }; let cwd = opt.cwd.take().unwrap_or_else(|| self.cwd().clone()); - let selected = self.hovered_and_selected(true).cloned().collect(); + let selected = self.hovered_and_selected().cloned().collect(); tokio::spawn(async move { if opt.interactive { let mut result = diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 1a47bbee..9586c1c2 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,16 +1,17 @@ use std::{collections::HashMap, ops::Deref}; +use indexmap::IndexMap; use yazi_fs::FilesOp; use yazi_shared::{timestamp_us, url::Url}; #[derive(Default)] pub struct Selected { - inner: HashMap, + inner: IndexMap, parents: HashMap, } impl Deref for Selected { - type Target = HashMap; + type Target = IndexMap; fn deref(&self) -> &Self::Target { &self.inner } } @@ -66,21 +67,26 @@ impl Selected { pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 } pub fn remove_many(&mut self, urls: &[impl AsRef], 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.as_ref().parent_url() { - grouped.entry(p).or_default().push(u); + let affected = if same { + self.remove_same(urls) + } else { + let mut grouped: HashMap<_, Vec<_>> = Default::default(); + for u in urls { + if let Some(p) = u.as_ref().parent_url() { + grouped.entry(p).or_default().push(u); + } } + grouped.into_values().map(|v| self.remove_same(&v)).sum() + }; + + if affected > 0 { + self.inner.sort_unstable_by(|_, a, _, b| a.cmp(b)); } - grouped.into_values().map(|v| self.remove_same(&v)).sum() + affected } fn remove_same(&mut self, urls: &[impl AsRef]) -> usize { - let count = urls.iter().filter_map(|u| self.inner.remove(u.as_ref())).count(); + let count = urls.iter().filter_map(|u| self.inner.swap_remove(u.as_ref())).count(); if count == 0 { return 0; } diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index 2e9b083e..cb317e52 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -85,29 +85,20 @@ impl Tab { } } - pub fn selected_or_hovered(&self, reorder: bool) -> Box + '_> { + pub fn selected_or_hovered(&self) -> Box + '_> { if self.selected.is_empty() { Box::new(self.hovered().map(|h| vec![&h.url]).unwrap_or_default().into_iter()) - } else if !reorder { - Box::new(self.selected.keys()) } else { - let mut vec: Vec<_> = self.selected.iter().collect(); - vec.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); - Box::new(vec.into_iter().map(|(k, _)| k)) + Box::new(self.selected.keys()) } } - pub fn hovered_and_selected(&self, reorder: bool) -> Box + '_> { + pub fn hovered_and_selected(&self) -> Box + '_> { let Some(h) = self.hovered() else { return Box::new(iter::empty()) }; - if self.selected.is_empty() { Box::new([&h.url, &h.url].into_iter()) - } else if !reorder { - Box::new([&h.url].into_iter().chain(self.selected.keys())) } else { - let mut vec: Vec<_> = self.selected.iter().collect(); - vec.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); - Box::new([&h.url].into_iter().chain(vec.into_iter().map(|(k, _)| k))) + Box::new([&h.url].into_iter().chain(self.selected.keys())) } } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 9bc7be01..fcdffd36 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -31,6 +31,7 @@ better-panic = "0.3.0" crossterm = { workspace = true } fdlimit = "0.3.0" futures = { workspace = true } +indexmap = { workspace = true } mlua = { workspace = true } ratatui = { workspace = true } scopeguard = { workspace = true } diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs index d1998410..c2af5465 100644 --- a/yazi-fm/src/lives/selected.rs +++ b/yazi-fm/src/lives/selected.rs @@ -1,5 +1,6 @@ -use std::{collections::{HashMap, hash_map}, ops::Deref}; +use std::ops::Deref; +use indexmap::{IndexMap, map::Keys}; use mlua::{AnyUserData, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, UserDataRefMut}; use yazi_plugin::{bindings::Cast, url::Url}; @@ -7,23 +8,23 @@ use super::{Iter, Lives}; #[derive(Clone, Copy)] pub(super) struct Selected { - inner: *const HashMap, + inner: *const IndexMap, } impl Deref for Selected { - type Target = HashMap; + type Target = IndexMap; fn deref(&self) -> &Self::Target { self.inner() } } impl Selected { #[inline] - pub(super) fn make(inner: &HashMap) -> mlua::Result { + pub(super) fn make(inner: &IndexMap) -> mlua::Result { Lives::scoped_userdata(Self { inner }) } #[inline] - fn inner(&self) -> &'static HashMap { unsafe { &*self.inner } } + fn inner(&self) -> &'static IndexMap { unsafe { &*self.inner } } } impl UserData for Selected { @@ -32,7 +33,7 @@ impl UserData for Selected { methods.add_meta_method(MetaMethod::Pairs, |lua, me, ()| { let iter = lua.create_function( - |lua, mut iter: UserDataRefMut, _>>| { + |lua, mut iter: UserDataRefMut, _>>| { if let Some(next) = iter.next() { (next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua) } else {