mod palette; mod terminal; use crate::input::OutputMode; use crate::HOME_DIR; use nu_ansi_term::Color; use palette::LimboColor; use schemars::JsonSchema; use serde::{Deserialize, Deserializer}; use std::fmt::Debug; use std::fs::read_to_string; use std::path::PathBuf; use std::sync::LazyLock; use terminal::{TerminalDetector, TerminalTheme}; use validator::Validate; pub static CONFIG_DIR: LazyLock = LazyLock::new(|| HOME_DIR.join(".config/limbo")); fn ok_or_default<'de, T, D>(deserializer: D) -> Result where T: Deserialize<'de> + Default + Validate + Debug, D: Deserializer<'de>, { let v: toml::Value = Deserialize::deserialize(deserializer)?; let x = T::deserialize(v) .map(|v| { let validate = v.validate(); if validate.is_err() { tracing::error!( "Invalid value for {}.\n Original config value: {:?}", validate.unwrap_err(), v ); T::default() } else { v } }) .unwrap_or_default(); Ok(x) } #[derive(Debug, Deserialize, Clone, Default, JsonSchema)] #[serde(default, deny_unknown_fields)] pub struct Config { #[serde(deserialize_with = "ok_or_default")] pub table: TableConfig, pub highlight: HighlightConfig, } impl Config { pub fn from_config_file(path: PathBuf) -> Self { if let Some(config) = Self::read_config_str(path) { Self::from_config_str(&config) } else { Self::default() } } pub fn from_config_str(config: &str) -> Self { toml::from_str(config) .inspect_err(|err| tracing::error!("{}", err)) .unwrap_or_default() } fn read_config_str(path: PathBuf) -> Option { if path.exists() { tracing::trace!("Trying to read from {:?}", path); let result = read_to_string(path); if result.is_err() { tracing::debug!("Error reading file: {:?}", result); } else { tracing::trace!("File read successfully"); }; result.ok() } else { None } } pub fn for_output_mode(mode: OutputMode) -> Self { let table = if mode == OutputMode::Pretty { TableConfig::adaptive_colors() } else { TableConfig::no_colors() }; Self { table, highlight: HighlightConfig::default(), } } } #[derive(Debug, Deserialize, Clone, JsonSchema, Validate)] #[serde(default, deny_unknown_fields)] pub struct TableConfig { #[serde(default = "TableConfig::default_header_color")] pub header_color: LimboColor, #[serde(default = "TableConfig::default_column_colors")] #[validate(length(min = 1))] pub column_colors: Vec, } impl Default for TableConfig { fn default() -> Self { // Always use adaptive colors based on terminal theme Self::adaptive_colors() } } impl TableConfig { // These methods are needed for serde default attributes fn default_header_color() -> LimboColor { // Use adaptive colors for serde defaults too Self::adaptive_colors().header_color } fn default_column_colors() -> Vec { // Use adaptive colors for serde defaults too Self::adaptive_colors().column_colors } /// Get adaptive colors based on detected terminal theme pub fn adaptive_colors() -> Self { let theme = TerminalDetector::detect_theme(); match theme { TerminalTheme::Light => Self::light_theme_colors(), TerminalTheme::Dark => Self::dark_theme_colors(), TerminalTheme::Unknown => Self::no_colors(), // No colors for unsupported platforms } } /// No colors configuration - for Windows or when detection fails fn no_colors() -> Self { Self { header_color: LimboColor(Color::Default), column_colors: vec![LimboColor(Color::Default)], } } /// Colors optimized for light terminal backgrounds fn light_theme_colors() -> Self { Self { header_color: LimboColor(Color::Black), column_colors: vec![ LimboColor(Color::Fixed(22)), // Dark green LimboColor(Color::Fixed(17)), // Dark blue LimboColor(Color::Fixed(88)), // Dark red LimboColor(Color::Fixed(94)), // Orange LimboColor(Color::Fixed(55)), // Purple ], } } /// Colors optimized for dark terminal backgrounds fn dark_theme_colors() -> Self { Self { header_color: LimboColor(Color::LightGray), column_colors: vec![ LimboColor(Color::LightGreen), LimboColor(Color::LightBlue), LimboColor(Color::LightCyan), LimboColor(Color::LightYellow), LimboColor(Color::LightMagenta), ], } } } #[derive(Debug, Deserialize, Clone, JsonSchema)] #[serde(default, deny_unknown_fields)] pub struct HighlightConfig { pub enable: bool, pub theme: String, pub prompt: LimboColor, pub hint: LimboColor, pub candidate: LimboColor, } impl Default for HighlightConfig { fn default() -> Self { Self { enable: true, theme: "base16-ocean.dark".to_string(), prompt: LimboColor(Color::Rgb(34u8, 197u8, 94u8)), hint: LimboColor(Color::Rgb(107u8, 114u8, 128u8)), candidate: LimboColor(Color::Green), } } }