diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 1efc59b3..0ab02b91 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -106,18 +106,18 @@ keymap = [ { on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, # Sorting - { on = [ ",", "m" ], run = "sort modified", desc = "Sort by modified time" }, - { on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" }, - { on = [ ",", "c" ], run = "sort created", desc = "Sort by created time" }, - { on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" }, - { on = [ ",", "e" ], run = "sort extension", desc = "Sort by extension" }, - { on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" }, - { on = [ ",", "a" ], run = "sort alphabetical", desc = "Sort alphabetically" }, - { on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" }, - { on = [ ",", "n" ], run = "sort natural", desc = "Sort naturally" }, - { on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" }, - { on = [ ",", "s" ], run = "sort size", desc = "Sort by size" }, - { on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" }, + { on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" }, + { on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" }, + { on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" }, + { on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" }, + { on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" }, + { on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" }, + { on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" }, + { on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" }, + { on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" }, + { on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" }, + { on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" }, + { on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" }, # Tabs { on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" }, diff --git a/yazi-core/src/notify/message.rs b/yazi-core/src/notify/message.rs index 8006467c..6610290c 100644 --- a/yazi-core/src/notify/message.rs +++ b/yazi-core/src/notify/message.rs @@ -11,20 +11,25 @@ pub struct Message { pub level: NotifyLevel, pub timeout: Duration, - pub instant: Instant, - pub percent: u8, + pub instant: Instant, + pub percent: u8, + pub max_width: usize, } impl From for Message { fn from(opt: NotifyOpt) -> Self { + let title = opt.title.lines().next().unwrap_or_default(); + let max_width = opt.content.lines().map(|s| s.width()).max().unwrap_or(0).max(title.width()); + Self { - title: opt.title, + title: title.to_owned(), content: opt.content, level: opt.level, timeout: opt.timeout, - instant: Instant::now(), - percent: 0, + instant: Instant::now(), + percent: 0, + max_width: max_width + NOTIFY_BORDER as usize, } } } diff --git a/yazi-fm/src/lives/files.rs b/yazi-fm/src/lives/files.rs index 62a8900c..1437986a 100644 --- a/yazi-fm/src/lives/files.rs +++ b/yazi-fm/src/lives/files.rs @@ -1,8 +1,8 @@ use std::ops::{Deref, Range}; -use mlua::{AnyUserData, Lua, MetaMethod, UserDataMethods}; +use mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods}; -use super::{File, SCOPE}; +use super::{File, Filter, SCOPE}; pub(super) struct Files { window: Range, @@ -28,6 +28,8 @@ impl Files { pub(super) fn register(lua: &Lua) -> mlua::Result<()> { lua.register_userdata_type::(|reg| { + reg.add_field_method_get("filter", |_, me| me.filter().map(Filter::make).transpose()); + reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.window.end - me.window.start)); reg.add_meta_method(MetaMethod::Index, |_, me, mut idx: usize| { diff --git a/yazi-fm/src/lives/filter.rs b/yazi-fm/src/lives/filter.rs new file mode 100644 index 00000000..686465fa --- /dev/null +++ b/yazi-fm/src/lives/filter.rs @@ -0,0 +1,28 @@ +use std::ops::Deref; + +use mlua::{AnyUserData, Lua}; + +use super::SCOPE; + +pub(super) struct Filter { + inner: *const yazi_core::folder::Filter, +} + +impl Deref for Filter { + type Target = yazi_core::folder::Filter; + + fn deref(&self) -> &Self::Target { unsafe { &*self.inner } } +} + +impl Filter { + #[inline] + pub(super) fn make(inner: &yazi_core::folder::Filter) -> mlua::Result> { + SCOPE.create_any_userdata(Self { inner }) + } + + pub(super) fn register(lua: &Lua) -> mlua::Result<()> { + lua.register_userdata_type::(|_| {})?; + + Ok(()) + } +} diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs index ee0ada44..7fd60203 100644 --- a/yazi-fm/src/lives/lives.rs +++ b/yazi-fm/src/lives/lives.rs @@ -18,6 +18,7 @@ impl Lives { super::Config::register(&LUA)?; super::File::register(&LUA)?; super::Files::register(&LUA)?; + super::Filter::register(&LUA)?; super::Folder::register(&LUA)?; super::Mode::register(&LUA)?; super::Preview::register(&LUA)?; diff --git a/yazi-fm/src/lives/mod.rs b/yazi-fm/src/lives/mod.rs index d910d4c7..edbf3b9d 100644 --- a/yazi-fm/src/lives/mod.rs +++ b/yazi-fm/src/lives/mod.rs @@ -3,6 +3,7 @@ mod config; mod file; mod files; +mod filter; mod folder; mod iter; mod lives; @@ -17,6 +18,7 @@ mod yanked; use config::*; use file::*; use files::*; +use filter::*; use folder::*; use iter::*; pub(super) use lives::*; diff --git a/yazi-fm/src/notify/layout.rs b/yazi-fm/src/notify/layout.rs index 49746acf..068011ec 100644 --- a/yazi-fm/src/notify/layout.rs +++ b/yazi-fm/src/notify/layout.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}}; use yazi_config::THEME; use yazi_core::notify::Message; @@ -15,9 +13,7 @@ impl<'a> Layout<'a> { pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } pub(crate) fn available(area: Rect) -> Rect { - let chunks = - layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(80), Constraint::Max(1)]) - .split(area); + let chunks = layout::Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area); let chunks = layout::Layout::vertical([Constraint::Max(1), Constraint::Fill(1)]).split(chunks[1]); @@ -25,12 +21,22 @@ impl<'a> Layout<'a> { chunks[1] } - fn tile(area: Rect, messages: &[Message]) -> Rc<[Rect]> { + fn tile(area: Rect, messages: &[Message]) -> Vec { layout::Layout::vertical( messages.iter().map(|m| Constraint::Length(m.height(area.width) as u16)), ) .spacing(1) .split(area) + .iter() + .zip(messages) + .map(|(&(mut r), m)| { + if r.width > m.max_width as u16 { + r.x = r.x.saturating_add(r.width - m.max_width as u16); + r.width = m.max_width as u16; + } + r + }) + .collect() } } @@ -51,7 +57,7 @@ impl<'a> Widget for Layout<'a> { let mut rect = tile[i].offset(Offset { x: (100 - m.percent) as i32 * tile[i].width as i32 / 100, y: 0 }); - rect.width = area.width.saturating_sub(rect.x); + rect.width -= rect.x - tile[i].x; yazi_plugin::elements::Clear::default().render(rect, buf); Paragraph::new(m.content.as_str()) @@ -59,7 +65,7 @@ impl<'a> Widget for Layout<'a> { .block( Block::bordered() .border_type(BorderType::Rounded) - .title(format!("{} {}", icon, m.title)) + .title(format!("{icon} {}", m.title)) .title_style(style) .border_style(style), ) diff --git a/yazi-plugin/preset/components/current.lua b/yazi-plugin/preset/components/current.lua index 54c35d4c..ee7958fc 100644 --- a/yazi-plugin/preset/components/current.lua +++ b/yazi-plugin/preset/components/current.lua @@ -2,12 +2,27 @@ Current = { area = ui.Rect.default, } +function Current:empty(area) + local folder = Folder:by_kind(Folder.CURRENT) + + local line + if folder.files.filter then + line = ui.Line("No filter results") + else + line = ui.Line(folder.stage == "loading" and "Loading..." or "No files") + end + + return { + ui.Paragraph(area, { line }):align(ui.Paragraph.CENTER), + } +end + function Current:render(area) self.area = area local files = Folder:by_kind(Folder.CURRENT).window if #files == 0 then - return {} + return self:empty(area) end local items, markers = {}, {} diff --git a/yazi-plugin/preset/plugins/file.lua b/yazi-plugin/preset/plugins/file.lua index 8e99c6b1..6f331b4b 100644 --- a/yazi-plugin/preset/plugins/file.lua +++ b/yazi-plugin/preset/plugins/file.lua @@ -9,9 +9,7 @@ function M:peek() p = ui.Paragraph.parse(self.area, "----- File Type Classification -----\n\n" .. output.stdout) else p = ui.Paragraph(self.area, { - ui.Line { - ui.Span(string.format("Spawn `%s` command returns %s", cmd, code)), - }, + ui.Line(string.format("Spawn `%s` command returns %s", cmd, code)), }) end diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 37b22112..989bb186 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -3,12 +3,19 @@ local M = {} function M:peek() local folder = Folder:by_kind(Folder.PREVIEW) if not folder or folder.cwd ~= self.file.url then - return {} + return end local bound = math.max(0, #folder.files - self.area.h) if self.skip > bound then - ya.manager_emit("peek", { bound, only_if = tostring(self.file.url), upper_bound = true }) + return ya.manager_emit("peek", { bound, only_if = tostring(self.file.url), upper_bound = true }) + end + + if #folder.files == 0 then + return ya.preview_widgets(self, { + ui.Paragraph(self.area, { ui.Line(folder.stage == "loading" and "Loading..." or "No files") }) + :align(ui.Paragraph.CENTER), + }) end local items, markers = {}, {} diff --git a/yazi-plugin/src/elements/line.rs b/yazi-plugin/src/elements/line.rs index 37bc43cd..ca926111 100644 --- a/yazi-plugin/src/elements/line.rs +++ b/yazi-plugin/src/elements/line.rs @@ -12,7 +12,7 @@ const RIGHT: u8 = 2; #[derive(Clone, FromLua)] pub struct Line(pub(super) ratatui::text::Line<'static>); -impl<'a> TryFrom> for Line { +impl TryFrom> for Line { type Error = mlua::Error; fn try_from(tb: Table) -> Result { @@ -33,13 +33,20 @@ impl<'a> TryFrom> for Line { } } +impl TryFrom> for Line { + type Error = mlua::Error; + + fn try_from(s: mlua::String) -> Result { + Ok(Self(ratatui::text::Line::from(s.to_string_lossy().into_owned()))) + } +} + impl Line { pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> { - let new = lua.create_function(|_, (_, value): (Table, Value)| { - if let Value::Table(tb) = value { - return Line::try_from(tb); - } - Err("expected a table of Spans or Lines".into_lua_err()) + let new = lua.create_function(|_, (_, value): (Table, Value)| match value { + Value::Table(tb) => Line::try_from(tb), + Value::String(s) => Line::try_from(s), + _ => Err("expected a String, or a table of Spans and Lines".into_lua_err()), })?; let parse = lua.create_function(|_, code: mlua::String| { diff --git a/yazi-shared/src/term/term.rs b/yazi-shared/src/term/term.rs index 81a46a5c..e9f9beb7 100644 --- a/yazi-shared/src/term/term.rs +++ b/yazi-shared/src/term/term.rs @@ -110,7 +110,9 @@ impl Term { } #[inline] - pub fn can_partial(&mut self) -> bool { self.last_area == self.inner.get_frame().size() } + pub fn can_partial(&mut self) -> bool { + self.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().size() + } pub fn size() -> WindowSize { let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };