feat: better theme system

This commit is contained in:
sxyazi 2023-09-16 21:46:48 +08:00
parent 15c34fed5c
commit 9ff11b2dbc
No known key found for this signature in database
48 changed files with 1095 additions and 286 deletions

View file

@ -5,6 +5,7 @@ members = [
"app",
"config",
"core",
"plugin",
"shared",
]

View file

@ -7,6 +7,7 @@ edition = "2021"
adaptor = { path = "../adaptor" }
config = { path = "../config" }
core = { path = "../core" }
plugin = { path = "../plugin" }
shared = { path = "../shared" }
# External dependencies

View file

@ -1,4 +1,4 @@
use core::{emit, files::FilesOp, input::InputMode, Event};
use core::{emit, files::FilesOp, input::InputMode, Ctx, Event};
use std::ffi::OsString;
use anyhow::{Ok, Result};
@ -7,7 +7,7 @@ use crossterm::event::KeyEvent;
use shared::{expand_url, Term};
use tokio::sync::oneshot;
use crate::{Ctx, Executor, Logs, Root, Signals};
use crate::{Executor, Logs, Root, Signals};
pub(super) struct App {
cx: Ctx,
@ -21,7 +21,7 @@ impl App {
let term = Term::start()?;
let signals = Signals::start()?;
let mut app = Self { cx: Ctx::new(), term: Some(term), signals };
let mut app = Self { cx: Ctx::make(), term: Some(term), signals };
while let Some(event) = app.signals.recv().await {
match event {

View file

@ -3,8 +3,6 @@ use core::{emit, files::FilesSorter, input::InputMode, manager::FinderCase};
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
use shared::{optional_bool, Url};
use super::Ctx;
pub(super) struct Executor;
impl Executor {

View file

@ -1,8 +1,9 @@
use core::Ctx;
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, style::{Color, Style}, widgets::{Paragraph, Widget}};
use shared::readable_path;
use super::Tabs;
use crate::Ctx;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,

View file

@ -1,11 +1,10 @@
use core::Ctx;
use std::ops::ControlFlow;
use config::THEME;
use ratatui::{buffer::Buffer, layout::{Alignment, Rect}, text::{Line, Span}, widgets::{Paragraph, Widget}};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::Ctx;
pub(super) struct Tabs<'a> {
cx: &'a Ctx,
}

View file

@ -1,6 +1,6 @@
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, style::{Color, Style, Stylize}, widgets::{List, ListItem, Widget}};
use core::Ctx;
use crate::context::Ctx;
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, style::{Color, Style, Stylize}, widgets::{List, ListItem, Widget}};
pub(super) struct Bindings<'a> {
cx: &'a Ctx,

View file

@ -1,7 +1,8 @@
use core::Ctx;
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, style::{Color, Style}, widgets::{Clear, Paragraph, Widget}};
use super::Bindings;
use crate::Ctx;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,

View file

@ -1,12 +1,10 @@
use core::input::InputMode;
use core::{input::InputMode, Ctx};
use std::ops::Range;
use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::{Line, Text}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}};
use shared::Term;
use crate::Ctx;
pub(crate) struct Input<'a> {
cx: &'a Ctx,
}

View file

@ -1,13 +1,13 @@
#![allow(clippy::module_inception)]
mod app;
mod context;
mod executor;
mod header;
mod help;
mod input;
mod logs;
mod manager;
mod parser;
mod root;
mod select;
mod signals;
@ -16,9 +16,9 @@ mod tasks;
mod which;
use app::*;
use context::*;
use executor::*;
use logs::*;
use parser::*;
use root::*;
use signals::*;
@ -30,6 +30,8 @@ async fn main() -> anyhow::Result<()> {
core::init();
plugin::init();
adaptor::init();
App::run().await

View file

@ -1,11 +1,9 @@
use core::files::File;
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 crate::Ctx;
pub(super) struct Folder<'a> {
cx: &'a Ctx,
folder: &'a core::manager::Folder,

View file

@ -1,8 +1,9 @@
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 crate::Ctx;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,

View file

@ -1,10 +1,9 @@
use core::manager::PreviewData;
use core::{manager::PreviewData, Ctx};
use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}};
use super::Folder;
use crate::Ctx;
pub(super) struct Preview<'a> {
cx: &'a Ctx,

70
app/src/parser.rs Normal file
View file

@ -0,0 +1,70 @@
use config::theme::Color;
use ratatui::{style::{Modifier, Style}, text::{Line, Span}, widgets::Paragraph};
pub struct Parser;
impl Parser {
pub fn span(s: &str) -> Span<'static> {
let Some((args, content)) = s.split_once(';') else {
return Span::raw(s.to_string());
};
let args = args.split(',').collect::<Vec<_>>();
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)
}
pub 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<_>>())
}
pub fn paragraph(s: &str) -> Paragraph {
let mut last = '\0';
let mut lines: Vec<String> = vec![String::new()];
for c in s.chars() {
if c == '\0' && last == '\\' {
let last = lines.last_mut().unwrap();
last.pop();
last.push(c);
} else if c == '\0' {
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<_>>())
}
}

View file

