feat!: rounded corners for indicator bar (#3419)

This commit is contained in:
三咲雅 misaki masa 2025-12-09 22:25:01 +08:00 committed by GitHub
parent 396f634bb8
commit 55ef88b726
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 340 additions and 186 deletions

View file

@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Improve the UX of the pick and input components ([#2906], [#2935])
- Show progress of each task in task manager ([#3121], [#3131], [#3134])
- New `overall` option to set the overall background color ([#3317])
- Rounded corners for indicator bar ([#3419])
- New `bulk_rename` command always renames files with the editor ([#2984])
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])
- New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
@ -45,6 +46,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Rename `name` to `url` for open, fetchers, spotters, preloaders, previewers, filetype, and `globs` icon rules to support virtual file system ([#3034])
- Rename `mime` fetcher to `mime.local`, and introduce `mime.dir` fetcher to support folder MIME types ([#3222])
- Reclassify `hovered` and `preview_hovered` under `[mgr]` of `theme.toml` into `[indicator]` as `current` and `preview`, respectively ([#3419])
- Remove `$0` parameter in opener rules to make the `open` command work under empty directories ([#3226])
- Return `Path` instead of `Url` from `Url:strip_prefix()` and `File.link_to` to enforce type safety ([#3361], [#3385])
- Use `body` instead of the term `content` in confirmations ([#2921])
@ -1551,3 +1553,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3391]: https://github.com/sxyazi/yazi/pull/3391
[#3393]: https://github.com/sxyazi/yazi/pull/3393
[#3396]: https://github.com/sxyazi/yazi/pull/3396
[#3419]: https://github.com/sxyazi/yazi/pull/3419

26
Cargo.lock generated
View file

@ -300,9 +300,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
[[package]]
name = "bcrypt-pbkdf"
@ -505,9 +505,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.48"
version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [
"find-msvc-tools",
"jobserver",
@ -662,6 +662,7 @@ dependencies = [
"itoa",
"rustversion",
"ryu",
"serde",
"static_assertions",
]
@ -1305,9 +1306,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
version = "1.1.6"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30f4148e3c9b7dbe0cc7e842ad5a61b28f9025f201d78149383e778a08bc9215"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -2825,9 +2826,9 @@ dependencies = [
[[package]]
name = "pxfm"
version = "0.1.26"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b"
checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
dependencies = [
"num-traits",
]
@ -2969,6 +2970,7 @@ dependencies = [
"itertools 0.13.0",
"lru 0.12.5",
"paste",
"serde",
"strum",
"unicode-segmentation",
"unicode-truncate",
@ -3526,9 +3528,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simd_helpers"
@ -5270,9 +5272,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8"
dependencies = [
"zune-core",
]

View file

@ -42,7 +42,7 @@ parking_lot = "0.12.5"
paste = "1.0.15"
percent-encoding = "2.3.2"
rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] }
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
ratatui = { version = "0.29.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.2"
russh = { version = "0.55.0", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"

View file

@ -50,7 +50,7 @@ impl UserData for Core {
}
};
}
Ok(match key.as_bytes().as_ref() {
Ok(match &*key.as_bytes() {
b"active" => reuse!(active, super::Tab::make(me.active())),
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),

View file

@ -68,8 +68,11 @@ impl UserData for File {
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
fields.add_field_method_get("is_hovered", |_, me| Ok(me.idx == me.folder.cursor));
fields.add_field_method_get("in_current", |_, me| {
Ok(&*me.folder as *const _ == &me.tab.current as *const _)
});
fields.add_field_method_get("in_preview", |_, me| {
Ok(me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
Ok(me.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
});
}

View file

@ -1,8 +1,7 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use yazi_plugin::runtime::SER_OPT;
use yazi_binding::{SER_OPT, cached_field};
use super::{Lives, PtrCell};

View file

@ -1,8 +1,7 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{cached_field, deprecate};
use yazi_plugin::runtime::SER_OPT;
use yazi_binding::{SER_OPT, cached_field, deprecate};
use super::{Lives, PtrCell};
use crate::lives::TaskSnap;

View file

@ -36,7 +36,7 @@ impl Rect {
})?;
let index = lua.create_function(move |lua, (_, key): (Table, mlua::String)| {
Ok(match key.as_bytes().as_ref() {
Ok(match &*key.as_bytes() {
b"default" => {
deprecate!(lua, "`ui.Rect.default` is deprecated, use `ui.Rect{{}}` instead, in your {}\nSee #2927 for more details: https://github.com/sxyazi/yazi/pull/2927");
Some(Self(Default::default()))

View file

@ -82,65 +82,122 @@ macro_rules! impl_style_method {
macro_rules! impl_style_shorthands {
($methods:ident, $($field:tt).+) => {
$methods.add_function_mut("fg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
use ratatui::style::Modifier;
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
match value {
mlua::Value::Nil => {
ud.borrow::<Self>()?.$($field).+.fg.map($crate::Color).into_lua(lua)
},
mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
me.bg.map($crate::Color).into_lua(lua)
}
mlua::Value::Nil | mlua::Value::Boolean(_) => {
me.fg.map($crate::Color).into_lua(lua)
}
_ => {
ud.borrow_mut::<Self>()?.$($field).+.fg = Some($crate::Color::try_from(value)?.0);
me.fg = Some($crate::Color::try_from(value)?.0);
ud.into_lua(lua)
}
}
});
$methods.add_function_mut("bg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
use ratatui::style::Modifier;
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
match value {
mlua::Value::Nil => {
ud.borrow::<Self>()?.$($field).+.bg.map($crate::Color).into_lua(lua)
mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
me.fg.map($crate::Color).into_lua(lua)
}
mlua::Value::Nil | mlua::Value::Boolean(_) => {
me.bg.map($crate::Color).into_lua(lua)
}
_ => {
ud.borrow_mut::<Self>()?.$($field).+.bg = Some($crate::Color::try_from(value)?.0);
me.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;
$methods.add_function_mut("bold", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::BOLD);
} else {
*me = me.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;
$methods.add_function_mut("dim", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::DIM);
} else {
*me = me.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;
$methods.add_function_mut("italic", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::ITALIC);
} else {
*me = me.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;
$methods.add_function_mut("underline", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::UNDERLINED);
} else {
*me = me.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;
$methods.add_function_mut("blink", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::SLOW_BLINK);
} else {
*me = me.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;
$methods.add_function_mut("blink_rapid", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::RAPID_BLINK);
} else {
*me = me.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;
$methods.add_function_mut("reverse", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::REVERSED);
} else {
*me = me.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;
$methods.add_function_mut("hidden", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::HIDDEN);
} else {
*me = me.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;
$methods.add_function_mut("crossed", |_, (ud, remove): (mlua::AnyUserData, bool)| {
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::CROSSED_OUT);
} else {
*me = me.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();
ud.borrow_mut::<Self>()?.$($field).+ = ratatui::style::Style::reset();
Ok(ud)
});
};

View file

@ -1,4 +1,6 @@
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, LuaSerdeExt, MetaMethod, Table, UserData, UserDataMethods, Value};
use crate::SER_OPT;
#[derive(Clone, Copy, Default)]
pub struct Style(pub ratatui::style::Style);
@ -34,6 +36,9 @@ impl UserData for Style {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_shorthands!(methods, 0);
methods
.add_method("raw", |lua, me, ()| lua.to_value_with(&yazi_config::Style::from(me.0), SER_OPT));
methods.add_function_mut("patch", |_, (ud, value): (AnyUserData, Value)| {
{
let mut me = ud.borrow_mut::<Self>()?;

View file

@ -1,4 +1,7 @@
use mlua::{IntoLua, Lua, Table, Value};
use mlua::{IntoLua, Lua, SerializeOptions, Table, Value};
pub const SER_OPT: SerializeOptions =
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
pub fn get_metatable(lua: &Lua, value: impl IntoLua) -> mlua::Result<Table> {
let (_, mt): (Value, Table) = unsafe {

View file

@ -29,10 +29,6 @@ overall = {}
[mgr]
cwd = { fg = "cyan" }
# Hovered
hovered = { reversed = true }
preview_hovered = { underline = true }
# Find
find_keyword = { fg = "yellow", bold = true, italic = true, underline = true }
find_position = { fg = "magenta", bg = "reset", bold = true, italic = true }
@ -91,6 +87,17 @@ unset_alt = { fg = "red", bg = "gray" }
# : }}}
# : Indicator of hovered file {{{
[indicator]
parent = { reversed = true }
current = { reversed = true }
preview = { underline = true }
padding = { open = "", close = "" }
# : }}}
# : Status bar {{{
[status]

View file

@ -29,10 +29,6 @@ overall = {}
[mgr]
cwd = { fg = "cyan" }
# Hovered
hovered = { reversed = true }
preview_hovered = { underline = true }
# Find
find_keyword = { fg = "yellow", bold = true, italic = true, underline = true }
find_position = { fg = "magenta", bg = "reset", bold = true, italic = true }
@ -91,6 +87,17 @@ unset_alt = { fg = "red", bg = "gray" }
# : }}}
# : Indicator of hovered file {{{
[indicator]
parent = { reversed = true }
current = { reversed = true }
preview = { underline = true }
padding = { open = "", close = "" }
# : }}}
# : Status bar {{{
[status]

View file

@ -1,27 +0,0 @@
use std::{ops::Deref, str::FromStr};
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Default)]
pub struct Color(ratatui::style::Color);
impl Deref for Color {
type Target = ratatui::style::Color;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<Color> for ratatui::style::Color {
fn from(value: Color) -> Self { value.0 }
}
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)
}
}

View file

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

View file

@ -1,75 +1,86 @@
use ratatui::style::Modifier;
use serde::Deserialize;
use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use crate::Color;
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
#[serde(default)]
pub bold: bool,
#[serde(default)]
pub dim: bool,
#[serde(default)]
pub italic: bool,
#[serde(default)]
pub underline: bool,
#[serde(default)]
pub blink: bool,
#[serde(default)]
pub blink_rapid: bool,
#[serde(default)]
pub reversed: bool,
#[serde(default)]
pub hidden: bool,
#[serde(default)]
pub crossed: bool,
pub bold: Option<bool>,
pub dim: Option<bool>,
pub italic: Option<bool>,
pub underline: Option<bool>,
pub blink: Option<bool>,
pub blink_rapid: Option<bool>,
pub reversed: Option<bool>,
pub hidden: Option<bool>,
pub crossed: Option<bool>,
}
impl From<Style> for ratatui::style::Style {
fn from(value: Style) -> Self {
impl From<ratatui::style::Style> for Style {
#[rustfmt::skip]
fn from(value: ratatui::style::Style) -> Self {
use ratatui::style::Modifier as M;
let sub = value.sub_modifier;
let all = value.add_modifier - sub;
Self {
fg: value.fg.map(Into::into),
bg: value.bg.map(Into::into),
underline_color: None,
add_modifier: value.into(),
sub_modifier: Modifier::empty(),
fg: value.fg.map(Into::into),
bg: value.bg.map(Into::into),
bold: if all.contains(M::BOLD) { Some(true) } else if sub.contains(M::BOLD) { Some(false) } else { None },
dim: if all.contains(M::DIM) { Some(true) } else if sub.contains(M::DIM) { Some(false) } else { None },
italic: if all.contains(M::ITALIC) { Some(true) } else if sub.contains(M::ITALIC) { Some(false) } else { None },
underline: if all.contains(M::UNDERLINED) { Some(true) } else if sub.contains(M::UNDERLINED) { Some(false) } else { None },
blink: if all.contains(M::SLOW_BLINK) { Some(true) } else if sub.contains(M::SLOW_BLINK) { Some(false) } else { None },
blink_rapid: if all.contains(M::RAPID_BLINK) { Some(true) } else if sub.contains(M::RAPID_BLINK) { Some(false) } else { None },
reversed: if all.contains(M::REVERSED) { Some(true) } else if sub.contains(M::REVERSED) { Some(false) } else { None },
hidden: if all.contains(M::HIDDEN) { Some(true) } else if sub.contains(M::HIDDEN) { Some(false) } else { None },
crossed: if all.contains(M::CROSSED_OUT) { Some(true) } else if sub.contains(M::CROSSED_OUT) { Some(false) } else { None },
}
}
}
impl From<Style> for ratatui::style::Modifier {
impl From<Style> for ratatui::style::Style {
#[rustfmt::skip]
fn from(value: Style) -> Self {
let mut modifier = Self::empty();
if value.bold {
modifier |= Self::BOLD;
use ratatui::style::Modifier as M;
let (mut add, mut sub) = (M::empty(), M::empty());
if let Some(b) = value.bold {
if b { add |= M::BOLD } else { sub |= M::BOLD };
}
if value.dim {
modifier |= Self::DIM;
if let Some(b) = value.dim {
if b { add |= M::DIM } else { sub |= M::DIM };
}
if value.italic {
modifier |= Self::ITALIC;
if let Some(b) = value.italic {
if b { add |= M::ITALIC } else { sub |= M::ITALIC };
}
if value.underline {
modifier |= Self::UNDERLINED;
if let Some(b) = value.underline {
if b { add |= M::UNDERLINED } else { sub |= M::UNDERLINED };
}
if value.blink {
modifier |= Self::SLOW_BLINK;
if let Some(b) = value.blink {
if b { add |= M::SLOW_BLINK } else { sub |= M::SLOW_BLINK };
}
if value.blink_rapid {
modifier |= Self::RAPID_BLINK;
if let Some(b) = value.blink_rapid {
if b { add |= M::RAPID_BLINK } else { sub |= M::RAPID_BLINK };
}
if value.reversed {
modifier |= Self::REVERSED;
if let Some(b) = value.reversed {
if b { add |= M::REVERSED } else { sub |= M::REVERSED };
}
if value.hidden {
modifier |= Self::HIDDEN;
if let Some(b) = value.hidden {
if b { add |= M::HIDDEN } else { sub |= M::HIDDEN };
}
if value.crossed {
modifier |= Self::CROSSED_OUT;
if let Some(b) = value.crossed {
if b { add |= M::CROSSED_OUT } else { sub |= M::CROSSED_OUT };
}
Self {
fg: value.fg.map(Into::into),
bg: value.bg.map(Into::into),
underline_color: None,
add_modifier: add,
sub_modifier: sub,
}
modifier
}
}

View file

@ -2,12 +2,13 @@ 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::{Color, Icon as I, Pattern, Style};
use crate::{Icon as I, Pattern, Style};
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct Icon {

View file

@ -11,21 +11,22 @@ use crate::Style;
#[derive(Deserialize, DeserializeOver1)]
pub struct Theme {
pub flavor: Flavor,
pub app: App,
pub mgr: Mgr,
pub tabs: Tabs,
pub mode: Mode,
pub status: Status,
pub which: Which,
pub confirm: Confirm,
pub spot: Spot,
pub notify: Notify,
pub pick: Pick,
pub input: Input,
pub cmp: Cmp,
pub tasks: Tasks,
pub help: Help,
pub flavor: Flavor,
pub app: App,
pub mgr: Mgr,
pub tabs: Tabs,
pub mode: Mode,
pub indicator: Indicator,
pub status: Status,
pub which: Which,
pub confirm: Confirm,
pub spot: Spot,
pub notify: Notify,
pub pick: Pick,
pub input: Input,
pub cmp: Cmp,
pub tasks: Tasks,
pub help: Help,
// File-specific styles
#[serde(skip_serializing)]
@ -43,10 +44,6 @@ pub struct App {
pub struct Mgr {
pub cwd: Style,
// Hovered
pub hovered: Style,
pub preview_hovered: Style,
// Find
pub find_keyword: Style,
pub find_position: Style,
@ -100,6 +97,20 @@ pub struct Mode {
pub unset_alt: Style,
}
#[derive(Deserialize, DeserializeOver2)]
pub struct Indicator {
pub parent: Style,
pub current: Style,
pub preview: Style,
pub padding: IndicatorPadding,
}
#[derive(Deserialize, DeserializeOver2)]
pub struct IndicatorPadding {
pub open: String,
pub close: String,
}
#[derive(Deserialize, DeserializeOver2)]
pub struct Status {
pub overall: Style,

View file

@ -25,7 +25,7 @@ impl App {
};
let id: mlua::String = t.get("_id")?;
match id.as_bytes().as_ref() {
match &*id.as_bytes() {
b"current" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
b"preview" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
b"progress" => layout.progress = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,

View file

@ -1,7 +1,7 @@
Entity = {
_inc = 1000,
_children = {
{ "spacer", id = 1, order = 1000 },
{ "padding", id = 1, order = 1000 },
{ "icon", id = 2, order = 2000 },
{ "prefix", id = 3, order = 3000 },
{ "highlights", id = 4, order = 4000 },
@ -12,7 +12,18 @@ Entity = {
function Entity:new(file) return setmetatable({ _file = file }, { __index = self }) end
function Entity:spacer() return " " end
function Entity:padding()
if not self._file.is_hovered or self._file.in_preview then
return " "
end
local style = self:style_rev()
if style then
return ui.Span(th.indicator.padding.open):style(style)
else
return " "
end
end
function Entity:icon()
local icon = self._file:icon()
@ -86,13 +97,25 @@ function Entity:redraw()
end
function Entity:style()
local s = self._file:style()
local s = self._file:style() or ui.Style()
if not self._file.is_hovered then
return s
elseif self._file.in_current then
return s:patch(th.indicator.current)
elseif self._file.in_preview then
return s and s:patch(th.mgr.preview_hovered) or th.mgr.preview_hovered
return s:patch(th.indicator.preview)
else
return s and s:patch(th.mgr.hovered) or th.mgr.hovered
return s:patch(th.indicator.parent)
end
end
function Entity:style_rev()
local s = self:style()
local bg = s:bg(true)
if bg then
return ui.Style():fg(bg):bg("reset"):reverse(true)
elseif s:raw().reversed then
return ui.Style():bg("reset"):reverse(true)
end
end

View file

@ -2,15 +2,30 @@ Linemode = {
_inc = 1000,
_children = {
{ "solo", id = 1, order = 1000 },
{ "spacer", id = 2, order = 2000 },
{ "padding", id = 2, order = 2000 },
},
}
function Linemode:new(file) return setmetatable({ _file = file }, { __index = self }) end
function Linemode:spacer() return " " end
function Linemode:padding()
if not self._file.is_hovered then
return " "
end
local style = Entity:new(self._file):style_rev()
if style then
return ui.Span(th.indicator.padding.close):style(style)
else
return " "
end
end
function Linemode:solo()
if not self._file.in_current then
return ""
end
local mode = cx.active.pref.linemode
if mode == "none" or mode == "solo" then
return ""

View file

@ -17,16 +17,19 @@ function Parent:redraw()
return {}
end
local items = {}
local left, right = {}, {}
for _, f in ipairs(self._folder.window) do
local entity = Entity:new(f)
items[#items + 1] = entity:redraw():truncate {
max = self._area.w,
ellipsis = entity:ellipsis(self._area.w),
}
left[#left + 1], right[#right + 1] = entity:redraw(), Linemode:new(f):redraw()
local max = math.max(0, self._area.w - right[#right]:width())
left[#left]:truncate { max = max, ellipsis = entity:ellipsis(max) }
end
return ui.List(items):area(self._area)
return {
ui.List(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Align.RIGHT),
}
end
-- Mouse events

View file

@ -31,7 +31,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
pub(super) fn area(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|_, s: mlua::String| {
let layout = LAYOUT.get();
Ok(match s.as_bytes().as_ref() {
Ok(match &*s.as_bytes() {
b"current" => Rect(layout.current),
b"preview" => Rect(layout.preview),
b"progress" => Rect(layout.progress),
@ -74,7 +74,7 @@ pub(super) fn redraw(lua: &Lua) -> mlua::Result<Value> {
let id: mlua::String = c.get("_id")?;
let mut layout = LAYOUT.get();
match id.as_bytes().as_ref() {
match &*id.as_bytes() {
b"current" => layout.current = *c.raw_get::<Rect>("_area")?,
b"preview" => layout.preview = *c.raw_get::<Rect>("_area")?,
b"progress" => layout.progress = *c.raw_get::<Rect>("_area")?,

View file

@ -34,7 +34,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
}
fn op(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (name, t): (mlua::String, Table)| match name.as_bytes().as_ref() {
lua.create_function(|lua, (name, t): (mlua::String, Table)| match &*name.as_bytes() {
b"part" => super::FilesOp::part(lua, t),
b"done" => super::FilesOp::done(lua, t),
b"size" => super::FilesOp::size(lua, t),
@ -75,7 +75,7 @@ fn write(lua: &Lua) -> mlua::Result<Function> {
fn create(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {
let result = match r#type.as_bytes().as_ref() {
let result = match &*r#type.as_bytes() {
b"dir" => provider::create_dir(&*url).await,
b"dir_all" => provider::create_dir_all(&*url).await,
_ => Err("Creation type must be 'dir' or 'dir_all'".into_lua_err())?,
@ -90,7 +90,7 @@ fn create(lua: &Lua) -> mlua::Result<Function> {
fn remove(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {
let result = match r#type.as_bytes().as_ref() {
let result = match &*r#type.as_bytes() {
b"file" => provider::remove_file(&*url).await,
b"dir" => provider::remove_dir(&*url).await,
b"dir_all" => provider::remove_dir_all(&*url).await,
@ -165,7 +165,7 @@ fn expand_url(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, value: Value| {
use yazi_fs::path::expand_url;
Ok(Url::new(match value {
Value::String(s) => expand_url(UrlCow::try_from(s.as_bytes().as_ref())?),
Value::String(s) => expand_url(UrlCow::try_from(&*s.as_bytes())?),
Value::UserData(ud) => expand_url(&*ud.borrow::<yazi_binding::Url>()?),
_ => Err("must be a string or a Url".into_lua_err())?,
}))

View file

@ -1,11 +1,8 @@
use mlua::{IntoLua, Lua, LuaSerdeExt, SerializeOptions, Value};
use yazi_binding::{Composer, ComposerGet, ComposerSet, Url};
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url};
use yazi_boot::ARGS;
use yazi_config::YAZI;
pub const SER_OPT: SerializeOptions =
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
match key {

View file

@ -1,5 +1,5 @@
use mlua::{IntoLua, Lua, Value};
use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url};
use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url, deprecate};
use yazi_config::THEME;
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
@ -9,6 +9,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
b"mgr" => mgr(),
b"tabs" => tabs(),
b"mode" => mode(),
b"indicator" => indicator(),
b"status" => status(),
b"which" => which(),
b"confirm" => confirm(),
@ -50,8 +51,20 @@ fn mgr() -> Composer<ComposerGet, ComposerSet> {
match key {
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"hovered" => {
deprecate!(
lua,
"`th.mgr.hovered` is deprecated, use `th.indicator.current` instead, in your {}\nSee #3419 for more details: https://github.com/sxyazi/yazi/pull/3419"
);
Style::from(THEME.indicator.current).into_lua(lua)
}
b"preview_hovered" => {
deprecate!(
lua,
"`th.mgr.preview_hovered` is deprecated, use `th.indicator.preview` instead, in your {}\nSee #3419 for more details: https://github.com/sxyazi/yazi/pull/3419"
);
Style::from(THEME.indicator.preview).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),
@ -131,6 +144,29 @@ fn mode() -> Composer<ComposerGet, ComposerSet> {
Composer::new(get, set)
}
fn indicator() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
let t = &THEME.indicator;
match key {
b"parent" => Style::from(t.parent).into_lua(lua),
b"current" => Style::from(t.current).into_lua(lua),
b"preview" => Style::from(t.preview).into_lua(lua),
b"padding" => lua
.create_table_from([
("open", lua.create_string(&t.padding.open)?),
("close", lua.create_string(&t.padding.close)?),
])?
.into_lua(lua),
_ => Ok(Value::Nil),
}
}
fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }
Composer::new(get, set)
}
fn status() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
let t = &THEME.status;

View file

@ -10,7 +10,7 @@ use super::Utils;
impl Utils {
pub(super) fn id(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, r#type: mlua::String| {
Ok(Id(match r#type.as_bytes().as_ref() {
Ok(Id(match &*r#type.as_bytes() {
b"app" => *yazi_dds::ID,
b"ft" => yazi_fs::FILES_TICKET.next(),
_ => Err("Invalid id type".into_lua_err())?,

View file

@ -1,8 +1,7 @@
use mlua::{Function, IntoLuaMulti, Lua, LuaSerdeExt, Value};
use yazi_binding::Error;
use yazi_binding::{Error, SER_OPT};
use super::Utils;
use crate::runtime::SER_OPT;
impl Utils {
pub(super) fn json_encode(lua: &Lua) -> mlua::Result<Function> {