mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
a9074c1447
commit
f3b82274b6
35 changed files with 946 additions and 830 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -220,9 +220,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.7.0"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
|
||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
|
|
@ -1252,9 +1252,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.1"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ impl<'a> Widget for Tabs<'a> {
|
|||
}
|
||||
|
||||
if i == tabs.idx() {
|
||||
Span::styled(format!(" {text} "), THEME.tab.active.get())
|
||||
Span::styled(format!(" {text} "), THEME.tab.active.into())
|
||||
} else {
|
||||
Span::styled(format!(" {text} "), THEME.tab.inactive.get())
|
||||
Span::styled(format!(" {text} "), THEME.tab.inactive.into())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ mod help;
|
|||
mod input;
|
||||
mod logs;
|
||||
mod manager;
|
||||
mod parser;
|
||||
mod root;
|
||||
mod select;
|
||||
mod signals;
|
||||
|
|
|
|||
|
|
@ -1,39 +1,22 @@
|
|||
use core::{files::File, Ctx};
|
||||
|
||||
use config::{MANAGER, THEME};
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{List, ListItem, Widget}};
|
||||
use shared::short_path;
|
||||
use config::THEME;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::Style, widgets::Widget};
|
||||
use tracing::info;
|
||||
|
||||
pub(super) struct Folder<'a> {
|
||||
cx: &'a Ctx,
|
||||
folder: &'a core::manager::Folder,
|
||||
is_preview: bool,
|
||||
is_selection: bool,
|
||||
is_find: bool,
|
||||
kind: FolderKind,
|
||||
}
|
||||
|
||||
pub(super) enum FolderKind {
|
||||
Parent = 0,
|
||||
Current = 1,
|
||||
Preview = 2,
|
||||
}
|
||||
|
||||
impl<'a> Folder<'a> {
|
||||
pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self {
|
||||
Self { cx, folder, is_preview: false, is_selection: false, is_find: false }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn with_preview(mut self, state: bool) -> Self {
|
||||
self.is_preview = state;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn with_selection(mut self, state: bool) -> Self {
|
||||
self.is_selection = state;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn with_find(mut self, state: bool) -> Self {
|
||||
self.is_find = state;
|
||||
self
|
||||
}
|
||||
pub(super) fn new(cx: &'a Ctx, kind: FolderKind) -> Self { Self { cx, kind } }
|
||||
}
|
||||
|
||||
impl<'a> Folder<'a> {
|
||||
|
|
@ -49,12 +32,12 @@ impl<'a> Folder<'a> {
|
|||
|
||||
#[inline]
|
||||
fn item_style(&self, file: &File) -> Style {
|
||||
let mimetype = &self.cx.manager.mimetype;
|
||||
let mime = self.cx.manager.mimetype.get(file.url());
|
||||
THEME
|
||||
.filetypes
|
||||
.iter()
|
||||
.find(|x| x.matches(file.url(), mimetype.get(file.url()), file.is_dir()))
|
||||
.map(|x| x.style.get())
|
||||
.find(|x| x.matches(file.url(), mime, file.is_dir()))
|
||||
.map(|x| x.style.into())
|
||||
.unwrap_or_else(Style::new)
|
||||
}
|
||||
|
||||
|
|
@ -87,73 +70,74 @@ impl<'a> Folder<'a> {
|
|||
|
||||
impl<'a> Widget for Folder<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let active = self.cx.manager.active();
|
||||
let mode = active.mode();
|
||||
|
||||
let window = if self.is_preview {
|
||||
self.folder.window_for(active.preview().skip())
|
||||
} else {
|
||||
self.folder.window()
|
||||
};
|
||||
|
||||
let items: Vec<_> = window
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
let is_selected = self.folder.files.is_selected(f.url());
|
||||
if (!self.is_selection && is_selected)
|
||||
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
||||
{
|
||||
buf.set_style(
|
||||
Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1 },
|
||||
if self.is_selection {
|
||||
THEME.marker.selecting.get()
|
||||
} else {
|
||||
THEME.marker.selected.get()
|
||||
},
|
||||
);
|
||||
let x = plugin::Folder { kind: self.kind as u8 }.render(self.cx, area);
|
||||
if x.is_err() {
|
||||
info!("{:?}", x);
|
||||
return;
|
||||
}
|
||||
|
||||
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())));
|
||||
}
|
||||
for x in x.unwrap() {
|
||||
x.render(buf);
|
||||
}
|
||||
|
||||
if let Some(idx) = active
|
||||
.finder()
|
||||
.filter(|&f| hovered && self.is_find && f.has_matched())
|
||||
.and_then(|finder| finder.matched_idx(f.url()))
|
||||
{
|
||||
let len = active.finder().unwrap().matched().len();
|
||||
spans.push(Span::styled(
|
||||
format!(
|
||||
" [{}/{}]",
|
||||
if idx > 99 { ">99".to_string() } else { (idx + 1).to_string() },
|
||||
if len > 99 { ">99".to_string() } else { len.to_string() }
|
||||
),
|
||||
// TODO: to be configured by THEME?
|
||||
Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC),
|
||||
));
|
||||
}
|
||||
// let items: Vec<_> = window
|
||||
// .iter()
|
||||
// .enumerate()
|
||||
// .map(|(i, f)| {
|
||||
// let is_selected = self.folder.files.is_selected(f.url());
|
||||
// if (!self.is_selection && is_selected)
|
||||
// || (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
||||
// {
|
||||
// buf.set_style(
|
||||
// Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1
|
||||
// }, if self.is_selection {
|
||||
// THEME.marker.selecting.get()
|
||||
// } else {
|
||||
// THEME.marker.selected.get()
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// 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()
|
||||
// .filter(|&f| hovered && self.is_find && f.has_matched())
|
||||
// .and_then(|finder| finder.matched_idx(f.url()))
|
||||
// {
|
||||
// let len = active.finder().unwrap().matched().len();
|
||||
// spans.push(Span::styled(
|
||||
// format!(
|
||||
// " [{}/{}]",
|
||||
// if idx > 99 { ">99".to_string() } else { (idx + 1).to_string() },
|
||||
// if len > 99 { ">99".to_string() } else { len.to_string() }
|
||||
// ),
|
||||
// // TODO: to be configured by THEME?
|
||||
// Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC),
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// ListItem::new(Line::from(spans)).style(style)
|
||||
// })
|
||||
// .collect();
|
||||
|
||||
ListItem::new(Line::from(spans)).style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
List::new(items).render(area, buf);
|
||||
// List::new(items).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use core::Ctx;
|
|||
use config::MANAGER;
|
||||
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}};
|
||||
|
||||
use super::{Folder, Preview};
|
||||
use super::{folder::FolderKind, Folder, Preview};
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -32,16 +32,13 @@ impl<'a> Widget for Layout<'a> {
|
|||
|
||||
// Parent
|
||||
let block = Block::new().borders(Borders::RIGHT).padding(Padding::new(1, 0, 0, 0));
|
||||
if let Some(parent) = manager.parent() {
|
||||
Folder::new(self.cx, parent).render(block.inner(chunks[0]), buf);
|
||||
if manager.parent().is_some() {
|
||||
Folder::new(self.cx, FolderKind::Parent).render(block.inner(chunks[0]), buf);
|
||||
}
|
||||
block.render(chunks[0], buf);
|
||||
|
||||
// Current
|
||||
Folder::new(self.cx, manager.current())
|
||||
.with_selection(manager.active().mode().is_visual())
|
||||
.with_find(manager.active().finder().is_some())
|
||||
.render(chunks[1], buf);
|
||||
Folder::new(self.cx, FolderKind::Current).render(chunks[1], buf);
|
||||
|
||||
// Preview
|
||||
let block = Block::new().borders(Borders::LEFT).padding(Padding::new(0, 1, 0, 0));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use core::{manager::PreviewData, Ctx};
|
|||
use ansi_to_tui::IntoText;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}};
|
||||
|
||||
use super::Folder;
|
||||
use super::{folder::FolderKind, Folder};
|
||||
|
||||
pub(super) struct Preview<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -27,9 +27,7 @@ impl<'a> Widget for Preview<'a> {
|
|||
|
||||
match &preview.lock.as_ref().unwrap().data {
|
||||
PreviewData::Folder => {
|
||||
if let Some(folder) = manager.active().history(hovered) {
|
||||
Folder::new(self.cx, folder).with_preview(true).render(area, buf);
|
||||
}
|
||||
Folder::new(self.cx, FolderKind::Preview).render(area, buf);
|
||||
}
|
||||
PreviewData::Text(s) => {
|
||||
let p = Paragraph::new(s.as_bytes().into_text().unwrap());
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use config::theme::Color;
|
||||
use ratatui::{prelude::{Alignment, Buffer, Rect}, style::{Modifier, Style}, text::{Line, Span}, widgets::{Paragraph, Widget}};
|
||||
|
||||
pub struct Parser;
|
||||
|
||||
impl Parser {
|
||||
fn span(s: &str) -> Span<'static> {
|
||||
let Some((args, content)) = s.split_once(';') else {
|
||||
return Span::raw(s.to_string());
|
||||
};
|
||||
|
||||
let args: Vec<_> = args.split(',').collect();
|
||||
if args.len() != 4 {
|
||||
return Span::raw(s.to_string());
|
||||
}
|
||||
|
||||
let mut style = Style::new();
|
||||
if let Ok(fg) = Color::try_from(args[0]) {
|
||||
style = style.fg(fg.into());
|
||||
}
|
||||
if let Ok(bg) = Color::try_from(args[1]) {
|
||||
style = style.bg(bg.into());
|
||||
}
|
||||
if let Ok(uc) = Color::try_from(args[2]) {
|
||||
style = style.underline_color(uc.into());
|
||||
}
|
||||
if let Ok(modifier) = args[3].parse::<u16>() {
|
||||
style = style.add_modifier(Modifier::from_bits_truncate(modifier));
|
||||
}
|
||||
Span::styled(content.to_string(), style)
|
||||
}
|
||||
|
||||
fn line(s: &str) -> Line<'static> {
|
||||
let mut last = '\0';
|
||||
let mut spans: Vec<String> = vec![String::new()];
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '\n' && last == '\\' {
|
||||
let last = spans.last_mut().unwrap();
|
||||
last.pop();
|
||||
last.push(c);
|
||||
} else if c == '\n' {
|
||||
spans.push(String::new());
|
||||
} else {
|
||||
spans.last_mut().unwrap().push(c);
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
Line::from(spans.into_iter().map(|s| Self::span(&s)).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn paragraph(s: &str) -> Paragraph {
|
||||
let mut last = '\0';
|
||||
let mut lines: Vec<String> = vec![String::new()];
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '\r' && last == '\\' {
|
||||
let last = lines.last_mut().unwrap();
|
||||
last.pop();
|
||||
last.push(c);
|
||||
} else if c == '\r' {
|
||||
lines.push(String::new());
|
||||
} else {
|
||||
lines.last_mut().unwrap().push(c);
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
Paragraph::new(lines.into_iter().map(|s| Self::line(&s)).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn area(args: &[&str]) -> Result<Rect> {
|
||||
Ok(Rect {
|
||||
x: args[0].parse()?,
|
||||
y: args[1].parse()?,
|
||||
width: args[2].parse()?,
|
||||
height: args[3].parse()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(s: &str, buf: &mut Buffer) {
|
||||
let Some(s) = s.strip_prefix('R') else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut last = '\0';
|
||||
let mut paragraphs: Vec<String> = vec![String::new()];
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '\0' && last == '\\' {
|
||||
let last = paragraphs.last_mut().unwrap();
|
||||
last.pop();
|
||||
last.push(c);
|
||||
} else if c == '\0' {
|
||||
paragraphs.push(String::new());
|
||||
} else {
|
||||
paragraphs.last_mut().unwrap().push(c);
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
|
||||
for paragraph in paragraphs {
|
||||
let Some((args, content)) = paragraph.split_once(';') else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let args: Vec<_> = args.split(',').collect();
|
||||
if args.len() != 5 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(area) = Self::area(&args) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut paragraph = Self::paragraph(content);
|
||||
if let Ok(align) = args[4].parse::<u8>() {
|
||||
paragraph = paragraph.alignment(match align {
|
||||
1 => Alignment::Center,
|
||||
2 => Alignment::Right,
|
||||
_ => Alignment::Left,
|
||||
});
|
||||
}
|
||||
|
||||
paragraph.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ use core::Ctx;
|
|||
use ratatui::{buffer::Buffer, prelude::Rect, widgets::Widget};
|
||||
use tracing::info;
|
||||
|
||||
use crate::parser::Parser;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
|
@ -21,8 +19,8 @@ impl<'a> Widget for Layout<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
if let Ok(s) = x {
|
||||
Parser::render(&s, buf);
|
||||
for x in x.unwrap() {
|
||||
x.render(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ impl<'a> Widget for Progress<'a> {
|
|||
}
|
||||
|
||||
Gauge::default()
|
||||
.gauge_style(THEME.status.progress_gauge.get())
|
||||
.gauge_style(THEME.status.progress_gauge.into())
|
||||
.percent(progress.0 as u16)
|
||||
.label(Span::styled(
|
||||
format!("{:>3}%, {} left", progress.0, progress.1),
|
||||
THEME.status.progress_label.get(),
|
||||
THEME.status.progress_label.into(),
|
||||
))
|
||||
.render(area, buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,35 @@
|
|||
use anyhow::{bail, Result};
|
||||
use ratatui::style;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct Color([u8; 3]);
|
||||
pub struct Color(ratatui::style::Color);
|
||||
|
||||
impl TryFrom<&str> for Color {
|
||||
type Error = anyhow::Error;
|
||||
impl FromStr for Color {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
if s.len() != 7 {
|
||||
bail!("Invalid color: {s}");
|
||||
}
|
||||
Ok(Self([
|
||||
u8::from_str_radix(&s[1..3], 16)?,
|
||||
u8::from_str_radix(&s[3..5], 16)?,
|
||||
u8::from_str_radix(&s[5..7], 16)?,
|
||||
]))
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
ratatui::style::Color::from_str(s).map(Self).map_err(|_| anyhow::anyhow!("invalid color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Color {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> { Self::try_from(s.as_str()) }
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(s.as_str()) }
|
||||
}
|
||||
|
||||
impl From<&Color> for style::Color {
|
||||
fn from(&Color(rgb): &Color) -> Self { style::Color::Rgb(rgb[0], rgb[1], rgb[2]) }
|
||||
}
|
||||
|
||||
impl From<Color> for style::Color {
|
||||
fn from(Color(rgb): Color) -> Self { style::Color::Rgb(rgb[0], rgb[1], rgb[2]) }
|
||||
}
|
||||
|
||||
impl Color {
|
||||
#[inline]
|
||||
pub fn fg(&self) -> style::Style { style::Style::new().fg(self.into()) }
|
||||
|
||||
#[inline]
|
||||
pub fn bg(&self) -> style::Style { style::Style::new().bg(self.into()) }
|
||||
impl From<Color> for ratatui::style::Color {
|
||||
fn from(value: Color) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl Serialize for Color {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("#{:02X}{:02X}{:02X}", self.0[0], self.0[1], self.0[2]))
|
||||
self.0.to_string().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use super::Style;
|
||||
use crate::{theme::Color, Pattern};
|
||||
use crate::{theme::{Color, StyleShadow}, Pattern};
|
||||
|
||||
pub struct Filetype {
|
||||
pub name: Option<Pattern>,
|
||||
|
|
@ -30,18 +30,31 @@ impl Filetype {
|
|||
{
|
||||
#[derive(Deserialize)]
|
||||
struct FiletypeOuter {
|
||||
rules: Vec<FiletypeOuterStyle>,
|
||||
rules: Vec<FiletypeRule>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct FiletypeOuterStyle {
|
||||
struct FiletypeRule {
|
||||
name: Option<Pattern>,
|
||||
mime: Option<Pattern>,
|
||||
|
||||
fg: Option<Color>,
|
||||
bg: Option<Color>,
|
||||
#[serde(default)]
|
||||
bold: bool,
|
||||
#[serde(default)]
|
||||
dim: bool,
|
||||
#[serde(default)]
|
||||
italic: bool,
|
||||
#[serde(default)]
|
||||
underline: bool,
|
||||
#[serde(default)]
|
||||
blink: bool,
|
||||
#[serde(default)]
|
||||
blink_rapid: bool,
|
||||
#[serde(default)]
|
||||
hidden: bool,
|
||||
#[serde(default)]
|
||||
crossed: bool,
|
||||
}
|
||||
|
||||
Ok(
|
||||
|
|
@ -51,12 +64,19 @@ impl Filetype {
|
|||
.map(|r| Filetype {
|
||||
name: r.name,
|
||||
mime: r.mime,
|
||||
style: Style {
|
||||
style: StyleShadow {
|
||||
fg: r.fg,
|
||||
bg: r.bg,
|
||||
bold: r.bold,
|
||||
dim: r.dim,
|
||||
italic: r.italic,
|
||||
underline: r.underline,
|
||||
},
|
||||
blink: r.blink,
|
||||
blink_rapid: r.blink_rapid,
|
||||
hidden: r.hidden,
|
||||
crossed: r.crossed,
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,34 +1,93 @@
|
|||
use ratatui::style::{self, Modifier};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ratatui::style::Modifier;
|
||||
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
|
||||
|
||||
use super::Color;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Deserialize)]
|
||||
#[serde(from = "StyleShadow")]
|
||||
pub struct Style {
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
#[serde(default)]
|
||||
pub bold: bool,
|
||||
#[serde(default)]
|
||||
pub underline: bool,
|
||||
pub modifier: Modifier,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn get(&self) -> style::Style {
|
||||
let mut style = style::Style::new();
|
||||
|
||||
if let Some(fg) = &self.fg {
|
||||
style = style.fg(fg.into());
|
||||
}
|
||||
if let Some(bg) = &self.bg {
|
||||
style = style.bg(bg.into());
|
||||
}
|
||||
if self.bold {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if self.underline {
|
||||
style = style.add_modifier(Modifier::UNDERLINED);
|
||||
}
|
||||
style
|
||||
impl Serialize for Style {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(3))?;
|
||||
map.serialize_entry("fg", &self.fg)?;
|
||||
map.serialize_entry("bg", &self.bg)?;
|
||||
map.serialize_entry("modifier", &self.modifier.bits())?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Style> for ratatui::style::Style {
|
||||
fn from(value: Style) -> Self {
|
||||
ratatui::style::Style {
|
||||
fg: value.fg.map(Into::into),
|
||||
bg: value.bg.map(Into::into),
|
||||
underline_color: None,
|
||||
add_modifier: value.modifier,
|
||||
sub_modifier: Modifier::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct StyleShadow {
|
||||
#[serde(default)]
|
||||
pub(super) fg: Option<Color>,
|
||||
#[serde(default)]
|
||||
pub(super) bg: Option<Color>,
|
||||
#[serde(default)]
|
||||
pub(super) bold: bool,
|
||||
#[serde(default)]
|
||||
pub(super) dim: bool,
|
||||
#[serde(default)]
|
||||
pub(super) italic: bool,
|
||||
#[serde(default)]
|
||||
pub(super) underline: bool,
|
||||
#[serde(default)]
|
||||
pub(super) blink: bool,
|
||||
#[serde(default)]
|
||||
pub(super) blink_rapid: bool,
|
||||
#[serde(default)]
|
||||
pub(super) hidden: bool,
|
||||
#[serde(default)]
|
||||
pub(super) crossed: bool,
|
||||
}
|
||||
|
||||
impl From<StyleShadow> for Style {
|
||||
fn from(value: StyleShadow) -> Self {
|
||||
let mut modifier = Modifier::empty();
|
||||
if value.bold {
|
||||
modifier |= Modifier::BOLD;
|
||||
}
|
||||
if value.dim {
|
||||
modifier |= Modifier::DIM;
|
||||
}
|
||||
if value.italic {
|
||||
modifier |= Modifier::ITALIC;
|
||||
}
|
||||
if value.underline {
|
||||
modifier |= Modifier::UNDERLINED;
|
||||
}
|
||||
if value.blink {
|
||||
modifier |= Modifier::SLOW_BLINK;
|
||||
}
|
||||
if value.blink_rapid {
|
||||
modifier |= Modifier::RAPID_BLINK;
|
||||
}
|
||||
if value.hidden {
|
||||
modifier |= Modifier::HIDDEN;
|
||||
}
|
||||
if value.crossed {
|
||||
modifier |= Modifier::CROSSED_OUT;
|
||||
}
|
||||
|
||||
Self { fg: value.fg, bg: value.bg, modifier }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,19 +90,6 @@ impl Folder {
|
|||
old != self.cursor
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window(&self) -> &[File] {
|
||||
let end = (self.offset + MANAGER.layout.folder_height()).min(self.files.len());
|
||||
&self.files[self.offset..end]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window_for(&self, offset: usize) -> &[File] {
|
||||
let start = offset.min(self.files.len().saturating_sub(1));
|
||||
let end = (offset + MANAGER.layout.folder_height()).min(self.files.len());
|
||||
&self.files[start..end]
|
||||
}
|
||||
|
||||
pub fn hover(&mut self, url: &Url) -> bool {
|
||||
let new = self.files.position(url).unwrap_or(self.cursor);
|
||||
if new > self.cursor {
|
||||
|
|
|
|||
|
|
@ -170,9 +170,6 @@ impl Preview {
|
|||
}
|
||||
|
||||
impl Preview {
|
||||
#[inline]
|
||||
pub fn lock(&self) -> &Option<PreviewLock> { &self.lock }
|
||||
|
||||
#[inline]
|
||||
pub fn skip(&self) -> usize { self.skip }
|
||||
|
||||
|
|
|
|||
57
plugin/preset/components/folder.lua
Normal file
57
plugin/preset/components/folder.lua
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
Folder = {}
|
||||
|
||||
function Folder:parent(area)
|
||||
local parent = cx.manager.parent
|
||||
if parent == nil then
|
||||
return ui.Paragraph(area, ui.Line {})
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for _, f in pairs(parent.files) do
|
||||
lines[#lines + 1] = ui.Line { ui.Span(f.name) }
|
||||
end
|
||||
|
||||
return { ui.Paragraph(area, lines) }
|
||||
end
|
||||
|
||||
function Folder:current(area)
|
||||
local hovered = nil
|
||||
if cx.manager.current.hovered ~= nil then
|
||||
hovered = cx.manager.current.hovered.url
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for _, f in pairs(cx.manager.current.files) do
|
||||
local line = ui.Line { ui.Span(f.name) }
|
||||
if f.url == hovered then
|
||||
line = line:style(THEME.selection.hovered)
|
||||
end
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
|
||||
return { ui.Paragraph(area, lines) }
|
||||
end
|
||||
|
||||
function Folder:preview(area)
|
||||
local target = cx.manager.preview.folder
|
||||
if target == nil then
|
||||
return ui.Paragraph(area, ui.Line {})
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for _, f in pairs(target.files) do
|
||||
lines[#lines + 1] = ui.Line { ui.Span(f.name) }
|
||||
end
|
||||
|
||||
return { ui.Paragraph(area, lines) }
|
||||
end
|
||||
|
||||
function Folder:render(area, args)
|
||||
if args.kind == 0 then
|
||||
return self:parent(area)
|
||||
elseif args.kind == 1 then
|
||||
return self:current(area)
|
||||
elseif args.kind == 2 then
|
||||
return self:preview(area)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,45 +1,54 @@
|
|||
Status = {}
|
||||
|
||||
function Status.mode()
|
||||
function Status.style()
|
||||
local mode = cx.manager.mode:upper()
|
||||
if mode == "SELECT" then
|
||||
return THEME.status.mode_select
|
||||
elseif mode == "UNSET" then
|
||||
return THEME.status.mode_unset
|
||||
else
|
||||
return THEME.status.mode_normal
|
||||
end
|
||||
end
|
||||
|
||||
function Status:mode()
|
||||
local mode = cx.manager.mode:upper()
|
||||
if mode == "UNSET" then
|
||||
mode = "UN-SET"
|
||||
end
|
||||
|
||||
return ui.Line(
|
||||
ui.Span(THEME.status.separator.opening):fg(THEME.status.mode_normal.bg),
|
||||
ui.Span(" " .. mode .. " "):style(THEME.status.mode_normal)
|
||||
)
|
||||
local style = self.style()
|
||||
return ui.Line {
|
||||
ui.Span(THEME.status.separator.opening):fg(style.bg),
|
||||
ui.Span(" " .. mode .. " "):style(style),
|
||||
}
|
||||
end
|
||||
|
||||
function Status.size()
|
||||
function Status:size()
|
||||
local h = cx.manager.current.hovered
|
||||
if h == nil then
|
||||
return ui.Span("")
|
||||
end
|
||||
|
||||
return ui.Line(
|
||||
ui.Span(" " .. utils.readable_size(h.length) .. " "):fg(THEME.status.mode_normal.bg):bg(THEME.status.fancy.bg),
|
||||
ui.Span(THEME.status.separator.closing):fg(THEME.status.fancy.bg)
|
||||
)
|
||||
local style = self.style()
|
||||
return ui.Line {
|
||||
ui.Span(" " .. utils.readable_size(h.length) .. " "):fg(style.bg):bg(THEME.status.fancy.bg),
|
||||
ui.Span(THEME.status.separator.closing):fg(THEME.status.fancy.bg),
|
||||
}
|
||||
end
|
||||
|
||||
function Status.name()
|
||||
function Status:name()
|
||||
local h = cx.manager.current.hovered
|
||||
if h == nil then
|
||||
return ui.Span("")
|
||||
end
|
||||
|
||||
return ui.Span(" " .. utils.basename(tostring(h.url)))
|
||||
return ui.Span(" " .. h.name)
|
||||
end
|
||||
|
||||
function Status.permissions()
|
||||
function Status:permissions()
|
||||
local h = cx.manager.current.hovered
|
||||
if h == nil then
|
||||
return ui.Span("")
|
||||
end
|
||||
|
||||
if h.permissions == nil then
|
||||
if h == nil or h.permissions == nil then
|
||||
return ui.Span("")
|
||||
end
|
||||
|
||||
|
|
@ -56,10 +65,10 @@ function Status.permissions()
|
|||
end
|
||||
spans[i] = ui.Span(c):style(style)
|
||||
end
|
||||
return ui.Line:from(spans)
|
||||
return ui.Line(spans)
|
||||
end
|
||||
|
||||
function Status.percentage()
|
||||
function Status:percentage()
|
||||
local percent = 0
|
||||
local cursor = cx.manager.current.cursor
|
||||
local length = #cx.manager.current.files
|
||||
|
|
@ -73,20 +82,22 @@ function Status.percentage()
|
|||
percent = string.format(" %3d%% ", percent)
|
||||
end
|
||||
|
||||
return ui.Line(
|
||||
local style = self.style()
|
||||
return ui.Line {
|
||||
ui.Span(" " .. THEME.status.separator.opening):fg(THEME.status.fancy.bg),
|
||||
ui.Span(percent):fg(THEME.status.mode_normal.bg):bg(THEME.status.fancy.bg)
|
||||
)
|
||||
ui.Span(percent):fg(style.bg):bg(THEME.status.fancy.bg),
|
||||
}
|
||||
end
|
||||
|
||||
function Status.position()
|
||||
function Status:position()
|
||||
local cursor = cx.manager.current.cursor
|
||||
local length = #cx.manager.current.files
|
||||
|
||||
return ui.Line(
|
||||
ui.Span(string.format(" %2d/%-2d ", cursor + 1, length)):style(THEME.status.mode_normal),
|
||||
ui.Span(THEME.status.separator.closing):fg(THEME.status.mode_normal.bg)
|
||||
)
|
||||
local style = self.style()
|
||||
return ui.Line {
|
||||
ui.Span(string.format(" %2d/%-2d ", cursor + 1, length)):style(style),
|
||||
ui.Span(THEME.status.separator.closing):fg(style.bg),
|
||||
}
|
||||
end
|
||||
|
||||
function Status:render(area)
|
||||
|
|
@ -95,11 +106,7 @@ function Status:render(area)
|
|||
:constraints({ ui.Constraint.Percentage(50), ui.Constraint.Percentage(50) })
|
||||
:split(area)
|
||||
|
||||
local left = ui.Line(self.mode(), self.size(), self.name())
|
||||
local right = ui.Line(self.permissions(), self.percentage(), self.position())
|
||||
|
||||
return ui.Paragraph.render(
|
||||
ui.Paragraph(left):area(chunks[1]),
|
||||
ui.Paragraph(right):align(ui.Alignment.RIGHT):area(chunks[2])
|
||||
)
|
||||
local left = ui.Line { self:mode(), self:size(), self:name() }
|
||||
local right = ui.Line { self:permissions(), self:percentage(), self:position() }
|
||||
return { ui.Paragraph(chunks[1], { left }), ui.Paragraph(chunks[2], { right }):align(ui.Alignment.RIGHT) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
local Line = {}
|
||||
|
||||
function Line:new(...)
|
||||
local o = {
|
||||
spans = { ... },
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Line:from(spans) return self:new(table.unpack(spans)) end
|
||||
|
||||
function Line:to_string()
|
||||
local s = ""
|
||||
for _, el in ipairs(self.spans) do
|
||||
local mt = getmetatable(el)
|
||||
if mt == ui.Line then
|
||||
for _, span in ipairs(el.spans) do
|
||||
s = s .. span:to_string():gsub("\n", "\\\n") .. "\n"
|
||||
end
|
||||
else
|
||||
s = s .. el:to_string():gsub("\n", "\\\n") .. "\n"
|
||||
end
|
||||
end
|
||||
return s.sub(s, 1, -2)
|
||||
end
|
||||
|
||||
setmetatable(Line, {
|
||||
__call = function(self, ...) return self:new(...) end,
|
||||
__tostring = function(self) return self:to_string() end,
|
||||
})
|
||||
|
||||
ui = ui or {}
|
||||
ui.Line = Line
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
local Paragraph = {}
|
||||
local Alignment = {
|
||||
LEFT = 0,
|
||||
CENTER = 1,
|
||||
RIGHT = 2,
|
||||
}
|
||||
|
||||
function Paragraph:new(...)
|
||||
local o = {
|
||||
alignment = 0,
|
||||
position = nil,
|
||||
lines = { ... },
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Paragraph:from(lines) return self:new(table.unpack(lines)) end
|
||||
|
||||
function Paragraph:align(align)
|
||||
self.alignment = align
|
||||
return self
|
||||
end
|
||||
|
||||
function Paragraph:area(rect)
|
||||
self.position = rect
|
||||
return self
|
||||
end
|
||||
|
||||
function Paragraph:to_string()
|
||||
local s = ""
|
||||
for _, line in ipairs(self.lines) do
|
||||
s = s .. line:to_string():gsub("\r", "\\\r") .. "\r"
|
||||
end
|
||||
return s.sub(s, 1, -2)
|
||||
end
|
||||
|
||||
function Paragraph.render(...)
|
||||
local s = "R"
|
||||
for _, paragraph in ipairs { ... } do
|
||||
s = s
|
||||
.. paragraph.position.x
|
||||
.. ","
|
||||
.. paragraph.position.y
|
||||
.. ","
|
||||
.. paragraph.position.width
|
||||
.. ","
|
||||
.. paragraph.position.height
|
||||
.. ","
|
||||
.. paragraph.alignment
|
||||
.. ";"
|
||||
.. paragraph:to_string():gsub("\0", "\\\0")
|
||||
.. "\0"
|
||||
end
|
||||
return s.sub(s, 1, -2)
|
||||
end
|
||||
|
||||
setmetatable(Paragraph, {
|
||||
__call = function(self, ...) return self:new(...) end,
|
||||
__tostring = function(self) return self:to_string() end,
|
||||
})
|
||||
|
||||
ui = ui or {}
|
||||
ui.Paragraph = Paragraph
|
||||
ui.Alignment = Alignment
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
local Span = {}
|
||||
|
||||
function Span:new(content)
|
||||
local o = {
|
||||
content = content,
|
||||
foreground = "",
|
||||
background = "",
|
||||
modifier = 0,
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Span:fg(color)
|
||||
self.foreground = color
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:bg(color)
|
||||
self.background = color
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:bold()
|
||||
self.modifier = self.modifier | 1
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:dim()
|
||||
self.modifier = self.modifier | 2
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:italic()
|
||||
self.modifier = self.modifier | 4
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:underline()
|
||||
self.modifier = self.modifier | 8
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:blink()
|
||||
self.modifier = self.modifier | 16
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:blink_rapid()
|
||||
self.modifier = self.modifier | 32
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:reverse()
|
||||
self.modifier = self.modifier | 64
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:hidden()
|
||||
self.modifier = self.modifier | 128
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:crossed()
|
||||
self.modifier = self.modifier | 256
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:reset()
|
||||
self.foreground = ""
|
||||
self.background = ""
|
||||
self.modifier = 0
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:style(style)
|
||||
if style.fg then
|
||||
self:fg(style.fg)
|
||||
end
|
||||
if style.bg then
|
||||
self:bg(style.bg)
|
||||
end
|
||||
if style.bold then
|
||||
self:bold()
|
||||
end
|
||||
if style.dim then
|
||||
self:dim()
|
||||
end
|
||||
if style.italic then
|
||||
self:italic()
|
||||
end
|
||||
if style.underline then
|
||||
self:underline()
|
||||
end
|
||||
if style.blink then
|
||||
self:blink()
|
||||
end
|
||||
if style.blink_rapid then
|
||||
self:blink_rapid()
|
||||
end
|
||||
if style.reverse then
|
||||
self:reverse()
|
||||
end
|
||||
if style.hidden then
|
||||
self:hidden()
|
||||
end
|
||||
if style.crossed then
|
||||
self:crossed()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Span:to_string()
|
||||
return string.format("%s,%s,,%s;%s", self.foreground, self.background, self.modifier, self.content)
|
||||
end
|
||||
|
||||
setmetatable(Span, {
|
||||
__call = function(self, content) return self:new(content) end,
|
||||
__tostring = function(self) return self:to_string() end,
|
||||
})
|
||||
|
||||
ui = ui or {}
|
||||
ui.Span = Span
|
||||
11
plugin/preset/ui.lua
Normal file
11
plugin/preset/ui.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
ui = {
|
||||
Alignment = {
|
||||
LEFT = 0,
|
||||
CENTER = 1,
|
||||
RIGHT = 2,
|
||||
},
|
||||
Direction = {
|
||||
HORIZONTAL = false,
|
||||
VERTICAL = true,
|
||||
},
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ impl Manager {
|
|||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
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("current", |_, me| me.named_user_value::<AnyUserData>("current"));
|
||||
reg.add_field_function_get("parent", |_, me| me.named_user_value::<AnyUserData>("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::Folder>(|reg| {
|
||||
|
|
@ -20,10 +21,6 @@ impl Manager {
|
|||
|
||||
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_method_get("window", |_, me| {
|
||||
// LUA.scope(|scope| scope.create_nonstatic_userdata(Files { files:
|
||||
// &me.0.files }))
|
||||
// });
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::files::Files>(|reg| {
|
||||
|
|
@ -46,6 +43,9 @@ impl Manager {
|
|||
})?;
|
||||
|
||||
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_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| {
|
||||
|
|
@ -60,6 +60,10 @@ impl Manager {
|
|||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<core::manager::Preview>(|reg| {
|
||||
reg.add_field_function_get("folder", |_, me| me.named_user_value::<AnyUserData>("folder"));
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -68,13 +72,14 @@ impl Manager {
|
|||
inner: &'a core::manager::Manager,
|
||||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value("current", Self::folder(scope, inner.current())?)?;
|
||||
ud.set_named_user_value("parent", inner.parent().and_then(|p| Self::folder(scope, p).ok()))?;
|
||||
ud.set_named_user_value("current", Self::folder(scope, inner.current())?)?;
|
||||
ud.set_named_user_value("preview", Self::preview(scope, inner.active())?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn folder<'a>(
|
||||
pub(crate) fn folder<'a>(
|
||||
scope: &mlua::Scope<'a, 'a>,
|
||||
inner: &'a core::manager::Folder,
|
||||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
|
|
@ -107,4 +112,24 @@ impl Manager {
|
|||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
scope.create_any_userdata_ref(inner)
|
||||
}
|
||||
|
||||
fn preview<'a>(
|
||||
scope: &mlua::Scope<'a, 'a>,
|
||||
tab: &'a core::manager::Tab,
|
||||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
let inner = tab.preview();
|
||||
|
||||
let ud = 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(scope, f).ok()),
|
||||
)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{MetaMethod, UserData};
|
||||
use mlua::{MetaMethod, UserData, UserDataRef};
|
||||
|
||||
pub struct Url(shared::Url);
|
||||
|
||||
|
|
@ -8,6 +8,11 @@ impl From<&shared::Url> for Url {
|
|||
|
||||
impl UserData for Url {
|
||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_function(
|
||||
MetaMethod::Eq,
|
||||
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(lhs.0 == rhs.0),
|
||||
);
|
||||
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.0.display().to_string()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
plugin/src/components.rs
Normal file
45
plugin/src/components.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use core::Ctx;
|
||||
|
||||
use mlua::{Result, Table, TableExt};
|
||||
use ratatui::layout;
|
||||
|
||||
use crate::{bindings, layout::{Paragraph, Rect}, GLOBALS, LUA};
|
||||
|
||||
pub struct Status;
|
||||
|
||||
impl Status {
|
||||
pub fn render(cx: &Ctx, area: layout::Rect) -> Result<Vec<Paragraph>> {
|
||||
LUA.scope(|scope| {
|
||||
let tbl = LUA.create_table()?;
|
||||
tbl.set("manager", bindings::Manager::make(scope, &cx.manager)?)?;
|
||||
tbl.set("tasks", bindings::Tasks::make(scope, &cx.tasks)?)?;
|
||||
GLOBALS.set("cx", tbl)?;
|
||||
|
||||
let comp: Table = GLOBALS.get("Status")?;
|
||||
comp.call_method::<_, _>("render", Rect(area))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Folder {
|
||||
pub kind: u8,
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
fn args(&self) -> Result<Table> {
|
||||
let tbl = LUA.create_table()?;
|
||||
tbl.set("kind", self.kind)?;
|
||||
Ok(tbl)
|
||||
}
|
||||
|
||||
pub fn render(self, cx: &Ctx, area: layout::Rect) -> Result<Vec<Paragraph>> {
|
||||
LUA.scope(|scope| {
|
||||
let tbl = LUA.create_table()?;
|
||||
tbl.set("manager", bindings::Manager::make(scope, &cx.manager)?)?;
|
||||
GLOBALS.set("cx", tbl)?;
|
||||
|
||||
let comp: Table = GLOBALS.get("Folder")?;
|
||||
comp.call_method::<_, _>("render", (Rect(area), self.args()?))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use ratatui::layout;
|
||||
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
// --- Rect
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Rect(layout::Rect);
|
||||
|
||||
impl From<layout::Rect> for Rect {
|
||||
fn from(value: layout::Rect) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Rect {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Rect",
|
||||
message: Some("expected a Rect".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Constraint
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Constraint(layout::Constraint);
|
||||
|
||||
impl Constraint {
|
||||
pub(super) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
|
||||
let constraint = LUA.create_table()?;
|
||||
constraint.set(
|
||||
"Percentage",
|
||||
LUA.create_function(|_, n: u16| Ok(Constraint(layout::Constraint::Percentage(n))))?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Ratio",
|
||||
LUA
|
||||
.create_function(|_, (a, b): (u32, u32)| Ok(Constraint(layout::Constraint::Ratio(a, b))))?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Length",
|
||||
LUA.create_function(|_, n: u16| Ok(Constraint(layout::Constraint::Length(n))))?,
|
||||
)?;
|
||||
constraint
|
||||
.set("Max", LUA.create_function(|_, n: u16| Ok(Constraint(layout::Constraint::Max(n))))?)?;
|
||||
constraint
|
||||
.set("Min", LUA.create_function(|_, n: u16| Ok(Constraint(layout::Constraint::Min(n))))?)?;
|
||||
|
||||
ui.set("Constraint", constraint)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Constraint {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Constraint",
|
||||
message: Some("expected a Constraint".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Constraint {}
|
||||
|
||||
// --- Layout
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Layout {
|
||||
direction: bool,
|
||||
margin: Option<layout::Margin>,
|
||||
constraints: Vec<layout::Constraint>,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub(super) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set("Layout", LUA.create_function(|_, ()| Ok(Self::default()))?)?;
|
||||
|
||||
let direction = LUA.create_table()?;
|
||||
direction.set("HORIZONTAL", false)?;
|
||||
direction.set("VERTICAL", true)?;
|
||||
ui.set("Direction", direction)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Layout {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Layout",
|
||||
message: Some("expected a Layout".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Layout {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("direction", |_, (ud, value): (AnyUserData, bool)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
me.direction = value;
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin", |_, (ud, value): (AnyUserData, u16)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
me.margin = Some(layout::Margin::new(value, value));
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin_h", |_, (ud, value): (AnyUserData, u16)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
if let Some(margin) = &mut me.margin {
|
||||
margin.horizontal = value;
|
||||
} else {
|
||||
me.margin = Some(layout::Margin::new(value, 0));
|
||||
}
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin_v", |_, (ud, value): (AnyUserData, u16)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
if let Some(margin) = &mut me.margin {
|
||||
margin.vertical = value;
|
||||
} else {
|
||||
me.margin = Some(layout::Margin::new(0, value));
|
||||
}
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("constraints", |_, (ud, value): (AnyUserData, Vec<Constraint>)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
me.constraints = value.into_iter().map(|c| c.0).collect();
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("split", |_, (ud, value): (AnyUserData, Rect)| {
|
||||
let me = ud.borrow::<Self>()?;
|
||||
|
||||
let mut layout = layout::Layout::new()
|
||||
.direction(if me.direction {
|
||||
layout::Direction::Vertical
|
||||
} else {
|
||||
layout::Direction::Horizontal
|
||||
})
|
||||
.constraints(me.constraints.as_slice());
|
||||
|
||||
if let Some(margin) = me.margin {
|
||||
layout = layout.horizontal_margin(margin.horizontal);
|
||||
layout = layout.vertical_margin(margin.vertical);
|
||||
}
|
||||
|
||||
let chunks: Vec<Rect> = layout.split(value.0).iter().copied().map(Rect).collect();
|
||||
Ok(chunks)
|
||||
});
|
||||
}
|
||||
}
|
||||
54
plugin/src/layout/constraint.rs
Normal file
54
plugin/src/layout/constraint.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use mlua::{FromLua, Lua, Table, UserData, Value};
|
||||
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Constraint(pub(super) ratatui::layout::Constraint);
|
||||
|
||||
impl Constraint {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
|
||||
let constraint = LUA.create_table()?;
|
||||
constraint.set(
|
||||
"Percentage",
|
||||
LUA
|
||||
.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Percentage(n))))?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Ratio",
|
||||
LUA.create_function(|_, (a, b): (u32, u32)| {
|
||||
Ok(Constraint(ratatui::layout::Constraint::Ratio(a, b)))
|
||||
})?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Length",
|
||||
LUA.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Length(n))))?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Max",
|
||||
LUA.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Max(n))))?,
|
||||
)?;
|
||||
constraint.set(
|
||||
"Min",
|
||||
LUA.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Min(n))))?,
|
||||
)?;
|
||||
|
||||
ui.set("Constraint", constraint)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Constraint {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Constraint",
|
||||
message: Some("expected a Constraint".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Constraint {}
|
||||
89
plugin/src/layout/layout.rs
Normal file
89
plugin/src/layout/layout.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
|
||||
use super::{Constraint, Rect};
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct Layout {
|
||||
direction: bool,
|
||||
margin: Option<ratatui::layout::Margin>,
|
||||
constraints: Vec<ratatui::layout::Constraint>,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set("Layout", LUA.create_function(|_, ()| Ok(Self::default()))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Layout {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Layout",
|
||||
message: Some("expected a Layout".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Layout {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("direction", |_, (ud, value): (AnyUserData, bool)| {
|
||||
ud.borrow_mut::<Self>()?.direction = value;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.margin = Some(ratatui::layout::Margin::new(value, value));
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin_h", |_, (ud, value): (AnyUserData, u16)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
if let Some(margin) = &mut me.margin {
|
||||
margin.horizontal = value;
|
||||
} else {
|
||||
me.margin = Some(ratatui::layout::Margin::new(value, 0));
|
||||
}
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("margin_v", |_, (ud, value): (AnyUserData, u16)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
if let Some(margin) = &mut me.margin {
|
||||
margin.vertical = value;
|
||||
} else {
|
||||
me.margin = Some(ratatui::layout::Margin::new(0, value));
|
||||
}
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("constraints", |_, (ud, value): (AnyUserData, Vec<Constraint>)| {
|
||||
ud.borrow_mut::<Self>()?.constraints = value.into_iter().map(|c| c.0).collect();
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("split", |_, (ud, value): (AnyUserData, Rect)| {
|
||||
let me = ud.borrow::<Self>()?;
|
||||
|
||||
let mut layout = ratatui::layout::Layout::new()
|
||||
.direction(if me.direction {
|
||||
ratatui::layout::Direction::Vertical
|
||||
} else {
|
||||
ratatui::layout::Direction::Horizontal
|
||||
})
|
||||
.constraints(me.constraints.as_slice());
|
||||
|
||||
if let Some(margin) = me.margin {
|
||||
layout = layout.horizontal_margin(margin.horizontal);
|
||||
layout = layout.vertical_margin(margin.vertical);
|
||||
}
|
||||
|
||||
let chunks: Vec<Rect> = layout.split(value.0).iter().copied().map(Rect).collect();
|
||||
Ok(chunks)
|
||||
});
|
||||
}
|
||||
}
|
||||
70
plugin/src/layout/line.rs
Normal file
70
plugin/src/layout/line.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
|
||||
use super::{Span, Style};
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Line(pub(super) ratatui::text::Line<'static>);
|
||||
|
||||
impl Line {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set(
|
||||
"Line",
|
||||
LUA.create_function(|_, value: Value| {
|
||||
if let Value::Table(tbl) = value {
|
||||
let seq: Vec<_> = tbl.sequence_values().filter_map(|v| v.ok()).collect();
|
||||
let mut spans = Vec::with_capacity(seq.len());
|
||||
for value in seq {
|
||||
if let Value::UserData(ud) = value {
|
||||
if let Ok(span) = ud.take::<Span>() {
|
||||
spans.push(span.0);
|
||||
} else if let Ok(line) = ud.take::<Line>() {
|
||||
spans.extend(line.0.spans.into_iter().collect::<Vec<_>>());
|
||||
} else {
|
||||
return Err(mlua::Error::external("expected a table of Spans or Lines"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(Self(ratatui::text::Line::from(spans)));
|
||||
}
|
||||
|
||||
Err(mlua::Error::external("expected a table of Spans or Lines"))
|
||||
})?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Line {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Line",
|
||||
message: Some("expected a Line".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Line {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("style", |_, (ud, style): (AnyUserData, Style)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
me.0.reset_style();
|
||||
me.0.patch_style(style.0);
|
||||
}
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("align", |_, (ud, align): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.0.alignment = Some(match align {
|
||||
1 => ratatui::prelude::Alignment::Center,
|
||||
2 => ratatui::prelude::Alignment::Right,
|
||||
_ => ratatui::prelude::Alignment::Left,
|
||||
});
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
17
plugin/src/layout/mod.rs
Normal file
17
plugin/src/layout/mod.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod constraint;
|
||||
mod layout;
|
||||
mod line;
|
||||
mod paragraph;
|
||||
mod rect;
|
||||
mod span;
|
||||
mod style;
|
||||
|
||||
pub(super) use constraint::*;
|
||||
pub(super) use layout::*;
|
||||
pub(super) use line::*;
|
||||
pub(super) use paragraph::*;
|
||||
pub(super) use rect::*;
|
||||
pub(super) use span::*;
|
||||
pub(super) use style::*;
|
||||
72
plugin/src/layout/paragraph.rs
Normal file
72
plugin/src/layout/paragraph.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Rect, Style};
|
||||
use crate::{layout::Line, GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Paragraph {
|
||||
area: ratatui::layout::Rect,
|
||||
|
||||
text: ratatui::text::Text<'static>,
|
||||
style: Option<ratatui::style::Style>,
|
||||
alignment: ratatui::prelude::Alignment,
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set(
|
||||
"Paragraph",
|
||||
LUA.create_function(|_, (area, lines): (Rect, Vec<Line>)| {
|
||||
Ok(Self {
|
||||
area: area.0,
|
||||
|
||||
text: lines.into_iter().map(|s| s.0).collect::<Vec<_>>().into(),
|
||||
style: None,
|
||||
alignment: Default::default(),
|
||||
})
|
||||
})?,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(self, buf: &mut ratatui::buffer::Buffer) {
|
||||
let mut p = ratatui::widgets::Paragraph::new(self.text);
|
||||
if let Some(style) = self.style {
|
||||
p = p.style(style);
|
||||
}
|
||||
|
||||
p = p.alignment(self.alignment);
|
||||
p.render(self.area, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Paragraph {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Paragraph",
|
||||
message: Some("expected a Paragraph".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Paragraph {
|
||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("style", |_, (ud, style): (AnyUserData, Style)| {
|
||||
ud.borrow_mut::<Self>()?.style = Some(style.0);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("align", |_, (ud, align): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.alignment = match align {
|
||||
1 => ratatui::prelude::Alignment::Center,
|
||||
2 => ratatui::prelude::Alignment::Right,
|
||||
_ => ratatui::prelude::Alignment::Left,
|
||||
};
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
26
plugin/src/layout/rect.rs
Normal file
26
plugin/src/layout/rect.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use mlua::{FromLua, Lua, UserData, Value};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Rect(pub(crate) ratatui::layout::Rect);
|
||||
|
||||
impl<'lua> FromLua<'lua> for Rect {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Rect",
|
||||
message: Some("expected a Rect".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
90
plugin/src/layout/span.rs
Normal file
90
plugin/src/layout/span.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use config::theme::Color;
|
||||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
|
||||
use super::Style;
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Span(pub(super) ratatui::text::Span<'static>);
|
||||
|
||||
impl Span {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set(
|
||||
"Span",
|
||||
LUA.create_function(|_, content: String| Ok(Self(ratatui::text::Span::raw(content))))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Span {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Span",
|
||||
message: Some("expected a Span".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Span {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("fg", |_, (ud, color): (AnyUserData, String)| {
|
||||
ud.borrow_mut::<Self>()?.0.style.fg = Color::try_from(color).ok().map(Into::into);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("bg", |_, (ud, color): (AnyUserData, String)| {
|
||||
ud.borrow_mut::<Self>()?.0.style.bg = Color::try_from(color).ok().map(Into::into);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("bold", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::BOLD;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("dim", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::DIM;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("italic", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::ITALIC;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("underline", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::UNDERLINED;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("blink", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::SLOW_BLINK;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("blink_rapid", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::RAPID_BLINK;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("hidden", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::HIDDEN;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("crossed", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier |= ratatui::style::Modifier::CROSSED_OUT;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("reset", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.style.add_modifier = ratatui::style::Modifier::empty();
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function("style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.0.style = match value {
|
||||
Value::Nil => ratatui::style::Style::default(),
|
||||
Value::Table(tbl) => Style::from(tbl).0,
|
||||
Value::UserData(ud) => ud.borrow::<Style>()?.0,
|
||||
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
||||
};
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
94
plugin/src/layout/style.rs
Normal file
94
plugin/src/layout/style.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use config::theme::Color;
|
||||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) struct Style(pub(super) ratatui::style::Style);
|
||||
|
||||
impl Style {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set("Style", LUA.create_function(|_, ()| Ok(Self::default()))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Table<'a>> for Style {
|
||||
fn from(value: Table) -> Self {
|
||||
let mut style = ratatui::style::Style::default();
|
||||
if let Ok(fg) = value.get::<_, String>("fg") {
|
||||
style.fg = Color::try_from(fg).ok().map(Into::into);
|
||||
}
|
||||
if let Ok(bg) = value.get::<_, String>("bg") {
|
||||
style.bg = Color::try_from(bg).ok().map(Into::into);
|
||||
}
|
||||
style.add_modifier = ratatui::style::Modifier::from_bits_truncate(
|
||||
value.get::<_, u16>("modifier").unwrap_or_default(),
|
||||
);
|
||||
Self(style)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Style {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Style",
|
||||
message: Some("expected a Style".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Style {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("fg", |_, (ud, color): (AnyUserData, String)| {
|
||||
ud.borrow_mut::<Self>()?.0.fg = Color::try_from(color).ok().map(Into::into);
|
||||
info!("fg: {:?}", ud.borrow::<Self>()?.0.fg);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("bg", |_, (ud, color): (AnyUserData, String)| {
|
||||
ud.borrow_mut::<Self>()?.0.bg = Color::try_from(color).ok().map(Into::into);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("bold", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::BOLD;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("dim", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::DIM;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("italic", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::ITALIC;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("underline", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::UNDERLINED;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("blink", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::SLOW_BLINK;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("blink_rapid", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::RAPID_BLINK;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("hidden", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::HIDDEN;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("crossed", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier |= ratatui::style::Modifier::CROSSED_OUT;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function("reset", |_, ud: AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.0.add_modifier = ratatui::style::Modifier::empty();
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
|
||||
mod bindings;
|
||||
mod components;
|
||||
mod layout;
|
||||
mod plugin;
|
||||
mod status;
|
||||
|
||||
pub use layout::*;
|
||||
pub use components::*;
|
||||
pub use plugin::*;
|
||||
pub use status::*;
|
||||
|
|
|
|||
|
|
@ -13,16 +13,13 @@ pub fn init() {
|
|||
let lua = Lua::new();
|
||||
|
||||
// Base
|
||||
lua.load(include_str!("../preset/ui.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/utils.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?;
|
||||
|
||||
// Elements
|
||||
lua.load(include_str!("../preset/elements/span.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/elements/line.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/elements/paragraph.lua")).exec()?;
|
||||
|
||||
// Components
|
||||
lua.load(include_str!("../preset/components/status.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/folder.lua")).exec()?;
|
||||
|
||||
// Initialize
|
||||
LUA.init(lua);
|
||||
|
|
@ -30,8 +27,12 @@ pub fn init() {
|
|||
bindings::init()?;
|
||||
|
||||
// Install
|
||||
layout::Layout::install()?;
|
||||
layout::Constraint::install()?;
|
||||
layout::Layout::install()?;
|
||||
layout::Line::install()?;
|
||||
layout::Paragraph::install()?;
|
||||
layout::Span::install()?;
|
||||
layout::Style::install()?;
|
||||
|
||||
let options =
|
||||
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
use core::Ctx;
|
||||
|
||||
use mlua::{Result, Table, TableExt};
|
||||
use ratatui::layout;
|
||||
|
||||
use crate::{bindings, Rect, GLOBALS, LUA};
|
||||
|
||||
pub struct Status;
|
||||
|
||||
impl Status {
|
||||
fn scope<T, F: FnOnce() -> Result<T>>(cx: &Ctx, f: F) -> Result<T> {
|
||||
// crate::Manager::register()?;
|
||||
|
||||
LUA.scope(|scope| {
|
||||
let manager = bindings::Manager::make(scope, &cx.manager)?;
|
||||
let tasks = bindings::Tasks::make(scope, &cx.tasks)?;
|
||||
|
||||
let cx = LUA.create_table()?;
|
||||
cx.set("manager", manager)?;
|
||||
cx.set("tasks", tasks)?;
|
||||
|
||||
GLOBALS.set("cx", cx)?;
|
||||
|
||||
f()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(cx: &Ctx, area: layout::Rect) -> Result<String> {
|
||||
Self::scope(cx, || {
|
||||
let status: Table = GLOBALS.get("Status")?;
|
||||
status.call_method::<_, String>("render", Rect::from(area))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue