chore: use if state for hovered icon

This commit is contained in:
stickyburn 2026-03-02 17:03:56 -05:00 committed by sxyazi
parent ec730960ac
commit d933c0e6f1
No known key found for this signature in database
6 changed files with 107 additions and 65 deletions

View file

@ -6,10 +6,8 @@ use crate::{Style, cached_field};
pub struct Icon { pub struct Icon {
inner: &'static yazi_config::Icon, inner: &'static yazi_config::Icon,
v_text: Option<Value>, v_text: Option<Value>,
v_style: Option<Value>, v_style: Option<Value>,
v_hovered_text: Option<Value>,
} }
impl Deref for Icon { impl Deref for Icon {
@ -20,7 +18,7 @@ impl Deref for Icon {
impl From<&'static yazi_config::Icon> for Icon { impl From<&'static yazi_config::Icon> for Icon {
fn from(icon: &'static yazi_config::Icon) -> Self { fn from(icon: &'static yazi_config::Icon) -> Self {
Self { inner: icon, v_text: None, v_style: None, v_hovered_text: None } Self { inner: icon, v_text: None, v_style: None }
} }
} }
@ -28,8 +26,5 @@ impl UserData for Icon {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, text, |lua, me| lua.create_string(&me.text)); cached_field!(fields, text, |lua, me| lua.create_string(&me.text));
cached_field!(fields, style, |_, me| Ok(Style::from(me.style))); cached_field!(fields, style, |_, me| Ok(Style::from(me.style)));
cached_field!(fields, hovered_text, |lua, me| {
me.hovered_text.as_ref().map(|s| lua.create_string(s)).transpose()
});
} }
} }

View file

@ -268,10 +268,10 @@ macro_rules! impl_file_methods {
Ok(me.hash_u64()) Ok(me.hash_u64())
}); });
$methods.add_method("icon", |_, me, ()| { $methods.add_method("icon", |_, me, hovered: Option<bool>| {
use $crate::Icon; use $crate::Icon;
// TODO: use a cache // TODO: use a cache
Ok(yazi_config::THEME.icon.matches(me).map(Icon::from)) Ok(yazi_config::THEME.icon.matches(me, hovered.unwrap_or(false)).map(Icon::from))
}); });
}; };
} }

View file

@ -997,7 +997,8 @@ conds = [
{ if = "dummy", text = "", fg = "#f44336" }, { if = "dummy", text = "", fg = "#f44336" },
# Fallback # Fallback
{ if = "dir", text = "", fg = "#03a9f4", hovered_text = "" }, { if = "dir & hovered", text = "" },
{ if = "dir", text = "", fg = "#03a9f4" },
{ if = "exec", text = "", fg = "#8bc34a" }, { if = "exec", text = "", fg = "#8bc34a" },
{ if = "!dir", text = "", fg = "#ffffff" }, { if = "!dir", text = "", fg = "#ffffff" },
] ]

View file

@ -4,5 +4,4 @@ use crate::Style;
pub struct Icon { pub struct Icon {
pub text: String, pub text: String,
pub style: Style, pub style: Style,
pub hovered_text: Option<String>,
} }

View file

@ -44,7 +44,7 @@ pub struct Icon {
} }
impl Icon { impl Icon {
pub fn matches(&self, file: &File) -> Option<&I> { pub fn matches(&self, file: &File, hovered: bool) -> Option<&I> {
if let Some(i) = self.match_by_glob(file) { if let Some(i) = self.match_by_glob(file) {
return Some(i); return Some(i);
} }
@ -65,6 +65,7 @@ impl Icon {
"sock" => file.is_sock(), "sock" => file.is_sock(),
"exec" => file.is_exec(), "exec" => file.is_exec(),
"sticky" => file.is_sticky(), "sticky" => file.is_sticky(),
"hovered" => hovered,
_ => false, _ => false,
}; };
self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i) self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i)
@ -135,19 +136,12 @@ impl<'de> Deserialize<'de> for PatIcons {
url: Pattern, url: Pattern,
text: String, text: String,
fg: Option<Color>, fg: Option<Color>,
hovered_text: Option<String>,
} }
Ok(Self( Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)? <Vec<Shadow>>::deserialize(deserializer)?
.into_iter() .into_iter()
.map(|s| { .map(|s| (s.url, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
(s.url, I {
text: s.text,
style: Style { fg: s.fg, ..Default::default() },
hovered_text: s.hovered_text,
})
})
.collect(), .collect(),
)) ))
} }
@ -172,19 +166,12 @@ impl<'de> Deserialize<'de> for StrIcons {
name: String, name: String,
text: String, text: String,
fg: Option<Color>, fg: Option<Color>,
hovered_text: Option<String>,
} }
Ok(Self( Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)? <Vec<Shadow>>::deserialize(deserializer)?
.into_iter() .into_iter()
.map(|s| { .map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
(s.name, I {
text: s.text,
style: Style { fg: s.fg, ..Default::default() },
hovered_text: s.hovered_text,
})
})
.collect(), .collect(),
)) ))
} }
@ -209,19 +196,12 @@ impl<'de> Deserialize<'de> for CondIcons {
r#if: Condition, r#if: Condition,
text: String, text: String,
fg: Option<Color>, fg: Option<Color>,
hovered_text: Option<String>,
} }
Ok(Self( Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)? <Vec<Shadow>>::deserialize(deserializer)?
.into_iter() .into_iter()
.map(|s| { .map(|s| (s.r#if, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
(s.r#if, I {
text: s.text,
style: Style { fg: s.fg, ..Default::default() },
hovered_text: s.hovered_text,
})
})
.collect(), .collect(),
)) ))
} }
@ -229,24 +209,94 @@ impl<'de> Deserialize<'de> for CondIcons {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr;
use yazi_fs::{File, cha::{Cha, ChaType}};
use yazi_shared::url::Url;
use super::*; use super::*;
fn create_test_file(name: &str, cha_type: ChaType) -> File {
let url = Url::regular(name).to_owned();
File::from_dummy(url, Some(cha_type))
}
#[test] #[test]
fn test_icon_struct_with_hovered_text() { fn test_matches_hovered() {
let icon = I { // icon with hovered only
text: "normal_icon".to_string(), let icon = Icon {
conds: CondIcons(vec![(Condition::from_str("hovered").unwrap(), I {
text: "hovered_icon".to_string(),
style: Style::default(), style: Style::default(),
hovered_text: Some("hovered_icon".to_string()), })]),
..Default::default()
}; };
assert_eq!(icon.text, "normal_icon"); let file = create_test_file("test.txt", ChaType::File);
assert_eq!(icon.hovered_text, Some("hovered_icon".to_string()));
// should match when hovered
let result = icon.matches(&file, true);
assert!(result.is_some());
assert_eq!(result.unwrap().text, "hovered_icon");
// should not match when not hovered
let result = icon.matches(&file, false);
assert!(result.is_none());
} }
#[test] #[test]
fn test_icon_struct_without_hovered_text() { fn test_matches_dir_and_hovered_condition() {
let icon = // icon with dir and hovered
I { text: "normal_icon".to_string(), style: Style::default(), hovered_text: None }; let icon = Icon {
conds: CondIcons(vec![(Condition::from_str("dir & hovered").unwrap(), I {
text: "dir_hovered".to_string(),
style: Style::default(),
})]),
..Default::default()
};
assert_eq!(icon.text, "normal_icon"); let dir_file = create_test_file("test_dir", ChaType::Dir);
assert_eq!(icon.hovered_text, None); let file = create_test_file("test.txt", ChaType::File);
// directory + hovered
let result = icon.matches(&dir_file, true);
assert!(result.is_some());
assert_eq!(result.unwrap().text, "dir_hovered");
// directory + not hovered
let result = icon.matches(&dir_file, false);
assert!(result.is_none());
// file + hovered isnt present
let result = icon.matches(&file, true);
assert!(result.is_none());
}
#[test]
fn test_matches_hovered_before_dir() {
// icon with hovered before dir
let icon = Icon {
conds: CondIcons(vec![
(Condition::from_str("dir & hovered").unwrap(), I {
text: "dir_hovered".to_string(),
style: Style::default(),
}),
(Condition::from_str("dir").unwrap(), I {
text: "dir_normal".to_string(),
style: Style::default(),
}),
]),
..Default::default()
};
let dir_file = create_test_file("test_dir", ChaType::Dir);
// hovered
let result = icon.matches(&dir_file, true);
assert!(result.is_some());
assert_eq!(result.unwrap().text, "dir_hovered");
// non-hovered
let result = icon.matches(&dir_file, false);
assert!(result.is_some());
assert_eq!(result.unwrap().text, "dir_normal");
} }
} }

View file

@ -26,12 +26,9 @@ function Entity:padding()
end end
function Entity:icon() function Entity:icon()
local icon = self._file:icon() local icon = self._file:icon(self._file.is_hovered)
if not icon then if not icon then
return "" return ""
elseif self._file.is_hovered then
local text = icon.hovered_text or icon.text
return text .. " "
else else
return ui.Line(icon.text .. " "):style(icon.style) return ui.Line(icon.text .. " "):style(icon.style)
end end