mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
71c86a50c9
commit
e2dfa9813a
26 changed files with 390 additions and 228 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1364,6 +1364,7 @@ dependencies = [
|
|||
"ratatui",
|
||||
"shared",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,34 +1,13 @@
|
|||
use core::Ctx;
|
||||
use ratatui::{prelude::{Buffer, Rect}, widgets::Widget};
|
||||
use tracing::info;
|
||||
|
||||
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, style::{Color, Style}, widgets::{Paragraph, Widget}};
|
||||
use shared::readable_path;
|
||||
pub(crate) struct Layout;
|
||||
|
||||
use super::Tabs;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl<'a> Widget for Layout<'a> {
|
||||
impl Widget for Layout {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let chunks = layout::Layout::new()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.split(area);
|
||||
|
||||
let cwd = &self.cx.manager.current().cwd;
|
||||
let location = if cwd.is_search() {
|
||||
format!("{} (search: {})", readable_path(cwd), cwd.frag().unwrap())
|
||||
} else {
|
||||
readable_path(cwd)
|
||||
};
|
||||
|
||||
Paragraph::new(location).style(Style::new().fg(Color::Cyan)).render(chunks[0], buf);
|
||||
|
||||
Tabs::new(self.cx).render(chunks[1], buf);
|
||||
let x = plugin::Header.render(area, buf);
|
||||
if x.is_err() {
|
||||
info!("{:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
mod layout;
|
||||
mod tabs;
|
||||
|
||||
pub(super) use layout::*;
|
||||
use tabs::*;
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
use core::Ctx;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use config::THEME;
|
||||
use ratatui::{buffer::Buffer, layout::{Alignment, Rect}, text::{Line, Span}, widgets::{Paragraph, Widget}};
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
pub(super) struct Tabs<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Tabs<'a> {
|
||||
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
|
||||
fn truncate(&self, name: &str) -> String {
|
||||
let mut width = 0;
|
||||
let flow =
|
||||
name.chars().try_fold(String::with_capacity(THEME.tab.max_width as usize), |mut s, c| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if s.width() < THEME.tab.max_width as usize {
|
||||
s.push(c);
|
||||
ControlFlow::Continue(s)
|
||||
} else {
|
||||
ControlFlow::Break(s)
|
||||
}
|
||||
});
|
||||
|
||||
match flow {
|
||||
ControlFlow::Break(s) => s,
|
||||
ControlFlow::Continue(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Tabs<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let tabs = self.cx.manager.tabs();
|
||||
|
||||
let line = Line::from(
|
||||
tabs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, tab)| {
|
||||
let mut text = format!("{}", i + 1);
|
||||
if THEME.tab.max_width >= 3 {
|
||||
text.push(' ');
|
||||
text.push_str(tab.name());
|
||||
text = self.truncate(&text);
|
||||
}
|
||||
|
||||
if i == tabs.idx() {
|
||||
Span::styled(format!(" {text} "), THEME.tab.active.into())
|
||||
} else {
|
||||
Span::styled(format!(" {text} "), THEME.tab.inactive.into())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
Paragraph::new(line).alignment(Alignment::Right).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ impl<'a> Widget for Root<'a> {
|
|||
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)])
|
||||
.split(area);
|
||||
|
||||
header::Layout::new(self.cx).render(chunks[0], buf);
|
||||
header::Layout.render(chunks[0], buf);
|
||||
manager::Layout::new(self.cx).render(chunks[1], buf);
|
||||
status::Layout.render(chunks[2], buf);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[tab]
|
||||
[tabs]
|
||||
active = { fg = "#1E2031", bg = "#80AEFA" }
|
||||
inactive = { fg = "#C8D3F8", bg = "#484D66" }
|
||||
max_width = 1
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use super::{Files, Filetype, Icon, Marker, Status, Style};
|
|||
use crate::{validation::check_validation, MERGED_THEME};
|
||||
|
||||
#[derive(Deserialize, Serialize, Validate)]
|
||||
pub struct Tab {
|
||||
pub struct Tabs {
|
||||
pub active: Style,
|
||||
pub inactive: Style,
|
||||
#[validate(range(min = 1, message = "Must be greater than 0"))]
|
||||
|
|
@ -23,7 +23,7 @@ pub struct Preview {
|
|||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Theme {
|
||||
pub tab: Tab,
|
||||
pub tabs: Tabs,
|
||||
pub status: Status,
|
||||
pub files: Files,
|
||||
pub marker: Marker,
|
||||
|
|
@ -38,7 +38,7 @@ impl Default for Theme {
|
|||
fn default() -> Self {
|
||||
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
|
||||
|
||||
check_validation(theme.tab.validate());
|
||||
check_validation(theme.tabs.validate());
|
||||
|
||||
theme.preview.syntect_theme = expand_path(&theme.preview.syntect_theme);
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ impl Manager {
|
|||
pub fn yank(&mut self, cut: bool) -> bool {
|
||||
self.yanked.0 = cut;
|
||||
self.yanked.1 = self.selected().into_iter().map(|f| f.url_owned()).collect();
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
pub fn quit(&self, tasks: &Tasks, no_cwd_file: bool) -> bool {
|
||||
|
|
|
|||
|
|
@ -458,18 +458,6 @@ impl Tab {
|
|||
pub fn in_selecting(&self) -> bool { self.mode.is_visual() || self.current.files.has_selected() }
|
||||
|
||||
// --- Current
|
||||
// TODO: remove this
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
self
|
||||
.current
|
||||
.cwd
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.or_else(|| self.current.cwd.to_str())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Vec<&File> {
|
||||
let pending = self.mode.visual().map(|(_, p)| Cow::Borrowed(p)).unwrap_or_default();
|
||||
let selected = self.current.files.selected(&pending, self.mode.is_unset());
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ core = { path = "../core" }
|
|||
shared = { path = "../shared" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
mlua = { version = "^0", features = [ "luajit52", "vendored", "serialize" ] }
|
||||
tracing = "^0"
|
||||
ratatui = "^0"
|
||||
anyhow = "^1"
|
||||
mlua = { version = "^0", features = [ "luajit52", "vendored", "serialize" ] }
|
||||
ratatui = "^0"
|
||||
tracing = "^0"
|
||||
unicode-width = "^0"
|
||||
|
|
|
|||
|
|
@ -23,13 +23,24 @@ function Folder:markers(area, markers)
|
|||
|
||||
local elements = {}
|
||||
local append = function(last)
|
||||
local rect = ui.Rect {
|
||||
x = area.x - 1,
|
||||
y = area.y + last[1] - 1,
|
||||
w = 1,
|
||||
h = 1 + last[2] - last[1],
|
||||
}
|
||||
elements[#elements + 1] = ui.Paragraph(rect, {}):style(THEME.marker.selected)
|
||||
local p = ui.Paragraph(
|
||||
ui.Rect {
|
||||
x = area.x - 1,
|
||||
y = area.y + last[1] - 1,
|
||||
w = 1,
|
||||
h = 1 + last[2] - last[1],
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
if last[3] == 1 then
|
||||
p = p:style(THEME.marker.copied)
|
||||
elseif last[3] == 2 then
|
||||
p = p:style(THEME.marker.cut)
|
||||
elseif last[3] == 3 then
|
||||
p = p:style(THEME.marker.selected)
|
||||
end
|
||||
elements[#elements + 1] = p
|
||||
end
|
||||
|
||||
local last = { markers[1][1], markers[1][1], markers[1][2] } -- start, end, type
|
||||
|
|
@ -89,9 +100,12 @@ function Folder:current(area)
|
|||
end
|
||||
items[#items + 1] = item
|
||||
|
||||
-- Mark selected/yanked files
|
||||
if f:selected() then
|
||||
markers[#markers + 1] = { i, 1 }
|
||||
-- Mark yanked/selected files
|
||||
local yanked = f:yanked()
|
||||
if yanked ~= 0 then
|
||||
markers[#markers + 1] = { i, yanked }
|
||||
elseif f:selected() then
|
||||
markers[#markers + 1] = { i, 3 }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
43
plugin/preset/components/header.lua
Normal file
43
plugin/preset/components/header.lua
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
Header = {}
|
||||
|
||||
function Header:cwd()
|
||||
local cwd = cx.active.current.cwd
|
||||
|
||||
local span
|
||||
if not cwd.is_search then
|
||||
span = ui.Span(utils.readable_path(tostring(cwd)))
|
||||
else
|
||||
span = ui.Span(string.format("%s (search: %s)", utils.readable_path(tostring(cwd)), cwd.frag))
|
||||
end
|
||||
return span:fg("cyan")
|
||||
end
|
||||
|
||||
function Header:tabs()
|
||||
local spans = {}
|
||||
for i = 1, #cx.tabs do
|
||||
local text = i
|
||||
if THEME.tabs.max_width > 2 then
|
||||
text = utils.truncate(text .. " " .. cx.tabs[i]:name(), THEME.tabs.max_width)
|
||||
end
|
||||
if i == cx.tabs.idx + 1 then
|
||||
spans[#spans + 1] = ui.Span(" " .. text .. " "):style(THEME.tabs.active)
|
||||
else
|
||||
spans[#spans + 1] = ui.Span(" " .. text .. " "):style(THEME.tabs.inactive)
|
||||
end
|
||||
end
|
||||
return ui.Line(spans)
|
||||
end
|
||||
|
||||
function Header:render(area)
|
||||
local chunks = ui.Layout()
|
||||
:direction(ui.Direction.HORIZONTAL)
|
||||
:constraints({ ui.Constraint.Percentage(50), ui.Constraint.Percentage(50) })
|
||||
:split(area)
|
||||
|
||||
local left = ui.Line { self:cwd() }
|
||||
local right = ui.Line { self:tabs() }
|
||||
return {
|
||||
ui.Paragraph(chunks[1], { left }),
|
||||
ui.Paragraph(chunks[2], { right }):align(ui.Alignment.RIGHT),
|
||||
}
|
||||
end
|
||||
|
|
@ -113,14 +113,19 @@ function Status:progress(area, offset)
|
|||
h = 1,
|
||||
})
|
||||
|
||||
local percent = 0
|
||||
if progress.processed ~= 0 then
|
||||
percent = math.floor(progress.processed * 100 / progress.found)
|
||||
if progress.fail == 0 then
|
||||
gauge = gauge:gauge_style(THEME.status.progress_normal)
|
||||
else
|
||||
gauge = gauge:gauge_style(THEME.status.progress_error)
|
||||
end
|
||||
|
||||
local percent = 99
|
||||
if progress.found ~= 0 then
|
||||
percent = math.min(99, progress.processed * 100 / progress.found)
|
||||
end
|
||||
|
||||
return {
|
||||
gauge
|
||||
:gauge_style(THEME.status.progress_normal)
|
||||
:percent(percent)
|
||||
:label(ui.Span(string.format("%3d%%, %d left", percent, left)):style(THEME.status.progress_label)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,3 +11,14 @@ function utils.readable_size(size)
|
|||
end
|
||||
return string.format("%.1f %s", size, units[i])
|
||||
end
|
||||
|
||||
function utils.readable_path(path)
|
||||
local home = os.getenv("HOME")
|
||||
if home == nil then
|
||||
return path
|
||||
elseif string.sub(path, 1, #home) == home then
|
||||
return "~" .. string.sub(path, #home + 1)
|
||||
else
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,43 +1,20 @@
|
|||
use core::Ctx;
|
||||
|
||||
use config::{MANAGER, THEME};
|
||||
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use mlua::{AnyUserData, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
use super::{Range, Url};
|
||||
use crate::{layout::Style, LUA};
|
||||
|
||||
struct File(core::files::File);
|
||||
|
||||
impl From<&core::files::File> for File {
|
||||
fn from(value: &core::files::File) -> Self { Self(value.clone()) }
|
||||
}
|
||||
|
||||
impl UserData for File {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("url", |_, me| Ok(Url::from(me.0.url())));
|
||||
fields.add_field_method_get("length", |_, me| Ok(me.0.length()));
|
||||
fields.add_field_method_get("link_to", |_, me| Ok(me.0.link_to().map(Url::from)));
|
||||
fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link()));
|
||||
fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden()));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tab<'a, 'b> {
|
||||
pub struct Active<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
cx: &'a core::Ctx,
|
||||
inner: &'a core::manager::Tab,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Tab<'a, 'b> {
|
||||
impl<'a, 'b> Active<'a, 'b> {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<core::manager::Tab>(|reg| {
|
||||
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
||||
reg.add_field_function_get("parent", |_, me| me.named_user_value::<Value>("parent"));
|
||||
reg.add_field_function_get("current", |_, me| me.named_user_value::<AnyUserData>("current"));
|
||||
reg.add_field_function_get("preview", |_, me| me.named_user_value::<AnyUserData>("preview"));
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::manager::Mode>(|reg| {
|
||||
reg.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
|
||||
reg.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
|
||||
|
|
@ -57,58 +34,6 @@ impl<'a, 'b> Tab<'a, 'b> {
|
|||
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<Value>("hovered"));
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::files::Files>(|reg| {
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
|
||||
reg.add_meta_function(MetaMethod::Pairs, |lua, me: AnyUserData| {
|
||||
let iter = lua.create_function(|lua, (me, i): (AnyUserData, usize)| {
|
||||
let files = me.borrow::<core::files::Files>()?;
|
||||
let i = i + 1;
|
||||
Ok(if i > files.len() {
|
||||
mlua::Variadic::new()
|
||||
} else {
|
||||
mlua::Variadic::from_iter([i.into_lua(lua)?, File::from(&files[i - 1]).into_lua(lua)?])
|
||||
})
|
||||
})?;
|
||||
Ok((iter, me, 0))
|
||||
});
|
||||
|
||||
reg.add_function("slice", |_, (me, skip, take): (AnyUserData, usize, usize)| {
|
||||
let files = me.borrow::<core::files::Files>()?;
|
||||
Ok(files.iter().skip(skip).take(take).map(File::from).collect::<Vec<_>>())
|
||||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::files::File>(|reg| {
|
||||
reg.add_field_method_get("name", |_, me| {
|
||||
Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
});
|
||||
reg.add_function("icon", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("icon")?.call::<_, String>(())
|
||||
});
|
||||
reg.add_function("style", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("style")?.call::<_, Style>(())
|
||||
});
|
||||
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<bool>("hovered"));
|
||||
reg.add_function("selected", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("selected")?.call::<_, bool>(me)
|
||||
});
|
||||
reg.add_function("highlights", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("highlights")?.call::<_, Value>(())
|
||||
});
|
||||
|
||||
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url())));
|
||||
reg.add_field_method_get("length", |_, me| Ok(me.length()));
|
||||
reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
|
||||
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
|
||||
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
|
||||
|
||||
// Meta
|
||||
reg.add_field_method_get("permissions", |_, me| {
|
||||
Ok(shared::permissions(me.meta().permissions()))
|
||||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::manager::Preview>(|reg| {
|
||||
reg.add_field_function_get("folder", |_, me| me.named_user_value::<Value>("folder"));
|
||||
})?;
|
||||
|
|
@ -116,13 +41,8 @@ impl<'a, 'b> Tab<'a, 'b> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
cx: &'a Ctx,
|
||||
inner: &'a core::manager::Tab,
|
||||
) -> Self {
|
||||
Self { scope, cx, inner }
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, cx: &'a Ctx) -> Self {
|
||||
Self { scope, cx, inner: cx.manager.active() }
|
||||
}
|
||||
|
||||
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
||||
|
|
@ -212,6 +132,20 @@ impl<'a, 'b> Tab<'a, 'b> {
|
|||
matches!(&folder.hovered, Some(f) if f.url() == inner.url()),
|
||||
)?;
|
||||
|
||||
ud.set_named_user_value(
|
||||
"yanked",
|
||||
self.scope.create_function(|_, ()| {
|
||||
let (cut, urls) = self.cx.manager.yanked();
|
||||
Ok(if !urls.contains(inner.url()) {
|
||||
0u8
|
||||
} else if *cut {
|
||||
2u8
|
||||
} else {
|
||||
1u8
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
|
||||
ud.set_named_user_value(
|
||||
"selected",
|
||||
self.scope.create_function(|_, me: AnyUserData| {
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
pub fn init() -> mlua::Result<()> {
|
||||
super::tab::Tab::init()?;
|
||||
super::active::Active::init()?;
|
||||
super::files::Files::init()?;
|
||||
super::tabs::Tabs::init()?;
|
||||
super::tasks::Tasks::init()?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
83
plugin/src/bindings/files.rs
Normal file
83
plugin/src/bindings/files.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
use super::Url;
|
||||
use crate::{layout::Style, LUA};
|
||||
|
||||
pub struct File(core::files::File);
|
||||
|
||||
impl From<&core::files::File> for File {
|
||||
fn from(value: &core::files::File) -> Self { Self(value.clone()) }
|
||||
}
|
||||
|
||||
impl UserData for File {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("url", |_, me| Ok(Url::from(me.0.url())));
|
||||
fields.add_field_method_get("length", |_, me| Ok(me.0.length()));
|
||||
fields.add_field_method_get("link_to", |_, me| Ok(me.0.link_to().map(Url::from)));
|
||||
fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link()));
|
||||
fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden()));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Files;
|
||||
|
||||
impl Files {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<core::files::Files>(|reg| {
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
|
||||
reg.add_meta_function(MetaMethod::Pairs, |lua, me: AnyUserData| {
|
||||
let iter = lua.create_function(|lua, (me, i): (AnyUserData, usize)| {
|
||||
let files = me.borrow::<core::files::Files>()?;
|
||||
let i = i + 1;
|
||||
Ok(if i > files.len() {
|
||||
mlua::Variadic::new()
|
||||
} else {
|
||||
mlua::Variadic::from_iter([i.into_lua(lua)?, File::from(&files[i - 1]).into_lua(lua)?])
|
||||
})
|
||||
})?;
|
||||
Ok((iter, me, 0))
|
||||
});
|
||||
|
||||
reg.add_function("slice", |_, (me, skip, take): (AnyUserData, usize, usize)| {
|
||||
let files = me.borrow::<core::files::Files>()?;
|
||||
Ok(files.iter().skip(skip).take(take).map(File::from).collect::<Vec<_>>())
|
||||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::files::File>(|reg| {
|
||||
reg.add_field_method_get("name", |_, me| {
|
||||
Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
});
|
||||
reg.add_function("icon", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("icon")?.call::<_, String>(())
|
||||
});
|
||||
reg.add_function("style", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("style")?.call::<_, Style>(())
|
||||
});
|
||||
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<bool>("hovered"));
|
||||
reg.add_function("yanked", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("yanked")?.call::<_, u8>(me)
|
||||
});
|
||||
reg.add_function("selected", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("selected")?.call::<_, bool>(me)
|
||||
});
|
||||
reg.add_function("highlights", |_, me: AnyUserData| {
|
||||
me.named_user_value::<Function>("highlights")?.call::<_, Value>(())
|
||||
});
|
||||
|
||||
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url())));
|
||||
reg.add_field_method_get("length", |_, me| Ok(me.length()));
|
||||
reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
|
||||
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
|
||||
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
|
||||
|
||||
// Meta
|
||||
reg.add_field_method_get("permissions", |_, me| {
|
||||
Ok(shared::permissions(me.meta().permissions()))
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod active;
|
||||
mod bindings;
|
||||
mod files;
|
||||
mod shared;
|
||||
mod tab;
|
||||
mod tabs;
|
||||
mod tasks;
|
||||
|
||||
pub use active::*;
|
||||
pub use bindings::*;
|
||||
pub use files::*;
|
||||
pub use shared::*;
|
||||
pub use tab::*;
|
||||
pub use tabs::*;
|
||||
pub use tasks::*;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ impl From<&shared::Url> for Url {
|
|||
}
|
||||
|
||||
impl UserData for Url {
|
||||
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("frag", |_, me| Ok(me.0.frag().map(ToOwned::to_owned)));
|
||||
fields.add_field_method_get("is_regular", |_, me| Ok(me.0.is_regular()));
|
||||
fields.add_field_method_get("is_search", |_, me| Ok(me.0.is_search()));
|
||||
fields.add_field_method_get("is_archive", |_, me| Ok(me.0.is_archive()));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_function(
|
||||
MetaMethod::Eq,
|
||||
|
|
|
|||
96
plugin/src/bindings/tabs.rs
Normal file
96
plugin/src/bindings/tabs.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use mlua::{AnyUserData, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
use crate::LUA;
|
||||
|
||||
pub struct Tabs<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
inner: &'a core::manager::Tabs,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Tabs<'a, 'b> {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<core::manager::Tabs>(|reg| {
|
||||
reg.add_field_method_get("idx", |_, me| Ok(me.idx()));
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
reg.add_meta_function(MetaMethod::Index, |_, (me, index): (AnyUserData, usize)| {
|
||||
let items = me.named_user_value::<Vec<AnyUserData>>("items")?;
|
||||
Ok(items.get(index - 1).cloned())
|
||||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::manager::Tab>(|reg| {
|
||||
reg.add_method("name", |_, me, ()| {
|
||||
Ok(
|
||||
me.current
|
||||
.cwd
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy())
|
||||
.or_else(|| Some(me.current.cwd.to_string_lossy()))
|
||||
.unwrap_or_default()
|
||||
.into_owned(),
|
||||
)
|
||||
});
|
||||
|
||||
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
||||
reg.add_field_function_get("parent", |_, me| me.named_user_value::<Value>("parent"));
|
||||
reg.add_field_function_get("current", |_, me| me.named_user_value::<AnyUserData>("current"));
|
||||
reg.add_field_function_get("preview", |_, me| me.named_user_value::<AnyUserData>("preview"));
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a core::manager::Tabs) -> Self {
|
||||
Self { scope, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(self.inner)?;
|
||||
|
||||
ud.set_named_user_value(
|
||||
"items",
|
||||
self.inner.iter().filter_map(|t| self.tab(t).ok()).collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn tab(&self, inner: &'a core::manager::Tab) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
|
||||
ud.set_named_user_value("parent", inner.parent.as_ref().and_then(|p| self.folder(p).ok()))?;
|
||||
ud.set_named_user_value("current", self.folder(&inner.current)?)?;
|
||||
ud.set_named_user_value("preview", self.preview(inner)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
pub(crate) fn folder(&self, inner: &'a core::manager::Folder) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value("files", self.files(&inner.files)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn files(&self, inner: &'a core::files::Files) -> mlua::Result<AnyUserData<'a>> {
|
||||
self.scope.create_any_userdata_ref(inner)
|
||||
}
|
||||
|
||||
fn preview(&self, tab: &'a core::manager::Tab) -> mlua::Result<AnyUserData<'a>> {
|
||||
let inner = tab.preview();
|
||||
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value(
|
||||
"folder",
|
||||
inner
|
||||
.lock
|
||||
.as_ref()
|
||||
.filter(|l| l.is_folder())
|
||||
.and_then(|l| tab.history(&l.url))
|
||||
.and_then(|f| self.folder(f).ok()),
|
||||
)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,13 @@ use mlua::{AnyUserData, LuaSerdeExt, UserDataFields};
|
|||
|
||||
use crate::LUA;
|
||||
|
||||
pub struct Tasks;
|
||||
pub struct Tasks<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
impl Tasks {
|
||||
inner: &'a core::tasks::Tasks,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Tasks<'a, 'b> {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<core::tasks::Tasks>(|reg| {
|
||||
reg.add_field_method_get("progress", |lua, me| lua.to_value(&me.progress))
|
||||
|
|
@ -13,10 +17,11 @@ impl Tasks {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn make<'a>(
|
||||
scope: &mlua::Scope<'a, 'a>,
|
||||
inner: &'a core::tasks::Tasks,
|
||||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
scope.create_any_userdata_ref(inner)
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a core::tasks::Tasks) -> Self {
|
||||
Self { scope, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
||||
self.scope.create_any_userdata_ref(self.inner)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,22 @@ fn layout(values: Vec<AnyUserData>, buf: &mut ratatui::prelude::Buffer) -> mlua:
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// --- Status
|
||||
pub struct Header;
|
||||
|
||||
impl Header {
|
||||
pub fn render(
|
||||
self,
|
||||
area: ratatui::layout::Rect,
|
||||
buf: &mut ratatui::prelude::Buffer,
|
||||
) -> mlua::Result<()> {
|
||||
let comp: Table = GLOBALS.get("Header")?;
|
||||
let values: Vec<AnyUserData> = comp.call_method::<_, _>("render", Rect(area))?;
|
||||
|
||||
layout(values, buf)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Status
|
||||
pub struct Status;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ mod config;
|
|||
mod layout;
|
||||
mod plugin;
|
||||
mod scope;
|
||||
mod utils;
|
||||
|
||||
pub use components::*;
|
||||
use config::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use mlua::{Lua, Table};
|
||||
use shared::RoCell;
|
||||
|
||||
use crate::{bindings, layout};
|
||||
use crate::{bindings, layout, utils};
|
||||
|
||||
pub(crate) static LUA: RoCell<Lua> = RoCell::new();
|
||||
pub(crate) static GLOBALS: RoCell<Table> = RoCell::new();
|
||||
|
|
@ -17,12 +17,14 @@ pub fn init() {
|
|||
lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?;
|
||||
|
||||
// Components
|
||||
lua.load(include_str!("../preset/components/status.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/folder.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/header.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/status.lua")).exec()?;
|
||||
|
||||
// Initialize
|
||||
LUA.init(lua);
|
||||
GLOBALS.init(LUA.globals());
|
||||
utils::init()?;
|
||||
bindings::init()?;
|
||||
|
||||
// Install
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ use crate::{bindings, GLOBALS, LUA};
|
|||
pub fn scope<'a>(cx: &'a Ctx, f: impl FnOnce(&Scope<'a, 'a>)) {
|
||||
let _ = LUA.scope(|scope| {
|
||||
let tbl = LUA.create_table()?;
|
||||
tbl.set("active", bindings::Tab::new(scope, cx, cx.manager.active()).make()?)?;
|
||||
tbl.set("tasks", bindings::Tasks::make(scope, &cx.tasks)?)?;
|
||||
tbl.set("active", bindings::Active::new(scope, cx).make()?)?;
|
||||
tbl.set("tabs", bindings::Tabs::new(scope, cx.manager.tabs()).make()?)?;
|
||||
tbl.set("tasks", bindings::Tasks::new(scope, &cx.tasks).make()?)?;
|
||||
GLOBALS.set("cx", tbl)?;
|
||||
|
||||
Ok(f(scope))
|
||||
|
|
|
|||
33
plugin/src/utils.rs
Normal file
33
plugin/src/utils.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use mlua::Table;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
pub fn init() -> mlua::Result<()> {
|
||||
let utils: Table = GLOBALS.get("utils")?;
|
||||
|
||||
utils.set(
|
||||
"truncate",
|
||||
LUA.create_function(|_, (text, max): (String, usize)| {
|
||||
let mut width = 0;
|
||||
let flow = text.chars().try_fold(String::with_capacity(max), |mut s, c| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if s.width() < max {
|
||||
s.push(c);
|
||||
ControlFlow::Continue(s)
|
||||
} else {
|
||||
ControlFlow::Break(s)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(match flow {
|
||||
ControlFlow::Break(s) => s,
|
||||
ControlFlow::Continue(s) => s,
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue