mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00: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
19 changed files with 276 additions and 94 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -1593,9 +1593,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.5"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
|
|
@ -2340,9 +2340,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
@ -2364,7 +2364,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi"
|
name = "yazi"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-to-tui",
|
"ansi-to-tui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yazi"
|
name = "yazi"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ keymap = [
|
||||||
{ on = [ "<Enter>" ], exec = "open" },
|
{ on = [ "<Enter>" ], exec = "open" },
|
||||||
|
|
||||||
# Selection
|
# 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" },
|
||||||
{ on = [ "V" ], exec = "visual_mode --unset" },
|
{ on = [ "V" ], exec = "visual_mode --unset" },
|
||||||
{ on = [ "<C-a>" ], exec = "select_all --state=true" },
|
{ on = [ "<C-a>" ], exec = "select_all --state=true" },
|
||||||
|
|
@ -33,13 +33,11 @@ keymap = [
|
||||||
|
|
||||||
# Operation
|
# Operation
|
||||||
{ on = [ "o" ], exec = "open" },
|
{ on = [ "o" ], exec = "open" },
|
||||||
{ on = [ "O" ], exec = "open --select" },
|
{ on = [ "O" ], exec = "open --interactive" },
|
||||||
{ on = [ "y" ], exec = "yank" },
|
{ on = [ "y" ], exec = "yank" },
|
||||||
{ on = [ "x" ], exec = "yank --cut" },
|
{ on = [ "x" ], exec = "yank --cut" },
|
||||||
{ on = [ "p" ], exec = "paste" },
|
{ on = [ "p" ], exec = "paste" },
|
||||||
{ on = [ "P" ], exec = "paste --force" },
|
{ on = [ "P" ], exec = "paste --force" },
|
||||||
{ on = [ "f" ], exec = "paste --follow" },
|
|
||||||
{ on = [ "F" ], exec = "paste --follow --force" },
|
|
||||||
{ on = [ "d" ], exec = "remove" },
|
{ on = [ "d" ], exec = "remove" },
|
||||||
{ on = [ "D" ], exec = "remove --permanently" },
|
{ on = [ "D" ], exec = "remove --permanently" },
|
||||||
{ on = [ "c" ], exec = "create" },
|
{ on = [ "c" ], exec = "create" },
|
||||||
|
|
@ -51,6 +49,16 @@ keymap = [
|
||||||
{ on = [ "z" ], exec = "jump zoxide" },
|
{ on = [ "z" ], exec = "jump zoxide" },
|
||||||
{ on = [ "Z" ], exec = "jump fzf" },
|
{ 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
|
# Tabs
|
||||||
{ on = [ "t" ], exec = "tab_create --current" },
|
{ on = [ "t" ], exec = "tab_create --current" },
|
||||||
|
|
||||||
|
|
@ -122,6 +130,7 @@ keymap = [
|
||||||
{ on = [ "a" ], exec = "insert --append" },
|
{ on = [ "a" ], exec = "insert --append" },
|
||||||
{ on = [ "v" ], exec = "visual" },
|
{ on = [ "v" ], exec = "visual" },
|
||||||
|
|
||||||
|
# Navigation
|
||||||
{ on = [ "h" ], exec = "move -1" },
|
{ on = [ "h" ], exec = "move -1" },
|
||||||
{ on = [ "l" ], exec = "move 1" },
|
{ on = [ "l" ], exec = "move 1" },
|
||||||
|
|
||||||
|
|
@ -137,14 +146,17 @@ keymap = [
|
||||||
{ on = [ "w" ], exec = "forward" },
|
{ on = [ "w" ], exec = "forward" },
|
||||||
{ on = [ "e" ], exec = "forward --end-of-word" },
|
{ on = [ "e" ], exec = "forward --end-of-word" },
|
||||||
|
|
||||||
|
# Deletion
|
||||||
{ on = [ "d" ], exec = "delete --cut" },
|
{ on = [ "d" ], exec = "delete --cut" },
|
||||||
{ on = [ "c" ], exec = "delete --cut --insert" },
|
{ on = [ "c" ], exec = "delete --cut --insert" },
|
||||||
{ on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] },
|
{ on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] },
|
||||||
|
|
||||||
|
# Copy/Paste
|
||||||
{ on = [ "y" ], exec = [ "yank" ] },
|
{ on = [ "y" ], exec = [ "yank" ] },
|
||||||
{ on = [ "p" ], exec = [ "paste" ] },
|
{ on = [ "p" ], exec = [ "paste" ] },
|
||||||
{ on = [ "P" ], exec = [ "paste --before" ] },
|
{ on = [ "P" ], exec = [ "paste --before" ] },
|
||||||
|
|
||||||
|
# Undo/Redo
|
||||||
{ on = [ "u" ], exec = [ "undo" ] },
|
{ on = [ "u" ], exec = [ "undo" ] },
|
||||||
{ on = [ "<C-r>" ], exec = [ "redo" ] },
|
{ 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.
|
- 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.
|
- yank: Copy the selected files.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(try_from = "String")]
|
#[serde(try_from = "String")]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
|
#[default]
|
||||||
Alphabetical,
|
Alphabetical,
|
||||||
Created,
|
Created,
|
||||||
Modified,
|
Modified,
|
||||||
|
|
@ -15,10 +16,10 @@ impl TryFrom<String> for SortBy {
|
||||||
|
|
||||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||||
Ok(match s.as_str() {
|
Ok(match s.as_str() {
|
||||||
|
"alphabetical" => Self::Alphabetical,
|
||||||
"created" => Self::Created,
|
"created" => Self::Created,
|
||||||
"modified" => Self::Modified,
|
"modified" => Self::Modified,
|
||||||
"size" => Self::Size,
|
"size" => Self::Size,
|
||||||
"alphabetical" => Self::Alphabetical,
|
|
||||||
_ => bail!("invalid sort_by value: {}", s),
|
_ => 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 anyhow::Result;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
@ -10,13 +10,13 @@ use crate::config::{manager::SortBy, MANAGER};
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
items: IndexMap<PathBuf, File>,
|
items: IndexMap<PathBuf, File>,
|
||||||
sort: FilesSort,
|
pub sort: FilesSort,
|
||||||
pub show_hidden: bool,
|
pub show_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
pub async fn read(paths: Vec<PathBuf>) -> IndexMap<PathBuf, File> {
|
pub async fn read(paths: Vec<PathBuf>) -> BTreeMap<PathBuf, File> {
|
||||||
let mut items = IndexMap::new();
|
let mut items = BTreeMap::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
if let Ok(file) = File::from(&path).await {
|
if let Ok(file) = File::from(&path).await {
|
||||||
items.insert(path, file);
|
items.insert(path, file);
|
||||||
|
|
@ -25,9 +25,9 @@ impl Files {
|
||||||
items
|
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 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 {
|
while let Ok(Some(item)) = it.next_entry().await {
|
||||||
if let Ok(meta) = item.metadata().await {
|
if let Ok(meta) = item.metadata().await {
|
||||||
let path = item.path();
|
let path = item.path();
|
||||||
|
|
@ -38,7 +38,75 @@ impl Files {
|
||||||
Ok(items)
|
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 {
|
fn cmp<T: Ord>(a: T, b: T, reverse: bool) -> std::cmp::Ordering {
|
||||||
if reverse { b.cmp(&a) } else { a.cmp(&b) }
|
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))
|
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
|
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 {
|
impl Deref for Files {
|
||||||
|
|
@ -112,7 +144,8 @@ impl DerefMut for Files {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FilesSort {
|
#[derive(PartialEq)]
|
||||||
|
pub struct FilesSort {
|
||||||
pub by: SortBy,
|
pub by: SortBy,
|
||||||
pub reverse: bool,
|
pub reverse: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -123,9 +156,10 @@ impl Default for FilesSort {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FilesOp {
|
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),
|
IOErr(PathBuf),
|
||||||
Search(PathBuf, IndexMap<PathBuf, File>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesOp {
|
impl FilesOp {
|
||||||
|
|
@ -133,15 +167,16 @@ impl FilesOp {
|
||||||
pub fn path(&self) -> PathBuf {
|
pub fn path(&self) -> PathBuf {
|
||||||
match self {
|
match self {
|
||||||
Self::Read(path, _) => path,
|
Self::Read(path, _) => path,
|
||||||
Self::IOErr(path) => path,
|
Self::Sort(path, _) => path,
|
||||||
Self::Search(path, _) => path,
|
Self::Search(path, _) => path,
|
||||||
|
Self::IOErr(path) => path,
|
||||||
}
|
}
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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]
|
#[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 {
|
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||||
let b = match op {
|
let b = match op {
|
||||||
FilesOp::Read(_, items) => self.files.update_read(items),
|
FilesOp::Read(_, items) => self.files.update_read(items),
|
||||||
|
FilesOp::Sort(_, items) => self.files.update_sort(items),
|
||||||
FilesOp::Search(_, items) => self.files.update_search(items),
|
FilesOp::Search(_, items) => self.files.update_search(items),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ impl Manager {
|
||||||
self.quit(tasks)
|
self.quit(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(&mut self, select: bool) -> bool {
|
pub fn open(&mut self, interactive: bool) -> bool {
|
||||||
let files = self.selected();
|
let files = self.selected();
|
||||||
if files.len() == 1 && files[0].meta.is_dir() {
|
if files.len() == 1 && files[0].meta.is_dir() {
|
||||||
return self.active_mut().enter();
|
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<_>>();
|
let files = files.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::<Vec<_>>();
|
||||||
if !select {
|
if !interactive {
|
||||||
emit!(Open(files, None));
|
emit!(Open(files, None));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -251,6 +251,20 @@ impl Manager {
|
||||||
b
|
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 {
|
pub fn update_ioerr(&mut self, op: FilesOp) -> bool {
|
||||||
let path = op.path();
|
let path = op.path();
|
||||||
let op = FilesOp::read_empty(&path);
|
let op = FilesOp::read_empty(&path);
|
||||||
|
|
@ -267,20 +281,6 @@ impl Manager {
|
||||||
true
|
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 {
|
pub fn update_mimetype(&mut self, mut mimes: BTreeMap<PathBuf, String>, tasks: &Tasks) -> bool {
|
||||||
mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
|
mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
|
||||||
if mimes.is_empty() {
|
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};
|
use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, Position, BLOCKER}, emit, misc::Defer};
|
||||||
|
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
|
pub(super) mode: Mode,
|
||||||
pub(super) current: Folder,
|
pub(super) current: Folder,
|
||||||
pub(super) parent: Option<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) history: BTreeMap<PathBuf, Folder>,
|
||||||
pub(super) preview: Preview,
|
pub(super) preview: Preview,
|
||||||
|
|
@ -20,11 +20,11 @@ pub struct Tab {
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn new(path: &Path) -> Self {
|
pub fn new(path: &Path) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mode: Default::default(),
|
||||||
current: Folder::new(path),
|
current: Folder::new(path),
|
||||||
parent: path.parent().map(|p| Folder::new(p)),
|
parent: path.parent().map(|p| Folder::new(p)),
|
||||||
search: None,
|
|
||||||
|
|
||||||
mode: Default::default(),
|
search: None,
|
||||||
|
|
||||||
history: Default::default(),
|
history: Default::default(),
|
||||||
preview: Preview::new(),
|
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 indexmap::IndexMap;
|
||||||
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||||
|
|
@ -92,7 +92,7 @@ impl Watcher {
|
||||||
for ori in linked {
|
for ori in linked {
|
||||||
emit!(Files(match &result {
|
emit!(Files(match &result {
|
||||||
Ok(items) => {
|
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 p = ori.join(p.strip_prefix(&path).unwrap());
|
||||||
let f = f.clone().set_path(&p);
|
let f = f.clone().set_path(&p);
|
||||||
(p, f)
|
(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 anyhow::{Error, Result};
|
||||||
use image::{imageops::FilterType, ImageFormat};
|
use image::{imageops::FilterType, ImageFormat};
|
||||||
use tokio::{fs, sync::mpsc};
|
use tokio::{fs, sync::mpsc};
|
||||||
|
|
||||||
use super::TaskOp;
|
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 {
|
pub struct Precache {
|
||||||
rx: async_channel::Receiver<PrecacheOp>,
|
rx: async_channel::Receiver<PrecacheOp>,
|
||||||
|
|
@ -20,6 +20,13 @@ pub(super) enum PrecacheOp {
|
||||||
Video(PrecacheOpVideo),
|
Video(PrecacheOpVideo),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct PrecacheOpSize {
|
||||||
|
pub id: usize,
|
||||||
|
pub target: PathBuf,
|
||||||
|
pub throttle: Arc<Throttle<(PathBuf, File)>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct PrecacheOpMime {
|
pub(super) struct PrecacheOpMime {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
|
|
@ -88,6 +95,7 @@ impl Precache {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) }
|
fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) }
|
||||||
|
|
||||||
pub(super) async fn mime(&self, task: PrecacheOpMime) -> Result<()> {
|
pub(super) async fn mime(&self, task: PrecacheOpMime) -> Result<()> {
|
||||||
|
|
@ -100,6 +108,22 @@ impl Precache {
|
||||||
self.done(task.id)
|
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<()> {
|
pub(super) fn image(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> {
|
||||||
for target in targets {
|
for target in targets {
|
||||||
self.sch.send(TaskOp::New(id, 0))?;
|
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 tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep};
|
||||||
use tracing::{info, trace};
|
use tracing::{info, trace};
|
||||||
|
|
||||||
use super::{File, FileOpDelete, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, Process, ProcessOpOpen, Task, TaskOp, TaskStage};
|
use super::{File, FileOpDelete, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, PrecacheOpSize, Process, ProcessOpOpen, Task, TaskOp, TaskStage};
|
||||||
use crate::{config::open::Opener, emit, misc::unique_path};
|
use crate::{config::open::Opener, emit, misc::{unique_path, Throttle}};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(super) struct Running {
|
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>) {
|
pub(super) fn precache_mime(&self, targets: Vec<PathBuf>) {
|
||||||
let name = format!("Preload mimetype for {} files", targets.len());
|
let name = format!("Preload mimetype for {} files", targets.len());
|
||||||
let id = self.running.write().add(name);
|
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 tracing::trace;
|
||||||
|
|
||||||
use super::{Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
|
|
@ -180,6 +180,25 @@ impl Tasks {
|
||||||
false
|
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]
|
#[inline]
|
||||||
pub fn precache_mime(&self, targets: Vec<&File>, mimetype: &HashMap<PathBuf, String>) -> bool {
|
pub fn precache_mime(&self, targets: Vec<&File>, mimetype: &HashMap<PathBuf, String>) -> bool {
|
||||||
let targets = targets
|
let targets = targets
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ impl Which {
|
||||||
self.cands = KEYMAP
|
self.cands = KEYMAP
|
||||||
.get(layer)
|
.get(layer)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|s| !s.on.is_empty() && s.on[0] == *key)
|
.filter(|s| s.on.len() > 1 && s.on[0] == *key)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod defer;
|
||||||
mod fns;
|
mod fns;
|
||||||
mod fs;
|
mod fs;
|
||||||
mod mime;
|
mod mime;
|
||||||
|
mod throttle;
|
||||||
mod tty;
|
mod tty;
|
||||||
|
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
|
|
@ -12,4 +13,5 @@ pub use defer::*;
|
||||||
pub use fns::*;
|
pub use fns::*;
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use mime::*;
|
pub use mime::*;
|
||||||
|
pub use throttle::*;
|
||||||
pub use tty::*;
|
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();
|
manager.refresh();
|
||||||
}
|
}
|
||||||
Event::Files(op) => {
|
Event::Files(op) => {
|
||||||
|
let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..));
|
||||||
let b = match op {
|
let b = match op {
|
||||||
FilesOp::Read(..) => manager.update_read(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::Search(..) => manager.update_search(op),
|
||||||
|
FilesOp::IOErr(..) => manager.update_ioerr(op),
|
||||||
};
|
};
|
||||||
if b {
|
if b {
|
||||||
emit!(Render);
|
emit!(Render);
|
||||||
}
|
}
|
||||||
|
if calc {
|
||||||
|
tasks.precache_size(&manager.current().files);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Pages(page) => {
|
Event::Pages(page) => {
|
||||||
if manager.current().page == page {
|
if manager.current().page == page {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::Ctx;
|
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;
|
pub struct Executor;
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl Executor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operation
|
// 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")),
|
"yank" => cx.manager.yank(exec.named.contains_key("cut")),
|
||||||
"paste" => {
|
"paste" => {
|
||||||
let dest = cx.manager.current().cwd.clone();
|
let dest = cx.manager.current().cwd.clone();
|
||||||
|
|
@ -115,6 +115,17 @@ impl Executor {
|
||||||
_ => false,
|
_ => 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
|
// Tabs
|
||||||
"tab_create" => {
|
"tab_create" => {
|
||||||
let path = if exec.named.contains_key("current") {
|
let path = if exec.named.contains_key("current") {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue