diff --git a/Cargo.lock b/Cargo.lock index c0f7bf4fb..92d7620f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1357,6 +1357,7 @@ dependencies = [ name = "plugin" version = "0.1.5" dependencies = [ + "ansi-to-tui", "anyhow", "config", "core", diff --git a/app/src/main.rs b/app/src/main.rs index d9b0e68c0..1b712467a 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -5,7 +5,6 @@ mod executor; mod help; mod input; mod logs; -mod manager; mod root; mod select; mod signals; diff --git a/app/src/manager/folder.rs b/app/src/manager/folder.rs deleted file mode 100644 index 58b984644..000000000 --- a/app/src/manager/folder.rs +++ /dev/null @@ -1,17 +0,0 @@ -use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; -use tracing::error; - -pub(super) enum Folder { - Parent = 0, - Current = 1, - Preview = 2, -} - -impl Widget for Folder { - fn render(self, area: Rect, buf: &mut Buffer) { - let folder = plugin::Folder { kind: self as u8 }; - if let Err(e) = folder.render(area, buf) { - error!("{:?}", e); - } - } -} diff --git a/app/src/manager/layout.rs b/app/src/manager/layout.rs deleted file mode 100644 index 0c470410e..000000000 --- a/app/src/manager/layout.rs +++ /dev/null @@ -1,51 +0,0 @@ -use core::Ctx; - -use config::{MANAGER, THEME}; -use plugin::layout::Bar; -use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}}; - -use super::{Folder, Preview}; - -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> { - fn render(self, area: Rect, buf: &mut Buffer) { - let layout = &MANAGER.layout; - let manager = &self.cx.manager; - - let chunks = layout::Layout::new() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Ratio(layout.parent, layout.all), - Constraint::Ratio(layout.current, layout.all), - Constraint::Ratio(layout.preview, layout.all), - ]) - .split(area); - - // Parent - Bar::new(chunks[0], Borders::RIGHT) - .symbol(&THEME.manager.border_symbol) - .style(THEME.manager.border_style.into()) - .render(buf); - if manager.parent().is_some() { - Folder::Parent.render(Block::new().padding(Padding::new(1, 1, 0, 0)).inner(chunks[0]), buf); - } - - // Current - Folder::Current.render(chunks[1], buf); - - // Preview - Bar::new(chunks[2], Borders::LEFT) - .symbol(&THEME.manager.border_symbol) - .style(THEME.manager.border_style.into()) - .render(buf); - Preview::new(self.cx) - .render(Block::new().padding(Padding::new(1, 1, 0, 0)).inner(chunks[2]), buf); - } -} diff --git a/app/src/manager/mod.rs b/app/src/manager/mod.rs deleted file mode 100644 index 8e0774686..000000000 --- a/app/src/manager/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod folder; -mod layout; -mod preview; - -use folder::*; -pub(super) use layout::*; -use preview::*; diff --git a/app/src/root.rs b/app/src/root.rs index 6d95303f2..7d0bf79ab 100644 --- a/app/src/root.rs +++ b/app/src/root.rs @@ -1,9 +1,9 @@ use core::Ctx; +use plugin::components; use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget}; -use tracing::error; -use super::{input, manager, select, tasks, which}; +use super::{input, select, tasks, which}; use crate::help; pub(super) struct Root<'a> { @@ -21,13 +21,9 @@ impl<'a> Widget for Root<'a> { .constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)]) .split(area); - if let Err(e) = plugin::Header.render(chunks[0], buf) { - error!("{:?}", e); - } - manager::Layout::new(self.cx).render(chunks[1], buf); - if let Err(e) = plugin::Status.render(chunks[2], buf) { - error!("{:?}", e); - } + components::Header::new(self.cx).render(chunks[0], buf); + components::Manager::new(self.cx).render(chunks[1], buf); + components::Status::new(self.cx).render(chunks[2], buf); if self.cx.tasks.visible { tasks::Layout::new(self.cx).render(area, buf); diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml index c475fa603..fc262a7ad 100644 --- a/plugin/Cargo.toml +++ b/plugin/Cargo.toml @@ -9,6 +9,7 @@ core = { path = "../core" } shared = { path = "../shared" } # External dependencies +ansi-to-tui = "^3" anyhow = "^1" mlua = { version = "^0", features = [ "luajit52", "vendored", "serialize" ] } ratatui = "^0" diff --git a/plugin/preset/components/manager.lua b/plugin/preset/components/manager.lua new file mode 100644 index 000000000..d2dc327cf --- /dev/null +++ b/plugin/preset/components/manager.lua @@ -0,0 +1,25 @@ +Manager = {} + +function Manager:render(area) + local chunks = ui.Layout() + :direction(ui.Direction.HORIZONTAL) + :constraints({ + ui.Constraint.Ratio(MANAGER.layout.parent, MANAGER.layout.all), + ui.Constraint.Ratio(MANAGER.layout.current, MANAGER.layout.all), + ui.Constraint.Ratio(MANAGER.layout.preview, MANAGER.layout.all), + }) + :split(area) + + return { + -- Parent + table.unpack(Folder:render(chunks[1]:padding(ui.Padding.x(1)), { kind = Folder.Kind.Parent })), + -- Current + table.unpack(Folder:render(chunks[2], { kind = Folder.Kind.Current })), + -- Preview + ui.Base(chunks[3]:padding(ui.Padding.x(1)), "Preview"), + + -- Borders + ui.Bar(chunks[1], ui.Position.RIGHT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style), + ui.Bar(chunks[3], ui.Position.LEFT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style), + } +end diff --git a/plugin/preset/ui.lua b/plugin/preset/ui.lua index 13d9c410a..2831a1b42 100644 --- a/plugin/preset/ui.lua +++ b/plugin/preset/ui.lua @@ -15,6 +15,17 @@ ui = { BOTTOM = 3, LEFT = 4, }, + + Padding = setmetatable({ + left = function(left) return ui.Padding.new(left, 0, 0, 0) end, + right = function(right) return ui.Padding.new(0, right, 0, 0) end, + top = function(top) return ui.Padding.new(0, 0, top, 0) end, + bottom = function(bottom) return ui.Padding.new(0, 0, 0, bottom) end, + x = function(x) return ui.Padding.new(x, x, 0, 0) end, + y = function(y) return ui.Padding.new(0, 0, y, y) end, + }, { + __call = function(...) return ui.Padding.new(...) end, + }), } function ui.highlight_ranges(s, ranges) diff --git a/plugin/src/components.rs b/plugin/src/components.rs deleted file mode 100644 index f26eae27c..000000000 --- a/plugin/src/components.rs +++ /dev/null @@ -1,76 +0,0 @@ -use mlua::{AnyUserData, Table, TableExt}; - -use crate::{layout::{Bar, Gauge, List, Paragraph, Rect}, GLOBALS, LUA}; - -#[inline] -fn layout(values: Vec, buf: &mut ratatui::prelude::Buffer) -> mlua::Result<()> { - for value in values { - if let Ok(c) = value.take::() { - c.render(buf) - } else if let Ok(c) = value.take::() { - c.render(buf) - } else if let Ok(c) = value.take::() { - c.render(buf) - } else if let Ok(c) = value.take::() { - c.render(buf) - } - } - 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 = comp.call_method::<_, _>("render", Rect(area))?; - - layout(values, buf) - } -} - -// --- Status -pub struct Status; - -impl Status { - pub fn render( - self, - area: ratatui::layout::Rect, - buf: &mut ratatui::prelude::Buffer, - ) -> mlua::Result<()> { - let comp: Table = GLOBALS.get("Status")?; - let values: Vec = comp.call_method::<_, _>("render", Rect(area))?; - - layout(values, buf) - } -} - -// --- Folder -pub struct Folder { - pub kind: u8, -} - -impl Folder { - fn args(&self) -> mlua::Result { - let tbl = LUA.create_table()?; - tbl.set("kind", self.kind)?; - Ok(tbl) - } - - pub fn render( - self, - area: ratatui::layout::Rect, - buf: &mut ratatui::prelude::Buffer, - ) -> mlua::Result<()> { - let comp: Table = GLOBALS.get("Folder")?; - let values: Vec = - comp.call_method::<_, _>("render", (Rect(area), self.args()?))?; - - layout(values, buf) - } -} diff --git a/plugin/src/components/base.rs b/plugin/src/components/base.rs new file mode 100644 index 000000000..b7e438648 --- /dev/null +++ b/plugin/src/components/base.rs @@ -0,0 +1,43 @@ +use mlua::{FromLua, Lua, Table, UserData, Value}; +use ratatui::widgets::Widget; + +use crate::{layout::Rect, GLOBALS, LUA}; + +#[derive(Clone)] +pub struct Base { + area: ratatui::layout::Rect, + + name: String, +} + +impl Base { + pub(crate) fn install() -> mlua::Result<()> { + let ui: Table = GLOBALS.get("ui")?; + ui.set( + "Base", + LUA.create_function(|_, (area, name): (Rect, String)| Ok(Self { area: area.0, name }))?, + ) + } + + pub fn render(self, cx: &core::Ctx, buf: &mut ratatui::buffer::Buffer) { + match self.name.as_ref() { + "Preview" => super::Preview::new(cx).render(self.area, buf), + _ => {} + } + } +} + +impl<'lua> FromLua<'lua> for Base { + fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result { + match value { + Value::UserData(ud) => Ok(ud.borrow::()?.clone()), + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Base", + message: Some("expected a Base".to_string()), + }), + } + } +} + +impl UserData for Base {} diff --git a/plugin/src/components/components.rs b/plugin/src/components/components.rs new file mode 100644 index 000000000..9009a54ad --- /dev/null +++ b/plugin/src/components/components.rs @@ -0,0 +1,39 @@ +use mlua::{AnyUserData, Table}; +use shared::RoCell; + +use super::Base; +use crate::{layout::{Bar, Gauge, List, Paragraph}, GLOBALS}; + +pub(super) static COMP_FOLDER: RoCell
= RoCell::new(); +pub(super) static COMP_HEADER: RoCell
= RoCell::new(); +pub(super) static COMP_MANAGER: RoCell
= RoCell::new(); +pub(super) static COMP_STATUS: RoCell
= RoCell::new(); + +pub fn init() -> mlua::Result<()> { + COMP_FOLDER.init(GLOBALS.get("Folder")?); + COMP_HEADER.init(GLOBALS.get("Header")?); + COMP_MANAGER.init(GLOBALS.get("Manager")?); + COMP_STATUS.init(GLOBALS.get("Status")?); + Ok(()) +} + +pub(super) fn layout( + values: Vec, + cx: &core::Ctx, + buf: &mut ratatui::prelude::Buffer, +) -> mlua::Result<()> { + for value in values { + if let Ok(c) = value.take::() { + c.render(buf) + } else if let Ok(c) = value.take::() { + c.render(buf) + } else if let Ok(c) = value.take::() { + c.render(buf) + } else if let Ok(c) = value.take::() { + c.render(cx, buf) + } else if let Ok(c) = value.take::() { + c.render(buf) + } + } + Ok(()) +} diff --git a/plugin/src/components/folder.rs b/plugin/src/components/folder.rs new file mode 100644 index 000000000..5b4a3830e --- /dev/null +++ b/plugin/src/components/folder.rs @@ -0,0 +1,35 @@ +use mlua::TableExt; +use ratatui::widgets::Widget; +use tracing::error; + +use super::{layout, COMP_FOLDER}; +use crate::{layout::Rect, LUA}; + +pub struct Folder<'a> { + cx: &'a core::Ctx, + kind: u8, +} + +impl<'a> Folder<'a> { + #[inline] + pub fn parent(cx: &'a core::Ctx) -> Self { Self { cx, kind: 0 } } + + #[inline] + pub fn current(cx: &'a core::Ctx) -> Self { Self { cx, kind: 1 } } + + #[inline] + pub fn preview(cx: &'a core::Ctx) -> Self { Self { cx, kind: 2 } } +} + +impl<'a> Widget for Folder<'a> { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let mut f = || { + let args = LUA.create_table()?; + args.set("kind", self.kind)?; + layout(COMP_FOLDER.call_method::<_, _>("render", (Rect(area), args))?, self.cx, buf) + }; + if let Err(e) = f() { + error!("{:?}", e); + } + } +} diff --git a/plugin/src/components/header.rs b/plugin/src/components/header.rs new file mode 100644 index 000000000..8c1fccc79 --- /dev/null +++ b/plugin/src/components/header.rs @@ -0,0 +1,24 @@ +use mlua::TableExt; +use ratatui::widgets::Widget; +use tracing::error; + +use super::{layout, COMP_HEADER}; +use crate::layout::Rect; + +pub struct Header<'a> { + cx: &'a core::Ctx, +} + +impl<'a> Header<'a> { + #[inline] + pub fn new(cx: &'a core::Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for Header<'a> { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let mut f = || layout(COMP_HEADER.call_method::<_, _>("render", Rect(area))?, self.cx, buf); + if let Err(e) = f() { + error!("{:?}", e); + } + } +} diff --git a/plugin/src/components/manager.rs b/plugin/src/components/manager.rs new file mode 100644 index 000000000..dd333d6a0 --- /dev/null +++ b/plugin/src/components/manager.rs @@ -0,0 +1,23 @@ +use mlua::TableExt; +use ratatui::widgets::Widget; +use tracing::error; + +use super::{layout, COMP_MANAGER}; +use crate::layout::Rect; + +pub struct Manager<'a> { + cx: &'a core::Ctx, +} + +impl<'a> Manager<'a> { + pub fn new(cx: &'a core::Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for Manager<'a> { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let mut f = || layout(COMP_MANAGER.call_method::<_, _>("render", Rect(area))?, self.cx, buf); + if let Err(e) = f() { + error!("{:?}", e); + } + } +} diff --git a/plugin/src/components/mod.rs b/plugin/src/components/mod.rs new file mode 100644 index 000000000..a247286aa --- /dev/null +++ b/plugin/src/components/mod.rs @@ -0,0 +1,17 @@ +#![allow(clippy::module_inception)] + +mod base; +mod components; +mod folder; +mod header; +mod manager; +mod preview; +mod status; + +pub use base::*; +pub use components::*; +pub use folder::*; +pub use header::*; +pub use manager::*; +use preview::*; +pub use status::*; diff --git a/app/src/manager/preview.rs b/plugin/src/components/preview.rs similarity index 94% rename from app/src/manager/preview.rs rename to plugin/src/components/preview.rs index 14265aec9..8176675f5 100644 --- a/app/src/manager/preview.rs +++ b/plugin/src/components/preview.rs @@ -27,7 +27,7 @@ impl<'a> Widget for Preview<'a> { match &preview.lock.as_ref().unwrap().data { PreviewData::Folder => { - Folder::Preview.render(area, buf); + Folder::preview(self.cx).render(area, buf); } PreviewData::Text(s) => { let p = Paragraph::new(s.as_bytes().into_text().unwrap()); diff --git a/plugin/src/components/status.rs b/plugin/src/components/status.rs new file mode 100644 index 000000000..fdb6a41e7 --- /dev/null +++ b/plugin/src/components/status.rs @@ -0,0 +1,24 @@ +use mlua::TableExt; +use ratatui::widgets::Widget; +use tracing::error; + +use super::{layout, COMP_STATUS}; +use crate::layout::Rect; + +pub struct Status<'a> { + cx: &'a core::Ctx, +} + +impl<'a> Status<'a> { + #[inline] + pub fn new(cx: &'a core::Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for Status<'a> { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let mut f = || layout(COMP_STATUS.call_method::<_, _>("render", Rect(area))?, self.cx, buf); + if let Err(e) = f() { + error!("{:?}", e); + } + } +} diff --git a/plugin/src/layout/bar.rs b/plugin/src/layout/bar.rs index 25b62d41c..b7f98ac99 100644 --- a/plugin/src/layout/bar.rs +++ b/plugin/src/layout/bar.rs @@ -3,7 +3,6 @@ use mlua::{AnyUserData, FromLua, Lua, Table, UserData, Value}; use super::{Rect, Style}; use crate::{GLOBALS, LUA}; -// --- Bar #[derive(Clone)] pub struct Bar { area: ratatui::layout::Rect, @@ -36,23 +35,6 @@ impl Bar { ) } - #[inline] - pub fn new(area: ratatui::layout::Rect, position: ratatui::widgets::Borders) -> Self { - Self { area, position, symbol: Default::default(), style: Default::default() } - } - - #[inline] - pub fn symbol(mut self, symbol: &str) -> Self { - self.symbol = symbol.to_owned(); - self - } - - #[inline] - pub fn style(mut self, style: ratatui::style::Style) -> Self { - self.style = Some(style); - self - } - pub fn render(self, buf: &mut ratatui::buffer::Buffer) { use ratatui::widgets::Borders; if self.area.area() == 0 { diff --git a/plugin/src/layout/mod.rs b/plugin/src/layout/mod.rs index cab01de9c..4354bb08d 100644 --- a/plugin/src/layout/mod.rs +++ b/plugin/src/layout/mod.rs @@ -6,17 +6,19 @@ mod gauge; mod layout; mod line; mod list; +mod padding; mod paragraph; mod rect; mod span; mod style; -pub use bar::*; +pub(super) use bar::*; pub(super) use constraint::*; pub(super) use gauge::*; pub(super) use layout::*; pub(super) use line::*; pub(super) use list::*; +pub(super) use padding::*; pub(super) use paragraph::*; pub(super) use rect::*; pub(super) use span::*; diff --git a/plugin/src/layout/padding.rs b/plugin/src/layout/padding.rs new file mode 100644 index 000000000..0cc15b441 --- /dev/null +++ b/plugin/src/layout/padding.rs @@ -0,0 +1,41 @@ +use mlua::{FromLua, Lua, Table, UserData, Value}; + +use crate::{GLOBALS, LUA}; + +#[derive(Clone, Copy)] +pub(crate) struct Padding(pub(crate) ratatui::widgets::Padding); + +impl Padding { + pub(crate) fn install() -> mlua::Result<()> { + let ui: Table = GLOBALS.get("ui")?; + let padding: Table = ui.get("Padding")?; + padding.set( + "new", + LUA.create_function(|_, args: (u16, u16, u16, u16)| { + Ok(Self(ratatui::widgets::Padding::new(args.0, args.1, args.2, args.3))) + })?, + ) + } +} + +impl<'lua> FromLua<'lua> for Padding { + fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result { + match value { + Value::UserData(ud) => Ok(*ud.borrow::()?), + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Padding", + message: Some("expected a Padding".to_string()), + }), + } + } +} + +impl UserData for Padding { + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("left", |_, me| Ok(me.0.left)); + fields.add_field_method_get("right", |_, me| Ok(me.0.right)); + fields.add_field_method_get("top", |_, me| Ok(me.0.top)); + fields.add_field_method_get("bottom", |_, me| Ok(me.0.bottom)); + } +} diff --git a/plugin/src/layout/rect.rs b/plugin/src/layout/rect.rs index cfbe868eb..62f6d5a55 100644 --- a/plugin/src/layout/rect.rs +++ b/plugin/src/layout/rect.rs @@ -1,5 +1,6 @@ use mlua::{FromLua, Lua, Table, UserData, Value}; +use super::Padding; use crate::{GLOBALS, LUA}; #[derive(Clone, Copy)] @@ -47,4 +48,16 @@ impl UserData for Rect { fields.add_field_method_get("top", |_, me| Ok(me.0.top())); fields.add_field_method_get("bottom", |_, me| Ok(me.0.bottom())); } + + fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("padding", |_, me, padding: Padding| { + let mut r = me.0; + r.x = r.x.saturating_add(padding.0.left); + r.y = r.y.saturating_add(padding.0.top); + + r.width = r.width.saturating_sub(padding.0.left + padding.0.right); + r.height = r.height.saturating_sub(padding.0.top + padding.0.bottom); + Ok(Self(r)) + }); + } } diff --git a/plugin/src/lib.rs b/plugin/src/lib.rs index e6e9b6dbd..b383da4b6 100644 --- a/plugin/src/lib.rs +++ b/plugin/src/lib.rs @@ -1,14 +1,13 @@ #![allow(clippy::unit_arg)] mod bindings; -mod components; +pub mod components; mod config; pub mod layout; mod plugin; mod scope; mod utils; -pub use components::*; use config::*; pub use plugin::*; pub use scope::*; diff --git a/plugin/src/plugin.rs b/plugin/src/plugin.rs index d28cc2431..3bdccfde3 100644 --- a/plugin/src/plugin.rs +++ b/plugin/src/plugin.rs @@ -3,7 +3,7 @@ use config::PLUGINS; use mlua::{Lua, Table}; use shared::RoCell; -use crate::{bindings, layout, utils}; +use crate::{bindings, components, layout, utils}; pub(crate) static LUA: RoCell = RoCell::new(); pub(crate) static GLOBALS: RoCell
= RoCell::new(); @@ -20,6 +20,7 @@ pub fn init() { // Components lua.load(include_str!("../preset/components/folder.lua")).exec()?; lua.load(include_str!("../preset/components/header.lua")).exec()?; + lua.load(include_str!("../preset/components/manager.lua")).exec()?; lua.load(include_str!("../preset/components/status.lua")).exec()?; // Initialize @@ -27,10 +28,13 @@ pub fn init() { GLOBALS.init(LUA.globals()); utils::init()?; bindings::init()?; + components::init()?; // Install crate::Config.install()?; + components::Base::install()?; + layout::Bar::install()?; layout::Constraint::install()?; layout::Gauge::install()?; @@ -38,6 +42,7 @@ pub fn init() { layout::Line::install()?; layout::List::install()?; layout::ListItem::install()?; + layout::Padding::install()?; layout::Paragraph::install()?; layout::Rect::install()?; layout::Span::install()?;