diff --git a/Cargo.lock b/Cargo.lock index 8382f05..82b21ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,6 +580,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lscolors" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dedc85d67baf5327114fad78ab9418f8893b1121c17d5538dd11005ad1ddf2" +dependencies = [ + "nu-ansi-term", +] + [[package]] name = "lua-src" version = "544.0.1" @@ -681,6 +690,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -728,6 +747,12 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1399,9 +1424,11 @@ dependencies = [ "indexmap", "lazy_static", "libc", + "lscolors", "mime_guess", "mlua", "natord", + "nu-ansi-term", "path-absolutize", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 0434897..4c91f4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,12 @@ fuzzy-matcher = "0.3.7" serde_json = "1.0.91" path-absolutize = "3.0.14" which = "4.3.0" +nu-ansi-term = "0.46.0" + +[dependencies.lscolors] +version = "0.13.0" +default-features = false +features = ["nu-ansi-term"] [dependencies.lazy_static] version = "1.4.0" diff --git a/src/init.lua b/src/init.lua index 4e51cb9..5fdffe6 100644 --- a/src/init.lua +++ b/src/init.lua @@ -700,9 +700,7 @@ xplr.config.general.global_key_bindings = { -- The style for the directory nodes -- -- Type: [Style](https://xplr.dev/en/style) -xplr.config.node_types.directory.style = { - fg = "Cyan", -} +xplr.config.node_types.directory.style = {} -- Metadata for the directory nodes. -- You can set as many metadata as you want. @@ -2554,43 +2552,21 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) end end end - - return r + local style = xplr.util.lscolor(m.absolute_path) + return xplr.util.paint(r, style) end -- Renders the third column in the table xplr.fn.builtin.fmt_general_table_row_cols_2 = function(m) - local no_color = os.getenv("NO_COLOR") - - local function green(x) - if no_color == nil then - return "\x1b[32m" .. x .. "\x1b[0m" - else - return x - end - end - - local function yellow(x) - if no_color == nil then - return "\x1b[33m" .. x .. "\x1b[0m" - else - return x - end - end - - local function red(x) - if no_color == nil then - return "\x1b[31m" .. x .. "\x1b[0m" - else - return x - end - end + local green = { fg = "Green" } + local yellow = { fg = "Yellow" } + local red = { fg = "Red" } local function bit(x, color, cond) if cond then - return color(x) + return xplr.util.paint(x, color) else - return color("-") + return xplr.util.paint("-", color) end end diff --git a/src/lua/util.rs b/src/lua/util.rs index 428b190..87d3153 100644 --- a/src/lua/util.rs +++ b/src/lua/util.rs @@ -2,7 +2,10 @@ use crate::app::VERSION; use crate::explorer; use crate::lua; use crate::msg::in_::external::ExplorerConfig; +use crate::ui; +use crate::ui::Style; use anyhow::Result; +use lscolors::LsColors; use mlua::Error as LuaError; use mlua::Lua; use mlua::LuaSerdeExt; @@ -30,6 +33,8 @@ pub(crate) fn create_table(lua: &Lua) -> Result { util = to_json(util, lua)?; util = from_yaml(util, lua)?; util = to_yaml(util, lua)?; + util = lscolor(util, lua)?; + util = paint(util, lua)?; Ok(util) } @@ -317,3 +322,56 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result> { util.set("to_yaml", func)?; Ok(util) } + +/// Get a style object for the given path +/// +/// Type: function( path ) -> style|nil +/// +/// Example: +/// +/// ```lua +/// xplr.util.lscolor("Desktop") +/// -- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} } +/// ``` +pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result> { + let lscolors = LsColors::from_env().unwrap_or_default(); + let func = lua.create_function(move |lua, path: String| { + if *ui::NO_COLOR { + return Ok(mlua::Nil); + } + + let style = lscolors.style_for_path(path).map(Style::from); + lua::serialize(lua, &style).map_err(LuaError::custom) + })?; + util.set("lscolor", func)?; + Ok(util) +} + +/// Format a string using a style object +/// +/// Type: function( string, style|nil ) -> string +/// +/// Example: +/// +/// ```lua +/// xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }) +/// -- "\u001b[31mDesktop\u001b[0m" +/// ``` +pub fn paint<'a>(util: Table<'a>, lua: &Lua) -> Result> { + let func = + lua.create_function(|lua, (string, style): (String, Option
)| { + if *ui::NO_COLOR { + return Ok(string); + } + + let Some(style) = style else { + return Ok(string); + }; + + let style: Style = lua.from_value(Value::Table(style))?; + let ansi_style: nu_ansi_term::Style = style.into(); + Ok::(ansi_style.paint(string).to_string()) + })?; + util.set("paint", func)?; + Ok(util) +} diff --git a/src/ui.rs b/src/ui.rs index 516c488..02ad889 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -7,6 +7,7 @@ use crate::permissions::Permissions; use ansi_to_tui::IntoText; use indexmap::IndexSet; use lazy_static::lazy_static; +use lscolors::{Color as LsColorsColor, Style as LsColorsStyle}; use mlua::Lua; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -292,6 +293,90 @@ impl Into for Style { } } +impl From<&LsColorsStyle> for Style { + fn from(style: &LsColorsStyle) -> Self { + fn convert_color(color: &LsColorsColor) -> Color { + match color { + LsColorsColor::Black => Color::Black, + LsColorsColor::Red => Color::Red, + LsColorsColor::Green => Color::Green, + LsColorsColor::Yellow => Color::Yellow, + LsColorsColor::Blue => Color::Blue, + LsColorsColor::Magenta => Color::Magenta, + LsColorsColor::Cyan => Color::Cyan, + LsColorsColor::White => Color::Gray, + LsColorsColor::BrightBlack => Color::DarkGray, + LsColorsColor::BrightRed => Color::LightRed, + LsColorsColor::BrightGreen => Color::LightGreen, + LsColorsColor::BrightYellow => Color::LightYellow, + LsColorsColor::BrightBlue => Color::LightBlue, + LsColorsColor::BrightMagenta => Color::LightMagenta, + LsColorsColor::BrightCyan => Color::LightCyan, + LsColorsColor::BrightWhite => Color::White, + LsColorsColor::Fixed(index) => Color::Indexed(*index), + LsColorsColor::RGB(r, g, b) => Color::Rgb(*r, *g, *b), + } + } + Self { + fg: style.foreground.as_ref().map(convert_color), + bg: style.background.as_ref().map(convert_color), + add_modifiers: None, + sub_modifiers: None, + } + } +} + +impl Into for Style { + fn into(self) -> nu_ansi_term::Style { + fn convert_color(color: Color) -> Option { + match color { + Color::Black => Some(nu_ansi_term::Color::Black), + Color::Red => Some(nu_ansi_term::Color::Red), + Color::Green => Some(nu_ansi_term::Color::Green), + Color::Yellow => Some(nu_ansi_term::Color::Yellow), + Color::Blue => Some(nu_ansi_term::Color::Blue), + Color::Magenta => Some(nu_ansi_term::Color::Purple), + Color::Cyan => Some(nu_ansi_term::Color::Cyan), + Color::Gray => Some(nu_ansi_term::Color::LightGray), + Color::DarkGray => Some(nu_ansi_term::Color::DarkGray), + Color::LightRed => Some(nu_ansi_term::Color::LightRed), + Color::LightGreen => Some(nu_ansi_term::Color::LightGreen), + Color::LightYellow => Some(nu_ansi_term::Color::LightYellow), + Color::LightBlue => Some(nu_ansi_term::Color::LightBlue), + Color::LightMagenta => Some(nu_ansi_term::Color::LightMagenta), + Color::LightCyan => Some(nu_ansi_term::Color::LightCyan), + Color::White => Some(nu_ansi_term::Color::White), + Color::Rgb(r, g, b) => Some(nu_ansi_term::Color::Rgb(r, g, b)), + Color::Indexed(index) => Some(nu_ansi_term::Color::Fixed(index)), + _ => None, + } + } + fn match_modifiers(style: &Style, f: F) -> bool + where + F: Fn(&IndexSet) -> bool, + { + style.add_modifiers.as_ref().map_or(false, f) + } + + nu_ansi_term::Style { + foreground: self.fg.and_then(convert_color), + background: self.bg.and_then(convert_color), + is_bold: match_modifiers(&self, |m| m.contains(&Modifier::Bold)), + is_dimmed: match_modifiers(&self, |m| m.contains(&Modifier::Dim)), + is_italic: match_modifiers(&self, |m| m.contains(&Modifier::Italic)), + is_underline: match_modifiers(&self, |m| m.contains(&Modifier::Underlined)), + is_blink: match_modifiers(&self, |m| { + m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink) + }), + is_reverse: match_modifiers(&self, |m| m.contains(&Modifier::Reversed)), + is_hidden: match_modifiers(&self, |m| m.contains(&Modifier::Hidden)), + is_strikethrough: match_modifiers(&self, |m| { + m.contains(&Modifier::CrossedOut) + }), + } + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub enum Constraint {