From f6f2c30709995b856534e6269e2f9349412eb383 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: Thu, 28 Mar 2024 00:13:29 +0800 Subject: [PATCH] feat: time-based selection order preservation (#843) --- yazi-adaptor/src/image.rs | 2 +- yazi-core/src/manager/commands/bulk_rename.rs | 2 +- yazi-core/src/manager/commands/open.rs | 2 +- yazi-core/src/manager/commands/remove.rs | 2 +- yazi-core/src/manager/commands/yank.rs | 2 +- yazi-core/src/manager/manager.rs | 4 +++- yazi-core/src/tab/commands/copy.rs | 2 +- yazi-core/src/tab/commands/select_all.rs | 2 +- yazi-core/src/tab/commands/visual_mode.rs | 6 +++--- yazi-core/src/tab/mode.rs | 10 +++++----- yazi-core/src/tab/selected.rs | 18 +++++++++--------- yazi-core/src/tab/tab.rs | 10 +++++++--- yazi-fm/src/lives/file.rs | 2 +- yazi-fm/src/lives/selected.rs | 16 +++++++++------- yazi-fm/src/logs.rs | 4 ++-- yazi-shared/src/throttle.rs | 8 ++++---- yazi-shared/src/time.rs | 4 ++-- 17 files changed, 52 insertions(+), 44 deletions(-) diff --git a/yazi-adaptor/src/image.rs b/yazi-adaptor/src/image.rs index 3fbb3b9f..8db29c18 100644 --- a/yazi-adaptor/src/image.rs +++ b/yazi-adaptor/src/image.rs @@ -39,7 +39,7 @@ impl Image { img.as_bytes(), img.width(), img.height(), - img.color().into(), + img.color(), )?; Ok::<_, anyhow::Error>(buf) }) diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index 8d3e31cc..869d2545 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -15,7 +15,7 @@ impl Manager { }; let cwd = self.cwd().clone(); - let old: Vec<_> = self.selected_or_hovered(); + let old: Vec<_> = self.selected_or_hovered(true); 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 926ca773..d4e5f586 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -33,7 +33,7 @@ impl Manager { }; let opt = opt.into() as Opt; - let selected = if opt.hovered { vec![&hovered] } else { self.selected_or_hovered() }; + let selected = if opt.hovered { vec![&hovered] } else { self.selected_or_hovered(false) }; if Self::quit_with_selected(&selected) { return; } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index 6ff0d18f..35381403 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -27,7 +27,7 @@ impl Manager { } let mut opt = opt.into() as Opt; - opt.targets = self.selected_or_hovered().into_iter().cloned().collect(); + opt.targets = self.selected_or_hovered(false).into_iter().cloned().collect(); if opt.force { return self.remove_do(opt, tasks); diff --git a/yazi-core/src/manager/commands/yank.rs b/yazi-core/src/manager/commands/yank.rs index 5383382d..ac6390b3 100644 --- a/yazi-core/src/manager/commands/yank.rs +++ b/yazi-core/src/manager/commands/yank.rs @@ -18,7 +18,7 @@ impl Manager { return; } - let selected: HashSet<_> = self.selected_or_hovered().into_iter().cloned().collect(); + let selected: HashSet<_> = self.selected_or_hovered(false).into_iter().cloned().collect(); if selected.is_empty() { return; } diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index b1fea58c..eab2304f 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -53,7 +53,9 @@ impl Manager { pub fn hovered_folder(&self) -> Option<&Folder> { self.tabs.active().hovered_folder() } #[inline] - pub fn selected_or_hovered(&self) -> Vec<&Url> { self.tabs.active().selected_or_hovered() } + pub fn selected_or_hovered(&self, sorted: bool) -> Vec<&Url> { + self.tabs.active().selected_or_hovered(sorted) + } #[inline] pub fn hovered_and_selected(&self) -> Vec<&Url> { 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 8e47ea24..ba22eff8 100644 --- a/yazi-core/src/tab/commands/copy.rs +++ b/yazi-core/src/tab/commands/copy.rs @@ -20,7 +20,7 @@ impl Tab { } let mut s = OsString::new(); - let mut it = self.selected_or_hovered().into_iter().peekable(); + let mut it = self.selected_or_hovered(true).into_iter().peekable(); while let Some(u) = it.next() { s.push(match opt.type_.as_str() { "path" => u.as_os_str(), diff --git a/yazi-core/src/tab/commands/select_all.rs b/yazi-core/src/tab/commands/select_all.rs index 7e7399ae..f081bbde 100644 --- a/yazi-core/src/tab/commands/select_all.rs +++ b/yazi-core/src/tab/commands/select_all.rs @@ -28,7 +28,7 @@ impl Tab { 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)), + None => iter.partition(|&u| self.selected.contains_key(u)), }; let same = !self.current.cwd.is_search(); diff --git a/yazi-core/src/tab/commands/visual_mode.rs b/yazi-core/src/tab/commands/visual_mode.rs index 6d618525..8e19ace5 100644 --- a/yazi-core/src/tab/commands/visual_mode.rs +++ b/yazi-core/src/tab/commands/visual_mode.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::BTreeSet; use yazi_shared::{event::Cmd, render}; @@ -18,9 +18,9 @@ impl Tab { let idx = self.current.cursor; if opt.unset { - self.mode = Mode::Unset(idx, HashSet::from([idx])); + self.mode = Mode::Unset(idx, BTreeSet::from([idx])); } else { - self.mode = Mode::Select(idx, HashSet::from([idx])); + self.mode = Mode::Select(idx, BTreeSet::from([idx])); }; render!(); } diff --git a/yazi-core/src/tab/mode.rs b/yazi-core/src/tab/mode.rs index 98f27139..05c8d8e5 100644 --- a/yazi-core/src/tab/mode.rs +++ b/yazi-core/src/tab/mode.rs @@ -1,15 +1,15 @@ -use std::{collections::HashSet, fmt::Display, mem}; +use std::{collections::BTreeSet, fmt::Display, mem}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Mode { #[default] Normal, - Select(usize, HashSet), - Unset(usize, HashSet), + Select(usize, BTreeSet), + Unset(usize, BTreeSet), } impl Mode { - pub fn visual_mut(&mut self) -> Option<(usize, &mut HashSet)> { + pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet)> { match self { Mode::Normal => None, Mode::Select(start, indices) => Some((*start, indices)), @@ -17,7 +17,7 @@ impl Mode { } } - pub fn take_visual(&mut self) -> Option<(usize, HashSet)> { + pub fn take_visual(&mut self) -> Option<(usize, BTreeSet)> { match mem::take(self) { Mode::Normal => None, Mode::Select(start, indices) => Some((start, indices)), diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 375fe011..8f6f3c06 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,15 +1,15 @@ -use std::{collections::{BTreeSet, HashMap}, ops::Deref}; +use std::{collections::HashMap, ops::Deref}; -use yazi_shared::fs::{FilesOp, Url}; +use yazi_shared::{fs::{FilesOp, Url}, timestamp_us}; #[derive(Default)] pub struct Selected { - inner: BTreeSet, + inner: HashMap, parents: HashMap, } impl Deref for Selected { - type Target = BTreeSet; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner } } @@ -43,7 +43,7 @@ impl Selected { let mut parent = urls[0].parent_url(); let mut parents = vec![]; while let Some(u) = parent { - if self.inner.contains(&u) { + if self.inner.contains_key(&u) { return 0; } @@ -51,8 +51,8 @@ impl Selected { parents.push(u); } - let len = self.inner.len(); - self.inner.extend(urls.iter().map(|&&u| u.clone())); + let (now, len) = (timestamp_us(), self.inner.len()); + self.inner.extend(urls.iter().enumerate().map(|(i, &&u)| (u.clone(), now + i as u64))); for u in parents { *self.parents.entry(u).or_insert(0) += self.inner.len() - len; @@ -78,7 +78,7 @@ impl Selected { } fn remove_same(&mut self, urls: &[impl AsRef]) -> usize { - let count = urls.iter().map(|u| self.inner.remove(u.as_ref())).filter(|&b| b).count(); + let count = urls.iter().map(|u| self.inner.remove(u.as_ref())).filter_map(|v| v).count(); if count == 0 { return 0; } @@ -106,7 +106,7 @@ impl Selected { let (removal, addition) = match op { FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]), FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => { - urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, &f.url)).unzip() + urls.iter().filter(|&(u, _)| self.contains_key(u)).map(|(u, f)| (u, &f.url)).unzip() } _ => (vec![], vec![]), }; diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index f8ccf62e..279dc7fe 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -58,11 +58,15 @@ impl Tab { impl Tab { // --- Current - pub fn selected_or_hovered(&self) -> Vec<&Url> { + pub fn selected_or_hovered(&self, sorted: bool) -> Vec<&Url> { if self.selected.is_empty() { self.current.hovered().map(|h| vec![&h.url]).unwrap_or_default() + } else if !sorted { + self.selected.keys().collect() } else { - self.selected.iter().collect() + let mut vec: Vec<_> = self.selected.iter().collect(); + vec.sort_unstable_by(|a, b| a.1.cmp(b.1)); + vec.into_iter().map(|(k, _)| k).collect() } } @@ -74,7 +78,7 @@ impl Tab { if self.selected.is_empty() { vec![&h.url, &h.url] } else { - [&h.url].into_iter().chain(self.selected.iter()).collect() + [&h.url].into_iter().chain(self.selected.keys()).collect() } } diff --git a/yazi-fm/src/lives/file.rs b/yazi-fm/src/lives/file.rs index e8c46cc4..a257d3a7 100644 --- a/yazi-fm/src/lives/file.rs +++ b/yazi-fm/src/lives/file.rs @@ -90,7 +90,7 @@ impl File { _ => 0u8, }) }); - reg.add_method("is_selected", |_, me, ()| Ok(me.tab().selected.contains(&me.url))); + reg.add_method("is_selected", |_, me, ()| Ok(me.tab().selected.contains_key(&me.url))); reg.add_method("in_parent", |_, me, ()| { Ok(me.tab().parent.as_ref().is_some_and(|f| me.folder().cwd == f.cwd)) }); diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs index f2e8937c..6310e147 100644 --- a/yazi-fm/src/lives/selected.rs +++ b/yazi-fm/src/lives/selected.rs @@ -1,4 +1,4 @@ -use std::{collections::{btree_set, BTreeSet}, ops::Deref}; +use std::{collections::{hash_map, HashMap}, ops::Deref}; use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut}; use yazi_plugin::{bindings::Cast, url::Url}; @@ -7,18 +7,20 @@ use super::{Iter, SCOPE}; #[derive(Clone, Copy)] pub(super) struct Selected { - inner: *const BTreeSet, + inner: *const HashMap, } impl Deref for Selected { - type Target = BTreeSet; + type Target = HashMap; fn deref(&self) -> &Self::Target { self.inner() } } impl Selected { #[inline] - pub(super) fn make(inner: &BTreeSet) -> mlua::Result> { + pub(super) fn make( + inner: &HashMap, + ) -> mlua::Result> { SCOPE.create_any_userdata(Self { inner }) } @@ -28,7 +30,7 @@ impl Selected { reg.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 { @@ -37,7 +39,7 @@ impl Selected { }, )?; - Ok((iter, Iter::make(me.inner().iter()))) + Ok((iter, Iter::make(me.inner().keys()))) }); })?; @@ -45,5 +47,5 @@ impl Selected { } #[inline] - fn inner(&self) -> &'static BTreeSet { unsafe { &*self.inner } } + fn inner(&self) -> &'static HashMap { unsafe { &*self.inner } } } diff --git a/yazi-fm/src/logs.rs b/yazi-fm/src/logs.rs index 6161dda2..3e2ceca8 100644 --- a/yazi-fm/src/logs.rs +++ b/yazi-fm/src/logs.rs @@ -15,8 +15,8 @@ impl Logs { let (handle, guard) = tracing_appender::non_blocking(appender); // let filter = EnvFilter::from_default_env(); - let subscriber = - Registry::default().with(fmt::layer().pretty().with_writer(handle).with_ansi(false)); + let subscriber = Registry::default() + .with(fmt::layer().pretty().with_writer(handle).with_ansi(cfg!(debug_assertions))); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); diff --git a/yazi-shared/src/throttle.rs b/yazi-shared/src/throttle.rs index 8ffb29af..0f5e2612 100644 --- a/yazi-shared/src/throttle.rs +++ b/yazi-shared/src/throttle.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, mem, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, tim use parking_lot::Mutex; -use crate::timestamp_ms; +use crate::timestamp_us; #[derive(Debug)] pub struct Throttle { @@ -17,7 +17,7 @@ impl Throttle { Self { total: AtomicUsize::new(total), interval, - last: AtomicU64::new(timestamp_ms() - interval.as_millis() as u64), + last: AtomicU64::new(timestamp_us() - interval.as_micros() as u64), buf: Default::default(), } } @@ -32,8 +32,8 @@ impl Throttle { } let last = self.last.load(Ordering::Relaxed); - let now = timestamp_ms(); - if now > self.interval.as_millis() as u64 + last { + let now = timestamp_us(); + if now > self.interval.as_micros() as u64 + last { self.last.store(now, Ordering::Relaxed); return self.flush(data, f); } diff --git a/yazi-shared/src/time.rs b/yazi-shared/src/time.rs index 580605e2..6a75756a 100644 --- a/yazi-shared/src/time.rs +++ b/yazi-shared/src/time.rs @@ -1,5 +1,5 @@ use std::time::{self, SystemTime}; -pub fn timestamp_ms() -> u64 { - SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_millis() as u64 +pub fn timestamp_us() -> u64 { + SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_micros() as u64 }