mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-18 14:21:32 +03:00
feat: time-based selection order preservation (#843)
This commit is contained in:
parent
e619a97ee2
commit
f6f2c30709
@ -39,7 +39,7 @@ impl Image {
|
|||||||
img.as_bytes(),
|
img.as_bytes(),
|
||||||
img.width(),
|
img.width(),
|
||||||
img.height(),
|
img.height(),
|
||||||
img.color().into(),
|
img.color(),
|
||||||
)?;
|
)?;
|
||||||
Ok::<_, anyhow::Error>(buf)
|
Ok::<_, anyhow::Error>(buf)
|
||||||
})
|
})
|
||||||
|
@ -15,7 +15,7 @@ impl Manager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let cwd = self.cwd().clone();
|
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 root = max_common_root(&old);
|
||||||
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
||||||
|
@ -33,7 +33,7 @@ impl Manager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let opt = opt.into() as Opt;
|
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) {
|
if Self::quit_with_selected(&selected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ impl Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut opt = opt.into() as Opt;
|
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 {
|
if opt.force {
|
||||||
return self.remove_do(opt, tasks);
|
return self.remove_do(opt, tasks);
|
||||||
|
@ -18,7 +18,7 @@ impl Manager {
|
|||||||
return;
|
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() {
|
if selected.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,9 @@ impl Manager {
|
|||||||
pub fn hovered_folder(&self) -> Option<&Folder> { self.tabs.active().hovered_folder() }
|
pub fn hovered_folder(&self) -> Option<&Folder> { self.tabs.active().hovered_folder() }
|
||||||
|
|
||||||
#[inline]
|
#[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]
|
#[inline]
|
||||||
pub fn hovered_and_selected(&self) -> Vec<&Url> { self.tabs.active().hovered_and_selected() }
|
pub fn hovered_and_selected(&self) -> Vec<&Url> { self.tabs.active().hovered_and_selected() }
|
||||||
|
@ -20,7 +20,7 @@ impl Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut s = OsString::new();
|
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() {
|
while let Some(u) = it.next() {
|
||||||
s.push(match opt.type_.as_str() {
|
s.push(match opt.type_.as_str() {
|
||||||
"path" => u.as_os_str(),
|
"path" => u.as_os_str(),
|
||||||
|
@ -28,7 +28,7 @@ impl Tab {
|
|||||||
let (removal, addition): (Vec<_>, Vec<_>) = match opt.into().state {
|
let (removal, addition): (Vec<_>, Vec<_>) = match opt.into().state {
|
||||||
Some(true) => (vec![], iter.collect()),
|
Some(true) => (vec![], iter.collect()),
|
||||||
Some(false) => (iter.collect(), vec![]),
|
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();
|
let same = !self.current.cwd.is_search();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use yazi_shared::{event::Cmd, render};
|
use yazi_shared::{event::Cmd, render};
|
||||||
|
|
||||||
@ -18,9 +18,9 @@ impl Tab {
|
|||||||
let idx = self.current.cursor;
|
let idx = self.current.cursor;
|
||||||
|
|
||||||
if opt.unset {
|
if opt.unset {
|
||||||
self.mode = Mode::Unset(idx, HashSet::from([idx]));
|
self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||||
} else {
|
} else {
|
||||||
self.mode = Mode::Select(idx, HashSet::from([idx]));
|
self.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||||
};
|
};
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use std::{collections::HashSet, fmt::Display, mem};
|
use std::{collections::BTreeSet, fmt::Display, mem};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
Select(usize, HashSet<usize>),
|
Select(usize, BTreeSet<usize>),
|
||||||
Unset(usize, HashSet<usize>),
|
Unset(usize, BTreeSet<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut HashSet<usize>)> {
|
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||||
match self {
|
match self {
|
||||||
Mode::Normal => None,
|
Mode::Normal => None,
|
||||||
Mode::Select(start, indices) => Some((*start, indices)),
|
Mode::Select(start, indices) => Some((*start, indices)),
|
||||||
@ -17,7 +17,7 @@ impl Mode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_visual(&mut self) -> Option<(usize, HashSet<usize>)> {
|
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
||||||
match mem::take(self) {
|
match mem::take(self) {
|
||||||
Mode::Normal => None,
|
Mode::Normal => None,
|
||||||
Mode::Select(start, indices) => Some((start, indices)),
|
Mode::Select(start, indices) => Some((start, indices)),
|
||||||
|
@ -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)]
|
#[derive(Default)]
|
||||||
pub struct Selected {
|
pub struct Selected {
|
||||||
inner: BTreeSet<Url>,
|
inner: HashMap<Url, u64>,
|
||||||
parents: HashMap<Url, usize>,
|
parents: HashMap<Url, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Selected {
|
impl Deref for Selected {
|
||||||
type Target = BTreeSet<Url>;
|
type Target = HashMap<Url, u64>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.inner }
|
fn deref(&self) -> &Self::Target { &self.inner }
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ impl Selected {
|
|||||||
let mut parent = urls[0].parent_url();
|
let mut parent = urls[0].parent_url();
|
||||||
let mut parents = vec![];
|
let mut parents = vec![];
|
||||||
while let Some(u) = parent {
|
while let Some(u) = parent {
|
||||||
if self.inner.contains(&u) {
|
if self.inner.contains_key(&u) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +51,8 @@ impl Selected {
|
|||||||
parents.push(u);
|
parents.push(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = self.inner.len();
|
let (now, len) = (timestamp_us(), self.inner.len());
|
||||||
self.inner.extend(urls.iter().map(|&&u| u.clone()));
|
self.inner.extend(urls.iter().enumerate().map(|(i, &&u)| (u.clone(), now + i as u64)));
|
||||||
|
|
||||||
for u in parents {
|
for u in parents {
|
||||||
*self.parents.entry(u).or_insert(0) += self.inner.len() - len;
|
*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<Url>]) -> usize {
|
fn remove_same(&mut self, urls: &[impl AsRef<Url>]) -> 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 {
|
if count == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ impl Selected {
|
|||||||
let (removal, addition) = match op {
|
let (removal, addition) = match op {
|
||||||
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
||||||
FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => {
|
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![]),
|
_ => (vec![], vec![]),
|
||||||
};
|
};
|
||||||
|
@ -58,11 +58,15 @@ impl Tab {
|
|||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
// --- Current
|
// --- Current
|
||||||
pub fn selected_or_hovered(&self) -> Vec<&Url> {
|
pub fn selected_or_hovered(&self, sorted: bool) -> Vec<&Url> {
|
||||||
if self.selected.is_empty() {
|
if self.selected.is_empty() {
|
||||||
self.current.hovered().map(|h| vec![&h.url]).unwrap_or_default()
|
self.current.hovered().map(|h| vec![&h.url]).unwrap_or_default()
|
||||||
|
} else if !sorted {
|
||||||
|
self.selected.keys().collect()
|
||||||
} else {
|
} 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() {
|
if self.selected.is_empty() {
|
||||||
vec![&h.url, &h.url]
|
vec![&h.url, &h.url]
|
||||||
} else {
|
} else {
|
||||||
[&h.url].into_iter().chain(self.selected.iter()).collect()
|
[&h.url].into_iter().chain(self.selected.keys()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ impl File {
|
|||||||
_ => 0u8,
|
_ => 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, ()| {
|
reg.add_method("in_parent", |_, me, ()| {
|
||||||
Ok(me.tab().parent.as_ref().is_some_and(|f| me.folder().cwd == f.cwd))
|
Ok(me.tab().parent.as_ref().is_some_and(|f| me.folder().cwd == f.cwd))
|
||||||
});
|
});
|
||||||
|
@ -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 mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut};
|
||||||
use yazi_plugin::{bindings::Cast, url::Url};
|
use yazi_plugin::{bindings::Cast, url::Url};
|
||||||
@ -7,18 +7,20 @@ use super::{Iter, SCOPE};
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(super) struct Selected {
|
pub(super) struct Selected {
|
||||||
inner: *const BTreeSet<yazi_shared::fs::Url>,
|
inner: *const HashMap<yazi_shared::fs::Url, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Selected {
|
impl Deref for Selected {
|
||||||
type Target = BTreeSet<yazi_shared::fs::Url>;
|
type Target = HashMap<yazi_shared::fs::Url, u64>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { self.inner() }
|
fn deref(&self) -> &Self::Target { self.inner() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selected {
|
impl Selected {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn make(inner: &BTreeSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
|
pub(super) fn make(
|
||||||
|
inner: &HashMap<yazi_shared::fs::Url, u64>,
|
||||||
|
) -> mlua::Result<AnyUserData<'static>> {
|
||||||
SCOPE.create_any_userdata(Self { inner })
|
SCOPE.create_any_userdata(Self { inner })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ impl Selected {
|
|||||||
|
|
||||||
reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
|
reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
|
||||||
let iter = lua.create_function(
|
let iter = lua.create_function(
|
||||||
|lua, mut iter: UserDataRefMut<Iter<btree_set::Iter<yazi_shared::fs::Url>, _>>| {
|
|lua, mut iter: UserDataRefMut<Iter<hash_map::Keys<yazi_shared::fs::Url, u64>, _>>| {
|
||||||
if let Some(next) = iter.next() {
|
if let Some(next) = iter.next() {
|
||||||
(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
|
(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
|
||||||
} else {
|
} 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]
|
#[inline]
|
||||||
fn inner(&self) -> &'static BTreeSet<yazi_shared::fs::Url> { unsafe { &*self.inner } }
|
fn inner(&self) -> &'static HashMap<yazi_shared::fs::Url, u64> { unsafe { &*self.inner } }
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ impl Logs {
|
|||||||
let (handle, guard) = tracing_appender::non_blocking(appender);
|
let (handle, guard) = tracing_appender::non_blocking(appender);
|
||||||
|
|
||||||
// let filter = EnvFilter::from_default_env();
|
// let filter = EnvFilter::from_default_env();
|
||||||
let subscriber =
|
let subscriber = Registry::default()
|
||||||
Registry::default().with(fmt::layer().pretty().with_writer(handle).with_ansi(false));
|
.with(fmt::layer().pretty().with_writer(handle).with_ansi(cfg!(debug_assertions)));
|
||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::{fmt::Debug, mem, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, tim
|
|||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::timestamp_ms;
|
use crate::timestamp_us;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Throttle<T> {
|
pub struct Throttle<T> {
|
||||||
@ -17,7 +17,7 @@ impl<T> Throttle<T> {
|
|||||||
Self {
|
Self {
|
||||||
total: AtomicUsize::new(total),
|
total: AtomicUsize::new(total),
|
||||||
interval,
|
interval,
|
||||||
last: AtomicU64::new(timestamp_ms() - interval.as_millis() as u64),
|
last: AtomicU64::new(timestamp_us() - interval.as_micros() as u64),
|
||||||
buf: Default::default(),
|
buf: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,8 +32,8 @@ impl<T> Throttle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let last = self.last.load(Ordering::Relaxed);
|
let last = self.last.load(Ordering::Relaxed);
|
||||||
let now = timestamp_ms();
|
let now = timestamp_us();
|
||||||
if now > self.interval.as_millis() as u64 + last {
|
if now > self.interval.as_micros() as u64 + last {
|
||||||
self.last.store(now, Ordering::Relaxed);
|
self.last.store(now, Ordering::Relaxed);
|
||||||
return self.flush(data, f);
|
return self.flush(data, f);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::time::{self, SystemTime};
|
use std::time::{self, SystemTime};
|
||||||
|
|
||||||
pub fn timestamp_ms() -> u64 {
|
pub fn timestamp_us() -> u64 {
|
||||||
SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_millis() as u64
|
SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_micros() as u64
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user