From bf9f8f4273c16c0275e4bb5bc3b374cc3cd1b253 Mon Sep 17 00:00:00 2001 From: Azad <49314270+Akmadan23@users.noreply.github.com> Date: Sat, 13 Jan 2024 16:19:40 +0100 Subject: [PATCH] feat: add support for highlighting by file type (#510) --- Cargo.lock | 14 +++---- yazi-config/preset/keymap.toml | 2 +- yazi-config/preset/theme.toml | 2 +- yazi-config/preset/yazi.toml | 2 +- yazi-config/src/theme/filetype.rs | 66 ++++++++++++++++++++++++++++--- yazi-fm/src/lives/folder.rs | 8 +--- yazi-plugin/src/bindings/cha.rs | 11 +++--- yazi-shared/src/fs/cha.rs | 64 +++++++++++++++++++----------- yazi-shared/src/fs/file.rs | 2 +- yazi-shared/src/fs/fns.rs | 6 +-- 10 files changed, 123 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f475a4ca..0cdceaf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2634,7 +2634,7 @@ dependencies = [ [[package]] name = "yazi-adaptor" -version = "0.1.5" +version = "0.2.0" dependencies = [ "anyhow", "arc-swap", @@ -2652,7 +2652,7 @@ dependencies = [ [[package]] name = "yazi-config" -version = "0.1.5" +version = "0.2.0" dependencies = [ "anyhow", "arc-swap", @@ -2677,7 +2677,7 @@ dependencies = [ [[package]] name = "yazi-core" -version = "0.1.5" +version = "0.2.0" dependencies = [ "anyhow", "base64", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "yazi-fm" -version = "0.1.5" +version = "0.2.0" dependencies = [ "ansi-to-tui", "anyhow", @@ -2734,7 +2734,7 @@ dependencies = [ [[package]] name = "yazi-plugin" -version = "0.1.5" +version = "0.2.0" dependencies = [ "ansi-to-tui", "anyhow", @@ -2767,7 +2767,7 @@ checksum = "f4b6c8e12e39ac0f79fa96f36e5b88e0da8d230691abd729eec709b43c74f632" [[package]] name = "yazi-scheduler" -version = "0.1.5" +version = "0.2.0" dependencies = [ "anyhow", "async-priority-channel", @@ -2788,7 +2788,7 @@ dependencies = [ [[package]] name = "yazi-shared" -version = "0.1.5" +version = "0.2.0" dependencies = [ "anyhow", "bitflags 2.4.1", diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 0957ca42..a464e73e 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -1,4 +1,4 @@ -# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. +# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. # If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas. "$schema" = "https://yazi-rs.github.io/schemas/keymap.json" diff --git a/yazi-config/preset/theme.toml b/yazi-config/preset/theme.toml index 9d86d265..dd8cae0b 100644 --- a/yazi-config/preset/theme.toml +++ b/yazi-config/preset/theme.toml @@ -1,4 +1,4 @@ -# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. +# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. # If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas. "$schema" = "https://yazi-rs.github.io/schemas/theme.json" diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index dfcd0341..f4a177ae 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -1,4 +1,4 @@ -# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. +# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. # If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas. "$schema" = "https://yazi-rs.github.io/schemas/yazi.json" diff --git a/yazi-config/src/theme/filetype.rs b/yazi-config/src/theme/filetype.rs index f5fdb16d..bd68e209 100644 --- a/yazi-config/src/theme/filetype.rs +++ b/yazi-config/src/theme/filetype.rs @@ -1,21 +1,36 @@ -use std::path::Path; +use std::str::FromStr; +use anyhow::bail; use serde::{Deserialize, Deserializer}; -use yazi_shared::MIME_DIR; +use yazi_shared::fs::File; use super::{Color, Style, StyleShadow}; use crate::Pattern; pub struct Filetype { + pub is: FiletypeIs, pub name: Option, pub mime: Option, pub style: Style, } impl Filetype { - pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool { - let is_dir = mime == Some(MIME_DIR); - self.name.as_ref().is_some_and(|n| n.match_path(path, is_dir)) + pub fn matches(&self, file: &File, mime: Option<&str>) -> bool { + let b = match self.is { + FiletypeIs::None => true, + FiletypeIs::Block => file.cha.is_block_device(), + FiletypeIs::Exec => file.cha.is_exec(), + FiletypeIs::Fifo => file.cha.is_fifo(), + FiletypeIs::Link => file.cha.is_link(), + FiletypeIs::Orphan => file.cha.is_orphan(), + FiletypeIs::Sock => file.cha.is_socket(), + FiletypeIs::Sticky => file.cha.is_sticky(), + }; + if !b { + return false; + } + + self.name.as_ref().is_some_and(|n| n.match_path(&file.url, file.is_dir())) || self.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s)) } } @@ -31,6 +46,8 @@ impl Filetype { } #[derive(Deserialize)] struct FiletypeRule { + #[serde(default)] + is: FiletypeIs, name: Option, mime: Option, @@ -61,6 +78,7 @@ impl Filetype { .rules .into_iter() .map(|r| Filetype { + is: r.is, name: r.name, mime: r.mime, style: StyleShadow { @@ -82,3 +100,41 @@ impl Filetype { ) } } + +// --- FiletypeIs +#[derive(Default, Deserialize)] +#[serde(try_from = "String")] +pub enum FiletypeIs { + #[default] + None, + Block, + Exec, + Fifo, + Link, + Orphan, + Sock, + Sticky, +} + +impl FromStr for FiletypeIs { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "block" => Self::Block, + "exec" => Self::Exec, + "fifo" => Self::Fifo, + "link" => Self::Link, + "orphan" => Self::Orphan, + "sock" => Self::Sock, + "sticky" => Self::Sticky, + _ => bail!("invalid filetype: {s}"), + }) + } +} + +impl TryFrom for FiletypeIs { + type Error = anyhow::Error; + + fn try_from(s: String) -> Result { Self::from_str(&s) } +} diff --git a/yazi-fm/src/lives/folder.rs b/yazi-fm/src/lives/folder.rs index d9c6531f..59ec9b86 100644 --- a/yazi-fm/src/lives/folder.rs +++ b/yazi-fm/src/lives/folder.rs @@ -87,13 +87,7 @@ impl<'a, 'b> Folder<'a, 'b> { cx.manager.mimetype.get(&file.url).map(|x| &**x) }; - Ok( - THEME - .filetypes - .iter() - .find(|&x| x.matches(&file.url, mime)) - .map(|x| Style::from(x.style)), - ) + Ok(THEME.filetypes.iter().find(|&x| x.matches(&file, mime)).map(|x| Style::from(x.style))) }); reg.add_function("is_hovered", |_, me: AnyUserData| { let folder = me.named_user_value::("folder")?; diff --git a/yazi-plugin/src/bindings/cha.rs b/yazi-plugin/src/bindings/cha.rs index e3e7ec0b..99ad8701 100644 --- a/yazi-plugin/src/bindings/cha.rs +++ b/yazi-plugin/src/bindings/cha.rs @@ -12,17 +12,18 @@ impl Cha { reg.add_field_method_get("is_dir", |_, me| Ok(me.is_dir())); reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden())); reg.add_field_method_get("is_link", |_, me| Ok(me.is_link())); - reg.add_field_method_get("is_bad_link", |_, me| Ok(me.is_bad_link())); + reg.add_field_method_get("is_orphan", |_, me| Ok(me.is_orphan())); + reg.add_field_method_get("is_block_device", |_, me| Ok(me.is_block_device())); + reg.add_field_method_get("is_char_device", |_, me| Ok(me.is_char_device())); + reg.add_field_method_get("is_fifo", |_, me| Ok(me.is_fifo())); + reg.add_field_method_get("is_socket", |_, me| Ok(me.is_socket())); #[cfg(unix)] { - reg.add_field_method_get("is_block_device", |_, me| Ok(me.is_block_device())); - reg.add_field_method_get("is_char_device", |_, me| Ok(me.is_char_device())); - reg.add_field_method_get("is_fifo", |_, me| Ok(me.is_fifo())); - reg.add_field_method_get("is_socket", |_, me| Ok(me.is_socket())); reg.add_field_method_get("uid", |_, me| Ok(me.uid)); reg.add_field_method_get("gid", |_, me| Ok(me.gid)); } + reg.add_field_method_get("length", |_, me| Ok(me.len)); reg.add_field_method_get("created", |_, me| { Ok(me.created.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())) diff --git a/yazi-shared/src/fs/cha.rs b/yazi-shared/src/fs/cha.rs index a28f2425..fd0eea22 100644 --- a/yazi-shared/src/fs/cha.rs +++ b/yazi-shared/src/fs/cha.rs @@ -5,16 +5,16 @@ use bitflags::bitflags; bitflags! { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ChaKind: u8 { - const DIR = 0b00000001; + const DIR = 0b00000001; - const HIDDEN = 0b00000010; - const LINK = 0b00000100; - const BAD_LINK = 0b00001000; + const HIDDEN = 0b00000010; + const LINK = 0b00000100; + const ORPHAN = 0b00001000; - const BLOCK_DEVICE = 0b00010000; - const CHAR_DEVICE = 0b00100000; - const FIFO = 0b01000000; - const SOCKET = 0b10000000; + const BLOCK_DEVICE = 0b00010000; + const CHAR_DEVICE = 0b00100000; + const FIFO = 0b01000000; + const SOCKET = 0b10000000; } } @@ -26,7 +26,7 @@ pub struct Cha { pub created: Option, pub modified: Option, #[cfg(unix)] - pub permissions: u32, + pub permissions: libc::mode_t, #[cfg(unix)] pub uid: u32, #[cfg(unix)] @@ -70,7 +70,7 @@ impl From for Cha { #[cfg(unix)] permissions: { use std::os::unix::prelude::PermissionsExt; - m.permissions().mode() + m.permissions().mode() as libc::mode_t }, #[cfg(unix)] uid: { @@ -96,30 +96,50 @@ impl Cha { impl Cha { #[inline] - pub fn is_dir(self) -> bool { self.kind.contains(ChaKind::DIR) } + pub fn is_dir(&self) -> bool { self.kind.contains(ChaKind::DIR) } #[inline] - pub fn is_hidden(self) -> bool { self.kind.contains(ChaKind::HIDDEN) } + pub fn is_hidden(&self) -> bool { self.kind.contains(ChaKind::HIDDEN) } #[inline] - pub fn is_link(self) -> bool { self.kind.contains(ChaKind::LINK) } + pub fn is_link(&self) -> bool { self.kind.contains(ChaKind::LINK) } #[inline] - pub fn is_bad_link(self) -> bool { self.kind.contains(ChaKind::BAD_LINK) } + pub fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) } - #[cfg(unix)] #[inline] - pub fn is_block_device(self) -> bool { self.kind.contains(ChaKind::BLOCK_DEVICE) } + pub fn is_block_device(&self) -> bool { self.kind.contains(ChaKind::BLOCK_DEVICE) } - #[cfg(unix)] #[inline] - pub fn is_char_device(self) -> bool { self.kind.contains(ChaKind::CHAR_DEVICE) } + pub fn is_char_device(&self) -> bool { self.kind.contains(ChaKind::CHAR_DEVICE) } - #[cfg(unix)] #[inline] - pub fn is_fifo(self) -> bool { self.kind.contains(ChaKind::FIFO) } + pub fn is_fifo(&self) -> bool { self.kind.contains(ChaKind::FIFO) } - #[cfg(unix)] #[inline] - pub fn is_socket(self) -> bool { self.kind.contains(ChaKind::SOCKET) } + pub fn is_socket(&self) -> bool { self.kind.contains(ChaKind::SOCKET) } + + #[inline] + pub fn is_exec(&self) -> bool { + #[cfg(unix)] + { + self.permissions & libc::S_IXUSR != 0 + } + #[cfg(windows)] + { + false + } + } + + #[inline] + pub fn is_sticky(&self) -> bool { + #[cfg(unix)] + { + self.permissions & libc::S_ISVTX != 0 + } + #[cfg(windows)] + { + false + } + } } diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 3b9c7e4e..6216f77a 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -37,7 +37,7 @@ impl File { } if is_link && meta.is_symlink() { - ck |= ChaKind::BAD_LINK; + ck |= ChaKind::ORPHAN; } if url.is_hidden() { diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index cfd1f6d7..c20735ca 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -94,11 +94,9 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver String { - use libc::{mode_t, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; - +pub fn permissions(m: libc::mode_t) -> String { + use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; let mut s = String::with_capacity(10); - let m = mode as mode_t; // File type s.push(match m & S_IFMT {