use crate::app::{HelpMenuLine, NodeFilterApplicable, NodeSorterApplicable}; use crate::app::{Node, ResolvedNode}; use crate::compat::{draw_custom_content, CustomContent}; use crate::config::PanelUiConfig; use crate::lua; use crate::permissions::Permissions; use crate::{app, path}; 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; use std::collections::HashMap; use std::env; use std::ops::BitXor; use time::macros::format_description; use tui::layout::Rect as TuiRect; use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout}; use tui::style::{Color as TuiColor, Modifier as TuiModifier, Style as TuiStyle}; use tui::text::{Line, Span, Text}; use tui::widgets::{ Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem, Paragraph, Row, Table, }; use tui::Frame; const DEFAULT_STYLE: TuiStyle = TuiStyle::new(); lazy_static! { pub static ref NO_COLOR: bool = env::var("NO_COLOR").is_ok(); } fn read_only_indicator(app: &app::App) -> &str { if app.config.general.read_only { "(r)" } else { "" } } fn selection_indicator(app: &app::App) -> String { let count = app.selection.len(); if count == 0 { String::new() } else { format!(" {{{count} sel}}") } } pub fn string_to_text<'a>(string: String) -> Text<'a> { if *NO_COLOR { Text::raw(string) } else { string .as_bytes() .into_text() .unwrap_or_else(|e| Text::raw(format!("{e:?}"))) } } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Rect { x: u16, y: u16, height: u16, width: u16, } impl From for Rect { fn from(tui: TuiRect) -> Self { Self { x: tui.x, y: tui.y, height: tui.height, width: tui.width, } } } #[derive(Debug, Clone, Serialize)] pub struct ContentRendererArg { pub app: app::LuaContextLight, pub screen_size: Rect, pub layout_size: Rect, pub scrolltop: u16, } #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct LayoutOptions { #[serde(default)] pub margin: Option, #[serde(default)] pub horizontal_margin: Option, #[serde(default)] pub vertical_margin: Option, #[serde(default)] pub constraints: Option>, } impl LayoutOptions { pub fn extend(mut self, other: &Self) -> Self { self.margin = other.margin.or(self.margin); self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin); self.vertical_margin = other.vertical_margin.or(self.vertical_margin); self.constraints = other.constraints.clone().or(self.constraints); self } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub enum CustomPanel { CustomParagraph { #[serde(default)] ui: PanelUiConfig, body: String, }, CustomList { #[serde(default)] ui: PanelUiConfig, body: Vec, }, CustomTable { #[serde(default)] ui: PanelUiConfig, widths: Vec, col_spacing: Option, body: Vec>, }, CustomLayout(Layout), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub enum Layout { Nothing, Table, InputAndLogs, Selection, HelpMenu, SortAndFilter, Static(Box), Dynamic(String), Horizontal { config: LayoutOptions, splits: Vec, }, Vertical { config: LayoutOptions, splits: Vec, }, /// For compatibility only. A better choice is Static or Dymanic layout. CustomContent(Box), } impl Default for Layout { fn default() -> Self { Self::Nothing } } impl Layout { pub fn extend(self, other: &Self) -> Self { match (self, other) { (s, Self::Nothing) => s, ( Self::Horizontal { config: sconfig, splits: _, }, Self::Horizontal { config: oconfig, splits: osplits, }, ) => Self::Horizontal { config: sconfig.extend(oconfig), splits: osplits.clone(), }, ( Self::Vertical { config: sconfig, splits: _, }, Self::Vertical { config: oconfig, splits: osplits, }, ) => Self::Vertical { config: sconfig.extend(oconfig), splits: osplits.clone(), }, (_, other) => other.clone(), } } pub fn replace(self, target: &Self, replacement: &Self) -> Self { match self { Self::Horizontal { splits, config } => Self::Horizontal { splits: splits .into_iter() .map(|s| s.replace(target, replacement)) .collect(), config, }, Self::Vertical { splits, config } => Self::Vertical { splits: splits .into_iter() .map(|s| s.replace(target, replacement)) .collect(), config, }, other => { if other == *target { replacement.clone() } else { other } } } } } #[derive( Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize, )] #[serde(deny_unknown_fields)] pub enum Border { Top, Right, Bottom, Left, } impl Border { pub fn bits(self) -> u8 { match self { Self::Top => TuiBorders::TOP.bits(), Self::Right => TuiBorders::RIGHT.bits(), Self::Bottom => TuiBorders::BOTTOM.bits(), Self::Left => TuiBorders::LEFT.bits(), } } } #[derive( Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize, )] #[serde(deny_unknown_fields)] pub enum BorderType { Plain, Rounded, Double, Thick, } impl Default for BorderType { fn default() -> Self { Self::Plain } } impl Into for BorderType { fn into(self) -> TuiBorderType { match self { BorderType::Plain => TuiBorderType::Plain, BorderType::Rounded => TuiBorderType::Rounded, BorderType::Double => TuiBorderType::Double, BorderType::Thick => TuiBorderType::Thick, } } } #[derive( Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize, )] #[serde(deny_unknown_fields)] pub enum Modifier { Bold, Dim, Italic, Underlined, SlowBlink, RapidBlink, Reversed, Hidden, CrossedOut, } impl Modifier { pub fn bits(self) -> u16 { match self { Self::Bold => TuiModifier::BOLD.bits(), Self::Dim => TuiModifier::DIM.bits(), Self::Italic => TuiModifier::ITALIC.bits(), Self::Underlined => TuiModifier::UNDERLINED.bits(), Self::SlowBlink => TuiModifier::SLOW_BLINK.bits(), Self::RapidBlink => TuiModifier::RAPID_BLINK.bits(), Self::Reversed => TuiModifier::REVERSED.bits(), Self::Hidden => TuiModifier::HIDDEN.bits(), Self::CrossedOut => TuiModifier::CROSSED_OUT.bits(), } } } fn extend_optional_modifiers( a: Option>, b: Option>, ) -> Option> { match (a, b) { (Some(mut a), Some(b)) => { a.extend(b); Some(a) } (Some(a), None) => Some(a), (None, Some(b)) => Some(b), (None, None) => None, } } // raratui doesn't support directly serializing `Color::Rgb` and // `Color::Indexed` anymore. // See https://github.com/ratatui-org/ratatui/pull/934 #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum Color { #[default] Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, White, Rgb(u8, u8, u8), Indexed(u8), } impl From for TuiColor { fn from(value: Color) -> Self { match value { Color::Reset => TuiColor::Reset, Color::Black => TuiColor::Black, Color::Red => TuiColor::Red, Color::Green => TuiColor::Green, Color::Yellow => TuiColor::Yellow, Color::Blue => TuiColor::Blue, Color::Magenta => TuiColor::Magenta, Color::Cyan => TuiColor::Cyan, Color::Gray => TuiColor::Gray, Color::DarkGray => TuiColor::DarkGray, Color::LightRed => TuiColor::LightRed, Color::LightGreen => TuiColor::LightGreen, Color::LightYellow => TuiColor::LightYellow, Color::LightBlue => TuiColor::LightBlue, Color::LightMagenta => TuiColor::LightMagenta, Color::LightCyan => TuiColor::LightCyan, Color::White => TuiColor::White, Color::Rgb(r, g, b) => TuiColor::Rgb(r, g, b), Color::Indexed(index) => TuiColor::Indexed(index), } } } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Style { pub fg: Option, pub bg: Option, pub add_modifiers: Option>, pub sub_modifiers: Option>, } impl Style { pub fn extend(mut self, other: &Self) -> Self { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.add_modifiers = extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone()); self.sub_modifiers = extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone()); self } } impl From