diff --git a/app/src/manager/folder.rs b/app/src/manager/folder.rs index ae49842a..46f49ea1 100644 --- a/app/src/manager/folder.rs +++ b/app/src/manager/folder.rs @@ -69,24 +69,6 @@ impl<'a> Widget for Folder<'a> { // ); // } // - // let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == - // f.url()); let style = if self.is_preview && hovered { - // THEME.preview.hovered.get() - // } else if hovered { - // THEME.selection.hovered.get() - // } else { - // self.item_style(f) - // }; - // - // let mut spans = Vec::with_capacity(10); - // spans.push(Span::raw(format!(" {} ", Self::icon(f)))); - // spans.extend(self.highlighted_item(f)); - // - // if let Some(link_to) = f.link_to() { - // if MANAGER.show_symlink { - // spans.push(Span::raw(format!(" -> {}", link_to.display()))); - // } - // } // // if let Some(idx) = active // .finder() diff --git a/config/src/manager/layout.rs b/config/src/manager/layout.rs index d7df5009..96730b02 100644 --- a/config/src/manager/layout.rs +++ b/config/src/manager/layout.rs @@ -1,12 +1,12 @@ use anyhow::bail; use crossterm::terminal::WindowSize; use ratatui::prelude::Rect; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use shared::Term; use super::{FOLDER_MARGIN, PREVIEW_BORDER, PREVIEW_MARGIN}; -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] #[serde(try_from = "Vec")] pub struct ManagerLayout { pub parent: u32, diff --git a/config/src/manager/manager.rs b/config/src/manager/manager.rs index f1acf380..69386b40 100644 --- a/config/src/manager/manager.rs +++ b/config/src/manager/manager.rs @@ -1,9 +1,9 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use super::{ManagerLayout, SortBy}; use crate::MERGED_YAZI; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Manager { pub layout: ManagerLayout, diff --git a/config/src/manager/sorting.rs b/config/src/manager/sorting.rs index 6d0b565f..35c937eb 100644 --- a/config/src/manager/sorting.rs +++ b/config/src/manager/sorting.rs @@ -1,7 +1,7 @@ use anyhow::bail; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] #[serde(try_from = "String")] pub enum SortBy { #[default] diff --git a/plugin/preset/components/folder.lua b/plugin/preset/components/folder.lua index ae444af4..647c0fba 100644 --- a/plugin/preset/components/folder.lua +++ b/plugin/preset/components/folder.lua @@ -1,25 +1,47 @@ -Folder = {} +Folder = { + Kind = { + Parent = 0, + Current = 1, + Preview = 2, + }, +} + +function Folder:by_kind(kind) + if kind == self.Kind.Parent then + return cx.manager.parent + elseif kind == self.Kind.Current then + return cx.manager.current + elseif kind == self.Kind.Preview then + return cx.manager.preview.folder + end +end + +function Folder:window(kind) + local folder = self:by_kind(kind) + if folder ~= nil then + return folder.files:slice(folder.offset, MANAGER.layout.folder_height()) + end +end + +function Folder:hovered(kind) + local folder = self:by_kind(kind) + return folder and folder.hovered +end function Folder:parent(area) - local parent = cx.manager.parent - if parent == nil then - return ui.Paragraph(area, ui.Line {}) - end - - local hovered = nil - if parent.hovered ~= nil then - hovered = parent.hovered.url + local window = self:window(self.Kind.Parent) + if window == nil then + return {} end + local hovered = (self:hovered(self.Kind.Parent) or {}).url local lines = {} - for _, f in pairs(parent.files) do - local line = ui.Line { ui.Span(" " .. f.icon .. " " .. f.name .. " ") } - - -- TODO: preview hovered + for _, f in pairs(window) do + local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") } if f.url == hovered then line = line:style(THEME.selection.hovered) else - line = line:style(f.style) + line = line:style(f:style()) end lines[#lines + 1] = line @@ -29,20 +51,25 @@ function Folder:parent(area) end function Folder:current(area) - local hovered = nil - if cx.manager.current.hovered ~= nil then - hovered = cx.manager.current.hovered.url - end - + local hovered = (self:hovered(self.Kind.Current) or {}).url local lines = {} - for _, f in pairs(cx.manager.current.files) do - local line = ui.Line { ui.Span(" " .. f.icon .. " " .. f.name .. " ") } + for _, f in pairs(self:window(self.Kind.Current)) do + local name = f.name - -- TODO: preview hovered + -- Show symlink target + if MANAGER.show_symlink then + local link_to = f.link_to + if link_to ~= nil then + name = name .. " -> " .. tostring(link_to) + end + end + + -- Highlight hovered file + local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. name .. " ") } if f.url == hovered then line = line:style(THEME.selection.hovered) else - line = line:style(f.style) + line = line:style(f:style()) end lines[#lines + 1] = line @@ -52,25 +79,32 @@ function Folder:current(area) end function Folder:preview(area) - local target = cx.manager.preview.folder - if target == nil then - return ui.Paragraph(area, ui.Line {}) + local window = self:window(self.Kind.Preview) + if window == nil then + return {} end + local hovered = (self:hovered(self.Kind.Preview) or {}).url local lines = {} - for _, f in pairs(target.files) do - lines[#lines + 1] = ui.Line { ui.Span(f.name) } + for _, f in pairs(window) do + local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") } + if f.url == hovered then + line = line:style(THEME.preview.hovered) + else + line = line:style(f:style()) + end + lines[#lines + 1] = line end return { ui.Paragraph(area, lines) } end function Folder:render(area, args) - if args.kind == 0 then + if args.kind == self.Kind.Parent then return self:parent(area) - elseif args.kind == 1 then + elseif args.kind == self.Kind.Current then return self:current(area) - elseif args.kind == 2 then + elseif args.kind == self.Kind.Preview then return self:preview(area) end end diff --git a/plugin/preset/components/status.lua b/plugin/preset/components/status.lua index 5821a221..eb571618 100644 --- a/plugin/preset/components/status.lua +++ b/plugin/preset/components/status.lua @@ -1,10 +1,9 @@ Status = {} function Status.style() - local mode = cx.manager.mode:upper() - if mode == "SELECT" then + if cx.manager.mode.is_select then return THEME.status.mode_select - elseif mode == "UNSET" then + elseif cx.manager.mode.is_unset then return THEME.status.mode_unset else return THEME.status.mode_normal @@ -12,7 +11,7 @@ function Status.style() end function Status:mode() - local mode = cx.manager.mode:upper() + local mode = tostring(cx.manager.mode):upper() if mode == "UNSET" then mode = "UN-SET" end diff --git a/plugin/src/bindings/manager.rs b/plugin/src/bindings/manager.rs index 474e433a..44b14ac0 100644 --- a/plugin/src/bindings/manager.rs +++ b/plugin/src/bindings/manager.rs @@ -1,5 +1,5 @@ use config::THEME; -use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserDataFields, UserDataMethods}; +use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserDataFields, UserDataMethods, Value}; use super::Url; use crate::{layout::Style, LUA}; @@ -9,19 +9,28 @@ pub struct Manager; impl Manager { pub(crate) fn init() -> mlua::Result<()> { LUA.register_userdata_type::(|reg| { - reg.add_field_method_get("mode", |_, me| Ok(me.active().mode().to_string())); - reg.add_field_function_get("parent", |_, me| me.named_user_value::("parent")); + reg.add_field_function_get("mode", |_, me| me.named_user_value::("mode")); + reg.add_field_function_get("parent", |_, me| me.named_user_value::("parent")); reg.add_field_function_get("current", |_, me| me.named_user_value::("current")); reg.add_field_function_get("preview", |_, me| me.named_user_value::("preview")); })?; + LUA.register_userdata_type::(|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())); + reg.add_field_method_get("is_visual", |_, me| Ok(me.is_visual())); + reg.add_method("pending", |_, me, (idx, state): (usize, bool)| Ok(me.pending(idx, state))); + + reg.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.to_string())); + })?; + LUA.register_userdata_type::(|reg| { reg.add_field_method_get("cwd", |_, me| Ok(Url::from(&me.cwd))); reg.add_field_method_get("offset", |_, me| Ok(me.offset())); reg.add_field_method_get("cursor", |_, me| Ok(me.cursor())); reg.add_field_function_get("files", |_, me| me.named_user_value::("files")); - reg.add_field_function_get("hovered", |_, me| me.named_user_value::("hovered")); + reg.add_field_function_get("hovered", |_, me| me.named_user_value::("hovered")); })?; LUA.register_userdata_type::(|reg| { @@ -41,24 +50,27 @@ impl Manager { })?; Ok((iter, me, 0)) }); + + reg.add_function("slice", |_, (me, skip, take): (AnyUserData, usize, usize)| { + let items = me.named_user_value::>("items")?; + Ok(items.iter().skip(skip).take(take).cloned().collect::>()) + }); })?; LUA.register_userdata_type::(|reg| { reg.add_field_method_get("name", |_, me| { Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string())) }); - reg.add_field_function_get("icon", |_, me| { + reg.add_function("icon", |_, me: AnyUserData| { me.named_user_value::("icon")?.call::<_, String>(()) }); - reg.add_field_function_get("style", |_, me| { + reg.add_function("style", |_, me: AnyUserData| { me.named_user_value::("style")?.call::<_, Style>(()) }); 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(|l| l.to_string_lossy().to_string())) - }); + 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())); @@ -69,7 +81,7 @@ impl Manager { })?; LUA.register_userdata_type::(|reg| { - reg.add_field_function_get("folder", |_, me| me.named_user_value::("folder")); + reg.add_field_function_get("folder", |_, me| me.named_user_value::("folder")); })?; Ok(()) @@ -80,6 +92,7 @@ impl Manager { inner: &'a core::manager::Manager, ) -> mlua::Result> { let ud = scope.create_any_userdata_ref(inner)?; + ud.set_named_user_value("mode", scope.create_any_userdata_ref(inner.active().mode())?)?; ud.set_named_user_value( "parent", inner.parent().and_then(|p| Self::folder(scope, inner, p).ok()), diff --git a/plugin/src/config.rs b/plugin/src/config.rs new file mode 100644 index 00000000..5802d4b3 --- /dev/null +++ b/plugin/src/config.rs @@ -0,0 +1,47 @@ +use mlua::{LuaSerdeExt, SerializeOptions, Table}; + +use crate::{layout::Rect, GLOBALS, LUA}; + +#[derive(Clone, Copy)] +pub(super) struct Config; + +impl Config { + pub(super) fn install(self) -> mlua::Result<()> { + let options = + SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false); + + self.theme(options)?; + self.manager(options)?; + Ok(()) + } + + fn theme(self, options: SerializeOptions) -> mlua::Result<()> { + GLOBALS.set("THEME", LUA.to_value_with(&*config::THEME, options)?) + } + + fn manager(self, options: SerializeOptions) -> mlua::Result<()> { + let manager = LUA.to_value_with(&*config::MANAGER, options)?; + { + let layout: Table = manager.as_table().unwrap().get("layout")?; + + layout.set( + "preview_rect", + LUA.create_function(|_, ()| Ok(Rect(config::MANAGER.layout.preview_rect())))?, + )?; + layout.set( + "preview_height", + LUA.create_function(|_, ()| Ok(config::MANAGER.layout.preview_height()))?, + )?; + layout.set( + "folder_rect", + LUA.create_function(|_, ()| Ok(Rect(config::MANAGER.layout.folder_rect())))?, + )?; + layout.set( + "folder_height", + LUA.create_function(|_, ()| Ok(config::MANAGER.layout.folder_height()))?, + )?; + } + + GLOBALS.set("MANAGER", manager) + } +} diff --git a/plugin/src/layout/rect.rs b/plugin/src/layout/rect.rs index 406b1508..cfbe868e 100644 --- a/plugin/src/layout/rect.rs +++ b/plugin/src/layout/rect.rs @@ -1,8 +1,27 @@ -use mlua::{FromLua, Lua, UserData, Value}; +use mlua::{FromLua, Lua, Table, UserData, Value}; + +use crate::{GLOBALS, LUA}; #[derive(Clone, Copy)] pub(crate) struct Rect(pub(crate) ratatui::layout::Rect); +impl Rect { + pub(crate) fn install() -> mlua::Result<()> { + let ui: Table = GLOBALS.get("ui")?; + ui.set( + "Rect", + LUA.create_function(|_, args: Table| { + Ok(Self(ratatui::layout::Rect { + x: args.get("x")?, + y: args.get("y")?, + width: args.get("w")?, + height: args.get("h")?, + })) + })?, + ) + } +} + impl<'lua> FromLua<'lua> for Rect { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result { match value { @@ -20,7 +39,12 @@ impl UserData for Rect { fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("x", |_, me| Ok(me.0.x)); fields.add_field_method_get("y", |_, me| Ok(me.0.y)); - fields.add_field_method_get("width", |_, me| Ok(me.0.width)); - fields.add_field_method_get("height", |_, me| Ok(me.0.height)); + fields.add_field_method_get("w", |_, me| Ok(me.0.width)); + fields.add_field_method_get("h", |_, me| Ok(me.0.height)); + + 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/lib.rs b/plugin/src/lib.rs index d6efac47..4a6fe959 100644 --- a/plugin/src/lib.rs +++ b/plugin/src/lib.rs @@ -2,8 +2,10 @@ mod bindings; mod components; +mod config; mod layout; mod plugin; pub use components::*; +use config::*; pub use plugin::*; diff --git a/plugin/src/plugin.rs b/plugin/src/plugin.rs index 401b2ac4..4ff90bdc 100644 --- a/plugin/src/plugin.rs +++ b/plugin/src/plugin.rs @@ -1,6 +1,5 @@ use anyhow::Result; -use config::THEME; -use mlua::{Lua, LuaSerdeExt, SerializeOptions, Table}; +use mlua::{Lua, Table}; use shared::RoCell; use crate::{bindings, layout}; @@ -27,17 +26,16 @@ pub fn init() { bindings::init()?; // Install + crate::Config.install()?; + layout::Constraint::install()?; layout::Layout::install()?; layout::Line::install()?; layout::Paragraph::install()?; + layout::Rect::install()?; layout::Span::install()?; layout::Style::install()?; - let options = - SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false); - GLOBALS.set("THEME", LUA.to_value_with(&*THEME, options)?)?; - Ok(()) }