Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions yazi-actor/src/mgr/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl Actor for Sort {

fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let pref = &mut cx.tab_mut().pref;
pref.sort_by = opt.by.unwrap_or(pref.sort_by);
pref.sort_by = opt.by.unwrap_or_else(|| pref.sort_by.clone());
pref.sort_reverse = opt.reverse.unwrap_or(pref.sort_reverse);
pref.sort_dir_first = opt.dir_first.unwrap_or(pref.sort_dir_first);
pref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive);
Expand All @@ -29,7 +29,7 @@ impl Actor for Sort {
render!();
false
} else {
f.files.set_sorter(sorter);
f.files.set_sorter(sorter.clone());
render_and!(f.files.catchup_revision())
}
};
Expand Down
4 changes: 2 additions & 2 deletions yazi-config/src/mgr/mgr.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Result, bail};
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_fs::{CWD, SortBy};
use yazi_fs::{CWD, SortBys};
use yazi_shared::{SyncCell, url::UrlBuf};

use super::{MgrRatio, MouseEvents};
Expand All @@ -11,7 +11,7 @@ pub struct Mgr {
pub ratio: SyncCell<MgrRatio>,

// Sorting
pub sort_by: SyncCell<SortBy>,
pub sort_by: SortBys,
pub sort_sensitive: SyncCell<bool>,
pub sort_reverse: SyncCell<bool>,
pub sort_dir_first: SyncCell<bool>,
Expand Down
8 changes: 4 additions & 4 deletions yazi-core/src/tab/preference.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use yazi_config::YAZI;
use yazi_fs::{FilesSorter, SortBy};
use yazi_fs::{FilesSorter, SortBys};

#[derive(Clone, PartialEq)]
pub struct Preference {
// Sorting
pub sort_by: SortBy,
pub sort_by: SortBys,
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_dir_first: bool,
Expand All @@ -19,7 +19,7 @@ impl Default for Preference {
fn default() -> Self {
Self {
// Sorting
sort_by: YAZI.mgr.sort_by.get(),
sort_by: YAZI.mgr.sort_by.clone(),
sort_sensitive: YAZI.mgr.sort_sensitive.get(),
sort_reverse: YAZI.mgr.sort_reverse.get(),
sort_dir_first: YAZI.mgr.sort_dir_first.get(),
Expand All @@ -35,7 +35,7 @@ impl Default for Preference {
impl From<&Preference> for FilesSorter {
fn from(value: &Preference) -> Self {
Self {
by: value.sort_by,
by: value.sort_by.clone(),
sensitive: value.sort_sensitive,
reverse: value.sort_reverse,
dir_first: value.sort_dir_first,
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/tasks/preload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Tasks {
}

pub fn prework_sorted(&self, targets: &Files) {
if targets.sorter().by != SortBy::Size {
if targets.sorter().by.0.contains(&SortBy::Size) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion yazi-fs/src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl Files {
return;
}

if self.sorter.by == SortBy::Size {
if self.sorter.by.0.contains(&SortBy::Size) {
self.revision += 1;
}
self.sizes.extend(sizes);
Expand Down
89 changes: 45 additions & 44 deletions yazi-fs/src/sorter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use std::cmp::Ordering;
use hashbrown::HashMap;
use yazi_shared::{LcgRng, natsort, translit::Transliterator, url::UrnBuf};

use crate::{File, SortBy};
use crate::{File, SortBy, SortBys};

#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FilesSorter {
pub by: SortBy,
pub by: SortBys,
pub sensitive: bool,
pub reverse: bool,
pub reverse: bool,
pub dir_first: bool,
pub translit: bool,
pub translit: bool,
}

impl FilesSorter {
Expand All @@ -28,45 +28,7 @@ impl FilesSorter {
}
};

match self.by {
SortBy::None => {}
SortBy::Mtime => items.sort_unstable_by(|a, b| {
let ord = self.cmp(a.mtime, b.mtime, self.promote(a, b));
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Btime => items.sort_unstable_by(|a, b| {
let ord = self.cmp(a.btime, b.btime, self.promote(a, b));
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Extension => items.sort_unstable_by(|a, b| {
let ord = if self.sensitive {
self.cmp(a.url.ext(), b.url.ext(), self.promote(a, b))
} else {
self.cmp_insensitive(
a.url.ext().map_or(&[], |s| s.as_encoded_bytes()),
b.url.ext().map_or(&[], |s| s.as_encoded_bytes()),
self.promote(a, b),
)
};
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Alphabetical => items.sort_unstable_by(by_alphabetical),
SortBy::Natural => self.sort_naturally(items),
SortBy::Size => items.sort_unstable_by(|a, b| {
let aa = if a.is_dir() { sizes.get(a.urn()).copied() } else { None };
let bb = if b.is_dir() { sizes.get(b.urn()).copied() } else { None };
let ord = self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b));
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Random => {
let mut rng = LcgRng::default();
items.sort_unstable_by(|a, b| self.cmp(rng.next(), rng.next(), self.promote(a, b)))
}
}
}

fn sort_naturally(&self, items: &mut [File]) {
items.sort_unstable_by(|a, b| {
let by_natural = |a: &File, b: &File| {
let promote = self.promote(a, b);
if promote != Ordering::Equal {
return promote;
Expand All @@ -83,6 +45,45 @@ impl FilesSorter {
};

if self.reverse { ordering.reverse() } else { ordering }
};

items.sort_unstable_by(|a, b| {
for key in &self.by.0 {
let ord = match key {
SortBy::None => Ordering::Equal,
SortBy::Mtime => self.cmp(a.mtime, b.mtime, self.promote(a, b)),
SortBy::Btime => self.cmp(a.btime, b.btime, self.promote(a, b)),
SortBy::Extension => {
if self.sensitive {
self.cmp(a.url.ext(), b.url.ext(), self.promote(a, b))
} else {
self.cmp_insensitive(
a.url.ext().map_or(&[], |s| s.as_encoded_bytes()),
b.url.ext().map_or(&[], |s| s.as_encoded_bytes()),
self.promote(a, b),
)
}
}
SortBy::Alphabetical => by_alphabetical(a, b),
SortBy::Natural => by_natural(a, b),
SortBy::Size => {
let aa = if a.is_dir() { sizes.get(a.urn()).copied() } else { None };
let bb = if b.is_dir() { sizes.get(b.urn()).copied() } else { None };
self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b))
}
SortBy::Random => {
let mut rng = LcgRng::default();
self.cmp(rng.next(), rng.next(), self.promote(a, b))
}
};

if ord != Ordering::Equal {
return ord;
}
}

// fallback to alphabetical sorting if all other keys are equal
by_alphabetical(a, b)
});
}

Expand Down
59 changes: 58 additions & 1 deletion yazi-fs/src/sorting.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, Serializer};

#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -38,3 +38,60 @@ impl Display for SortBy {
})
}
}

#[derive(Clone, Debug, PartialEq, Default)]
pub struct SortBys(pub Vec<SortBy>);

#[derive(Deserialize)]
#[serde(untagged)]
enum SortBysDef {
Single(SortBy),
Multiple(Vec<SortBy>),
}

impl<'de> Deserialize<'de> for SortBys {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let helper = SortBysDef::deserialize(deserializer)?;
Ok(match helper {
SortBysDef::Single(s) => SortBys(vec![s]),
SortBysDef::Multiple(v) => SortBys(v),
})
}
}

impl Serialize for SortBys {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.0.len() {
1 => {
// serialize the single SortBy as a single value
self.0[0].serialize(serializer)
}
_ => {
// empty or multi -> serialize as an array
self.0.serialize(serializer)
}
}
}
}

impl Display for SortBys {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.0.iter();
f.write_str("[")?;
if let Some(first) = iter.next() {
f.write_str(&first.to_string())?;
for sort_by in iter {
f.write_str(", ")?;
f.write_str(&sort_by.to_string())?;
}
}
f.write_str("]")?;
Ok(())
}
}
9 changes: 6 additions & 3 deletions yazi-parser/src/mgr/sort.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::str::FromStr;

use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_fs::SortBy;
use yazi_fs::{SortBy, SortBys};
use yazi_shared::event::CmdCow;

#[derive(Debug, Default)]
pub struct SortOpt {
pub by: Option<SortBy>,
pub by: Option<SortBys>,
pub reverse: Option<bool>,
pub dir_first: Option<bool>,
pub sensitive: Option<bool>,
Expand All @@ -17,8 +17,11 @@ impl TryFrom<CmdCow> for SortOpt {
type Error = anyhow::Error;

fn try_from(c: CmdCow) -> Result<Self, Self::Error> {
let by =
(0..).map_while(|i| c.str(i)).map(SortBy::from_str).collect::<Result<Vec<SortBy>, _>>()?;

Ok(Self {
by: c.first_str().map(SortBy::from_str).transpose()?,
by: if by.is_empty() { None } else { Some(SortBys(by)) },
reverse: c.maybe_bool("reverse"),
dir_first: c.maybe_bool("dir-first"),
sensitive: c.maybe_bool("sensitive"),
Expand Down
Loading