diff --git a/yazi-actor/src/mgr/sort.rs b/yazi-actor/src/mgr/sort.rs index 81b77676f..e487672df 100644 --- a/yazi-actor/src/mgr/sort.rs +++ b/yazi-actor/src/mgr/sort.rs @@ -3,7 +3,7 @@ use yazi_core::tab::Folder; use yazi_dds::spark::SparkKind; use yazi_fs::{FilesSorter, FolderStage}; use yazi_macro::{act, render, render_and, succ}; -use yazi_parser::mgr::SortOpt; +use yazi_parser::mgr::{SortBoolState, SortOpt}; use yazi_shared::{Source, data::Data}; use crate::{Actor, Ctx}; @@ -18,10 +18,10 @@ impl Actor for Sort { fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let pref = &mut cx.tab_mut().pref; pref.sort_by = opt.by.unwrap_or(pref.sort_by); - 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_reverse = opt.reverse.unwrap_or_default().bool(pref.sort_reverse); + pref.sort_dir_first = opt.dir_first.unwrap_or_default().bool(pref.sort_dir_first); pref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive); - pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit); + pref.sort_translit = opt.translit.unwrap_or_default().bool(pref.sort_translit); pref.sort_fallback = opt.fallback.unwrap_or(pref.sort_fallback); let sorter = FilesSorter::from(&*pref); @@ -59,7 +59,15 @@ impl Actor for Sort { succ!(); } - fn hook(cx: &Ctx, _: &Self::Options) -> Option { + fn hook(cx: &Ctx, opt: &Self::Options) -> Option { + let has_toggle = [opt.reverse, opt.dir_first, opt.translit] + .iter() + .any(|f| matches!(f, Some(SortBoolState::Toggle))); + + if has_toggle { + return None; + } + match cx.source() { Source::Ind => Some(SparkKind::IndSort), Source::Key => Some(SparkKind::KeySort), diff --git a/yazi-parser/src/mgr/sort.rs b/yazi-parser/src/mgr/sort.rs index fc689e3ad..33ebacef7 100644 --- a/yazi-parser/src/mgr/sort.rs +++ b/yazi-parser/src/mgr/sort.rs @@ -1,16 +1,19 @@ +use std::str::FromStr; + +use anyhow::bail; use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::SER_OPT; use yazi_fs::{SortBy, SortFallback}; -use yazi_shared::event::ActionCow; +use yazi_shared::{data::Data, event::ActionCow}; #[derive(Debug, Default, Deserialize, Serialize)] pub struct SortOpt { pub by: Option, - pub reverse: Option, - pub dir_first: Option, + pub reverse: Option, + pub dir_first: Option, pub sensitive: Option, - pub translit: Option, + pub translit: Option, pub fallback: Option, } @@ -36,3 +39,91 @@ impl FromLua for SortOpt { impl IntoLua for SortOpt { fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value_with(&self, SER_OPT) } } + +// --- State +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum SortBoolState { + #[default] + None, + On, + Off, + Toggle, +} + +impl serde::Serialize for SortBoolState { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::On => serializer.serialize_bool(true), + Self::Off => serializer.serialize_bool(false), + Self::Toggle => serializer.serialize_str("toggle"), + Self::None => serializer.serialize_str("none"), + } + } +} + +impl FromStr for SortBoolState { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "yes" => Ok(Self::On), + "no" => Ok(Self::Off), + "toggle" => Ok(Self::Toggle), + _ => bail!("unknown sort bool state: {s:?}"), + } + } +} + +impl<'de> serde::Deserialize<'de> for SortBoolState { + fn deserialize>(deserializer: D) -> Result { + use serde::de::{self, Visitor}; + + struct V; + + impl<'de> Visitor<'de> for V { + type Value = SortBoolState; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(r#"a boolean or one of "yes", "no", "toggle""#) + } + + fn visit_bool(self, v: bool) -> Result { + Ok(if v { SortBoolState::On } else { SortBoolState::Off }) + } + + fn visit_str(self, v: &str) -> Result { + v.parse().map_err(de::Error::custom) + } + + fn visit_string(self, v: String) -> Result { + self.visit_str(&v) + } + } + + deserializer.deserialize_any(V) + } +} + +impl SortBoolState { + pub fn bool(self, old: bool) -> bool { + match self { + Self::None => old, + Self::On => true, + Self::Off => false, + Self::Toggle => !old, + } + } +} + +impl TryFrom<&Data> for SortBoolState { + type Error = anyhow::Error; + + fn try_from(value: &Data) -> Result { + match value { + Data::Boolean(true) => Ok(Self::On), + Data::Boolean(false) => Ok(Self::Off), + Data::String(s) => s.parse(), + _ => bail!("not a valid bool state"), + } + } +}