@ -1,6 +1,8 @@
use core::Ctx;
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
use super::{header, input, manager, select, status, tasks, which, Ctx};
use super::{header, input, manager, select, status, tasks, which};
use crate::help;
pub(super) struct Root<'a> {

View file

@ -1,6 +1,6 @@
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
use core::Ctx;
use crate::Ctx;
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
pub(crate) struct Select<'a> {
cx: &'a Ctx,

View file

@ -1,7 +1,9 @@
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::Widget};
use core::Ctx;
use super::{Left, Right};
use crate::Ctx;
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, text::Line, widgets::{Paragraph, Widget}};
use tracing::info;
use crate::Parser;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,
@ -18,7 +20,32 @@ impl<'a> Widget for Layout<'a> {
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(area);
Left::new(self.cx).render(chunks[0], buf);
Right::new(self.cx).render(chunks[1], buf);
// Left::new(self.cx).render(chunks[0], buf);
// Right::new(self.cx).render(chunks[1], buf);
let mut spans = vec![];
if let Ok(mode) = plugin::Status::mode(self.cx) {
spans.extend(Parser::line(&mode).spans);
}
let x = plugin::Status::size(self.cx);
if x.is_err() {
info!("Error: {:?}", x);
return;
}
if let Ok(size) = x {
spans.extend(Parser::line(&size).spans);
}
let x = plugin::Status::name(self.cx);
if x.is_err() {
info!("Error: {:?}", x);
return;
}
if let Ok(name) = x {
spans.extend(Parser::line(&name).spans);
}
Paragraph::new(Line::from(spans)).render(chunks[0], buf);
}
}

View file

@ -1,8 +1,6 @@
use config::THEME;
use ratatui::{buffer::Buffer, layout::Rect, style::Modifier, text::{Line, Span}, widgets::{Paragraph, Widget}};
use shared::readable_size;
use core::Ctx;
use crate::Ctx;
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
pub(super) struct Left<'a> {
cx: &'a Ctx,
@ -14,40 +12,35 @@ impl<'a> Left<'a> {
impl<'a> Widget for Left<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let folder = self.cx.manager.current();
let mode = self.cx.manager.active().mode();
// Colors
let primary = mode.color(&THEME.status.primary);
let secondary = mode.color(&THEME.status.secondary);
let body = mode.color(&THEME.status.body);
// Separator
let separator = &THEME.status.separator;
// Mode
let mut spans = Vec::with_capacity(5);
spans.push(Span::styled(&separator.opening, primary.fg()));
spans.push(Span::styled(
format!(" {mode} "),
primary.bg().fg(**secondary).add_modifier(Modifier::BOLD),
));
if let Some(h) = &folder.hovered {
// Length
{
let size = if h.is_dir() { folder.files.size(h.url()) } else { None };
spans.push(Span::styled(
format!(" {} ", readable_size(size.unwrap_or(h.length()))),
body.bg().fg(**primary),
));
spans.push(Span::styled(&separator.closing, body.fg()));
}
// Filename
spans.push(Span::raw(format!(" {} ", h.name_display().unwrap())));
}
Paragraph::new(Line::from(spans)).render(area, buf);
// let folder = self.cx.manager.current();
// let mode = self.cx.manager.active().mode();
//
// // Separator
// let separator = &THEME.status.separator;
//
// // Mode
// let mut spans = Vec::with_capacity(5);
// spans.push(Span::styled(&separator.opening, primary.fg()));
// spans.push(Span::styled(
// format!(" {mode} "),
// primary.bg().fg(**secondary).add_modifier(Modifier::BOLD),
// ));
//
// if let Some(h) = &folder.hovered {
// // Length
// {
// let size = if h.is_dir() { folder.files.size(h.url()) } else { None };
// spans.push(Span::styled(
// format!(" {} ", readable_size(size.unwrap_or(h.length()))),
// body.bg().fg(**primary),
// ));
// spans.push(Span::styled(&separator.closing, body.fg()));
// }
//
// // Filename
// spans.push(Span::raw(format!(" {} ", h.name_display().unwrap())));
// }
//
// Paragraph::new(Line::from(spans)).render(area, buf);
}
}

View file

@ -1,8 +1,8 @@
use core::Ctx;
use config::THEME;
use ratatui::{buffer::Buffer, layout::Rect, text::Span, widgets::{Gauge, Widget}};
use crate::Ctx;
pub(super) struct Progress<'a> {
cx: &'a Ctx,
}
@ -19,11 +19,11 @@ impl<'a> Widget for Progress<'a> {
}
Gauge::default()
.gauge_style(THEME.progress.gauge.get())
.gauge_style(THEME.status.progress_gauge.get())
.percent(progress.0 as u16)
.label(Span::styled(
format!("{:>3}%, {} left", progress.0, progress.1),
THEME.progress.label.get(),
THEME.status.progress_label.get(),
))
.render(area, buf);
}

View file

