limbo/cli/config/mod.rs

193 lines
5.6 KiB
Rust

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<PathBuf> = LazyLock::new(|| HOME_DIR.join(".config/limbo"));
fn ok_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
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<String> {
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<LimboColor>,
}
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<LimboColor> {
// 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),
}
}
}