mirror of
https://github.com/sxyazi/yazi.git
synced 2024-10-26 11:11:12 +03:00
feat: switch sorting by key; precalculate and cache directory size when sorting by size; use BTreeMap
to improve performance
- Switch sorting by key - Precalculate and cache directory size when sorting by size - Use `BTreeMap` to improve performance
This commit is contained in:
parent
688afeaf27
commit
e2a4894111
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "yazi"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -25,7 +25,7 @@ keymap = [
|
||||
{ on = [ "<Enter>" ], exec = "open" },
|
||||
|
||||
# Selection
|
||||
{ on = [ "<Space>" ], exec = "select --state=none" },
|
||||
{ on = [ "<Space>" ], exec = [ "select --state=none", "arrow 1" ] },
|
||||
{ on = [ "v" ], exec = "visual_mode" },
|
||||
{ on = [ "V" ], exec = "visual_mode --unset" },
|
||||
{ on = [ "<C-a>" ], 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 = [ "<C-r>" ], exec = [ "redo" ] },
|
||||
]
|
||||
|
@ -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"}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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<String> for SortBy {
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
@ -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<PathBuf, File>,
|
||||
sort: FilesSort,
|
||||
pub sort: FilesSort,
|
||||
pub show_hidden: bool,
|
||||
}
|
||||
|
||||
impl Files {
|
||||
pub async fn read(paths: Vec<PathBuf>) -> IndexMap<PathBuf, File> {
|
||||
let mut items = IndexMap::new();
|
||||
pub async fn read(paths: Vec<PathBuf>) -> BTreeMap<PathBuf, File> {
|
||||
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<IndexMap<PathBuf, File>> {
|
||||
pub async fn read_dir(path: &Path) -> Result<BTreeMap<PathBuf, File>> {
|
||||
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<File> {
|
||||
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<PathBuf, File>) -> 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<PathBuf, File>) -> 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<PathBuf, File>) -> 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<T: Ord>(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<File> {
|
||||
self.items.get_index(idx).map(|(_, file)| file.clone())
|
||||
}
|
||||
|
||||
pub fn update_read(&mut self, mut items: IndexMap<PathBuf, File>) -> 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<PathBuf, File>) -> 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<PathBuf, File>),
|
||||
Read(PathBuf, BTreeMap<PathBuf, File>),
|
||||
Sort(PathBuf, BTreeMap<PathBuf, File>),
|
||||
Search(PathBuf, BTreeMap<PathBuf, File>),
|
||||
IOErr(PathBuf),
|
||||
Search(PathBuf, IndexMap<PathBuf, File>),
|
||||
}
|
||||
|
||||
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()) }
|
||||
}
|
||||
|
@ -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!(),
|
||||
};
|
||||
|
@ -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::<Vec<_>>();
|
||||
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<PathBuf, String>, tasks: &Tasks) -> bool {
|
||||
mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
|
||||
if mimes.is_empty() {
|
||||
|
@ -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<Folder>,
|
||||
search: Option<JoinHandle<Result<()>>>,
|
||||
|
||||
pub(super) mode: Mode,
|
||||
search: Option<JoinHandle<Result<()>>>,
|
||||
|
||||
pub(super) history: BTreeMap<PathBuf, Folder>,
|
||||
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(),
|
||||
|
@ -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)
|
||||
|
@ -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<PrecacheOp>,
|
||||
@ -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<Throttle<(PathBuf, File)>>,
|
||||
}
|
||||
|
||||
#[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<PathBuf>) -> Result<()> {
|
||||
for target in targets {
|
||||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
|
@ -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<PathBuf>) {
|
||||
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<PathBuf>) {
|
||||
let name = format!("Preload mimetype for {} files", targets.len());
|
||||
let id = self.running.write().add(name);
|
||||
|
@ -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::<Vec<_>>();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_size(targets);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn precache_mime(&self, targets: Vec<&File>, mimetype: &HashMap<PathBuf, String>) -> bool {
|
||||
let targets = targets
|
||||
|
@ -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;
|
||||
|
@ -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::*;
|
||||
|
54
src/misc/throttle.rs
Normal file
54
src/misc/throttle.rs
Normal file
@ -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<T> {
|
||||
total: AtomicUsize,
|
||||
interval: Duration,
|
||||
last: AtomicU64,
|
||||
buf: Mutex<Vec<T>>,
|
||||
}
|
||||
|
||||
impl<T> Throttle<T> {
|
||||
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<F>(&self, data: T, f: F)
|
||||
where
|
||||
F: FnOnce(Vec<T>),
|
||||
{
|
||||
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<F>(&self, data: T, f: F)
|
||||
where
|
||||
F: FnOnce(Vec<T>),
|
||||
{
|
||||
let mut buf = mem::replace(&mut *self.buf.lock(), Vec::new());
|
||||
buf.push(data);
|
||||
f(buf)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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") {
|
||||
|
Loading…
Reference in New Issue
Block a user