diff --git a/Cargo.lock b/Cargo.lock index 2865f08a..02810056 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1593,9 +1593,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" @@ -2340,9 +2340,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11" dependencies = [ "memchr", ] @@ -2364,7 +2364,7 @@ dependencies = [ [[package]] name = "yazi" -version = "0.1.0" +version = "0.1.2" dependencies = [ "ansi-to-tui", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index ad29a8f8..3e18f4ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi" -version = "0.1.0" +version = "0.1.2" edition = "2021" [dependencies] diff --git a/config/keymap.toml b/config/keymap.toml index 5ca5dc63..ee6d49c2 100644 --- a/config/keymap.toml +++ b/config/keymap.toml @@ -25,7 +25,7 @@ keymap = [ { on = [ "" ], exec = "open" }, # Selection - { on = [ "" ], exec = "select --state=none" }, + { on = [ "" ], exec = [ "select --state=none", "arrow 1" ] }, { on = [ "v" ], exec = "visual_mode" }, { on = [ "V" ], exec = "visual_mode --unset" }, { on = [ "" ], exec = "select_all --state=true" }, @@ -33,13 +33,11 @@ keymap = [ # Operation { on = [ "o" ], exec = "open" }, - { on = [ "O" ], exec = "open --select" }, + { on = [ "O" ], exec = "open --interactive" }, { on = [ "y" ], exec = "yank" }, { on = [ "x" ], exec = "yank --cut" }, { on = [ "p" ], exec = "paste" }, { on = [ "P" ], exec = "paste --force" }, - { on = [ "f" ], exec = "paste --follow" }, - { on = [ "F" ], exec = "paste --follow --force" }, { on = [ "d" ], exec = "remove" }, { on = [ "D" ], exec = "remove --permanently" }, { on = [ "c" ], exec = "create" }, @@ -51,6 +49,16 @@ keymap = [ { on = [ "z" ], exec = "jump zoxide" }, { on = [ "Z" ], exec = "jump fzf" }, + # Sorting + { on = [ ",", "a" ], exec = "sort alphabetical" }, + { on = [ ",", "A" ], exec = "sort alphabetical --reverse" }, + { on = [ ",", "c" ], exec = "sort created --reverse" }, + { on = [ ",", "C" ], exec = "sort created" }, + { on = [ ",", "m" ], exec = "sort modified --reverse" }, + { on = [ ",", "M" ], exec = "sort modified" }, + { on = [ ",", "s" ], exec = "sort size --reverse" }, + { on = [ ",", "S" ], exec = "sort size" }, + # Tabs { on = [ "t" ], exec = "tab_create --current" }, @@ -122,6 +130,7 @@ keymap = [ { on = [ "a" ], exec = "insert --append" }, { on = [ "v" ], exec = "visual" }, + # Navigation { on = [ "h" ], exec = "move -1" }, { on = [ "l" ], exec = "move 1" }, @@ -137,14 +146,17 @@ keymap = [ { on = [ "w" ], exec = "forward" }, { on = [ "e" ], exec = "forward --end-of-word" }, + # Deletion { on = [ "d" ], exec = "delete --cut" }, { on = [ "c" ], exec = "delete --cut --insert" }, { on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] }, + # Copy/Paste { on = [ "y" ], exec = [ "yank" ] }, { on = [ "p" ], exec = [ "paste" ] }, { on = [ "P" ], exec = [ "paste --before" ] }, + # Undo/Redo { on = [ "u" ], exec = [ "undo" ] }, { on = [ "" ], exec = [ "redo" ] }, ] diff --git a/cspell.json b/cspell.json index e088f861..109e30dc 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize"],"language":"en"} +{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent"],"version":"0.2"} diff --git a/docs/keymap.md b/docs/keymap.md index a187cdf4..747c9bce 100644 --- a/docs/keymap.md +++ b/docs/keymap.md @@ -42,7 +42,7 @@ - open: Open the selected files. - - `--select`: Open the selected files with an interactive ui to choose the opening method. + - `--interactive`: Open the selected files with an interactive ui to choose the opening method. - yank: Copy the selected files. diff --git a/src/config/manager/sorting.rs b/src/config/manager/sorting.rs index c8c3aea7..f342cb40 100644 --- a/src/config/manager/sorting.rs +++ b/src/config/manager/sorting.rs @@ -1,9 +1,10 @@ use anyhow::bail; use serde::Deserialize; -#[derive(Debug, Deserialize, Clone, Copy)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] #[serde(try_from = "String")] pub enum SortBy { + #[default] Alphabetical, Created, Modified, @@ -15,10 +16,10 @@ impl TryFrom for SortBy { fn try_from(s: String) -> Result { Ok(match s.as_str() { + "alphabetical" => Self::Alphabetical, "created" => Self::Created, "modified" => Self::Modified, "size" => Self::Size, - "alphabetical" => Self::Alphabetical, _ => bail!("invalid sort_by value: {}", s), }) } diff --git a/src/core/files/files.rs b/src/core/files/files.rs index 57eef14e..ac13eba0 100644 --- a/src/core/files/files.rs +++ b/src/core/files/files.rs @@ -1,4 +1,4 @@ -use std::{ops::{Deref, DerefMut}, path::{Path, PathBuf}}; +use std::{collections::BTreeMap, ops::{Deref, DerefMut}, path::{Path, PathBuf}}; use anyhow::Result; use indexmap::IndexMap; @@ -10,13 +10,13 @@ use crate::config::{manager::SortBy, MANAGER}; #[derive(Default)] pub struct Files { items: IndexMap, - sort: FilesSort, + pub sort: FilesSort, pub show_hidden: bool, } impl Files { - pub async fn read(paths: Vec) -> IndexMap { - let mut items = IndexMap::new(); + pub async fn read(paths: Vec) -> BTreeMap { + let mut items = BTreeMap::new(); for path in paths { if let Ok(file) = File::from(&path).await { items.insert(path, file); @@ -25,9 +25,9 @@ impl Files { items } - pub async fn read_dir(path: &Path) -> Result> { + pub async fn read_dir(path: &Path) -> Result> { let mut it = fs::read_dir(path).await?; - let mut items = IndexMap::new(); + let mut items = BTreeMap::new(); while let Ok(Some(item)) = it.next_entry().await { if let Ok(meta) = item.metadata().await { let path = item.path(); @@ -38,7 +38,75 @@ impl Files { Ok(items) } - pub fn sort(&mut self) { + #[inline] + pub fn duplicate(&self, idx: usize) -> Option { + self.items.get_index(idx).map(|(_, file)| file.clone()) + } + + #[inline] + pub fn set_sort(&mut self, sort: FilesSort) -> bool { + if self.sort == sort { + return false; + } + self.sort = sort; + self.sort() + } + + pub fn update_read(&mut self, mut items: BTreeMap) -> bool { + if !self.show_hidden { + items.retain(|_, item| !item.is_hidden); + } + + for (path, item) in &mut items { + if let Some(old) = self.items.get(path) { + item.is_selected = old.is_selected; + + // Calculate the size of directories is expensive, so we keep the old value, + // before a new value is calculated and comes to. + if item.meta.is_dir() { + item.length = old.length; + } + } + } + + self.items.clear(); + self.items.extend(items); + self.sort(); + true + } + + pub fn update_sort(&mut self, mut items: BTreeMap) -> bool { + for (path, item) in &mut items { + if let Some(old) = self.items.get(path) { + item.is_selected = old.is_selected; + } + } + + self.items.extend(items); + self.sort(); + true + } + + pub fn update_search(&mut self, items: BTreeMap) -> bool { + if !items.is_empty() { + self.items.extend(items); + self.sort(); + return true; + } + + if !self.items.is_empty() { + self.items.clear(); + return true; + } + + false + } + + fn sort(&mut self) -> bool { + if self.items.is_empty() { + return false; + } + fn cmp(a: T, b: T, reverse: bool) -> std::cmp::Ordering { if reverse { b.cmp(&a) } else { a.cmp(&b) } } @@ -62,44 +130,8 @@ impl Files { self.items.sort_by(|_, a, _, b| cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse)) } } - } - - #[inline] - pub fn duplicate(&self, idx: usize) -> Option { - self.items.get_index(idx).map(|(_, file)| file.clone()) - } - - pub fn update_read(&mut self, mut items: IndexMap) -> bool { - if !self.show_hidden { - items.retain(|_, item| !item.is_hidden); - } - - for (path, item) in &mut items { - if let Some(old) = self.items.get(path) { - item.length = old.length; - item.is_selected = old.is_selected; - } - } - - self.items = items; - self.sort(); true } - - pub fn update_search(&mut self, items: IndexMap) -> bool { - if !items.is_empty() { - self.items.extend(items); - self.sort(); - return true; - } - - if !self.items.is_empty() { - self.items.clear(); - return true; - } - - false - } } impl Deref for Files { @@ -112,7 +144,8 @@ impl DerefMut for Files { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items } } -struct FilesSort { +#[derive(PartialEq)] +pub struct FilesSort { pub by: SortBy, pub reverse: bool, } @@ -123,9 +156,10 @@ impl Default for FilesSort { #[derive(Debug)] pub enum FilesOp { - Read(PathBuf, IndexMap), + Read(PathBuf, BTreeMap), + Sort(PathBuf, BTreeMap), + Search(PathBuf, BTreeMap), IOErr(PathBuf), - Search(PathBuf, IndexMap), } impl FilesOp { @@ -133,15 +167,16 @@ impl FilesOp { pub fn path(&self) -> PathBuf { match self { Self::Read(path, _) => path, - Self::IOErr(path) => path, + Self::Sort(path, _) => path, Self::Search(path, _) => path, + Self::IOErr(path) => path, } .clone() } #[inline] - pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), IndexMap::new()) } + pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), BTreeMap::new()) } #[inline] - pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), IndexMap::new()) } + pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), BTreeMap::new()) } } diff --git a/src/core/manager/folder.rs b/src/core/manager/folder.rs index adab0521..f72090f3 100644 --- a/src/core/manager/folder.rs +++ b/src/core/manager/folder.rs @@ -31,6 +31,7 @@ impl Folder { pub fn update(&mut self, op: FilesOp) -> bool { let b = match op { FilesOp::Read(_, items) => self.files.update_read(items), + FilesOp::Sort(_, items) => self.files.update_sort(items), FilesOp::Search(_, items) => self.files.update_search(items), _ => unreachable!(), }; diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index a1e114e6..5c36c183 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -114,7 +114,7 @@ impl Manager { self.quit(tasks) } - pub fn open(&mut self, select: bool) -> bool { + pub fn open(&mut self, interactive: bool) -> bool { let files = self.selected(); if files.len() == 1 && files[0].meta.is_dir() { return self.active_mut().enter(); @@ -143,7 +143,7 @@ impl Manager { } let files = files.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::>(); - if !select { + if !interactive { emit!(Open(files, None)); return; } @@ -251,6 +251,20 @@ impl Manager { b } + pub fn update_search(&mut self, op: FilesOp) -> bool { + let path = op.path(); + if self.current().in_search && self.current().cwd == path { + return self.current_mut().update(op); + } + + let rep = mem::replace(self.current_mut(), Folder::new_search(&path)); + if !rep.in_search { + self.active_mut().history.insert(path, rep); + } + self.current_mut().update(op); + true + } + pub fn update_ioerr(&mut self, op: FilesOp) -> bool { let path = op.path(); let op = FilesOp::read_empty(&path); @@ -267,20 +281,6 @@ impl Manager { true } - pub fn update_search(&mut self, op: FilesOp) -> bool { - let path = op.path(); - if self.current().in_search && self.current().cwd == path { - return self.current_mut().update(op); - } - - let rep = mem::replace(self.current_mut(), Folder::new_search(&path)); - if !rep.in_search { - self.active_mut().history.insert(path, rep); - } - self.current_mut().update(op); - true - } - pub fn update_mimetype(&mut self, mut mimes: BTreeMap, tasks: &Tasks) -> bool { mimes.retain(|f, m| self.mimetype.get(f) != Some(m)); if mimes.is_empty() { diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index dd5c7433..d7c9dde9 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -7,11 +7,11 @@ use super::{Folder, Mode, Preview}; use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, Position, BLOCKER}, emit, misc::Defer}; pub struct Tab { + pub(super) mode: Mode, pub(super) current: Folder, pub(super) parent: Option, - search: Option>>, - pub(super) mode: Mode, + search: Option>>, pub(super) history: BTreeMap, pub(super) preview: Preview, @@ -20,11 +20,11 @@ pub struct Tab { impl Tab { pub fn new(path: &Path) -> Self { Self { + mode: Default::default(), current: Folder::new(path), parent: path.parent().map(|p| Folder::new(p)), - search: None, - mode: Default::default(), + search: None, history: Default::default(), preview: Preview::new(), diff --git a/src/core/manager/watcher.rs b/src/core/manager/watcher.rs index 43fd2364..d4728703 100644 --- a/src/core/manager/watcher.rs +++ b/src/core/manager/watcher.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeSet, path::{Path, PathBuf}, sync::Arc}; +use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}, sync::Arc}; use indexmap::IndexMap; use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; @@ -92,7 +92,7 @@ impl Watcher { for ori in linked { emit!(Files(match &result { Ok(items) => { - let files = IndexMap::from_iter(items.iter().map(|(p, f)| { + let files = BTreeMap::from_iter(items.iter().map(|(p, f)| { let p = ori.join(p.strip_prefix(&path).unwrap()); let f = f.clone().set_path(&p); (p, f) diff --git a/src/core/tasks/precache.rs b/src/core/tasks/precache.rs index 852feb2b..f8641010 100644 --- a/src/core/tasks/precache.rs +++ b/src/core/tasks/precache.rs @@ -1,11 +1,11 @@ -use std::path::{Path, PathBuf}; +use std::{collections::BTreeMap, path::{Path, PathBuf}, sync::Arc}; use anyhow::{Error, Result}; use image::{imageops::FilterType, ImageFormat}; use tokio::{fs, sync::mpsc}; use super::TaskOp; -use crate::{config::PREVIEW, core::external, emit}; +use crate::{config::PREVIEW, core::{external, files::{File, FilesOp}}, emit, misc::{calculate_size, Throttle}}; pub struct Precache { rx: async_channel::Receiver, @@ -20,6 +20,13 @@ pub(super) enum PrecacheOp { Video(PrecacheOpVideo), } +#[derive(Debug)] +pub(super) struct PrecacheOpSize { + pub id: usize, + pub target: PathBuf, + pub throttle: Arc>, +} + #[derive(Debug)] pub(super) struct PrecacheOpMime { pub id: usize, @@ -88,6 +95,7 @@ impl Precache { Ok(()) } + #[inline] fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) } pub(super) async fn mime(&self, task: PrecacheOpMime) -> Result<()> { @@ -100,6 +108,22 @@ impl Precache { self.done(task.id) } + pub(super) async fn size(&self, task: PrecacheOpSize) -> Result<()> { + self.sch.send(TaskOp::New(task.id, 0))?; + + let length = Some(calculate_size(&task.target).await); + if let Ok(mut file) = File::from(&task.target).await { + file.length = length; + task.throttle.done((task.target, file), |buf| { + let parent = buf[0].0.parent().unwrap().to_path_buf(); + emit!(Files(FilesOp::Sort(parent, BTreeMap::from_iter(buf)))); + }); + }; + + self.sch.send(TaskOp::Adv(task.id, 1, 0))?; + self.done(task.id) + } + pub(super) fn image(&self, id: usize, targets: Vec) -> Result<()> { for target in targets { self.sch.send(TaskOp::New(id, 0))?; diff --git a/src/core/tasks/scheduler.rs b/src/core/tasks/scheduler.rs index 0ce07d0e..14eac7b4 100644 --- a/src/core/tasks/scheduler.rs +++ b/src/core/tasks/scheduler.rs @@ -6,8 +6,8 @@ use parking_lot::RwLock; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep}; use tracing::{info, trace}; -use super::{File, FileOpDelete, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, Process, ProcessOpOpen, Task, TaskOp, TaskStage}; -use crate::{config::open::Opener, emit, misc::unique_path}; +use super::{File, FileOpDelete, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, PrecacheOpSize, Process, ProcessOpOpen, Task, TaskOp, TaskStage}; +use crate::{config::open::Opener, emit, misc::{unique_path, Throttle}}; #[derive(Default)] pub(super) struct Running { @@ -381,6 +381,24 @@ impl Scheduler { }); } + pub(super) fn precache_size(&self, targets: Vec) { + let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300))); + + for target in targets { + let name = format!("Calculate the size of {:?}", target); + let id = self.running.write().add(name); + + let _ = self.todo.send_blocking({ + let precache = self.precache.clone(); + let throttle = throttle.clone(); + async move { + precache.size(PrecacheOpSize { id, target, throttle }).await.ok(); + } + .boxed() + }); + } + } + pub(super) fn precache_mime(&self, targets: Vec) { let name = format!("Preload mimetype for {} files", targets.len()); let id = self.running.write().add(name); diff --git a/src/core/tasks/tasks.rs b/src/core/tasks/tasks.rs index 07ec0a14..665d0502 100644 --- a/src/core/tasks/tasks.rs +++ b/src/core/tasks/tasks.rs @@ -3,7 +3,7 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::{Path, Pa use tracing::trace; use super::{Scheduler, TASKS_PADDING, TASKS_PERCENT}; -use crate::{config::{open::Opener, OPEN}, core::{files::File, input::InputOpt, Position}, emit, misc::{tty_size, MimeKind}}; +use crate::{config::{manager::SortBy, open::Opener, OPEN}, core::{files::{File, Files}, input::InputOpt, Position}, emit, misc::{tty_size, MimeKind}}; #[derive(Clone, Debug)] pub struct Task { @@ -180,6 +180,25 @@ impl Tasks { false } + #[inline] + pub fn precache_size(&self, targets: &Files) -> bool { + if targets.sort.by != SortBy::Size { + return false; + } + + let targets = targets + .iter() + .filter(|(_, f)| f.meta.is_dir() && f.length.is_none()) + .map(|(p, _)| p.clone()) + .collect::>(); + + if !targets.is_empty() { + self.scheduler.precache_size(targets); + } + + false + } + #[inline] pub fn precache_mime(&self, targets: Vec<&File>, mimetype: &HashMap) -> bool { let targets = targets diff --git a/src/core/which/which.rs b/src/core/which/which.rs index 194920c2..dd063d0c 100644 --- a/src/core/which/which.rs +++ b/src/core/which/which.rs @@ -23,7 +23,7 @@ impl Which { self.cands = KEYMAP .get(layer) .into_iter() - .filter(|s| !s.on.is_empty() && s.on[0] == *key) + .filter(|s| s.on.len() > 1 && s.on[0] == *key) .cloned() .collect(); self.visible = true; diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 3d5de665..f9421683 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -4,6 +4,7 @@ mod defer; mod fns; mod fs; mod mime; +mod throttle; mod tty; pub use buffer::*; @@ -12,4 +13,5 @@ pub use defer::*; pub use fns::*; pub use fs::*; pub use mime::*; +pub use throttle::*; pub use tty::*; diff --git a/src/misc/throttle.rs b/src/misc/throttle.rs new file mode 100644 index 00000000..9404655b --- /dev/null +++ b/src/misc/throttle.rs @@ -0,0 +1,54 @@ +use std::{fmt::Debug, mem, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, time::{self, Duration, SystemTime}}; + +use parking_lot::Mutex; + +#[derive(Debug)] +pub struct Throttle { + total: AtomicUsize, + interval: Duration, + last: AtomicU64, + buf: Mutex>, +} + +impl Throttle { + pub fn new(total: usize, interval: Duration) -> Self { + Self { + total: AtomicUsize::new(total), + interval, + last: AtomicU64::new( + SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_millis() as u64 + - interval.as_millis() as u64, + ), + buf: Default::default(), + } + } + + pub fn done(&self, data: T, f: F) + where + F: FnOnce(Vec), + { + let total = self.total.fetch_sub(1, Ordering::Relaxed); + if total == 1 { + return self.flush(data, f); + } + + let last = self.last.load(Ordering::Relaxed); + let now = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_millis() as u64; + if now > self.interval.as_millis() as u64 + last { + self.last.store(now, Ordering::Relaxed); + return self.flush(data, f); + } + + self.buf.lock().push(data); + } + + #[inline] + fn flush(&self, data: T, f: F) + where + F: FnOnce(Vec), + { + let mut buf = mem::replace(&mut *self.buf.lock(), Vec::new()); + buf.push(data); + f(buf) + } +} diff --git a/src/ui/app.rs b/src/ui/app.rs index 6c615b8f..a4465906 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -100,14 +100,19 @@ impl App { manager.refresh(); } Event::Files(op) => { + let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..)); let b = match op { FilesOp::Read(..) => manager.update_read(op), - FilesOp::IOErr(..) => manager.update_ioerr(op), + FilesOp::Sort(..) => manager.update_read(op), FilesOp::Search(..) => manager.update_search(op), + FilesOp::IOErr(..) => manager.update_ioerr(op), }; if b { emit!(Render); } + if calc { + tasks.precache_size(&manager.current().files); + } } Event::Pages(page) => { if manager.current().page == page { diff --git a/src/ui/dispatcher.rs b/src/ui/dispatcher.rs index 20343c4e..5be44529 100644 --- a/src/ui/dispatcher.rs +++ b/src/ui/dispatcher.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use super::Ctx; -use crate::{config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}, core::input::InputMode, emit, misc::optional_bool}; +use crate::{config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP}, core::{files::FilesSort, input::InputMode}, emit, misc::optional_bool}; pub struct Executor; @@ -80,7 +80,7 @@ impl Executor { } // Operation - "open" => cx.manager.open(exec.named.contains_key("select")), + "open" => cx.manager.open(exec.named.contains_key("interactive")), "yank" => cx.manager.yank(exec.named.contains_key("cut")), "paste" => { let dest = cx.manager.current().cwd.clone(); @@ -115,6 +115,17 @@ impl Executor { _ => false, }, + // Sorting + "sort" => { + let b = cx.manager.current_mut().files.set_sort(FilesSort { + by: SortBy::try_from(exec.args.get(0).cloned().unwrap_or_default()) + .unwrap_or_default(), + reverse: exec.named.contains_key("reverse"), + }); + cx.tasks.precache_size(&cx.manager.current().files); + b + } + // Tabs "tab_create" => { let path = if exec.named.contains_key("current") {