This commit is contained in:
sxyazi 2023-09-30 14:48:46 +08:00
parent 2616419858
commit 2c2e1668fc
No known key found for this signature in database
11 changed files with 177 additions and 78 deletions

View file

@ -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 // if let Some(idx) = active
// .finder() // .finder()

View file

@ -1,12 +1,12 @@
use anyhow::bail; use anyhow::bail;
use crossterm::terminal::WindowSize; use crossterm::terminal::WindowSize;
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use shared::Term; use shared::Term;
use super::{FOLDER_MARGIN, PREVIEW_BORDER, PREVIEW_MARGIN}; 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<u32>")] #[serde(try_from = "Vec<u32>")]
pub struct ManagerLayout { pub struct ManagerLayout {
pub parent: u32, pub parent: u32,

View file

@ -1,9 +1,9 @@
use serde::Deserialize; use serde::{Deserialize, Serialize};
use super::{ManagerLayout, SortBy}; use super::{ManagerLayout, SortBy};
use crate::MERGED_YAZI; use crate::MERGED_YAZI;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct Manager { pub struct Manager {
pub layout: ManagerLayout, pub layout: ManagerLayout,

View file

@ -1,7 +1,7 @@
use anyhow::bail; 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")] #[serde(try_from = "String")]
pub enum SortBy { pub enum SortBy {
#[default] #[default]

View file

@ -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) function Folder:parent(area)
local parent = cx.manager.parent local window = self:window(self.Kind.Parent)
if parent == nil then if window == nil then
return ui.Paragraph(area, ui.Line {}) return {}
end
local hovered = nil
if parent.hovered ~= nil then
hovered = parent.hovered.url
end end
local hovered = (self:hovered(self.Kind.Parent) or {}).url
local lines = {} local lines = {}
for _, f in pairs(parent.files) do for _, f in pairs(window) do
local line = ui.Line { ui.Span(" " .. f.icon .. " " .. f.name .. " ") } local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") }
-- TODO: preview hovered
if f.url == hovered then if f.url == hovered then
line = line:style(THEME.selection.hovered) line = line:style(THEME.selection.hovered)
else else
line = line:style(f.style) line = line:style(f:style())
end end
lines[#lines + 1] = line lines[#lines + 1] = line
@ -29,20 +51,25 @@ function Folder:parent(area)
end end
function Folder:current(area) function Folder:current(area)
local hovered = nil local hovered = (self:hovered(self.Kind.Current) or {}).url
if cx.manager.current.hovered ~= nil then local lines = {}
hovered = cx.manager.current.hovered.url for _, f in pairs(self:window(self.Kind.Current)) do
local name = f.name
-- 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 end
local lines = {} -- Highlight hovered file
for _, f in pairs(cx.manager.current.files) do local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. name .. " ") }
local line = ui.Line { ui.Span(" " .. f.icon .. " " .. f.name .. " ") }
-- TODO: preview hovered
if f.url == hovered then if f.url == hovered then
line = line:style(THEME.selection.hovered) line = line:style(THEME.selection.hovered)
else else
line = line:style(f.style) line = line:style(f:style())
end end
lines[#lines + 1] = line lines[#lines + 1] = line
@ -52,25 +79,32 @@ function Folder:current(area)
end end
function Folder:preview(area) function Folder:preview(area)
local target = cx.manager.preview.folder local window = self:window(self.Kind.Preview)
if target == nil then if window == nil then
return ui.Paragraph(area, ui.Line {}) return {}
end end
local hovered = (self:hovered(self.Kind.Preview) or {}).url
local lines = {} local lines = {}
for _, f in pairs(target.files) do for _, f in pairs(window) do
lines[#lines + 1] = ui.Line { ui.Span(f.name) } 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 end
return { ui.Paragraph(area, lines) } return { ui.Paragraph(area, lines) }
end end
function Folder:render(area, args) function Folder:render(area, args)
if args.kind == 0 then if args.kind == self.Kind.Parent then
return self:parent(area) return self:parent(area)
elseif args.kind == 1 then elseif args.kind == self.Kind.Current then
return self:current(area) return self:current(area)
elseif args.kind == 2 then elseif args.kind == self.Kind.Preview then
return self:preview(area) return self:preview(area)
end end
end end

View file

@ -1,10 +1,9 @@
Status = {} Status = {}
function Status.style() function Status.style()
local mode = cx.manager.mode:upper() if cx.manager.mode.is_select then
if mode == "SELECT" then
return THEME.status.mode_select return THEME.status.mode_select
elseif mode == "UNSET" then elseif cx.manager.mode.is_unset then
return THEME.status.mode_unset return THEME.status.mode_unset
else else
return THEME.status.mode_normal return THEME.status.mode_normal
@ -12,7 +11,7 @@ function Status.style()
end end
function Status:mode() function Status:mode()
local mode = cx.manager.mode:upper() local mode = tostring(cx.manager.mode):upper()
if mode == "UNSET" then if mode == "UNSET" then
mode = "UN-SET" mode = "UN-SET"
end end

View file

@ -1,5 +1,5 @@
use config::THEME; use config::THEME;
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserDataFields, UserDataMethods}; use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserDataFields, UserDataMethods, Value};
use super::Url; use super::Url;
use crate::{layout::Style, LUA}; use crate::{layout::Style, LUA};
@ -9,19 +9,28 @@ pub struct Manager;
impl Manager { impl Manager {
pub(crate) fn init() -> mlua::Result<()> { pub(crate) fn init() -> mlua::Result<()> {
LUA.register_userdata_type::<core::manager::Manager>(|reg| { LUA.register_userdata_type::<core::manager::Manager>(|reg| {
reg.add_field_method_get("mode", |_, me| Ok(me.active().mode().to_string())); reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
reg.add_field_function_get("parent", |_, me| me.named_user_value::<AnyUserData>("parent")); 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("current", |_, me| me.named_user_value::<AnyUserData>("current"));
reg.add_field_function_get("preview", |_, me| me.named_user_value::<AnyUserData>("preview")); 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()));
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::<core::manager::Folder>(|reg| { LUA.register_userdata_type::<core::manager::Folder>(|reg| {
reg.add_field_method_get("cwd", |_, me| Ok(Url::from(&me.cwd))); 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("offset", |_, me| Ok(me.offset()));
reg.add_field_method_get("cursor", |_, me| Ok(me.cursor())); reg.add_field_method_get("cursor", |_, me| Ok(me.cursor()));
reg.add_field_function_get("files", |_, me| me.named_user_value::<AnyUserData>("files")); reg.add_field_function_get("files", |_, me| me.named_user_value::<AnyUserData>("files"));
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<AnyUserData>("hovered")); reg.add_field_function_get("hovered", |_, me| me.named_user_value::<Value>("hovered"));
})?; })?;
LUA.register_userdata_type::<core::files::Files>(|reg| { LUA.register_userdata_type::<core::files::Files>(|reg| {
@ -41,24 +50,27 @@ impl Manager {
})?; })?;
Ok((iter, me, 0)) Ok((iter, me, 0))
}); });
reg.add_function("slice", |_, (me, skip, take): (AnyUserData, usize, usize)| {
let items = me.named_user_value::<Vec<AnyUserData>>("items")?;
Ok(items.iter().skip(skip).take(take).cloned().collect::<Vec<_>>())
});
})?; })?;
LUA.register_userdata_type::<core::files::File>(|reg| { LUA.register_userdata_type::<core::files::File>(|reg| {
reg.add_field_method_get("name", |_, me| { reg.add_field_method_get("name", |_, me| {
Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string())) 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::<Function>("icon")?.call::<_, String>(()) me.named_user_value::<Function>("icon")?.call::<_, String>(())
}); });
reg.add_field_function_get("style", |_, me| { reg.add_function("style", |_, me: AnyUserData| {
me.named_user_value::<Function>("style")?.call::<_, Style>(()) me.named_user_value::<Function>("style")?.call::<_, Style>(())
}); });
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url()))); 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("length", |_, me| Ok(me.length()));
reg.add_field_method_get("link_to", |_, me| { reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
Ok(me.link_to().map(|l| l.to_string_lossy().to_string()))
});
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link())); reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden())); reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
@ -69,7 +81,7 @@ impl Manager {
})?; })?;
LUA.register_userdata_type::<core::manager::Preview>(|reg| { LUA.register_userdata_type::<core::manager::Preview>(|reg| {
reg.add_field_function_get("folder", |_, me| me.named_user_value::<AnyUserData>("folder")); reg.add_field_function_get("folder", |_, me| me.named_user_value::<Value>("folder"));
})?; })?;
Ok(()) Ok(())
@ -80,6 +92,7 @@ impl Manager {
inner: &'a core::manager::Manager, inner: &'a core::manager::Manager,
) -> mlua::Result<AnyUserData<'a>> { ) -> mlua::Result<AnyUserData<'a>> {
let ud = scope.create_any_userdata_ref(inner)?; 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( ud.set_named_user_value(
"parent", "parent",
inner.parent().and_then(|p| Self::folder(scope, inner, p).ok()), inner.parent().and_then(|p| Self::folder(scope, inner, p).ok()),

47
plugin/src/config.rs Normal file
View file

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

View file

@ -1,8 +1,27 @@
use mlua::{FromLua, Lua, UserData, Value}; use mlua::{FromLua, Lua, Table, UserData, Value};
use crate::{GLOBALS, LUA};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) struct Rect(pub(crate) ratatui::layout::Rect); 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 { impl<'lua> FromLua<'lua> for Rect {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
match value { match value {
@ -20,7 +39,12 @@ impl UserData for Rect {
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { 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("x", |_, me| Ok(me.0.x));
fields.add_field_method_get("y", |_, me| Ok(me.0.y)); 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("w", |_, me| Ok(me.0.width));
fields.add_field_method_get("height", |_, me| Ok(me.0.height)); 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()));
} }
} }

View file

@ -2,8 +2,10 @@
mod bindings; mod bindings;
mod components; mod components;
mod config;
mod layout; mod layout;
mod plugin; mod plugin;
pub use components::*; pub use components::*;
use config::*;
pub use plugin::*; pub use plugin::*;

View file

@ -1,6 +1,5 @@
use anyhow::Result; use anyhow::Result;
use config::THEME; use mlua::{Lua, Table};
use mlua::{Lua, LuaSerdeExt, SerializeOptions, Table};
use shared::RoCell; use shared::RoCell;
use crate::{bindings, layout}; use crate::{bindings, layout};
@ -27,17 +26,16 @@ pub fn init() {
bindings::init()?; bindings::init()?;
// Install // Install
crate::Config.install()?;
layout::Constraint::install()?; layout::Constraint::install()?;
layout::Layout::install()?; layout::Layout::install()?;
layout::Line::install()?; layout::Line::install()?;
layout::Paragraph::install()?; layout::Paragraph::install()?;
layout::Rect::install()?;
layout::Span::install()?; layout::Span::install()?;
layout::Style::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(()) Ok(())
} }