fix: proper invalid UTF-8 path support (#2884)

This commit is contained in:
三咲雅 misaki masa 2025-06-16 20:18:19 +08:00 committed by sxyazi
parent 56a4e87d7e
commit a0ab614108
No known key found for this signature in database
50 changed files with 577 additions and 367 deletions

View file

@ -109,16 +109,15 @@ jobs:
matrix:
include:
- target: x86_64-unknown-linux-musl
image: rust-musl-cross:x86_64-musl
- target: aarch64-unknown-linux-musl
image: rust-musl-cross:aarch64-musl
container:
image: docker://ghcr.io/rust-cross/${{ matrix.image }}
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
steps:
- uses: actions/checkout@v4
- name: Add musl target
run: rustup target add ${{ matrix.target }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9

2
Cargo.lock generated
View file

@ -3445,7 +3445,9 @@ version = "25.6.11"
dependencies = [
"mlua",
"paste",
"ratatui",
"serde_json",
"yazi-config",
"yazi-fs",
"yazi-macro",
"yazi-shared",

View file

@ -17,8 +17,7 @@ cp yazi-boot/completions/* "$ARTIFACT_NAME/completions"
cp README.md LICENSE "$ARTIFACT_NAME"
# Zip the artifact
if ! command -v zip &> /dev/null
then
sudo apt-get update && sudo apt-get install -yq zip
if ! command -v zip &> /dev/null; then
apt-get update && apt-get install -yq zip
fi
zip -r "$ARTIFACT_NAME.zip" "$ARTIFACT_NAME"

View file

@ -9,6 +9,7 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-config = { path = "../yazi-config", version = "25.6.11" }
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
yazi-macro = { path = "../yazi-macro", version = "25.6.11" }
yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
@ -16,4 +17,5 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
# External dependencies
mlua = { workspace = true }
paste = { workspace = true }
ratatui = { workspace = true }
serde_json = { workspace = true }

20
yazi-binding/src/color.rs Normal file
View file

@ -0,0 +1,20 @@
use std::str::FromStr;
use mlua::{ExternalError, ExternalResult, UserData, Value};
#[derive(Clone, Copy, Default)]
pub struct Color(pub ratatui::style::Color);
impl TryFrom<Value> for Color {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(Self(match value {
Value::String(s) => ratatui::style::Color::from_str(&s.to_str()?).into_lua_err()?,
Value::UserData(ud) => ud.borrow::<Self>()?.0,
_ => Err("expected a Color".into_lua_err())?,
}))
}
}
impl UserData for Color {}

View file

@ -1,25 +1,24 @@
use std::ops::Deref;
use mlua::{UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use crate::elements::Style;
use crate::{Style, cached_field};
pub struct Icon {
inner: &'static yazi_shared::theme::Icon,
inner: &'static yazi_config::Icon,
v_text: Option<Value>,
v_style: Option<Value>,
}
impl Deref for Icon {
type Target = yazi_shared::theme::Icon;
type Target = yazi_config::Icon;
fn deref(&self) -> &Self::Target { self.inner }
}
impl From<&'static yazi_shared::theme::Icon> for Icon {
fn from(icon: &'static yazi_shared::theme::Icon) -> Self {
impl From<&'static yazi_config::Icon> for Icon {
fn from(icon: &'static yazi_config::Icon) -> Self {
Self { inner: icon, v_text: None, v_style: None }
}
}

View file

@ -1,3 +1,3 @@
mod macros;
yazi_macro::mod_flat!(error id stage url urn);
yazi_macro::mod_flat!(color error icon id stage style url urn);

View file

@ -17,3 +17,71 @@ macro_rules! cached_field {
});
};
}
#[macro_export]
macro_rules! impl_style_shorthands {
($methods:ident, $($field:tt).+) => {
$methods.add_function_mut("fg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
match value {
mlua::Value::Nil => {
ud.borrow::<Self>()?.$($field).+.fg.map($crate::Color).into_lua(lua)
},
_ => {
ud.borrow_mut::<Self>()?.$($field).+.fg = Some($crate::Color::try_from(value)?.0);
ud.into_lua(lua)
}
}
});
$methods.add_function_mut("bg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
match value {
mlua::Value::Nil => {
ud.borrow::<Self>()?.$($field).+.bg.map($crate::Color).into_lua(lua)
}
_ => {
ud.borrow_mut::<Self>()?.$($field).+.bg = Some($crate::Color::try_from(value)?.0);
ud.into_lua(lua)
}
}
});
$methods.add_function_mut("bold", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::BOLD;
Ok(ud)
});
$methods.add_function_mut("dim", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::DIM;
Ok(ud)
});
$methods.add_function_mut("italic", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::ITALIC;
Ok(ud)
});
$methods.add_function_mut("underline", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::UNDERLINED;
Ok(ud)
});
$methods.add_function_mut("blink", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::SLOW_BLINK;
Ok(ud)
});
$methods.add_function_mut("blink_rapid", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::RAPID_BLINK;
Ok(ud)
});
$methods.add_function_mut("reverse", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::REVERSED;
Ok(ud)
});
$methods.add_function_mut("hidden", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::HIDDEN;
Ok(ud)
});
$methods.add_function_mut("crossed", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::CROSSED_OUT;
Ok(ud)
});
$methods.add_function_mut("reset", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier = ratatui::style::Modifier::empty();
Ok(ud)
});
};
}

45
yazi-binding/src/style.rs Normal file
View file

@ -0,0 +1,45 @@
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
#[derive(Clone, Copy, Default)]
pub struct Style(pub ratatui::style::Style);
impl Style {
pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new = lua.create_function(|_, (_, value): (Table, Value)| Self::try_from(value))?;
let style = lua.create_table()?;
style.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?));
style.into_lua(lua)
}
}
impl TryFrom<Value> for Style {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(Self(match value {
Value::Nil => Default::default(),
Value::UserData(ud) => ud.borrow::<Self>()?.0,
_ => Err("expected a Style or nil".into_lua_err())?,
}))
}
}
impl From<yazi_config::Style> for Style {
fn from(value: yazi_config::Style) -> Self { Self(value.into()) }
}
impl UserData for Style {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_shorthands!(methods, 0);
methods.add_function_mut("patch", |_, (ud, value): (AnyUserData, Value)| {
{
let mut me = ud.borrow_mut::<Self>()?;
me.0 = me.0.patch(Self::try_from(value)?.0);
}
Ok(ud)
})
}
}

21
yazi-config/src/color.rs Normal file
View file

@ -0,0 +1,21 @@
use std::str::FromStr;
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Default)]
pub struct Color(ratatui::style::Color);
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
ratatui::style::Color::from_str(&String::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
.map(Self)
}
}
impl From<Color> for ratatui::style::Color {
fn from(value: Color) -> Self { value.0 }
}

7
yazi-config/src/icon.rs Normal file
View file

@ -0,0 +1,7 @@
use crate::Style;
#[derive(Clone, Debug)]
pub struct Icon {
pub text: String,
pub style: Style,
}

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which);
yazi_macro::mod_flat!(layout pattern platform preset priority yazi);
yazi_macro::mod_flat!(color icon layout pattern platform preset priority style yazi);
use std::io::{Read, Write};

View file

@ -1,11 +1,11 @@
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_fs::{CWD, SortBy};
use super::{MgrRatio, MouseEvents};
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
#[derive(Debug, Deserialize, DeserializeOver2)]
pub struct Mgr {
pub ratio: MgrRatio,

View file

@ -1,7 +1,7 @@
use ratatui::style::Modifier;
use serde::{Deserialize, Serialize, Serializer, ser::SerializeMap};
use serde::Deserialize;
use super::Color;
use crate::Color;
#[derive(Clone, Copy, Debug, Default, Deserialize)]
pub struct Style {
@ -29,19 +29,6 @@ pub struct Style {
pub crossed: bool,
}
impl Serialize for Style {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("fg", &self.fg)?;
map.serialize_entry("bg", &self.bg)?;
map.serialize_entry("modifier", &Modifier::from(*self).bits())?;
map.end()
}
}
impl From<Style> for ratatui::style::Style {
fn from(value: Style) -> Self {
ratatui::style::Style {

View file

@ -3,10 +3,9 @@ use std::ops::Deref;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_fs::File;
use yazi_shared::theme::Style;
use super::Is;
use crate::Pattern;
use crate::{Pattern, Style};
#[derive(Deserialize, DeserializeOver2)]
pub struct Filetype {

View file

@ -4,9 +4,9 @@ use anyhow::Result;
use serde::{Deserialize, Deserializer};
use yazi_codegen::DeserializeOver2;
use yazi_fs::File;
use yazi_shared::{Condition, theme::{Color, Icon as I, Style}};
use yazi_shared::Condition;
use crate::Pattern;
use crate::{Color, Icon as I, Pattern, Style};
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct Icon {

View file

@ -1,14 +1,14 @@
use std::path::PathBuf;
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use yazi_codegen::{DeserializeOver1, DeserializeOver2};
use yazi_fs::expand_path;
use yazi_shared::theme::Style;
use super::{Filetype, Flavor, Icon};
use crate::Style;
#[derive(Deserialize, DeserializeOver1, Serialize)]
#[derive(Deserialize, DeserializeOver1)]
pub struct Theme {
pub flavor: Flavor,
pub mgr: Mgr,
@ -32,31 +32,31 @@ pub struct Theme {
pub icon: Icon,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Mgr {
cwd: Style,
pub cwd: Style,
// Hovered
hovered: Style,
preview_hovered: Style,
pub hovered: Style,
pub preview_hovered: Style,
// Find
find_keyword: Style,
find_position: Style,
pub find_keyword: Style,
pub find_position: Style,
// Symlink
symlink_target: Style,
pub symlink_target: Style,
// Marker
marker_copied: Style,
marker_cut: Style,
marker_marked: Style,
marker_selected: Style,
pub marker_copied: Style,
pub marker_cut: Style,
pub marker_marked: Style,
pub marker_selected: Style,
// Count
count_copied: Style,
count_cut: Style,
count_selected: Style,
pub count_copied: Style,
pub count_cut: Style,
pub count_selected: Style,
// Border
pub border_symbol: String,
@ -66,7 +66,7 @@ pub struct Mgr {
pub syntect_theme: PathBuf,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Tabs {
pub active: Style,
pub inactive: Style,
@ -75,13 +75,13 @@ pub struct Tabs {
pub sep_outer: TabsSep,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct TabsSep {
pub open: String,
pub close: String,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Mode {
pub normal_main: Style,
pub normal_alt: Style,
@ -93,7 +93,7 @@ pub struct Mode {
pub unset_alt: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Status {
pub overall: Style,
pub sep_left: StatusSep,
@ -112,13 +112,13 @@ pub struct Status {
pub progress_error: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct StatusSep {
pub open: String,
pub close: String,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Which {
pub cols: u8,
pub mask: Style,
@ -130,7 +130,7 @@ pub struct Which {
pub separator_style: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Confirm {
pub border: Style,
pub title: Style,
@ -142,7 +142,7 @@ pub struct Confirm {
pub btn_labels: [String; 2],
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Spot {
pub border: Style,
pub title: Style,
@ -151,7 +151,7 @@ pub struct Spot {
pub tbl_cell: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Notify {
pub title_info: Style,
pub title_warn: Style,
@ -162,14 +162,14 @@ pub struct Notify {
pub icon_error: String,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Pick {
pub border: Style,
pub active: Style,
pub inactive: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Input {
pub border: Style,
pub title: Style,
@ -177,7 +177,7 @@ pub struct Input {
pub selected: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Cmp {
pub border: Style,
pub active: Style,
@ -188,14 +188,14 @@ pub struct Cmp {
pub icon_command: String,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Tasks {
pub border: Style,
pub title: Style,
pub hovered: Style,
}
#[derive(Deserialize, DeserializeOver2, Serialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Help {
pub on: Style,
pub run: Style,

View file

@ -100,36 +100,36 @@ mod tests {
use super::*;
fn compare(s: &str, parent: &str, child: &str) -> bool {
fn compare(s: &str, parent: &str, child: &str) {
let (p, c) = Cmp::split_path(s).unwrap();
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(&p);
p == Path::new(parent) && c == child
assert_eq!((p, c.as_str()), (Path::new(parent), child));
}
#[cfg(unix)]
#[test]
fn test_split() {
yazi_fs::init();
assert!(compare("", "", ""));
assert!(compare(" ", "", " "));
assert!(compare("/", "/", ""));
assert!(compare("//", "//", ""));
assert!(compare("/foo", "/", "foo"));
assert!(compare("/foo/", "/foo/", ""));
assert!(compare("/foo/bar", "/foo/", "bar"));
compare("", "", "");
compare(" ", "", " ");
compare("/", "/", "");
compare("//", "//", "");
compare("/foo", "/", "foo");
compare("/foo/", "/foo/", "");
compare("/foo/bar", "/foo/", "bar");
}
#[cfg(windows)]
#[test]
fn test_split() {
yazi_fs::init();
assert!(compare("foo", "", "foo"));
assert!(compare("foo\\", "foo\\", ""));
assert!(compare("foo\\bar", "foo\\", "bar"));
assert!(compare("foo\\bar\\", "foo\\bar\\", ""));
assert!(compare("C:\\", "C:\\", ""));
assert!(compare("C:\\foo", "C:\\", "foo"));
assert!(compare("C:\\foo\\", "C:\\foo\\", ""));
assert!(compare("C:\\foo\\bar", "C:\\foo\\", "bar"));
compare("foo", "", "foo");
compare("foo\\", "foo\\", "");
compare("foo\\bar", "foo\\", "bar");
compare("foo\\bar\\", "foo\\bar\\", "");
compare("C:\\", "C:\\", "");
compare("C:\\foo", "C:\\", "foo");
compare("C:\\foo\\", "C:\\foo\\", "");
compare("C:\\foo\\bar", "C:\\foo\\", "bar");
}
}

View file

@ -1,4 +1,5 @@
use yazi_boot::BOOT;
use yazi_fs::expand_path;
use yazi_macro::render;
use yazi_proxy::AppProxy;
use yazi_shared::{event::CmdCow, url::Url};
@ -8,20 +9,21 @@ use crate::{mgr::Tabs, tab::{Tab, commands::CdSource}};
const MAX_TABS: usize = 9;
struct Opt {
url: Url,
current: bool,
wd: Option<Url>,
}
impl From<CmdCow> for Opt {
fn from(mut c: CmdCow) -> Self {
if c.bool("current") {
Self { url: Default::default(), current: true }
} else {
Self {
url: c.take_first_url().unwrap_or_else(|| Url::from(&BOOT.cwds[0])),
current: false,
}
return Self { wd: None };
}
let Some(mut wd) = c.take_first_url() else {
return Self { wd: Some(Url::from(&BOOT.cwds[0])) };
};
if wd.is_regular() && !c.bool("raw") {
wd = Url::from(expand_path(wd));
}
Self { wd: Some(wd) }
}
}
@ -34,8 +36,8 @@ impl Tabs {
}
let mut tab = Tab::default();
if !opt.current {
tab.cd((opt.url, CdSource::Tab));
if let Some(wd) = opt.wd {
tab.cd((wd, CdSource::Tab));
} else if let Some(h) = self.active().hovered() {
tab.pref = self.active().pref.clone();
tab.apply_files_attrs();

View file

@ -13,31 +13,22 @@ use crate::tab::Tab;
struct Opt {
target: Url,
source: CdSource,
interactive: bool,
source: CdSource,
}
impl From<CmdCow> for Opt {
fn from(mut c: CmdCow) -> Self {
Self {
source: CdSource::Cd,
interactive: c.bool("interactive"),
..Self::from(c.take_first_url().unwrap_or_default())
let mut target = c.take_first_url().unwrap_or_default();
if target.is_regular() && !c.bool("raw") {
target = Url::from(expand_path(target));
}
Self { target, interactive: c.bool("interactive"), source: CdSource::Cd }
}
}
impl From<Url> for Opt {
fn from(target: Url) -> Self { Self::from((target, CdSource::Cd)) }
}
impl From<(Url, CdSource)> for Opt {
fn from((mut target, source): (Url, CdSource)) -> Self {
if target.is_regular() {
target = Url::from(expand_path(&target));
}
Self { target, source, interactive: false }
}
fn from((target, source): (Url, CdSource)) -> Self { Self { target, interactive: false, source } }
}
impl Tab {

View file

@ -14,8 +14,8 @@ struct Opt {
impl From<CmdCow> for Opt {
fn from(mut c: CmdCow) -> Self {
let mut target = c.take_first_url().unwrap_or_default();
if target.is_regular() {
target = Url::from(expand_path(&target));
if target.is_regular() && !c.bool("raw") {
target = Url::from(expand_path(target));
}
Self { target, source: CdSource::Reveal, no_dummy: c.bool("no-dummy") }

View file

@ -1,8 +1,9 @@
use std::ops::Deref;
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::Style;
use yazi_config::THEME;
use yazi_plugin::{bindings::Range, elements::Style};
use yazi_plugin::bindings::Range;
use super::Lives;
use crate::{Ctx, lives::PtrCell};

View file

@ -61,8 +61,8 @@ impl Widget for Notify<'_> {
Block::bordered()
.border_type(BorderType::Rounded)
.title(format!("{} {}", m.level.icon(), m.title))
.title_style(*m.level.style())
.border_style(*m.level.style()),
.title_style(m.level.style())
.border_style(m.level.style()),
)
.render(rect, buf);
}

View file

@ -2,7 +2,7 @@ use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher
use anyhow::Result;
use tokio::fs;
use yazi_shared::{SyncCell, theme::IconCache, url::{Url, Urn, UrnBuf}};
use yazi_shared::url::{Url, Urn, UrnBuf};
use crate::cha::Cha;
@ -11,7 +11,6 @@ pub struct File {
pub url: Url,
pub cha: Cha,
pub link_to: Option<Url>,
pub icon: SyncCell<IconCache>,
}
impl Deref for File {
@ -35,13 +34,13 @@ impl File {
let cha = Cha::from_follow(&url, meta).await;
Self { url, cha, link_to, icon: Default::default() }
Self { url, cha, link_to }
}
#[inline]
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
let cha = Cha::from_dummy(&url, ft);
Self { url, cha, link_to: None, icon: Default::default() }
Self { url, cha, link_to: None }
}
#[inline]
@ -59,12 +58,7 @@ impl File {
#[inline]
pub fn rebase(&self, parent: &Url) -> Self {
Self {
url: self.url.rebase(parent),
cha: self.cha,
link_to: self.link_to.clone(),
icon: Default::default(),
}
Self { url: self.url.rebase(parent), cha: self.cha, link_to: self.link_to.clone() }
}
}

View file

@ -164,7 +164,7 @@ impl Partitions {
// Unmangle '\t', '\n', ' ', '#', and r'\'
// https://elixir.bootlin.com/linux/v6.13-rc3/source/fs/proc_namespace.c#L89
fn unmangle_octal(s: &str) -> Cow<str> {
fn unmangle_octal(s: &str) -> Cow<'_, str> {
let mut s = Cow::Borrowed(s);
for (a, b) in
[(r"\011", "\t"), (r"\012", "\n"), (r"\040", " "), (r"\043", "#"), (r"\134", r"\")]

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, env, ffi::OsString, future::Future, io, path::{Component, Path, PathBuf}};
use std::{borrow::Cow, env, ffi::{OsStr, OsString}, future::Future, io, path::{Component, Path, PathBuf}};
use tokio::fs;
use yazi_shared::url::{Loc, Url};
@ -37,28 +37,29 @@ pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
fn _expand_path(p: &Path) -> PathBuf {
// ${HOME} or $HOME
#[cfg(unix)]
let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
let re = regex::bytes::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
// %USERPROFILE%
#[cfg(windows)]
let re = regex::Regex::new(r"%([^%]+)%").unwrap();
let re = regex::bytes::Regex::new(r"%([^%]+)%").unwrap();
let s = p.to_string_lossy();
let s = re.replace_all(&s, |caps: &regex::Captures| {
let b = re.replace_all(p.as_os_str().as_encoded_bytes(), |caps: &regex::bytes::Captures| {
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned())
str::from_utf8(name.as_bytes())
.ok()
.and_then(env::var_os)
.map_or_else(|| caps.get(0).unwrap().as_bytes().to_owned(), |s| s.into_encoded_bytes())
});
// Windows paths that only have a drive letter but no root, e.g. "D:"
#[cfg(windows)]
if s.len() == 2 {
let b = s.as_bytes();
if b.len() == 2 {
if b[1] == b':' && b[0].is_ascii_alphabetic() {
return PathBuf::from(s.to_uppercase() + "\\");
return PathBuf::from(format!("{}:\\", b[0].to_ascii_uppercase() as char));
}
}
let p = Path::new(s.as_ref());
let p = unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(b.as_ref())) };
if let Ok(rest) = p.strip_prefix("~") {
clean_path(dirs::home_dir().unwrap_or_default().join(rest))
} else if p.is_absolute() {
@ -175,7 +176,7 @@ pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
}
#[cfg(windows)]
pub fn backslash_to_slash(p: &Path) -> Cow<Path> {
pub fn backslash_to_slash(p: &Path) -> Cow<'_, Path> {
let bytes = p.as_os_str().as_encoded_bytes();
// Fast path to skip if there are no backslashes

View file

@ -41,9 +41,9 @@ function Status:mode()
local style = self:style()
return ui.Line {
ui.Span(th.status.sep_left.open):fg(style.main.bg):bg("reset"),
ui.Span(th.status.sep_left.open):fg(style.main:bg()):bg("reset"),
ui.Span(" " .. mode .. " "):style(style.main),
ui.Span(th.status.sep_left.close):fg(style.main.bg):bg(style.alt.bg),
ui.Span(th.status.sep_left.close):fg(style.main:bg()):bg(style.alt:bg()),
}
end
@ -54,7 +54,7 @@ function Status:size()
local style = self:style()
return ui.Line {
ui.Span(" " .. ya.readable_size(size) .. " "):style(style.alt),
ui.Span(th.status.sep_left.close):fg(style.alt.bg),
ui.Span(th.status.sep_left.close):fg(style.alt:bg()),
}
end
@ -114,7 +114,7 @@ function Status:percent()
local style = self:style()
return ui.Line {
ui.Span(" " .. th.status.sep_right.open):fg(style.alt.bg),
ui.Span(" " .. th.status.sep_right.open):fg(style.alt:bg()),
ui.Span(percent):style(style.alt),
}
end
@ -125,9 +125,9 @@ function Status:position()
local style = self:style()
return ui.Line {
ui.Span(th.status.sep_right.open):fg(style.main.bg):bg(style.alt.bg),
ui.Span(th.status.sep_right.open):fg(style.main:bg()):bg(style.alt:bg()),
ui.Span(string.format(" %2d/%-2d ", math.min(cursor + 1, length), length)):style(style.main),
ui.Span(th.status.sep_right.close):fg(style.main.bg):bg("reset"),
ui.Span(th.status.sep_right.close):fg(style.main:bg()):bg("reset"),
}
end

View file

@ -17,7 +17,7 @@ function Tabs:redraw()
end
local lines = {
ui.Line(th.tabs.sep_outer.open):fg(th.tabs.inactive.bg),
ui.Line(th.tabs.sep_outer.open):fg(th.tabs.inactive:bg()),
}
local pos = lines[1]:width()
@ -36,7 +36,7 @@ function Tabs:redraw()
self._offsets[i], pos = pos, pos + lines[#lines]:width()
end
lines[#lines + 1] = ui.Line(th.tabs.sep_outer.close):fg(th.tabs.inactive.bg)
lines[#lines + 1] = ui.Line(th.tabs.sep_outer.close):fg(th.tabs.inactive:bg())
return ui.Line(lines):area(self._area)
end

View file

@ -22,7 +22,7 @@ function M:entry()
local urls = M.split_urls(cwd, output)
if #urls == 1 then
local cha = #selected == 0 and fs.cha(urls[1])
ya.emit(cha and cha.is_dir and "cd" or "reveal", { urls[1] })
ya.emit(cha and cha.is_dir and "cd" or "reveal", { urls[1], raw = true })
elseif #urls > 1 then
urls.state = #selected > 0 and "off" or "on"
ya.emit("toggle_all", urls)

View file

@ -112,7 +112,7 @@ local function entry()
local target = output.stdout:gsub("\n$", "")
if target ~= "" then
ya.emit("cd", { target })
ya.emit("cd", { target, raw = true })
end
end

View file

@ -1,3 +1,3 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(calculator cha chan icon image input layer mouse permit range);
yazi_macro::mod_flat!(calculator cha chan image input layer mouse permit range);

View file

@ -1,31 +1,282 @@
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
use mlua::{IntoLua, Lua, Value};
use yazi_binding::{Style, Url};
use yazi_config::THEME;
use super::OPTS;
use crate::Composer;
pub struct Theme;
impl Theme {
pub fn compose(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| match key {
b"mgr" => Self::mgr(lua),
b"tabs" => Self::tabs(lua),
b"mode" => Self::mode(lua),
b"status" => Self::status(lua),
b"which" => Self::which(lua),
b"confirm" => Self::confirm(lua),
b"spot" => Self::spot(lua),
b"notify" => Self::notify(lua),
b"pick" => Self::pick(lua),
b"input" => Self::input(lua),
b"cmp" => Self::cmp(lua),
b"tasks" => Self::tasks(lua),
b"help" => Self::help(lua),
_ => Ok(Value::Nil),
})
}
fn mgr(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let m = &THEME.mgr;
match key {
b"mgr" => lua.to_value_with(&THEME.mgr, OPTS)?,
b"tabs" => lua.to_value_with(&THEME.tabs, OPTS)?,
b"mode" => lua.to_value_with(&THEME.mode, OPTS)?,
b"status" => lua.to_value_with(&THEME.status, OPTS)?,
b"which" => lua.to_value_with(&THEME.which, OPTS)?,
b"confirm" => lua.to_value_with(&THEME.confirm, OPTS)?,
b"spot" => lua.to_value_with(&THEME.spot, OPTS)?,
b"notify" => lua.to_value_with(&THEME.notify, OPTS)?,
b"pick" => lua.to_value_with(&THEME.pick, OPTS)?,
b"input" => lua.to_value_with(&THEME.input, OPTS)?,
b"cmp" => lua.to_value_with(&THEME.cmp, OPTS)?,
b"tasks" => lua.to_value_with(&THEME.tasks, OPTS)?,
b"help" => lua.to_value_with(&THEME.help, OPTS)?,
_ => return Ok(Value::Nil),
b"cwd" => Style::from(m.cwd).into_lua(lua),
b"hovered" => Style::from(m.hovered).into_lua(lua),
b"preview_hovered" => Style::from(m.preview_hovered).into_lua(lua),
b"find_keyword" => Style::from(m.find_keyword).into_lua(lua),
b"find_position" => Style::from(m.find_position).into_lua(lua),
b"symlink_target" => Style::from(m.symlink_target).into_lua(lua),
b"marker_copied" => Style::from(m.marker_copied).into_lua(lua),
b"marker_cut" => Style::from(m.marker_cut).into_lua(lua),
b"marker_marked" => Style::from(m.marker_marked).into_lua(lua),
b"marker_selected" => Style::from(m.marker_selected).into_lua(lua),
b"count_copied" => Style::from(m.count_copied).into_lua(lua),
b"count_cut" => Style::from(m.count_cut).into_lua(lua),
b"count_selected" => Style::from(m.count_selected).into_lua(lua),
b"border_symbol" => lua.create_string(&m.border_symbol)?.into_lua(lua),
b"border_style" => Style::from(m.border_style).into_lua(lua),
b"syntect_theme" => Url::new(&m.syntect_theme).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn tabs(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.tabs;
match key {
b"active" => Style::from(t.active).into_lua(lua),
b"inactive" => Style::from(t.inactive).into_lua(lua),
b"sep_inner" => lua
.create_table_from([
("open", lua.create_string(&t.sep_inner.open)?),
("close", lua.create_string(&t.sep_inner.close)?),
])?
.into_lua(lua),
b"sep_outer" => lua
.create_table_from([
("open", lua.create_string(&t.sep_outer.open)?),
("close", lua.create_string(&t.sep_outer.close)?),
])?
.into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn mode(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.mode;
match key {
b"normal_main" => Style::from(t.normal_main).into_lua(lua),
b"normal_alt" => Style::from(t.normal_alt).into_lua(lua),
b"select_main" => Style::from(t.select_main).into_lua(lua),
b"select_alt" => Style::from(t.select_alt).into_lua(lua),
b"unset_main" => Style::from(t.unset_main).into_lua(lua),
b"unset_alt" => Style::from(t.unset_alt).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn status(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.status;
match key {
b"overall" => Style::from(t.overall).into_lua(lua),
b"sep_left" => lua
.create_table_from([
("open", lua.create_string(&t.sep_left.open)?),
("close", lua.create_string(&t.sep_left.close)?),
])?
.into_lua(lua),
b"sep_right" => lua
.create_table_from([
("open", lua.create_string(&t.sep_right.open)?),
("close", lua.create_string(&t.sep_right.close)?),
])?
.into_lua(lua),
b"perm_sep" => Style::from(t.perm_sep).into_lua(lua),
b"perm_type" => Style::from(t.perm_type).into_lua(lua),
b"perm_read" => Style::from(t.perm_read).into_lua(lua),
b"perm_write" => Style::from(t.perm_write).into_lua(lua),
b"perm_exec" => Style::from(t.perm_exec).into_lua(lua),
b"progress_label" => Style::from(t.progress_label).into_lua(lua),
b"progress_normal" => Style::from(t.progress_normal).into_lua(lua),
b"progress_error" => Style::from(t.progress_error).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn which(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.which;
match key {
b"cols" => t.cols.into_lua(lua),
b"mask" => Style::from(t.mask).into_lua(lua),
b"cand" => Style::from(t.cand).into_lua(lua),
b"rest" => Style::from(t.rest).into_lua(lua),
b"desc" => Style::from(t.desc).into_lua(lua),
b"separator" => lua.create_string(&t.separator)?.into_lua(lua),
b"separator_style" => Style::from(t.separator_style).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn confirm(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.confirm;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"title" => Style::from(t.title).into_lua(lua),
b"content" => Style::from(t.content).into_lua(lua),
b"list" => Style::from(t.list).into_lua(lua),
b"btn_yes" => Style::from(t.btn_yes).into_lua(lua),
b"btn_no" => Style::from(t.btn_no).into_lua(lua),
b"btn_labels" => lua
.create_sequence_from([
lua.create_string(&t.btn_labels[0])?,
lua.create_string(&t.btn_labels[1])?,
])?
.into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn spot(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.spot;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"title" => Style::from(t.title).into_lua(lua),
b"tbl_col" => Style::from(t.tbl_col).into_lua(lua),
b"tbl_cell" => Style::from(t.tbl_cell).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn notify(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.notify;
match key {
b"title_info" => Style::from(t.title_info).into_lua(lua),
b"title_warn" => Style::from(t.title_warn).into_lua(lua),
b"title_error" => Style::from(t.title_error).into_lua(lua),
b"icon_info" => lua.create_string(&t.icon_info)?.into_lua(lua),
b"icon_warn" => lua.create_string(&t.icon_warn)?.into_lua(lua),
b"icon_error" => lua.create_string(&t.icon_error)?.into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn pick(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.pick;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"active" => Style::from(t.active).into_lua(lua),
b"inactive" => Style::from(t.inactive).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn input(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.input;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"title" => Style::from(t.title).into_lua(lua),
b"value" => Style::from(t.value).into_lua(lua),
b"selected" => Style::from(t.selected).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn cmp(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.cmp;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"active" => Style::from(t.active).into_lua(lua),
b"inactive" => Style::from(t.inactive).into_lua(lua),
b"icon_file" => lua.create_string(&t.icon_file)?.into_lua(lua),
b"icon_folder" => lua.create_string(&t.icon_folder)?.into_lua(lua),
b"icon_command" => lua.create_string(&t.icon_command)?.into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn tasks(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.tasks;
match key {
b"border" => Style::from(t.border).into_lua(lua),
b"title" => Style::from(t.title).into_lua(lua),
b"hovered" => Style::from(t.hovered).into_lua(lua),
_ => Ok(Value::Nil),
}
})
}
fn help(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, |lua, key| {
let t = &THEME.help;
match key {
b"on" => Style::from(t.on).into_lua(lua),
b"run" => Style::from(t.run).into_lua(lua),
b"desc" => Style::from(t.desc).into_lua(lua),
b"hovered" => Style::from(t.hovered).into_lua(lua),
b"footer" => Style::from(t.footer).into_lua(lua),
_ => Ok(Value::Nil),
}
.into_lua(lua)
})
}
}

View file

@ -22,7 +22,7 @@ pub fn compose(lua: &Lua) -> mlua::Result<Value> {
b"Rect" => super::Rect::compose(lua)?,
b"Row" => super::Row::compose(lua)?,
b"Span" => super::Span::compose(lua)?,
b"Style" => super::Style::compose(lua)?,
b"Style" => yazi_binding::Style::compose(lua)?,
b"Table" => super::Table::compose(lua)?,
b"Text" => super::Text::compose(lua)?,
b"Wrap" => super::Wrap::compose(lua)?,

View file

@ -1,8 +1,8 @@
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui::widgets::Widget;
use yazi_binding::Style;
use super::{Area, Span};
use crate::elements::Style;
#[derive(Clone, Debug, Default)]
pub struct Gauge {

View file

@ -107,7 +107,7 @@ impl UserData for Line {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods);
crate::impl_style_method!(methods, inner.style);
crate::impl_style_shorthands!(methods, inner.style);
yazi_binding::impl_style_shorthands!(methods, inner.style);
methods.add_method("width", |_, me, ()| Ok(me.inner.width()));
methods.add_function_mut("align", |_, (ud, align): (AnyUserData, Align)| {

View file

@ -1,3 +1,3 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(align area bar border cell clear constraint edge elements gauge layout line list pad pos rect renderable row span style table text utils wrap);
yazi_macro::mod_flat!(align area bar border cell clear constraint edge elements gauge layout line list pad pos rect renderable row span table text utils wrap);

View file

@ -69,7 +69,7 @@ impl TryFrom<Value> for Span {
impl UserData for Span {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_method!(methods, 0.style);
crate::impl_style_shorthands!(methods, 0.style);
yazi_binding::impl_style_shorthands!(methods, 0.style);
methods.add_method("visible", |_, Span(me), ()| {
Ok(me.content.chars().any(|c| c.width().unwrap_or(0) > 0))

View file

@ -1,66 +0,0 @@
use std::str::FromStr;
use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use yazi_shared::theme::Color;
#[derive(Clone, Copy, Default)]
pub struct Style(pub(super) ratatui::style::Style);
impl Style {
pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new = lua.create_function(|_, (_, value): (Table, Value)| Self::try_from(value))?;
let style = lua.create_table()?;
style.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?));
style.into_lua(lua)
}
}
impl TryFrom<Table> for Style {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let mut style = ratatui::style::Style::default();
if let Ok(fg) = value.raw_get::<mlua::String>("fg") {
style.fg = Some(Color::from_str(&fg.to_str()?).into_lua_err()?.into());
}
if let Ok(bg) = value.raw_get::<mlua::String>("bg") {
style.bg = Some(Color::from_str(&bg.to_str()?).into_lua_err()?.into());
}
style.add_modifier =
ratatui::style::Modifier::from_bits_truncate(value.raw_get("modifier").unwrap_or_default());
Ok(Self(style))
}
}
impl TryFrom<Value> for Style {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(Self(match value {
Value::Nil => Default::default(),
Value::Table(tb) => Self::try_from(tb)?.0,
Value::UserData(ud) => ud.borrow::<Self>()?.0,
_ => Err("expected a Style or Table or nil".into_lua_err())?,
}))
}
}
impl From<yazi_shared::theme::Style> for Style {
fn from(value: yazi_shared::theme::Style) -> Self { Self(value.into()) }
}
impl UserData for Style {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_shorthands!(methods, 0);
methods.add_function_mut("patch", |_, (ud, value): (AnyUserData, Value)| {
{
let mut me = ud.borrow_mut::<Self>()?;
me.0 = me.0.patch(Self::try_from(value)?.0);
}
Ok(ud)
})
}
}

View file

@ -1,8 +1,9 @@
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, UserData, Value};
use ratatui::widgets::StatefulWidget;
use yazi_binding::Style;
use super::{Area, Row};
use crate::elements::{Constraint, Style};
use crate::elements::Constraint;
const EXPECTED: &str = "expected a table of Rows";

View file

@ -112,7 +112,7 @@ impl UserData for Text {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods);
crate::impl_style_method!(methods, inner.style);
crate::impl_style_shorthands!(methods, inner.style);
yazi_binding::impl_style_shorthands!(methods, inner.style);
methods.add_function_mut("align", |_, (ud, align): (AnyUserData, Align)| {
ud.borrow_mut::<Self>()?.inner.alignment = Some(align.0);

View file

@ -2,7 +2,7 @@
macro_rules! impl_style_method {
($methods:ident, $($field:tt).+) => {
$methods.add_function_mut("style", |_, (ud, value): (mlua::AnyUserData, mlua::Value)| {
ud.borrow_mut::<Self>()?.$($field).+ = $crate::elements::Style::try_from(value)?.0;
ud.borrow_mut::<Self>()?.$($field).+ = yazi_binding::Style::try_from(value)?.0;
Ok(ud)
});
};
@ -26,60 +26,6 @@ macro_rules! impl_area_method {
};
}
#[macro_export]
macro_rules! impl_style_shorthands {
($methods:ident, $($field:tt).+) => {
$methods.add_function_mut("fg", |_, (ud, color): (mlua::AnyUserData, String)| {
ud.borrow_mut::<Self>()?.$($field).+.fg = yazi_shared::theme::Color::try_from(color).ok().map(Into::into);
Ok(ud)
});
$methods.add_function_mut("bg", |_, (ud, color): (mlua::AnyUserData, String)| {
ud.borrow_mut::<Self>()?.$($field).+.bg = yazi_shared::theme::Color::try_from(color).ok().map(Into::into);
Ok(ud)
});
$methods.add_function_mut("bold", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::BOLD;
Ok(ud)
});
$methods.add_function_mut("dim", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::DIM;
Ok(ud)
});
$methods.add_function_mut("italic", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::ITALIC;
Ok(ud)
});
$methods.add_function_mut("underline", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::UNDERLINED;
Ok(ud)
});
$methods.add_function_mut("blink", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::SLOW_BLINK;
Ok(ud)
});
$methods.add_function_mut("blink_rapid", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::RAPID_BLINK;
Ok(ud)
});
$methods.add_function_mut("reverse", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::REVERSED;
Ok(ud)
});
$methods.add_function_mut("hidden", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::HIDDEN;
Ok(ud)
});
$methods.add_function_mut("crossed", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::CROSSED_OUT;
Ok(ud)
});
$methods.add_function_mut("reset", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier = ratatui::style::Modifier::empty();
Ok(ud)
});
};
}
#[macro_export]
macro_rules! impl_file_fields {
($fields:ident) => {
@ -104,18 +50,9 @@ macro_rules! impl_file_methods {
$methods.add_method("hash", |_, me, ()| Ok(me.hash_u64()));
$methods.add_method("icon", |_, me, ()| {
use yazi_shared::theme::IconCache;
use $crate::bindings::Icon;
Ok(match me.icon.get() {
IconCache::Missing => {
let matched = yazi_config::THEME.icon.matches(me);
me.icon.set(matched.map_or(IconCache::Undefined, IconCache::Icon));
matched.map(Icon::from)
}
IconCache::Undefined => None,
IconCache::Icon(cached) => Some(Icon::from(cached)),
})
use yazi_binding::Icon;
// TODO: use a cache
Ok(yazi_config::THEME.icon.matches(me).map(Icon::from))
});
};
}

View file

@ -2,8 +2,8 @@ use std::{str::FromStr, time::Duration};
use mlua::{ExternalError, ExternalResult};
use serde::Deserialize;
use yazi_config::THEME;
use yazi_shared::{event::CmdCow, theme::Style};
use yazi_config::{Style, THEME};
use yazi_shared::event::CmdCow;
pub struct NotifyOpt {
pub title: String,
@ -60,11 +60,11 @@ impl NotifyLevel {
}
}
pub fn style(self) -> &'static Style {
pub fn style(self) -> Style {
match self {
Self::Info => &THEME.notify.title_info,
Self::Warn => &THEME.notify.title_warn,
Self::Error => &THEME.notify.title_error,
Self::Info => THEME.notify.title_info,
Self::Warn => THEME.notify.title_warn,
Self::Error => THEME.notify.title_error,
}
}
}

View file

@ -9,7 +9,7 @@ pub struct TabProxy;
impl TabProxy {
pub fn cd(target: &Url) {
emit!(Call(Cmd::args("mgr:cd", [target])));
emit!(Call(Cmd::args("mgr:cd", [target]).with("raw", true)));
}
pub fn reveal(target: &Url) {

View file

@ -7,7 +7,7 @@ authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi shared library"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
rust-version = "1.86.0"
rust-version = "1.87.0"
[dependencies]
yazi-macro = { path = "../yazi-macro", version = "25.6.11" }

View file

@ -1,6 +1,6 @@
#![allow(clippy::option_map_unit_fn)]
yazi_macro::mod_pub!(errors event shell theme translit url);
yazi_macro::mod_pub!(errors event shell translit url);
yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time utf8);

View file

@ -39,7 +39,7 @@ pub fn escape_str(s: &str) -> Cow<'_, str> {
}
#[cfg(windows)]
pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<'_, std::ffi::OsStr> {
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let wide = s.encode_wide();

View file

@ -1,35 +0,0 @@
use std::str::FromStr;
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(try_from = "String")]
pub struct Color(ratatui::style::Color);
impl FromStr for Color {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
ratatui::style::Color::from_str(s).map(Self).map_err(|_| anyhow!("invalid color: {s}"))
}
}
impl TryFrom<String> for Color {
type Error = anyhow::Error;
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(&s) }
}
impl From<Color> for ratatui::style::Color {
fn from(value: Color) -> Self { value.0 }
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.to_string().serialize(serializer)
}
}

View file

@ -1,15 +0,0 @@
use super::Style;
#[derive(Clone, Debug)]
pub struct Icon {
pub text: String,
pub style: Style,
}
#[derive(Clone, Copy, Debug, Default)]
pub enum IconCache {
#[default]
Missing,
Undefined,
Icon(&'static Icon),
}

View file

@ -1 +0,0 @@
yazi_macro::mod_flat!(color icon style);

View file

@ -21,7 +21,8 @@ impl Urn {
#[cfg(unix)]
#[inline]
pub fn is_hidden(&self) -> bool {
self.name().is_some_and(|s| s.as_encoded_bytes().starts_with(b"."))
use std::os::unix::ffi::OsStrExt;
self.name().is_some_and(|s| s.as_bytes().starts_with(b"."))
}
}