use std::ops::Deref; use anyhow::Result; use hashbrown::HashMap; use ratatui::style::Color; use serde::{Deserialize, Deserializer}; use yazi_codegen::DeserializeOver2; use yazi_fs::File; use yazi_shared::{Condition, url::UrlLike}; use crate::{Icon as I, Pattern, Style}; #[derive(Default, Deserialize, DeserializeOver2)] pub struct Icon { globs: PatIcons, #[serde(default)] prepend_globs: PatIcons, #[serde(default)] append_globs: PatIcons, dirs: StrIcons, #[serde(default)] prepend_dirs: StrIcons, #[serde(default)] append_dirs: StrIcons, files: StrIcons, #[serde(default)] prepend_files: StrIcons, #[serde(default)] append_files: StrIcons, exts: StrIcons, #[serde(default)] prepend_exts: StrIcons, #[serde(default)] append_exts: StrIcons, conds: CondIcons, #[serde(default)] prepend_conds: CondIcons, #[serde(default)] append_conds: CondIcons, } impl Icon { pub fn matches(&self, file: &File) -> Option<&I> { if let Some(i) = self.match_by_glob(file) { return Some(i); } if let Some(i) = self.match_by_name(file) { return Some(i); } let f = |s: &str| match s { "dir" => file.is_dir(), "hidden" => file.is_hidden(), "link" => file.is_link(), "orphan" => file.is_orphan(), "dummy" => file.is_dummy(), "block" => file.is_block(), "char" => file.is_char(), "fifo" => file.is_fifo(), "sock" => file.is_sock(), "exec" => file.is_exec(), "sticky" => file.is_sticky(), _ => false, }; self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i) } fn match_by_glob(&self, file: &File) -> Option<&I> { self.globs.iter().find(|(p, _)| p.match_url(&file.url, file.is_dir())).map(|(_, i)| i) } fn match_by_name(&self, file: &File) -> Option<&I> { let name = file.name()?.to_str().ok()?; if file.is_dir() { self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase())) } else { self .files .get(name) .or_else(|| self.files.get(&name.to_ascii_lowercase())) .or_else(|| self.match_by_ext(file)) } } fn match_by_ext(&self, file: &File) -> Option<&I> { let ext = file.url.ext()?.to_str().ok()?; self.exts.get(ext).or_else(|| self.exts.get(&ext.to_ascii_lowercase())) } } impl Icon { pub(crate) fn reshape(self) -> Result { Ok(Self { globs: PatIcons( self.prepend_globs.0.into_iter().chain(self.globs.0).chain(self.append_globs.0).collect(), ), dirs: StrIcons( self.append_dirs.0.into_iter().chain(self.dirs.0).chain(self.prepend_dirs.0).collect(), ), files: StrIcons( self.append_files.0.into_iter().chain(self.files.0).chain(self.prepend_files.0).collect(), ), exts: StrIcons( self.append_exts.0.into_iter().chain(self.exts.0).chain(self.prepend_exts.0).collect(), ), conds: CondIcons( self.prepend_conds.0.into_iter().chain(self.conds.0).chain(self.append_conds.0).collect(), ), ..Default::default() }) } } #[derive(Default)] pub struct PatIcons(Vec<(Pattern, I)>); impl Deref for PatIcons { type Target = Vec<(Pattern, I)>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'de> Deserialize<'de> for PatIcons { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Shadow { url: Pattern, text: String, fg: Option, } Ok(Self( >::deserialize(deserializer)? .into_iter() .map(|s| (s.url, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } })) .collect(), )) } } #[derive(Default)] pub struct StrIcons(HashMap); impl Deref for StrIcons { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 } } impl<'de> Deserialize<'de> for StrIcons { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Shadow { name: String, text: String, fg: Option, } Ok(Self( >::deserialize(deserializer)? .into_iter() .map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } })) .collect(), )) } } #[derive(Default)] pub struct CondIcons(Vec<(Condition, I)>); impl Deref for CondIcons { type Target = Vec<(Condition, I)>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'de> Deserialize<'de> for CondIcons { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Shadow { r#if: Condition, text: String, fg: Option, } Ok(Self( >::deserialize(deserializer)? .into_iter() .map(|s| (s.r#if, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } })) .collect(), )) } }