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:
三咲雅 · Misaki Masa 2023-07-25 11:31:34 +08:00 committed by GitHub
parent 688afeaf27
commit e2a4894111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 276 additions and 94 deletions

10
Cargo.lock generated
View File

@ -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",

View File

@ -1,6 +1,6 @@
[package]
name = "yazi"
version = "0.1.0"
version = "0.1.2"
edition = "2021"
[dependencies]

View File

@ -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" ] },
]

View File

@ -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"}

View File

@ -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.

View File

@ -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),
})
}

View File

@ -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()) }
}

View File

@ -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!(),
};

View File

@ -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() {

View File

@ -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(),

View File

@ -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)

View File

@ -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))?;

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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
View 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)
}
}

View File

@ -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 {

View File

@ -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") {