@ -1,94 +1,96 @@
use core::Ctx;
use config::THEME;
use ratatui::{buffer::Buffer, layout::{Alignment, Rect}, text::{Line, Span}, widgets::{Paragraph, Widget}};
use super::Progress;
use crate::Ctx;
pub(super) struct Right<'a> {
cx: &'a Ctx,
}
impl<'a> Right<'a> {
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
#[cfg(not(target_os = "windows"))]
fn permissions(&self, s: &str) -> Vec<Span> {
// Colors
let mode = self.cx.manager.active().mode();
let tertiary = mode.color(&THEME.status.tertiary);
let info = mode.color(&THEME.status.info);
let success = mode.color(&THEME.status.success);
let warning = mode.color(&THEME.status.warning);
let danger = mode.color(&THEME.status.danger);
s.chars()
.map(|c| match c {
'-' => Span::styled("-", tertiary.fg()),
'r' => Span::styled("r", warning.fg()),
'w' => Span::styled("w", danger.fg()),
'x' | 's' | 'S' | 't' | 'T' => Span::styled(c.to_string(), info.fg()),
_ => Span::styled(c.to_string(), success.fg()),
})
.collect()
}
fn position(&self) -> Vec<Span> {
// Colors
let mode = self.cx.manager.active().mode();
let primary = mode.color(&THEME.status.primary);
let secondary = mode.color(&THEME.status.secondary);
let body = mode.color(&THEME.status.body);
// Separator
let separator = &THEME.status.separator;
let cursor = self.cx.manager.current().cursor();
let length = self.cx.manager.current().files.len();
let percent = if cursor == 0 || length == 0 { 0 } else { (cursor + 1) * 100 / length };
vec![
Span::raw(" "),
Span::styled(&separator.opening, body.fg()),
Span::styled(
if percent == 0 { " Top ".to_string() } else { format!(" {:>3}% ", percent) },
body.bg().fg(**primary),
),
Span::styled(
format!(" {:>2}/{:<2} ", (cursor + 1).min(length), length),
primary.bg().fg(**secondary),
),
Span::styled(&separator.closing, primary.fg()),
]
}
}
// impl<'a> Right<'a> {
// pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
//
// #[cfg(not(target_os = "windows"))]
// fn permissions(&self, s: &str) -> Vec<Span> {
// // Colors
// let mode = self.cx.manager.active().mode();
// let tertiary = mode.color(&THEME.status.tertiary);
// let info = mode.color(&THEME.status.info);
// let success = mode.color(&THEME.status.success);
// let warning = mode.color(&THEME.status.warning);
// let danger = mode.color(&THEME.status.danger);
//
// s.chars()
// .map(|c| match c {
// '-' => Span::styled("-", tertiary.fg()),
// 'r' => Span::styled("r", warning.fg()),
// 'w' => Span::styled("w", danger.fg()),
// 'x' | 's' | 'S' | 't' | 'T' => Span::styled(c.to_string(), info.fg()),
// _ => Span::styled(c.to_string(), success.fg()),
// })
// .collect()
// }
//
// fn position(&self) -> Vec<Span> {
// // Colors
// let mode = self.cx.manager.active().mode();
// let primary = mode.color(&THEME.status.primary);
// let secondary = mode.color(&THEME.status.secondary);
// let body = mode.color(&THEME.status.body);
//
// // Separator
// let separator = &THEME.status.separator;
//
// let cursor = self.cx.manager.current().cursor();
// let length = self.cx.manager.current().files.len();
// let percent = if cursor == 0 || length == 0 { 0 } else { (cursor + 1) * 100 /
// length };
//
// vec![
// Span::raw(" "),
// Span::styled(&separator.opening, body.fg()),
// Span::styled(
// if percent == 0 { " Top ".to_string() } else { format!(" {:>3}% ", percent)
// }, body.bg().fg(**primary),
// ),
// Span::styled(
// format!(" {:>2}/{:<2} ", (cursor + 1).min(length), length),
// primary.bg().fg(**secondary),
// ),
// Span::styled(&separator.closing, primary.fg()),
// ]
// }
// }
impl Widget for Right<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let manager = self.cx.manager.current();
let mut spans = Vec::with_capacity(20);
// Permissions
#[cfg(not(target_os = "windows"))]
if let Some(h) = &manager.hovered {
use std::os::unix::prelude::PermissionsExt;
spans.extend(self.permissions(&shared::file_mode(h.meta().permissions().mode())))
}
// Position
spans.extend(self.position());
// Progress
let line = Line::from(spans);
Progress::new(self.cx).render(
Rect {
x: area.x + area.width.saturating_sub(21 + line.width() as u16),
y: area.y,
width: 20.min(area.width),
height: 1,
},
buf,
);
Paragraph::new(line).alignment(Alignment::Right).render(area, buf);
// let manager = self.cx.manager.current();
// let mut spans = Vec::with_capacity(20);
//
// // Permissions
// #[cfg(not(target_os = "windows"))]
// if let Some(h) = &manager.hovered {
// use std::os::unix::prelude::PermissionsExt;
// spans.extend(self.permissions(&shared::file_mode(h.meta().permissions().
// mode()))) }
//
// // Position
// spans.extend(self.position());
//
// // Progress
// let line = Line::from(spans);
// Progress::new(self.cx).render(
// Rect {
// x: area.x + area.width.saturating_sub(21 + line.width() as u16),
// y: area.y,
// width: 20.min(area.width),
// height: 1,
// },
// buf,
// );
//
// Paragraph::new(line).alignment(Alignment::Right).render(area, buf);
}
}

View file

@ -1,9 +1,8 @@
use core::tasks::TASKS_PERCENT;
use core::{tasks::TASKS_PERCENT, Ctx};
use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Direction, Rect}, style::{Color, Modifier, Style}, widgets::{Block, BorderType, Borders, List, ListItem, Padding, Widget}};
use super::Clear;
use crate::Ctx;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,

View file

@ -1,7 +1,8 @@
use core::Ctx;
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, style::{Color, Style}, widgets::{Block, Clear, Widget}};
use super::Side;
use crate::Ctx;
pub(crate) struct Which<'a> {
cx: &'a Ctx,

View file

@ -4,20 +4,18 @@ inactive = { fg = "#C8D3F8", bg = "#484D66" }
max_width = 1
[status]
primary = { normal = "#80AEFA", select = "#CD9EFC", unset = "#FFA577" }
secondary = { normal = "#1E2031", select = "#23273B", unset = "#23273B" }
tertiary = { normal = "#6D738F", select = "#6D738F", unset = "#6D738F" }
body = { normal = "#484D66", select = "#484D66", unset = "#484D66" }
emphasis = { normal = "#C8D3F8", select = "#C8D3F8", unset = "#C8D3F8" }
info = { normal = "#7AD9E5", select = "#7AD9E5", unset = "#7AD9E5" }
success = { normal = "#97DC8D", select = "#97DC8D", unset = "#97DC8D" }
warning = { normal = "#F3D398", select = "#F3D398", unset = "#F3D398" }
danger = { normal = "#FA7F94", select = "#FA7F94", unset = "#FA7F94" }
plain = { fg = "#FFFFFF" }
fancy = { bg = "#45475D" }
separator = { opening = "", closing = "" }
[progress]
gauge = { fg = "#FFA577", bg = "#484D66" }
label = { fg = "#FFFFFF", bold = true }
# Mode
mode_normal = { fg = "#181827", bg = "#7DB5FF", bold = true }
mode_select = { fg = "#1E1E30", bg = "#D2A4FE", bold = true }
mode_unset = { fg = "#1E1E30", bg = "#FFAF80", bold = true }
# Progress
progress_label = { fg = "#FFFFFF", bold = true }
progress_gauge = { fg = "#FFA577", bg = "#484D66" }
[selection]
hovered = { fg = "#1E2031", bg = "#80AEFA" }

0
config/src/bindings.rs Normal file
View file

View file

@ -2,6 +2,7 @@
use shared::RoCell;
mod bindings;
mod boot;
pub mod keymap;
mod log;

View file

@ -1,47 +1,53 @@
use std::ops::Deref;
use anyhow::{bail, Result};
use ratatui::style;
use serde::Deserialize;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Deserialize)]
#[serde(try_from = "String")]
pub struct Color(pub(super) style::Color);
pub struct Color([u8; 3]);
impl Default for Color {
fn default() -> Self { Self(style::Color::Reset) }
impl TryFrom<&str> for Color {
type Error = 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)?,
]))
}
}
impl TryFrom<String> for Color {
type Error = anyhow::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.len() < 7 {
bail!("Invalid color: {s}");
}
Ok(Self(style::Color::Rgb(
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 try_from(s: String) -> Result<Self, Self::Error> { Self::try_from(s.as_str()) }
}
impl Deref for Color {
type Target = style::Color;
impl From<&Color> for style::Color {
fn from(&Color(rgb): &Color) -> Self { style::Color::Rgb(rgb[0], rgb[1], rgb[2]) }
}
fn deref(&self) -> &Self::Target { &self.0 }
impl From<Color> for style::Color {
fn from(Color(rgb): Color) -> Self { style::Color::Rgb(rgb[0], rgb[1], rgb[2]) }
}
impl Color {
pub fn fg(&self) -> style::Style { style::Style::new().fg(self.0) }
#[inline]
pub fn fg(&self) -> style::Style { style::Style::new().fg(self.into()) }
pub fn bg(&self) -> style::Style { style::Style::new().bg(self.0) }
#[inline]
pub fn bg(&self) -> style::Style { style::Style::new().bg(self.into()) }
}
#[derive(Deserialize)]
pub struct ColorGroup {
pub normal: Color,
pub select: Color,
pub unset: Color,
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("#{:02X}{:02X}{:02X}", self.0[0], self.0[1], self.0[2]))
}
}

View file

@ -38,8 +38,10 @@ impl Filetype {
mime: Option<Pattern>,
fg: Option<Color>,
bg: Option<Color>,
bold: Option<bool>,
underline: Option<bool>,
#[serde(default)]
bold: bool,
#[serde(default)]
underline: bool,
}
Ok(

14
config/src/theme/list.rs Normal file
View file

@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
use super::Style;
#[derive(Deserialize, Serialize)]
pub struct Selection {
pub hovered: Style,
}
#[derive(Deserialize, Serialize)]
pub struct Marker {
pub selecting: Style,
pub selected: Style,
}

View file

@ -1,11 +1,15 @@
mod color;
mod filetype;
mod icon;
mod list;
mod status;
mod style;
mod theme;
pub use color::*;
pub use filetype::*;
pub use icon::*;
pub use list::*;
pub use status::*;
pub use style::*;
pub use theme::*;

View file

@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use super::Style;
#[derive(Deserialize, Serialize)]
pub struct Status {
pub plain: Style,
pub fancy: Style,
pub separator: StatusSeparator,
// Mode
pub mode_normal: Style,
pub mode_select: Style,
pub mode_unset: Style,
// Progress
pub progress_label: Style,
pub progress_gauge: Style,
}
#[derive(Deserialize, Serialize)]
pub struct StatusSeparator {
pub opening: String,
pub closing: String,
}

View file

@ -1,14 +1,16 @@
use ratatui::style::{self, Modifier};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use super::Color;
#[derive(Deserialize)]
#[derive(Deserialize, Serialize)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub bold: Option<bool>,
pub underline: Option<bool>,
#[serde(default)]
pub bold: bool,
#[serde(default)]
pub underline: bool,
}
impl Style {
@ -16,24 +18,16 @@ impl Style {
let mut style = style::Style::new();
if let Some(fg) = &self.fg {
style = style.fg(fg.0);
style = style.fg(fg.into());
}
if let Some(bg) = &self.bg {
style = style.bg(bg.0);
style = style.bg(bg.into());
}
if let Some(bold) = self.bold {
if bold {
style = style.add_modifier(Modifier::BOLD);
} else {
style = style.remove_modifier(Modifier::BOLD);
}
if self.bold {
style = style.add_modifier(Modifier::BOLD);
}
if let Some(underline) = self.underline {
if underline {
style = style.add_modifier(Modifier::UNDERLINED);
} else {
style = style.remove_modifier(Modifier::UNDERLINED);
}
if self.underline {
style = style.add_modifier(Modifier::UNDERLINED);
}
style
}

View file

@ -1,13 +1,13 @@
use std::path::PathBuf;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use shared::expand_path;
use validator::Validate;
use super::{ColorGroup, Filetype, Icon, Style};
use super::{Filetype, Icon, Marker, Selection, Status, Style};
use crate::{validation::check_validation, MERGED_THEME};
#[derive(Deserialize, Validate)]
#[derive(Deserialize, Serialize, Validate)]
pub struct Tab {
pub active: Style,
pub inactive: Style,
@ -15,60 +15,22 @@ pub struct Tab {
pub max_width: u8,
}
#[derive(Deserialize)]
pub struct Status {
pub primary: ColorGroup,
pub secondary: ColorGroup,
pub tertiary: ColorGroup,
pub body: ColorGroup,
pub emphasis: ColorGroup,
pub info: ColorGroup,
pub success: ColorGroup,
pub warning: ColorGroup,
pub danger: ColorGroup,
pub separator: StatusSeparator,
}
#[derive(Deserialize)]
pub struct StatusSeparator {
pub opening: String,
pub closing: String,
}
#[derive(Deserialize)]
pub struct Progress {
pub gauge: Style,
pub label: Style,
}
#[derive(Deserialize)]
pub struct Selection {
pub hovered: Style,
}
#[derive(Deserialize)]
pub struct Marker {
pub selecting: Style,
pub selected: Style,
}
#[derive(Deserialize)]
#[derive(Deserialize, Serialize)]
pub struct Preview {
pub hovered: Style,
pub syntect_theme: PathBuf,
}
#[derive(Deserialize)]
#[derive(Deserialize, Serialize)]
pub struct Theme {
pub tab: Tab,
pub status: Status,
pub progress: Progress,
pub selection: Selection,
pub marker: Marker,
pub preview: Preview,
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize")]
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
pub filetypes: Vec<Filetype>,
#[serde(deserialize_with = "Icon::deserialize")]
#[serde(deserialize_with = "Icon::deserialize", skip_serializing)]
pub icons: Vec<Icon>,
}

View file

@ -1,10 +1,10 @@
use core::{help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
use config::keymap::KeymapLayer;
use crossterm::terminal::WindowSize;
use ratatui::prelude::Rect;
use shared::Term;
use crate::{help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
pub struct Ctx {
pub manager: Manager,
pub which: Which,
@ -15,7 +15,7 @@ pub struct Ctx {
}
impl Ctx {
pub(super) fn new() -> Self {
pub fn make() -> Self {
Self {
manager: Manager::make(),
which: Default::default(),
@ -26,7 +26,7 @@ impl Ctx {
}
}
pub(super) fn area(&self, pos: &Position) -> Rect {
pub fn area(&self, pos: &Position) -> Rect {
let WindowSize { columns, rows, .. } = Term::size();
let (x, y) = match pos {
@ -57,7 +57,7 @@ impl Ctx {
}
#[inline]
pub(super) fn cursor(&self) -> Option<(u16, u16)> {
pub fn cursor(&self) -> Option<(u16, u16)> {
if self.input.visible {
let Rect { x, y, .. } = self.area(&self.input.position);
return Some((x + 1 + self.input.cursor(), y + 1));
@ -69,7 +69,7 @@ impl Ctx {
}
#[inline]
pub(super) fn layer(&self) -> KeymapLayer {
pub fn layer(&self) -> KeymapLayer {
if self.which.visible {
KeymapLayer::Which
} else if self.help.visible() {
@ -86,7 +86,7 @@ impl Ctx {
}
#[inline]
pub(super) fn image_layer(&self) -> bool {
pub fn image_layer(&self) -> bool {
!matches!(self.layer(), KeymapLayer::Which | KeymapLayer::Help | KeymapLayer::Tasks)
}
}

View file

@ -75,7 +75,10 @@ impl File {
#[inline]
pub fn length(&self) -> u64 { self.length }
// --- Link to
// --- Link to / Is link
#[inline]
pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() }
#[inline]
pub fn is_link(&self) -> bool { self.is_link }
}

View file

@ -7,6 +7,7 @@
)]
mod blocker;
mod context;
mod event;
pub mod external;
pub mod files;
@ -21,6 +22,7 @@ pub mod tasks;
pub mod which;
pub use blocker::*;
pub use context::*;
pub use event::*;
pub use highlighter::*;
pub use position::*;

View file

@ -1,8 +1,6 @@
use std::{collections::BTreeSet, fmt::Display};
use std::collections::BTreeSet;
use config::theme::{self, ColorGroup};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Mode {
#[default]
Normal,
@ -11,15 +9,6 @@ pub enum Mode {
}
impl Mode {
#[inline]
pub fn color<'a>(&self, group: &'a ColorGroup) -> &'a theme::Color {
match *self {
Mode::Normal => &group.normal,
Mode::Select(..) => &group.select,
Mode::Unset(..) => &group.unset,
}
}
#[inline]
pub fn visual(&self) -> Option<(usize, &BTreeSet<usize>)> {
match self {
@ -59,12 +48,13 @@ impl Mode {
pub fn is_visual(&self) -> bool { matches!(self, Mode::Select(..) | Mode::Unset(..)) }
}
impl Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Mode::Normal => write!(f, "NORMAL"),
Mode::Select(..) => write!(f, "SELECT"),
Mode::Unset(..) => write!(f, "UN-SET"),
impl ToString for Mode {
fn to_string(&self) -> String {
match self {
Mode::Normal => "normal",
Mode::Select(..) => "select",
Mode::Unset(..) => "unset",
}
.to_string()
}
}

14
plugin/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "plugin"
version = "0.1.0"
edition = "2021"
[dependencies]
config = { path = "../config" }
core = { path = "../core" }
shared = { path = "../shared" }
# External dependencies
anyhow = "^1"
mlua = { version = "^0", features = [ "lua54", "serialize" ] }
tracing = "^0"

View file

@ -0,0 +1,22 @@
MIT LICENSE
Copyright (c) 2022 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,312 @@
local inspect = { Options = {} }
inspect.KEY = setmetatable({}, { __tostring = function() return "inspect.KEY" end })
inspect.METATABLE = setmetatable({}, { __tostring = function() return "inspect.METATABLE" end })
local rep = string.rep
local match = string.match
local char = string.char
local gsub = string.gsub
local fmt = string.format
local function rawpairs(t) return next, t, nil end
local function smartQuote(str)
if match(str, '"') and not match(str, "'") then
return "'" .. str .. "'"
end
return '"' .. gsub(str, '"', '\\"') .. '"'
end
local shortControlCharEscapes = {
["\a"] = "\\a",
["\b"] = "\\b",
["\f"] = "\\f",
["\n"] = "\\n",
["\r"] = "\\r",
["\t"] = "\\t",
["\v"] = "\\v",
["\127"] = "\\127",
}
local longControlCharEscapes = { ["\127"] = "\127" }
for i = 0, 31 do
local ch = char(i)
if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\" .. i
longControlCharEscapes[ch] = fmt("\\%03d", i)
end
end
local function escape(str)
return (gsub(gsub(gsub(str, "\\", "\\\\"), "(%c)%f[0-9]", longControlCharEscapes), "%c", shortControlCharEscapes))
end
local luaKeywords = {
["and"] = true,
["break"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["goto"] = true,
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["then"] = true,
["true"] = true,
["until"] = true,
["while"] = true,
}
local function isIdentifier(str)
return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") and not luaKeywords[str]
end
local flr = math.floor
local function isSequenceKey(k, sequenceLength)
return type(k) == "number" and flr(k) == k and 1 <= k and k <= sequenceLength
end
local defaultTypeOrders = {
["number"] = 1,
["boolean"] = 2,
["string"] = 3,
["table"] = 4,
["function"] = 5,
["userdata"] = 6,
["thread"] = 7,
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
if ta == tb and (ta == "string" or ta == "number") then
return a < b
end
local dta = defaultTypeOrders[ta] or 100
local dtb = defaultTypeOrders[tb] or 100
return dta == dtb and ta < tb or dta < dtb
end
local function getKeys(t)
local seqLen = 1
while t[seqLen] ~= nil do
seqLen = seqLen + 1
end
seqLen = seqLen - 1
local keys, keysLen = {}, 0
for k in rawpairs(t) do
if not isSequenceKey(k, seqLen) then
keysLen = keysLen + 1
keys[keysLen] = k
end
end
table.sort(keys, sortKeys)
return keys, keysLen, seqLen
end
local function countCycles(x, cycles)
if type(x) == "table" then
if cycles[x] then
cycles[x] = cycles[x] + 1
else
cycles[x] = 1
for k, v in rawpairs(x) do
countCycles(k, cycles)
countCycles(v, cycles)
end
countCycles(getmetatable(x), cycles)
end
end
end
local function makePath(path, a, b)
local newPath = {}
local len = #path
for i = 1, len do
newPath[i] = path[i]
end
newPath[len + 1] = a
newPath[len + 2] = b
return newPath
end
local function processRecursive(process, item, path, visited)
if item == nil then
return nil
end
if visited[item] then
return visited[item]
end
local processed = process(item, path)
if type(processed) == "table" then
local processedCopy = {}
visited[item] = processedCopy
local processedKey
for k, v in rawpairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
end
end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
if type(mt) ~= "table" then
mt = nil
end
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
local function puts(buf, str)
buf.n = buf.n + 1
buf[buf.n] = str
end
local Inspector = {}
local Inspector_mt = { __index = Inspector }
local function tabify(inspector) puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) end
function Inspector:getId(v)
local id = self.ids[v]
local ids = self.ids
if not id then
local tv = type(v)
id = (ids[tv] or 0) + 1
ids[v], ids[tv] = id, id
end
return tostring(id)
end
function Inspector:putValue(v)
local buf = self.buf
local tv = type(v)
if tv == "string" then
puts(buf, smartQuote(escape(v)))
elseif tv == "number" or tv == "boolean" or tv == "nil" or tv == "cdata" or tv == "ctype" then
puts(buf, tostring(v))
elseif tv == "table" and not self.ids[v] then
local t = v
if t == inspect.KEY or t == inspect.METATABLE then
puts(buf, tostring(t))
elseif self.level >= self.depth then
puts(buf, "{...}")
else
if self.cycles[t] > 1 then
puts(buf, fmt("<%d>", self:getId(t)))
end
local keys, keysLen, seqLen = getKeys(t)
puts(buf, "{")
self.level = self.level + 1
for i = 1, seqLen + keysLen do
if i > 1 then
puts(buf, ",")
end
if i <= seqLen then
puts(buf, " ")
self:putValue(t[i])
else
local k = keys[i - seqLen]
tabify(self)
if isIdentifier(k) then
puts(buf, k)
else
puts(buf, "[")
self:putValue(k)
puts(buf, "]")
end
puts(buf, " = ")
self:putValue(t[k])
end
end
local mt = getmetatable(t)
if type(mt) == "table" then
if seqLen + keysLen > 0 then
puts(buf, ",")
end
tabify(self)
puts(buf, "<metatable> = ")
self:putValue(mt)
end
self.level = self.level - 1
if keysLen > 0 or type(mt) == "table" then
tabify(self)
elseif seqLen > 0 then
puts(buf, " ")
end
puts(buf, "}")
end
else
puts(buf, fmt("<%s %d>", tv, self:getId(v)))
end
end
function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or math.huge
local newline = options.newline or "\n"
local indent = options.indent or " "
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local cycles = {}
countCycles(root, cycles)
local inspector = setmetatable({
buf = { n = 0 },
ids = {},
cycles = cycles,
depth = depth,
level = 0,
newline = newline,
indent = indent,
}, Inspector_mt)
inspector:putValue(root)
return table.concat(inspector.buf)
end
setmetatable(inspect, {
__call = function(_, root, options) return inspect.inspect(root, options) end,
})
yazi = yazi or {}
yazi.inspect = inspect
yazi.print = function(...)
local args = { ... }
for i = 1, #args do
print(inspect(args[i]))
end
end

28
plugin/preset/line.lua Normal file
View file

@ -0,0 +1,28 @@
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 _, span in ipairs(self.spans) do
s = s .. span:to_string():gsub("\n", "\\\n") .. "\n"
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,
})
yazi = yazi or {}
yazi.Line = Line

View file

@ -0,0 +1,28 @@
local Paragraph = {}
function Paragraph:new(...)
local o = {
lines = { ... },
}
setmetatable(o, self)
self.__index = self
return o
end
function Paragraph:from(lines) return self:new(table.unpack(lines)) end
function Paragraph:to_string()
local s = ""
for _, line in ipairs(self.lines) do
s = s .. line: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,
})
yazi = yazi or {}
yazi.Paragraph = Paragraph

124
plugin/preset/span.lua Normal file
View file

@ -0,0 +1,124 @@
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,
})
yazi = yazi or {}
yazi.Span = Span

42
plugin/preset/status.lua Normal file
View file

@ -0,0 +1,42 @@
function mode()
local mode = cx.manager.mode:upper()
if mode == "UNSET" then
mode = "UN-SET"
end
return yazi
.Line(
yazi.Span(THEME.status.separator.opening):fg(THEME.status.mode_normal.bg),
yazi.Span(" " .. mode .. " "):style(THEME.status.mode_normal)
)
:to_string()
end
function size()
local hovered = cx.manager.hovered
if hovered == nil then
return ""
end
return yazi
.Line(
yazi.Span(" " .. hovered.length .. " "):fg(THEME.status.mode_normal.bg):bg(THEME.status.fancy.bg),
yazi.Span(THEME.status.separator.closing):fg(THEME.status.fancy.bg)
)
:to_string()
end
function name()
local hovered = cx.manager.hovered
if hovered == nil then
return ""
end
return yazi.Span(" " .. yazi.basename(hovered.url)):to_string()
end
function permissions() end
function percentage() end
function position() end

3
plugin/preset/utils.lua Normal file
View file

@ -0,0 +1,3 @@
yazi = yazi or {}
function yazi.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end

65
plugin/src/bindings.rs Normal file
View file

@ -0,0 +1,65 @@
use mlua::{LuaSerdeExt, UserData, Value};
use shared::Url;
// Manager
pub struct Manager<'a>(&'a core::manager::Manager);
impl<'a> Manager<'a> {
pub fn new(manager: &'a core::manager::Manager) -> Self { Self(manager) }
}
impl<'a> UserData for Manager<'a> {
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("mode", |_, this| Ok(this.0.active().mode().to_string()));
fields.add_field_method_get("hovered", |_, this| {
Ok(this.0.current().hovered.as_ref().map(File::from))
})
}
}
// Tasks
pub struct Tasks<'a>(&'a core::tasks::Tasks);
impl<'a> Tasks<'a> {
pub fn new(tasks: &'a core::tasks::Tasks) -> Self { Self(tasks) }
}
impl<'a> UserData for Tasks<'a> {
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("progress", |lua, this| lua.to_value(&this.0.progress))
}
}
// File
pub struct File {
pub(super) url: Url,
pub(super) length: u64,
pub(super) link_to: Option<Url>,
pub(super) is_link: bool,
}
impl From<&core::files::File> for File {
fn from(value: &core::files::File) -> Self {
Self {
url: value.url_owned(),
length: value.length(),
link_to: value.link_to().cloned(),
is_link: value.is_link(),
}
}
}
impl UserData for File {
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("url", |_, this| Ok(this.url.to_string_lossy().to_string()));
fields.add_field_method_get("length", |_, this| Ok(this.length));
fields.add_field_method_get("link_to", |_, this| {
Ok(this.link_to.as_ref().map(|l| l.to_string_lossy().to_string()))
});
fields.add_field_method_get("is_link", |_, this| Ok(this.is_link));
}
}

9
plugin/src/lib.rs Normal file
View file

@ -0,0 +1,9 @@
#![allow(clippy::unit_arg)]
mod bindings;
mod plugin;
mod status;
pub use bindings::*;
pub use plugin::*;
pub use status::*;

24
plugin/src/plugin.rs Normal file
View file

@ -0,0 +1,24 @@
use anyhow::Result;
use config::THEME;
use mlua::{Lua, LuaSerdeExt};
use shared::RoCell;
pub(crate) static LUA: RoCell<Lua> = RoCell::new();
pub fn init() {
fn inner() -> Result<()> {
let lua = Lua::new();
lua.load(include_str!("../preset/utils.lua")).exec()?;
lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?;
lua.load(include_str!("../preset/span.lua")).exec()?;
lua.load(include_str!("../preset/line.lua")).exec()?;
lua.load(include_str!("../preset/paragraph.lua")).exec()?;
lua.load(include_str!("../preset/status.lua")).exec()?;
lua.globals().set("THEME", lua.to_value(&*THEME)?)?;
Ok(LUA.init(lua))
}
inner().expect("failed to initialize Lua");
}

44
plugin/src/status.rs Normal file
View file

@ -0,0 +1,44 @@
use core::Ctx;
use mlua::{Function, Result};
use crate::{bindings, LUA};
pub struct Status;
impl Status {
fn scoped<T, F: FnOnce() -> Result<T>>(cx: &Ctx, f: F) -> Result<T> {
LUA.scope(|scope| {
let manager = scope.create_nonstatic_userdata(bindings::Manager::new(&cx.manager))?;
let tasks = scope.create_nonstatic_userdata(bindings::Tasks::new(&cx.tasks))?;
let cx = LUA.create_table()?;
cx.set("manager", manager)?;
cx.set("tasks", tasks)?;
LUA.globals().set("cx", cx)?;
f()
})
}
pub fn mode(cx: &Ctx) -> Result<String> {
Self::scoped(cx, || {
let mode: Function = LUA.globals().get("mode")?;
mode.call::<_, String>(())
})
}
pub fn size(cx: &Ctx) -> Result<String> {
Self::scoped(cx, || {
let size: Function = LUA.globals().get("size")?;
size.call::<_, String>(())
})
}
pub fn name(cx: &Ctx) -> Result<String> {
Self::scoped(cx, || {
let size: Function = LUA.globals().get("name")?;
size.call::<_, String>(())
})
}
}