Add the excluded command to toggle/show/hide excluded files via keymaps

This commit is contained in:
Carlos de Paula 2025-11-05 20:37:43 -03:00
parent e32db2a539
commit f068dafec2
No known key found for this signature in database
7 changed files with 150 additions and 4 deletions

View file

@ -0,0 +1,52 @@
use anyhow::Result;
use yazi_core::tab::Folder;
use yazi_fs::FolderStage;
use yazi_macro::{act, render, render_and, succ};
use yazi_parser::mgr::ExcludedOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Excluded;
impl Actor for Excluded {
type Options = ExcludedOpt;
const NAME: &str = "excluded";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let state = opt.state.bool(cx.tab().current.files.show_excluded());
let hovered = cx.hovered().map(|f| f.urn().to_owned());
let apply = |f: &mut Folder| {
if f.stage == FolderStage::Loading {
render!();
false
} else {
f.files.set_show_excluded(state);
render_and!(f.files.catchup_revision())
}
};
// Apply to CWD and parent
if let (a, Some(b)) = (apply(cx.current_mut()), cx.parent_mut().map(apply))
&& (a | b)
{
act!(mgr:hover, cx)?;
act!(mgr:update_paged, cx)?;
}
// Apply to hovered
if let Some(h) = cx.hovered_folder_mut()
&& apply(h)
{
render!(h.repos(None));
act!(mgr:peek, cx, true)?;
} else if hovered.as_deref() != cx.hovered().map(|f| f.urn()) {
act!(mgr:peek, cx)?;
act!(mgr:watch, cx)?;
}
succ!()
}
}

View file

@ -10,6 +10,7 @@ yazi_macro::mod_flat!(
enter
escape
exclude_add
excluded
filter
filter_do
find

View file

@ -24,6 +24,7 @@ pub enum Spark<'a> {
EscapeSelect(yazi_parser::VoidOpt),
EscapeVisual(yazi_parser::VoidOpt),
ExcludeAdd(yazi_parser::mgr::ExcludeAddOpt),
Excluded(yazi_parser::mgr::ExcludedOpt),
Filter(yazi_parser::mgr::FilterOpt),
FilterDo(yazi_parser::mgr::FilterOpt),
Find(yazi_parser::mgr::FindOpt),
@ -149,6 +150,7 @@ impl<'a> IntoLua for Spark<'a> {
Self::EscapeSelect(b) => b.into_lua(lua),
Self::EscapeVisual(b) => b.into_lua(lua),
Self::ExcludeAdd(b) => b.into_lua(lua),
Self::Excluded(b) => b.into_lua(lua),
Self::Filter(b) => b.into_lua(lua),
Self::FilterDo(b) => b.into_lua(lua),
Self::Find(b) => b.into_lua(lua),
@ -310,6 +312,7 @@ try_from_spark!(mgr::RevealOpt, mgr:reveal);
try_from_spark!(mgr::SearchOpt, mgr:search, mgr:search_do);
try_from_spark!(mgr::SeekOpt, mgr:seek);
try_from_spark!(mgr::ShellOpt, mgr:shell);
try_from_spark!(mgr::ExcludedOpt, mgr:excluded);
try_from_spark!(mgr::SortOpt, mgr:sort);
try_from_spark!(mgr::SpotOpt, mgr:spot);
try_from_spark!(mgr::TabCloseOpt, mgr:tab_close);

View file

@ -111,6 +111,7 @@ impl<'a> Executor<'a> {
on!(copy);
on!(shell);
on!(hidden);
on!(excluded);
on!(ignore);
on!(linemode);
on!(search);

View file

@ -19,6 +19,7 @@ pub struct Files {
sorter: FilesSorter,
filter: Option<Filter>,
show_hidden: bool,
show_excluded: bool,
ignore_filter: Option<IgnoreFilter>,
}
@ -261,12 +262,23 @@ impl Files {
files.into_iter().partition(|f| {
(f.is_hidden() && !self.show_hidden)
|| !filter.matches(f.urn())
|| self.ignore_filter.as_ref().is_some_and(|ig| ig.matches_url(&f.url))
|| (!self.show_excluded
&& self.ignore_filter.as_ref().is_some_and(|ig| ig.matches_url(&f.url)))
})
} else if let Some(ignore_filter) = &self.ignore_filter {
files
.into_iter()
.partition(|f| (f.is_hidden() && !self.show_hidden) || ignore_filter.matches_url(&f.url))
if self.show_excluded {
// Show excluded files - only hide based on hidden status
if self.show_hidden {
(vec![], files.into_iter().collect())
} else {
files.into_iter().partition(|f| f.is_hidden())
}
} else {
// Hide excluded files - apply ignore filter
files
.into_iter()
.partition(|f| (f.is_hidden() && !self.show_hidden) || ignore_filter.matches_url(&f.url))
}
} else if self.show_hidden {
(vec![], files.into_iter().collect())
} else {
@ -341,6 +353,26 @@ impl Files {
true
}
// --- Show excluded
#[inline]
pub fn show_excluded(&self) -> bool { self.show_excluded }
pub fn set_show_excluded(&mut self, state: bool) -> bool {
if self.show_excluded == state {
return false;
}
self.show_excluded = state;
// Re-split files with the new state
let it = mem::take(&mut self.items).into_iter().chain(mem::take(&mut self.hidden));
(self.hidden, self.items) = self.split_files(it);
self.sorter.sort(&mut self.items, &self.sizes);
self.revision += 1;
true
}
// --- Show hidden
pub fn set_show_hidden(&mut self, state: bool) {
if self.show_hidden == state {

View file

@ -0,0 +1,56 @@
use std::str::FromStr;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::event::CmdCow;
#[derive(Debug, Default)]
pub struct ExcludedOpt {
pub state: ExcludedOptState,
}
impl TryFrom<CmdCow> for ExcludedOpt {
type Error = anyhow::Error;
fn try_from(c: CmdCow) -> Result<Self, Self::Error> {
Ok(Self { state: c.str(0).parse().unwrap_or_default() })
}
}
impl FromLua for ExcludedOpt {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
}
impl IntoLua for ExcludedOpt {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
}
// --- State
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ExcludedOptState {
#[default]
None,
Show,
Hide,
Toggle,
}
impl FromStr for ExcludedOptState {
type Err = serde::de::value::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::deserialize(serde::de::value::StrDeserializer::new(s))
}
}
impl ExcludedOptState {
pub fn bool(self, old: bool) -> bool {
match self {
Self::None => old,
Self::Show => true,
Self::Hide => false,
Self::Toggle => !old,
}
}
}

View file

@ -6,6 +6,7 @@ yazi_macro::mod_flat!(
download
escape
exclude_add
excluded
filter
find
find_arrow