mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: add support for highlighting by file type (#510)
This commit is contained in:
parent
876419a6c4
commit
bf9f8f4273
10 changed files with 123 additions and 54 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
|
@ -2634,7 +2634,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-adaptor"
|
name = "yazi-adaptor"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|
@ -2652,7 +2652,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-config"
|
name = "yazi-config"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|
@ -2677,7 +2677,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-core"
|
name = "yazi-core"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|
@ -2707,7 +2707,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-fm"
|
name = "yazi-fm"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-to-tui",
|
"ansi-to-tui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -2734,7 +2734,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-plugin"
|
name = "yazi-plugin"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-to-tui",
|
"ansi-to-tui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -2767,7 +2767,7 @@ checksum = "f4b6c8e12e39ac0f79fa96f36e5b88e0da8d230691abd729eec709b43c74f632"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-scheduler"
|
name = "yazi-scheduler"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-priority-channel",
|
"async-priority-channel",
|
||||||
|
|
@ -2788,7 +2788,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-shared"
|
name = "yazi-shared"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
|
|
|
||||||
|
|
@ -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.
|
# 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"
|
"$schema" = "https://yazi-rs.github.io/schemas/keymap.json"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
# 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"
|
"$schema" = "https://yazi-rs.github.io/schemas/theme.json"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
# 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"
|
"$schema" = "https://yazi-rs.github.io/schemas/yazi.json"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,36 @@
|
||||||
use std::path::Path;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use yazi_shared::MIME_DIR;
|
use yazi_shared::fs::File;
|
||||||
|
|
||||||
use super::{Color, Style, StyleShadow};
|
use super::{Color, Style, StyleShadow};
|
||||||
use crate::Pattern;
|
use crate::Pattern;
|
||||||
|
|
||||||
pub struct Filetype {
|
pub struct Filetype {
|
||||||
|
pub is: FiletypeIs,
|
||||||
pub name: Option<Pattern>,
|
pub name: Option<Pattern>,
|
||||||
pub mime: Option<Pattern>,
|
pub mime: Option<Pattern>,
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filetype {
|
impl Filetype {
|
||||||
pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool {
|
pub fn matches(&self, file: &File, mime: Option<&str>) -> bool {
|
||||||
let is_dir = mime == Some(MIME_DIR);
|
let b = match self.is {
|
||||||
self.name.as_ref().is_some_and(|n| n.match_path(path, is_dir))
|
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))
|
|| self.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +46,8 @@ impl Filetype {
|
||||||
}
|
}
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct FiletypeRule {
|
struct FiletypeRule {
|
||||||
|
#[serde(default)]
|
||||||
|
is: FiletypeIs,
|
||||||
name: Option<Pattern>,
|
name: Option<Pattern>,
|
||||||
mime: Option<Pattern>,
|
mime: Option<Pattern>,
|
||||||
|
|
||||||
|
|
@ -61,6 +78,7 @@ impl Filetype {
|
||||||
.rules
|
.rules
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| Filetype {
|
.map(|r| Filetype {
|
||||||
|
is: r.is,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
mime: r.mime,
|
mime: r.mime,
|
||||||
style: StyleShadow {
|
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<Self, Self::Err> {
|
||||||
|
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<String> for FiletypeIs {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(&s) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,7 @@ impl<'a, 'b> Folder<'a, 'b> {
|
||||||
cx.manager.mimetype.get(&file.url).map(|x| &**x)
|
cx.manager.mimetype.get(&file.url).map(|x| &**x)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(
|
Ok(THEME.filetypes.iter().find(|&x| x.matches(&file, mime)).map(|x| Style::from(x.style)))
|
||||||
THEME
|
|
||||||
.filetypes
|
|
||||||
.iter()
|
|
||||||
.find(|&x| x.matches(&file.url, mime))
|
|
||||||
.map(|x| Style::from(x.style)),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
reg.add_function("is_hovered", |_, me: AnyUserData| {
|
reg.add_function("is_hovered", |_, me: AnyUserData| {
|
||||||
let folder = me.named_user_value::<FolderRef>("folder")?;
|
let folder = me.named_user_value::<FolderRef>("folder")?;
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,18 @@ impl Cha {
|
||||||
reg.add_field_method_get("is_dir", |_, me| Ok(me.is_dir()));
|
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_hidden", |_, me| Ok(me.is_hidden()));
|
||||||
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
|
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)]
|
#[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("uid", |_, me| Ok(me.uid));
|
||||||
reg.add_field_method_get("gid", |_, me| Ok(me.gid));
|
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("length", |_, me| Ok(me.len));
|
||||||
reg.add_field_method_get("created", |_, me| {
|
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()))
|
Ok(me.created.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ use bitflags::bitflags;
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct ChaKind: u8 {
|
pub struct ChaKind: u8 {
|
||||||
const DIR = 0b00000001;
|
const DIR = 0b00000001;
|
||||||
|
|
||||||
const HIDDEN = 0b00000010;
|
const HIDDEN = 0b00000010;
|
||||||
const LINK = 0b00000100;
|
const LINK = 0b00000100;
|
||||||
const BAD_LINK = 0b00001000;
|
const ORPHAN = 0b00001000;
|
||||||
|
|
||||||
const BLOCK_DEVICE = 0b00010000;
|
const BLOCK_DEVICE = 0b00010000;
|
||||||
const CHAR_DEVICE = 0b00100000;
|
const CHAR_DEVICE = 0b00100000;
|
||||||
const FIFO = 0b01000000;
|
const FIFO = 0b01000000;
|
||||||
const SOCKET = 0b10000000;
|
const SOCKET = 0b10000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct Cha {
|
||||||
pub created: Option<SystemTime>,
|
pub created: Option<SystemTime>,
|
||||||
pub modified: Option<SystemTime>,
|
pub modified: Option<SystemTime>,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub permissions: u32,
|
pub permissions: libc::mode_t,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub uid: u32,
|
pub uid: u32,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
@ -70,7 +70,7 @@ impl From<Metadata> for Cha {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
permissions: {
|
permissions: {
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
m.permissions().mode()
|
m.permissions().mode() as libc::mode_t
|
||||||
},
|
},
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
uid: {
|
uid: {
|
||||||
|
|
@ -96,30 +96,50 @@ impl Cha {
|
||||||
|
|
||||||
impl Cha {
|
impl Cha {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_dir(self) -> bool { self.kind.contains(ChaKind::DIR) }
|
pub fn is_dir(&self) -> bool { self.kind.contains(ChaKind::DIR) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_hidden(self) -> bool { self.kind.contains(ChaKind::HIDDEN) }
|
pub fn is_hidden(&self) -> bool { self.kind.contains(ChaKind::HIDDEN) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_link(self) -> bool { self.kind.contains(ChaKind::LINK) }
|
pub fn is_link(&self) -> bool { self.kind.contains(ChaKind::LINK) }
|
||||||
|
|
||||||
#[inline]
|
#[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]
|
#[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]
|
#[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]
|
#[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]
|
#[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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl File {
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_link && meta.is_symlink() {
|
if is_link && meta.is_symlink() {
|
||||||
ck |= ChaKind::BAD_LINK;
|
ck |= ChaKind::ORPHAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.is_hidden() {
|
if url.is_hidden() {
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,9 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver<Result<u64,
|
||||||
// Convert a file mode to a string representation
|
// Convert a file mode to a string representation
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[allow(clippy::collapsible_else_if)]
|
#[allow(clippy::collapsible_else_if)]
|
||||||
pub fn permissions(mode: u32) -> String {
|
pub fn permissions(m: libc::mode_t) -> 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};
|
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 mut s = String::with_capacity(10);
|
||||||
let m = mode as mode_t;
|
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
s.push(match m & S_IFMT {
|
s.push(match m & S_IFMT {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue