This commit is contained in:
sxyazi 2023-10-10 17:55:04 +08:00
parent 71c86a50c9
commit e2dfa9813a
No known key found for this signature in database
26 changed files with 390 additions and 228 deletions

1
Cargo.lock generated
View file

@ -1364,6 +1364,7 @@ dependencies = [
"ratatui",
"shared",
"tracing",
"unicode-width",
]
[[package]]

View file

@ -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);
}
}
}

View file

@ -1,5 +1,3 @@
mod layout;
mod tabs;
pub(super) use layout::*;
use tabs::*;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -1,4 +1,4 @@
[tab]
[tabs]
active = { fg = "#1E2031", bg = "#80AEFA" }
inactive = { fg = "#C8D3F8", bg = "#484D66" }
max_width = 1

View file

@ -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);

View file

@ -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 {

View file

@ -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());

View file

@ -11,5 +11,6 @@ shared = { path = "../shared" }
# External dependencies
anyhow = "^1"
mlua = { version = "^0", features = [ "luajit52", "vendored", "serialize" ] }
tracing = "^0"
ratatui = "^0"
tracing = "^0"
unicode-width = "^0"

View file

@ -23,13 +23,24 @@ function Folder:markers(area, markers)
local elements = {}
local append = function(last)
local rect = ui.Rect {
local p = ui.Paragraph(
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)
},
{}
)
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

View 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

View file

@ -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)),
}

View file

@ -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

View file

@ -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| {

View file

@ -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(())

View 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(())
}
}

View file

@ -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::*;

View file

@ -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,

View 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)
}
}

View file

@ -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)
}
}

View file

@ -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;

View file

@ -6,6 +6,7 @@ mod config;
mod layout;
mod plugin;
mod scope;
mod utils;
pub use components::*;
use config::*;

View file

@ -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

View file

@ -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
View 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(())
}