diff --git a/app/src/parser.rs b/app/src/parser.rs index 6cc962a1..a7929f28 100644 --- a/app/src/parser.rs +++ b/app/src/parser.rs @@ -53,6 +53,25 @@ impl Parser { let mut last = '\0'; let mut lines: Vec = 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::>()) + } + + pub fn layout(s: &str) -> Paragraph { + let mut last = '\0'; + let mut lines: Vec = vec![String::new()]; + for c in s.chars() { if c == '\0' && last == '\\' { let last = lines.last_mut().unwrap(); diff --git a/app/src/status/layout.rs b/app/src/status/layout.rs index 7f76a38c..fec9cb2d 100644 --- a/app/src/status/layout.rs +++ b/app/src/status/layout.rs @@ -47,5 +47,36 @@ impl<'a> Widget for Layout<'a> { } Paragraph::new(Line::from(spans)).render(chunks[0], buf); + + // Right + let mut spans = vec![]; + + let x = plugin::Status::permissions(self.cx); + if x.is_err() { + info!("Error: {:?}", x); + return; + } + if let Ok(name) = x { + spans.extend(Parser::line(&name).spans); + } + + let x = plugin::Status::percentage(self.cx); + if x.is_err() { + info!("Error: {:?}", x); + return; + } + if let Ok(name) = x { + spans.extend(Parser::line(&name).spans); + } + + let x = plugin::Status::position(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[1], buf); } } diff --git a/app/src/status/left.rs b/app/src/status/left.rs deleted file mode 100644 index de8287ec..00000000 --- a/app/src/status/left.rs +++ /dev/null @@ -1,46 +0,0 @@ -use core::Ctx; - -use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; - -pub(super) struct Left<'a> { - cx: &'a Ctx, -} - -impl<'a> Left<'a> { - pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } } -} - -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(); - // - // // 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); - } -} diff --git a/app/src/status/mod.rs b/app/src/status/mod.rs index 35d6b256..e74211c4 100644 --- a/app/src/status/mod.rs +++ b/app/src/status/mod.rs @@ -1,9 +1,4 @@ mod layout; -mod left; mod progress; -mod right; pub(super) use layout::*; -use left::*; -use progress::*; -use right::*; diff --git a/app/src/status/right.rs b/app/src/status/right.rs deleted file mode 100644 index 4e326856..00000000 --- a/app/src/status/right.rs +++ /dev/null @@ -1,96 +0,0 @@ -use core::Ctx; - -use config::THEME; -use ratatui::{buffer::Buffer, layout::{Alignment, Rect}, text::{Line, Span}, widgets::{Paragraph, Widget}}; - -use super::Progress; - -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 { -// // 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 { -// // 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); - } -} diff --git a/config/preset/theme.toml b/config/preset/theme.toml index 71dcddb8..dec410e4 100644 --- a/config/preset/theme.toml +++ b/config/preset/theme.toml @@ -17,6 +17,12 @@ mode_unset = { fg = "#1E1E30", bg = "#FFAF80", bold = true } progress_label = { fg = "#FFFFFF", bold = true } progress_gauge = { fg = "#FFA577", bg = "#484D66" } +# Permissions +permissions_t = { fg = "#6D738F" } +permissions_r = { fg = "#F3D398" } +permissions_w = { fg = "#FA7F94" } +permissions_x = { fg = "#7AD9E5" } + [selection] hovered = { fg = "#1E2031", bg = "#80AEFA" } diff --git a/config/src/theme/status.rs b/config/src/theme/status.rs index 7f4e1c46..37959d2b 100644 --- a/config/src/theme/status.rs +++ b/config/src/theme/status.rs @@ -16,6 +16,12 @@ pub struct Status { // Progress pub progress_label: Style, pub progress_gauge: Style, + + // Permissions + pub permissions_t: Style, + pub permissions_r: Style, + pub permissions_w: Style, + pub permissions_x: Style, } #[derive(Deserialize, Serialize)] diff --git a/core/src/files/file.rs b/core/src/files/file.rs index b73f02e4..bb710dcb 100644 --- a/core/src/files/file.rs +++ b/core/src/files/file.rs @@ -81,4 +81,8 @@ impl File { #[inline] pub fn is_link(&self) -> bool { self.is_link } + + // -- Is hidden + #[inline] + pub fn is_hidden(&self) -> bool { self.is_hidden } } diff --git a/plugin/preset/components/status.lua b/plugin/preset/components/status.lua new file mode 100644 index 00000000..8b3ce368 --- /dev/null +++ b/plugin/preset/components/status.lua @@ -0,0 +1,92 @@ +function layout() end + +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.current_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.current_hovered + if hovered == nil then + return "" + end + + return yazi.Span(" " .. utils.basename(hovered.url)):to_string() +end + +function permissions() + local hovered = cx.manager.current_hovered + if hovered == nil then + return "" + end + + if hovered.permissions == nil then + return "" + end + + local spans = {} + for i = 1, #hovered.permissions do + local c = hovered.permissions:sub(i, i) + local style = THEME.status.permissions_t + if c == "r" then + style = THEME.status.permissions_r + elseif c == "w" then + style = THEME.status.permissions_w + elseif c == "x" or c == "s" or c == "S" or c == "t" or c == "T" then + style = THEME.status.permissions_x + end + spans[i] = yazi.Span(c):style(style) + end + return yazi.Line:from(spans):to_string() +end + +function percentage() + local percent = 0 + local cursor = cx.manager.current_cursor + local length = cx.manager.current_length + if cursor ~= 0 and length ~= 0 then + percent = math.floor((cursor + 1) * 100 / length) + end + + if percent == 0 then + percent = " Top " + else + percent = string.format(" %3d%% ", percent) + end + + return yazi + .Line( + yazi.Span(THEME.status.separator.opening):fg(THEME.status.fancy.bg), + yazi.Span(percent):fg(THEME.status.mode_normal.bg):bg(THEME.status.fancy.bg) + ) + :to_string() +end + +function position() + local cursor = cx.manager.current_cursor + local length = cx.manager.current_length + return string.format(" %d/%d ", cursor + 1, length) +end diff --git a/plugin/preset/line.lua b/plugin/preset/elements/line.lua similarity index 100% rename from plugin/preset/line.lua rename to plugin/preset/elements/line.lua diff --git a/plugin/preset/paragraph.lua b/plugin/preset/elements/paragraph.lua similarity index 90% rename from plugin/preset/paragraph.lua rename to plugin/preset/elements/paragraph.lua index 2bdd92a9..04f532fd 100644 --- a/plugin/preset/paragraph.lua +++ b/plugin/preset/elements/paragraph.lua @@ -14,7 +14,7 @@ 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" + s = s .. line:to_string():gsub("\r", "\\\r") .. "\r" end return s.sub(s, 1, -2) end diff --git a/plugin/preset/span.lua b/plugin/preset/elements/span.lua similarity index 100% rename from plugin/preset/span.lua rename to plugin/preset/elements/span.lua diff --git a/plugin/preset/layout.lua b/plugin/preset/layout.lua new file mode 100644 index 00000000..421f5f99 --- /dev/null +++ b/plugin/preset/layout.lua @@ -0,0 +1,43 @@ +local Layout = {} + +function Layout:new(...) + local o = { + direction = "R", + dimension = {}, + elements = { ... }, + } + setmetatable(o, self) + self.__index = self + return o +end + +function Layout:from(elements) return self:new(table.unpack(elements)) end + +function Layout:rows(rows) + self.direction = "R" + self.dimension = rows + return self:to_string() +end + +function Layout:cols(cols) + self.direction = "C" + self.dimension = cols + return self:to_string() +end + +function Layout:to_string() + local s = "" + for i, element in ipairs(self.elements) do + if i == 1 then + s = s .. self.direction + end + s = s .. self.dimension[i] .. "\0" .. element:to_string():gsub("\0", "\\\0") + end +end + +setmetatable(Layout, { + __call = function(self, ...) return self:new(...) end, +}) + +yazi = yazi or {} +yazi.Layout = Layout diff --git a/plugin/preset/status.lua b/plugin/preset/status.lua deleted file mode 100644 index 7dfa3f11..00000000 --- a/plugin/preset/status.lua +++ /dev/null @@ -1,42 +0,0 @@ -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 diff --git a/plugin/preset/utils.lua b/plugin/preset/utils.lua index e69da097..d1560a5f 100644 --- a/plugin/preset/utils.lua +++ b/plugin/preset/utils.lua @@ -1,3 +1,3 @@ -yazi = yazi or {} +utils = utils or {} -function yazi.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end +function utils.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end diff --git a/plugin/src/bindings.rs b/plugin/src/bindings.rs index fb76b4d8..fe61c9df 100644 --- a/plugin/src/bindings.rs +++ b/plugin/src/bindings.rs @@ -1,5 +1,4 @@ -use mlua::{LuaSerdeExt, UserData, Value}; -use shared::Url; +use mlua::{LuaSerdeExt, UserData}; // Manager @@ -13,9 +12,11 @@ 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| { + fields.add_field_method_get("current_cursor", |_, this| Ok(this.0.current().cursor())); + fields.add_field_method_get("current_length", |_, this| Ok(this.0.current().files.len())); + fields.add_field_method_get("current_hovered", |_, this| { Ok(this.0.current().hovered.as_ref().map(File::from)) - }) + }); } } @@ -35,31 +36,25 @@ impl<'a> UserData for Tasks<'a> { // File -pub struct File { - pub(super) url: Url, - pub(super) length: u64, - pub(super) link_to: Option, - pub(super) is_link: bool, -} +pub struct File(core::files::File); 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(), - } - } + fn from(value: &core::files::File) -> Self { Self(value.clone()) } } 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("url", |_, this| Ok(this.0.url().to_string_lossy().to_string())); + fields.add_field_method_get("length", |_, this| Ok(this.0.length())); fields.add_field_method_get("link_to", |_, this| { - Ok(this.link_to.as_ref().map(|l| l.to_string_lossy().to_string())) + Ok(this.0.link_to().map(|l| l.to_string_lossy().to_string())) + }); + fields.add_field_method_get("is_link", |_, this| Ok(this.0.is_link())); + fields.add_field_method_get("is_hidden", |_, this| Ok(this.0.is_hidden())); + + // Meta + fields.add_field_method_get("permissions", |_, this| { + Ok(shared::permissions(this.0.meta().permissions())) }); - fields.add_field_method_get("is_link", |_, this| Ok(this.is_link)); } } diff --git a/plugin/src/status.rs b/plugin/src/status.rs index e4ecb7ec..47f06ace 100644 --- a/plugin/src/status.rs +++ b/plugin/src/status.rs @@ -41,4 +41,25 @@ impl Status { size.call::<_, String>(()) }) } + + pub fn permissions(cx: &Ctx) -> Result { + Self::scoped(cx, || { + let size: Function = LUA.globals().get("permissions")?; + size.call::<_, String>(()) + }) + } + + pub fn percentage(cx: &Ctx) -> Result { + Self::scoped(cx, || { + let size: Function = LUA.globals().get("percentage")?; + size.call::<_, String>(()) + }) + } + + pub fn position(cx: &Ctx) -> Result { + Self::scoped(cx, || { + let size: Function = LUA.globals().get("position")?; + size.call::<_, String>(()) + }) + } } diff --git a/shared/src/fs.rs b/shared/src/fs.rs index 66b260f3..83019054 100644 --- a/shared/src/fs.rs +++ b/shared/src/fs.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, path::{Path, PathBuf}}; +use std::{collections::VecDeque, fs::Permissions, path::{Path, PathBuf}}; use anyhow::Result; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; @@ -92,61 +92,70 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver String { - use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; +pub fn permissions(permissions: Permissions) -> Option { + #[cfg(windows)] + { + return None; + } - #[cfg(target_os = "macos")] - let m = mode as u16; - #[cfg(target_os = "freebsd")] - let m = mode as u16; - #[cfg(target_os = "netbsd")] - let m = mode; - #[cfg(target_os = "linux")] - let m = mode; + Some({ + use std::os::unix::prelude::PermissionsExt; - let mut s = String::with_capacity(10); + use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; - // File type - s.push(match m & S_IFMT { - S_IFBLK => 'b', - S_IFCHR => 'c', - S_IFDIR => 'd', - S_IFIFO => 'p', - S_IFLNK => 'l', - S_IFSOCK => 's', - _ => '-', - }); + #[cfg(target_os = "macos")] + let m = permissions.mode() as u16; + #[cfg(target_os = "freebsd")] + let m = permissions.mode() as u16; + #[cfg(target_os = "netbsd")] + let m = permissions.mode(); + #[cfg(target_os = "linux")] + let m = permissions.mode(); - // Owner - s.push(if m & S_IRUSR != 0 { 'r' } else { '-' }); - s.push(if m & S_IWUSR != 0 { 'w' } else { '-' }); - s.push(if m & S_IXUSR != 0 { - if m & S_ISUID != 0 { 's' } else { 'x' } - } else { - if m & S_ISUID != 0 { 'S' } else { '-' } - }); + let mut s = String::with_capacity(10); - // Group - s.push(if m & S_IRGRP != 0 { 'r' } else { '-' }); - s.push(if m & S_IWGRP != 0 { 'w' } else { '-' }); - s.push(if m & S_IXGRP != 0 { - if m & S_ISGID != 0 { 's' } else { 'x' } - } else { - if m & S_ISGID != 0 { 'S' } else { '-' } - }); + // File type + s.push(match m & S_IFMT { + S_IFBLK => 'b', + S_IFCHR => 'c', + S_IFDIR => 'd', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + _ => '-', + }); - // Other - s.push(if m & S_IROTH != 0 { 'r' } else { '-' }); - s.push(if m & S_IWOTH != 0 { 'w' } else { '-' }); - s.push(if m & S_IXOTH != 0 { - if m & S_ISVTX != 0 { 't' } else { 'x' } - } else { - if m & S_ISVTX != 0 { 'T' } else { '-' } - }); + // Owner + s.push(if m & S_IRUSR != 0 { 'r' } else { '-' }); + s.push(if m & S_IWUSR != 0 { 'w' } else { '-' }); + s.push(if m & S_IXUSR != 0 { + if m & S_ISUID != 0 { 's' } else { 'x' } + } else { + if m & S_ISUID != 0 { 'S' } else { '-' } + }); - s + // Group + s.push(if m & S_IRGRP != 0 { 'r' } else { '-' }); + s.push(if m & S_IWGRP != 0 { 'w' } else { '-' }); + s.push(if m & S_IXGRP != 0 { + if m & S_ISGID != 0 { 's' } else { 'x' } + } else { + if m & S_ISGID != 0 { 'S' } else { '-' } + }); + + // Other + s.push(if m & S_IROTH != 0 { 'r' } else { '-' }); + s.push(if m & S_IWOTH != 0 { 'w' } else { '-' }); + s.push(if m & S_IXOTH != 0 { + if m & S_ISVTX != 0 { 't' } else { 'x' } + } else { + if m & S_ISVTX != 0 { 'T' } else { '-' } + }); + + s + }) } // Find the max common root of a list of files