feat: when there are no files in the list, add a placeholder message (#900)

This commit is contained in:
三咲雅 · Misaki Masa 2024-04-12 10:09:29 +08:00 committed by GitHub
parent 23c38ebae0
commit 3c67cae42d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 113 additions and 40 deletions

View file

@ -106,18 +106,18 @@ keymap = [
{ on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, { on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" },
# Sorting # Sorting
{ on = [ ",", "m" ], run = "sort modified", desc = "Sort by modified time" }, { on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" }, { on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created", desc = "Sort by created time" }, { on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" }, { on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension", desc = "Sort by extension" }, { on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" }, { on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical", desc = "Sort alphabetically" }, { on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" }, { on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural", desc = "Sort naturally" }, { on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" }, { on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size", desc = "Sort by size" }, { on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" }, { on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" },
# Tabs # Tabs
{ on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" }, { on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" },

View file

@ -11,20 +11,25 @@ pub struct Message {
pub level: NotifyLevel, pub level: NotifyLevel,
pub timeout: Duration, pub timeout: Duration,
pub instant: Instant, pub instant: Instant,
pub percent: u8, pub percent: u8,
pub max_width: usize,
} }
impl From<NotifyOpt> for Message { impl From<NotifyOpt> for Message {
fn from(opt: NotifyOpt) -> Self { 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 { Self {
title: opt.title, title: title.to_owned(),
content: opt.content, content: opt.content,
level: opt.level, level: opt.level,
timeout: opt.timeout, timeout: opt.timeout,
instant: Instant::now(), instant: Instant::now(),
percent: 0, percent: 0,
max_width: max_width + NOTIFY_BORDER as usize,
} }
} }
} }

View file

@ -1,8 +1,8 @@
use std::ops::{Deref, Range}; 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 { pub(super) struct Files {
window: Range<usize>, window: Range<usize>,
@ -28,6 +28,8 @@ impl Files {
pub(super) fn register(lua: &Lua) -> mlua::Result<()> { pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|reg| { lua.register_userdata_type::<Self>(|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::Len, |_, me, ()| Ok(me.window.end - me.window.start));
reg.add_meta_method(MetaMethod::Index, |_, me, mut idx: usize| { reg.add_meta_method(MetaMethod::Index, |_, me, mut idx: usize| {

View file

@ -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<AnyUserData<'static>> {
SCOPE.create_any_userdata(Self { inner })
}
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|_| {})?;
Ok(())
}
}

View file

@ -18,6 +18,7 @@ impl Lives {
super::Config::register(&LUA)?; super::Config::register(&LUA)?;
super::File::register(&LUA)?; super::File::register(&LUA)?;
super::Files::register(&LUA)?; super::Files::register(&LUA)?;
super::Filter::register(&LUA)?;
super::Folder::register(&LUA)?; super::Folder::register(&LUA)?;
super::Mode::register(&LUA)?; super::Mode::register(&LUA)?;
super::Preview::register(&LUA)?; super::Preview::register(&LUA)?;

View file

@ -3,6 +3,7 @@
mod config; mod config;
mod file; mod file;
mod files; mod files;
mod filter;
mod folder; mod folder;
mod iter; mod iter;
mod lives; mod lives;
@ -17,6 +18,7 @@ mod yanked;
use config::*; use config::*;
use file::*; use file::*;
use files::*; use files::*;
use filter::*;
use folder::*; use folder::*;
use iter::*; use iter::*;
pub(super) use lives::*; pub(super) use lives::*;

View file

@ -1,5 +1,3 @@
use std::rc::Rc;
use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}}; use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}};
use yazi_config::THEME; use yazi_config::THEME;
use yazi_core::notify::Message; 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 new(cx: &'a Ctx) -> Self { Self { cx } }
pub(crate) fn available(area: Rect) -> Rect { pub(crate) fn available(area: Rect) -> Rect {
let chunks = let chunks = layout::Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area);
layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(80), Constraint::Max(1)])
.split(area);
let chunks = let chunks =
layout::Layout::vertical([Constraint::Max(1), Constraint::Fill(1)]).split(chunks[1]); layout::Layout::vertical([Constraint::Max(1), Constraint::Fill(1)]).split(chunks[1]);
@ -25,12 +21,22 @@ impl<'a> Layout<'a> {
chunks[1] chunks[1]
} }
fn tile(area: Rect, messages: &[Message]) -> Rc<[Rect]> { fn tile(area: Rect, messages: &[Message]) -> Vec<Rect> {
layout::Layout::vertical( layout::Layout::vertical(
messages.iter().map(|m| Constraint::Length(m.height(area.width) as u16)), messages.iter().map(|m| Constraint::Length(m.height(area.width) as u16)),
) )
.spacing(1) .spacing(1)
.split(area) .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 = let mut rect =
tile[i].offset(Offset { x: (100 - m.percent) as i32 * tile[i].width as i32 / 100, y: 0 }); 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); yazi_plugin::elements::Clear::default().render(rect, buf);
Paragraph::new(m.content.as_str()) Paragraph::new(m.content.as_str())
@ -59,7 +65,7 @@ impl<'a> Widget for Layout<'a> {
.block( .block(
Block::bordered() Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.title(format!("{} {}", icon, m.title)) .title(format!("{icon} {}", m.title))
.title_style(style) .title_style(style)
.border_style(style), .border_style(style),
) )

View file

@ -2,12 +2,27 @@ Current = {
area = ui.Rect.default, 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) function Current:render(area)
self.area = area self.area = area
local files = Folder:by_kind(Folder.CURRENT).window local files = Folder:by_kind(Folder.CURRENT).window
if #files == 0 then if #files == 0 then
return {} return self:empty(area)
end end
local items, markers = {}, {} local items, markers = {}, {}

View file

@ -9,9 +9,7 @@ function M:peek()
p = ui.Paragraph.parse(self.area, "----- File Type Classification -----\n\n" .. output.stdout) p = ui.Paragraph.parse(self.area, "----- File Type Classification -----\n\n" .. output.stdout)
else else
p = ui.Paragraph(self.area, { p = ui.Paragraph(self.area, {
ui.Line { ui.Line(string.format("Spawn `%s` command returns %s", cmd, code)),
ui.Span(string.format("Spawn `%s` command returns %s", cmd, code)),
},
}) })
end end

View file

@ -3,12 +3,19 @@ local M = {}
function M:peek() function M:peek()
local folder = Folder:by_kind(Folder.PREVIEW) local folder = Folder:by_kind(Folder.PREVIEW)
if not folder or folder.cwd ~= self.file.url then if not folder or folder.cwd ~= self.file.url then
return {} return
end end
local bound = math.max(0, #folder.files - self.area.h) local bound = math.max(0, #folder.files - self.area.h)
if self.skip > bound then 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 end
local items, markers = {}, {} local items, markers = {}, {}

View file

@ -12,7 +12,7 @@ const RIGHT: u8 = 2;
#[derive(Clone, FromLua)] #[derive(Clone, FromLua)]
pub struct Line(pub(super) ratatui::text::Line<'static>); pub struct Line(pub(super) ratatui::text::Line<'static>);
impl<'a> TryFrom<Table<'a>> for Line { impl TryFrom<Table<'_>> for Line {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(tb: Table) -> Result<Self, Self::Error> { fn try_from(tb: Table) -> Result<Self, Self::Error> {
@ -33,13 +33,20 @@ impl<'a> TryFrom<Table<'a>> for Line {
} }
} }
impl TryFrom<mlua::String<'_>> for Line {
type Error = mlua::Error;
fn try_from(s: mlua::String) -> Result<Self, Self::Error> {
Ok(Self(ratatui::text::Line::from(s.to_string_lossy().into_owned())))
}
}
impl Line { impl Line {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> { pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let new = lua.create_function(|_, (_, value): (Table, Value)| { let new = lua.create_function(|_, (_, value): (Table, Value)| match value {
if let Value::Table(tb) = value { Value::Table(tb) => Line::try_from(tb),
return 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()),
Err("expected a table of Spans or Lines".into_lua_err())
})?; })?;
let parse = lua.create_function(|_, code: mlua::String| { let parse = lua.create_function(|_, code: mlua::String| {

View file

@ -110,7 +110,9 @@ impl Term {
} }
#[inline] #[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 { pub fn size() -> WindowSize {
let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 }; let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };