mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: sort naturally (#82)
This commit is contained in:
parent
a7a085e41c
commit
388ad43537
11 changed files with 136 additions and 87 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -381,6 +381,7 @@ dependencies = [
|
|||
"crossterm 0.27.0",
|
||||
"futures",
|
||||
"indexmap 2.0.0",
|
||||
"natord",
|
||||
"notify",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
|
|
@ -1133,6 +1134,12 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "natord"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use core::{emit, files::FilesSort, input::InputMode};
|
||||
use core::{emit, files::FilesSorter, input::InputMode};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
|
||||
|
|
@ -130,7 +130,7 @@ impl Executor {
|
|||
|
||||
// Sorting
|
||||
"sort" => {
|
||||
let b = cx.manager.current_mut().files.set_sort(FilesSort {
|
||||
let b = cx.manager.current_mut().files.set_sorter(FilesSorter {
|
||||
by: SortBy::try_from(exec.args.get(0).cloned().unwrap_or_default())
|
||||
.unwrap_or_default(),
|
||||
reverse: exec.named.contains_key("reverse"),
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
- sort_by: File sorting method
|
||||
|
||||
- `"alphabetical"`: Sort alphabetically
|
||||
- `"alphabetical"`: Sort alphabetically, e.g. `1.md` < `10.md` < `2.md`
|
||||
- `"created"`: Sort by creation time
|
||||
- `"modified"`: Sort by last modified time
|
||||
- `"natural"`: Sort naturally, e.g. `1.md` < `2.md` < `10.md`
|
||||
- `"size"`: Sort by file size
|
||||
|
||||
- sort_reverse: Display files in reverse order
|
||||
|
|
|
|||
|
|
@ -62,14 +62,16 @@ keymap = [
|
|||
{ on = [ "c", "n" ], exec = "copy name_without_ext" },
|
||||
|
||||
# Sorting
|
||||
{ on = [ ",", "a" ], exec = "sort alphabetical" },
|
||||
{ on = [ ",", "A" ], exec = "sort alphabetical --reverse" },
|
||||
{ on = [ ",", "c" ], exec = "sort created" },
|
||||
{ on = [ ",", "C" ], exec = "sort created --reverse" },
|
||||
{ on = [ ",", "m" ], exec = "sort modified" },
|
||||
{ on = [ ",", "M" ], exec = "sort modified --reverse" },
|
||||
{ on = [ ",", "s" ], exec = "sort size" },
|
||||
{ on = [ ",", "S" ], exec = "sort size --reverse" },
|
||||
{ on = [ ",", "a" ], exec = "sort alphabetical --dir_first" },
|
||||
{ on = [ ",", "A" ], exec = "sort alphabetical --reverse --dir_first" },
|
||||
{ on = [ ",", "c" ], exec = "sort created --dir_first" },
|
||||
{ on = [ ",", "C" ], exec = "sort created --reverse --dir_first" },
|
||||
{ on = [ ",", "m" ], exec = "sort modified --dir_first" },
|
||||
{ on = [ ",", "M" ], exec = "sort modified --reverse --dir_first" },
|
||||
{ on = [ ",", "n" ], exec = "sort natural --dir_first" },
|
||||
{ on = [ ",", "N" ], exec = "sort natural --reverse --dir_first" },
|
||||
{ on = [ ",", "s" ], exec = "sort size --dir_first" },
|
||||
{ on = [ ",", "S" ], exec = "sort size --reverse --dir_first" },
|
||||
|
||||
# Tabs
|
||||
{ on = [ "t" ], exec = "tab_create --current" },
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub enum SortBy {
|
|||
Alphabetical,
|
||||
Created,
|
||||
Modified,
|
||||
Natural,
|
||||
Size,
|
||||
}
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ impl TryFrom<String> for SortBy {
|
|||
"alphabetical" => Self::Alphabetical,
|
||||
"created" => Self::Created,
|
||||
"modified" => Self::Modified,
|
||||
"natural" => Self::Natural,
|
||||
"size" => Self::Size,
|
||||
_ => bail!("invalid sort_by value: {s}"),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ async-channel = "^1"
|
|||
crossterm = "^0"
|
||||
futures = "^0"
|
||||
indexmap = "^2"
|
||||
natord = "^1"
|
||||
notify = { version = "^6", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = "^0"
|
||||
ratatui = "^0"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::{cmp::Ordering, collections::BTreeMap, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||
use std::{collections::BTreeMap, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use config::{manager::SortBy, MANAGER};
|
||||
use config::MANAGER;
|
||||
use indexmap::IndexMap;
|
||||
use tokio::fs;
|
||||
|
||||
use super::File;
|
||||
use super::{File, FilesSorter};
|
||||
|
||||
pub struct Files {
|
||||
items: IndexMap<PathBuf, File>,
|
||||
pub sort: FilesSort,
|
||||
pub sorter: FilesSorter,
|
||||
pub show_hidden: bool,
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ impl Default for Files {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
items: Default::default(),
|
||||
sort: Default::default(),
|
||||
sorter: Default::default(),
|
||||
show_hidden: MANAGER.show_hidden,
|
||||
}
|
||||
}
|
||||
|
|
@ -53,12 +53,12 @@ impl Files {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_sort(&mut self, sort: FilesSort) -> bool {
|
||||
if self.sort == sort {
|
||||
pub fn set_sorter(&mut self, sort: FilesSorter) -> bool {
|
||||
if self.sorter == sort {
|
||||
return false;
|
||||
}
|
||||
self.sort = sort;
|
||||
self.sort()
|
||||
self.sorter = sort;
|
||||
self.sorter.sort(&mut self.items)
|
||||
}
|
||||
|
||||
pub fn update_read(&mut self, mut items: BTreeMap<PathBuf, File>) -> bool {
|
||||
|
|
@ -80,7 +80,7 @@ impl Files {
|
|||
|
||||
self.items.clear();
|
||||
self.items.extend(items);
|
||||
self.sort();
|
||||
self.sorter.sort(&mut self.items);
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -92,14 +92,14 @@ impl Files {
|
|||
}
|
||||
|
||||
self.items.extend(items);
|
||||
self.sort();
|
||||
self.sorter.sort(&mut self.items);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_search(&mut self, items: BTreeMap<PathBuf, File>) -> bool {
|
||||
if !items.is_empty() {
|
||||
self.items.extend(items);
|
||||
self.sort();
|
||||
self.sorter.sort(&mut self.items);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -110,51 +110,6 @@ impl Files {
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
fn sort(&mut self) -> bool {
|
||||
if self.items.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
fn cmp<T: Ord>(a: T, b: T, reverse: bool, promote: Ordering) -> Ordering {
|
||||
if promote != Ordering::Equal {
|
||||
promote
|
||||
} else {
|
||||
if reverse { b.cmp(&a) } else { a.cmp(&b) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn promote(a: &File, b: &File, dir_first: bool) -> Ordering {
|
||||
if dir_first { b.meta.is_dir().cmp(&a.meta.is_dir()) } else { Ordering::Equal }
|
||||
}
|
||||
|
||||
let reverse = self.sort.reverse;
|
||||
let dir_first = self.sort.dir_first;
|
||||
match self.sort.by {
|
||||
SortBy::Alphabetical => {
|
||||
self.items.sort_by(|_, a, _, b| cmp(&a.path, &b.path, reverse, promote(a, b, dir_first)))
|
||||
}
|
||||
SortBy::Created => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
||||
return cmp(aa, bb, reverse, promote(a, b, dir_first));
|
||||
}
|
||||
Ordering::Equal
|
||||
}),
|
||||
SortBy::Modified => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) {
|
||||
return cmp(aa, bb, reverse, promote(a, b, dir_first));
|
||||
}
|
||||
Ordering::Equal
|
||||
}),
|
||||
SortBy::Size => self.items.sort_by(|_, a, _, b| {
|
||||
cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse, promote(a, b, dir_first))
|
||||
}),
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Files {
|
||||
|
|
@ -167,23 +122,6 @@ impl DerefMut for Files {
|
|||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct FilesSort {
|
||||
pub by: SortBy,
|
||||
pub reverse: bool,
|
||||
pub dir_first: bool,
|
||||
}
|
||||
|
||||
impl Default for FilesSort {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
by: MANAGER.sort_by,
|
||||
reverse: MANAGER.sort_reverse,
|
||||
dir_first: MANAGER.sort_dir_first,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FilesOp {
|
||||
Read(PathBuf, BTreeMap<PathBuf, File>),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
mod file;
|
||||
mod files;
|
||||
mod sorter;
|
||||
|
||||
pub use file::*;
|
||||
pub use files::*;
|
||||
pub use sorter::*;
|
||||
|
|
|
|||
96
core/src/files/sorter.rs
Normal file
96
core/src/files/sorter.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use std::{cmp::Ordering, path::PathBuf};
|
||||
|
||||
use config::{manager::SortBy, MANAGER};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::File;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct FilesSorter {
|
||||
pub by: SortBy,
|
||||
pub reverse: bool,
|
||||
pub dir_first: bool,
|
||||
}
|
||||
|
||||
impl Default for FilesSorter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
by: MANAGER.sort_by,
|
||||
reverse: MANAGER.sort_reverse,
|
||||
dir_first: MANAGER.sort_dir_first,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilesSorter {
|
||||
pub(super) fn sort(&self, items: &mut IndexMap<PathBuf, File>) -> bool {
|
||||
if items.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.by {
|
||||
SortBy::Alphabetical => {
|
||||
items.sort_unstable_by(|_, a, _, b| self.cmp(&a.path, &b.path, self.promote(a, b)))
|
||||
}
|
||||
SortBy::Created => items.sort_unstable_by(|_, a, _, b| {
|
||||
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
||||
return self.cmp(aa, bb, self.promote(a, b));
|
||||
}
|
||||
Ordering::Equal
|
||||
}),
|
||||
SortBy::Modified => items.sort_unstable_by(|_, a, _, b| {
|
||||
if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) {
|
||||
return self.cmp(aa, bb, self.promote(a, b));
|
||||
}
|
||||
Ordering::Equal
|
||||
}),
|
||||
SortBy::Natural => self.sort_naturally(items),
|
||||
SortBy::Size => items.sort_unstable_by(|_, a, _, b| {
|
||||
self.cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), self.promote(a, b))
|
||||
}),
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn sort_naturally(&self, items: &mut IndexMap<PathBuf, File>) {
|
||||
let mut indices = Vec::with_capacity(items.len());
|
||||
let mut entities = Vec::with_capacity(items.len());
|
||||
for (i, (path, file)) in items.into_iter().enumerate() {
|
||||
indices.push(i);
|
||||
entities.push((path.to_string_lossy(), file));
|
||||
}
|
||||
|
||||
indices.sort_unstable_by(|&a, &b| {
|
||||
let promote = self.promote(entities[a].1, entities[b].1);
|
||||
if promote != Ordering::Equal {
|
||||
promote
|
||||
} else if self.reverse {
|
||||
natord::compare(&entities[b].0, &entities[a].0)
|
||||
} else {
|
||||
natord::compare(&entities[a].0, &entities[b].0)
|
||||
}
|
||||
});
|
||||
|
||||
let mut new = IndexMap::with_capacity(indices.len());
|
||||
for i in indices {
|
||||
let file = entities[i].1.clone();
|
||||
new.insert(file.path(), file);
|
||||
}
|
||||
*items = new;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
fn cmp<T: Ord>(&self, a: T, b: T, promote: Ordering) -> Ordering {
|
||||
if promote != Ordering::Equal {
|
||||
promote
|
||||
} else {
|
||||
if self.reverse { b.cmp(&a) } else { a.cmp(&b) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn promote(&self, a: &File, b: &File) -> Ordering {
|
||||
if self.dir_first { b.meta.is_dir().cmp(&a.meta.is_dir()) } else { Ordering::Equal }
|
||||
}
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ impl Tasks {
|
|||
|
||||
#[inline]
|
||||
pub fn precache_size(&self, targets: &Files) -> bool {
|
||||
if targets.sort.by != SortBy::Size {
|
||||
if targets.sorter.by != SortBy::Size {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"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","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel"],"language":"en","flagWords":[],"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","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord"],"language":"en","version":"0.2"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue