From 464acfe47b3cbea2713b91d553ff648b8f07ff89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Thu, 15 May 2025 14:34:10 +0800 Subject: [PATCH] feat: show file extension for truncated file list items (#2759) --- Cargo.lock | 36 ++++++++--------- Cargo.toml | 2 +- yazi-plugin/preset/components/current.lua | 8 ++-- yazi-plugin/preset/components/entity.lua | 16 +++++++- yazi-plugin/preset/components/parent.lua | 6 ++- yazi-plugin/preset/plugins/folder.lua | 6 ++- yazi-plugin/src/elements/line.rs | 47 ++++++++++++++++------- yazi-plugin/src/elements/span.rs | 38 +++++++++++++++++- yazi-plugin/src/utils/layer.rs | 7 ++-- 9 files changed, 122 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d316a015..bd6ca3ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,9 +252,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitstream-io" @@ -572,7 +572,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm_winapi", "mio", "parking_lot", @@ -588,7 +588,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm_winapi", "derive_more", "document-features", @@ -1163,7 +1163,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -1305,7 +1305,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -1527,7 +1527,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "filetime", "fsevent-sys", "inotify", @@ -1658,7 +1658,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "libc", "objc2", @@ -1998,7 +1998,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cassowary", "compact_str", "crossterm 0.28.1", @@ -2078,7 +2078,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2169,7 +2169,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2182,7 +2182,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -3006,9 +3006,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "78c888f2a25014723b17ac60a626d626549f773e0d702b1320956f8d769ae1f8" [[package]] name = "which" @@ -3278,7 +3278,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -3377,7 +3377,7 @@ name = "yazi-config" version = "25.5.14" dependencies = [ "anyhow", - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm 0.29.0", "dirs", "globset", @@ -3399,7 +3399,7 @@ name = "yazi-core" version = "25.5.14" dependencies = [ "anyhow", - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm 0.29.0", "dirs", "futures", @@ -3509,7 +3509,7 @@ version = "25.5.14" dependencies = [ "anyhow", "arc-swap", - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation-sys", "dirs", "foldhash", diff --git a/Cargo.toml b/Cargo.toml index 0d319ad1..25ecfccf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ lto = false ansi-to-tui = "7.0.0" anyhow = "1.0.98" base64 = "0.22.1" -bitflags = "2.9.0" +bitflags = "2.9.1" clap = { version = "4.5.38", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.29.0", features = [ "event-stream" ] } diff --git a/yazi-plugin/preset/components/current.lua b/yazi-plugin/preset/components/current.lua index dfa04be2..a9979e21 100644 --- a/yazi-plugin/preset/components/current.lua +++ b/yazi-plugin/preset/components/current.lua @@ -34,9 +34,11 @@ function Current:redraw() local left, right = {}, {} for _, f in ipairs(files) do - left[#left + 1] = Entity:new(f):redraw() - right[#right + 1] = Linemode:new(f):redraw() - left[#left]:truncate { max = math.max(0, self._area.w - right[#right]:width()) } + local entity = Entity:new(f) + 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 { diff --git a/yazi-plugin/preset/components/entity.lua b/yazi-plugin/preset/components/entity.lua index 157e4907..5d38fa58 100644 --- a/yazi-plugin/preset/components/entity.lua +++ b/yazi-plugin/preset/components/entity.lua @@ -79,7 +79,8 @@ end function Entity:redraw() local lines = {} for _, c in ipairs(self._children) do - lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self) + local line = ui.Line((type(c[1]) == "string" and self[c[1]] or c[1])(self)) + c.width, lines[#lines + 1] = line:width(), line end return ui.Line(lines):style(self:style()) end @@ -95,6 +96,19 @@ function Entity:style() end end +function Entity:ellipsis(max) + local adv, f = 0, self._file + for _, child in ipairs(self._children) do + adv = adv + child.width + if adv > max then + return not f.cha.is_dir and f.url.ext and "…." .. f.url.ext or "…" + elseif child.id == 4 then + break + end + end + return "…" +end + -- Children function Entity:children_add(fn, order) self._inc = self._inc + 1 diff --git a/yazi-plugin/preset/components/parent.lua b/yazi-plugin/preset/components/parent.lua index a01f48f4..34fe8bf0 100644 --- a/yazi-plugin/preset/components/parent.lua +++ b/yazi-plugin/preset/components/parent.lua @@ -19,7 +19,11 @@ function Parent:redraw() local items = {} for _, f in ipairs(self._folder.window) do - items[#items + 1] = Entity:new(f):redraw():truncate { max = self._area.w } + local entity = Entity:new(f) + items[#items + 1] = entity:redraw():truncate { + max = self._area.w, + ellipsis = entity:ellipsis(self._area.w), + } end return { diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 547a6024..9d8b339d 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -21,7 +21,11 @@ function M:peek(job) local items = {} for _, f in ipairs(folder.window) do - items[#items + 1] = Entity:new(f):redraw():truncate { max = job.area.w } + local entity = Entity:new(f) + items[#items + 1] = entity:redraw():truncate { + max = job.area.w, + ellipsis = entity:ellipsis(job.area.w), + } end ya.preview_widget(job, { diff --git a/yazi-plugin/src/elements/line.rs b/yazi-plugin/src/elements/line.rs index b2fc9392..d8083691 100644 --- a/yazi-plugin/src/elements/line.rs +++ b/yazi-plugin/src/elements/line.rs @@ -133,27 +133,46 @@ impl UserData for Line { }); methods.add_function_mut("truncate", |_, (ud, t): (AnyUserData, Table)| { let mut me = ud.borrow_mut::()?; - let max = t.raw_get("max")?; - let mut width = 0; + let max = t.raw_get("max")?; + if max < 1 { + me.inner.spans.clear(); + return Ok(ud); + } + + let ellipsis = match t.raw_get::("ellipsis")? { + Value::Nil => (1, ratatui::text::Span::raw("…")), + v => { + let mut span = Span::try_from(v)?; + (span.truncate(max), span.0) + } + }; + + let (mut width, mut last) = (0, None); 'outer: for (x, span) in me.inner.iter_mut().enumerate() { for (y, c) in span.content.char_indices() { width += c.width().unwrap_or(0); - if width < max { - continue; - } else if width == max && span.content[y..].chars().nth(1).is_none() { - continue; + match last { + None if width > max - ellipsis.0 => last = Some((false, x, y)), + Some((false, x, y)) if width > max => { + last = Some((true, x, y)); + break 'outer; + } + _ => {} } - - match &mut span.content { - Cow::Borrowed(s) => span.content = Cow::Borrowed(&s[..y]), - Cow::Owned(s) => s.truncate(y), - } - me.inner.spans.truncate(x + 1); - me.inner.spans.push(ratatui::text::Span::raw("…")); - break 'outer; } } + + if let Some((true, x, y)) = last { + let spans = &mut me.inner.spans; + match &mut spans[x].content { + Cow::Borrowed(s) => spans[x].content = Cow::Borrowed(&s[..y]), + Cow::Owned(s) => s.truncate(y), + } + spans.truncate(x + 1); + spans.push(ellipsis.1); + } + Ok(ud) }); } diff --git a/yazi-plugin/src/elements/span.rs b/yazi-plugin/src/elements/span.rs index fd9bc909..98e2d756 100644 --- a/yazi-plugin/src/elements/span.rs +++ b/yazi-plugin/src/elements/span.rs @@ -1,4 +1,6 @@ -use mlua::{ExternalError, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use std::borrow::Cow; + +use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use unicode_width::UnicodeWidthChar; const EXPECTED: &str = "expected a string or Span"; @@ -14,6 +16,36 @@ impl Span { Ok(span) } + + pub(super) fn truncate(&mut self, max: usize) -> usize { + if max < 1 { + match &mut self.0.content { + Cow::Borrowed(_) => self.0.content = Cow::Borrowed(""), + Cow::Owned(s) => s.clear(), + } + return 0; + } + + let mut adv = 0; + let mut last; + for (i, c) in self.0.content.char_indices() { + (last, adv) = (adv, adv + c.width().unwrap_or(0)); + if adv < max { + continue; + } else if adv == max && self.0.content[i..].chars().nth(1).is_none() { + return max; + } + match &mut self.0.content { + Cow::Borrowed(s) => self.0.content = format!("{}…", &s[..i]).into(), + Cow::Owned(s) => { + s.truncate(i); + s.push('…'); + } + } + return last + 1; + } + adv + } } impl TryFrom for Span { @@ -42,5 +74,9 @@ impl UserData for Span { methods.add_method("visible", |_, Span(me), ()| { Ok(me.content.chars().any(|c| c.width().unwrap_or(0) > 0)) }); + methods.add_function_mut("truncate", |_, (ud, t): (AnyUserData, Table)| { + ud.borrow_mut::()?.truncate(t.raw_get("max")?); + Ok(ud) + }); } } diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index ac795158..8784241c 100644 --- a/yazi-plugin/src/utils/layer.rs +++ b/yazi-plugin/src/utils/layer.rs @@ -68,10 +68,9 @@ impl Utils { pub(super) fn confirm(lua: &Lua) -> mlua::Result { fn content(t: &Table) -> mlua::Result> { - Ok(match t.raw_get::("content") { - Ok(v) if v.is_nil() => Default::default(), - Ok(v) => Text::try_from(v)?.into(), - Err(e) => Err(e)?, + Ok(match t.raw_get::("content")? { + Value::Nil => Default::default(), + v => Text::try_from(v)?.into(), }) }