From 84dfbba4f0f3465eea58e97156f67732b0f70966 Mon Sep 17 00:00:00 2001 From: sigoden Date: Sat, 28 Jun 2025 20:17:53 +0800 Subject: feat: support autodetecting dark/light theme (#1335) --- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/config/mod.rs | 35 ++++++++++++++++++++++------------- src/utils/mod.rs | 18 ------------------ 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c2300f..c1df608 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,7 @@ dependencies = [ "simplelog", "syntect", "sys-locale", + "terminal-colorsaurus", "textwrap", "time", "tokio", @@ -3313,6 +3314,32 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal-colorsaurus" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7afe4c174a3cbfb52ebcb11b28965daf74fe9111d4e07e40689d05af06e26e8" +dependencies = [ + "cfg-if", + "libc", + "memchr", + "mio 1.0.3", + "terminal-trx", + "windows-sys 0.59.0", + "xterm-color", +] + +[[package]] +name = "terminal-trx" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975b4233aefa1b02456d5e53b22c61653c743e308c51cf4181191d8ce41753ab" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "textwrap" version = "0.16.2" @@ -4428,6 +4455,12 @@ dependencies = [ "markup5ever 0.12.1", ] +[[package]] +name = "xterm-color" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 85ea0d7..3692b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ os_info = { version = "3.8.2", default-features = false } bm25 = { version = "2.0.1", features = ["parallelism"] } which = "7.0.1" fuzzy-matcher = "0.3.7" +terminal-colorsaurus = "0.4.8" [dependencies.reqwest] version = "0.12.0" diff --git a/src/config/mod.rs b/src/config/mod.rs index 2193c1e..b52debe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -39,6 +39,7 @@ use std::{ sync::{Arc, OnceLock}, }; use syntect::highlighting::ThemeSet; +use terminal_colorsaurus::{color_scheme, ColorScheme, QueryOptions}; pub const TEMP_ROLE_NAME: &str = "%%"; pub const TEMP_RAG_NAME: &str = "temp"; @@ -136,7 +137,7 @@ pub struct Config { pub document_loaders: HashMap, pub highlight: bool, - pub light_theme: bool, + pub theme: Option, pub left_prompt: Option, pub right_prompt: Option, @@ -211,7 +212,7 @@ impl Default for Config { document_loaders: Default::default(), highlight: true, - light_theme: false, + theme: None, left_prompt: None, right_prompt: None, @@ -604,7 +605,7 @@ impl Config { ("wrap", wrap), ("wrap_code", self.wrap_code.to_string()), ("highlight", self.highlight.to_string()), - ("light_theme", self.light_theme.to_string()), + ("theme", format_option_value(&self.theme)), ("config_file", display_path(&Self::config_file())), ("env_file", display_path(&Self::env_file())), ("roles_dir", display_path(&Self::roles_dir())), @@ -1229,7 +1230,7 @@ impl Config { if !need_compress { return; } - let color = if config.read().light_theme { + let color = if config.read().light_theme() { nu_ansi_term::Color::LightGray } else { nu_ansi_term::Color::DarkGray @@ -1295,7 +1296,7 @@ impl Config { if !need_autoname { return; } - let color = if config.read().light_theme { + let color = if config.read().light_theme() { nu_ansi_term::Color::LightGray } else { nu_ansi_term::Color::DarkGray @@ -1897,9 +1898,13 @@ impl Config { Ok(models_override.list) } + pub fn light_theme(&self) -> bool { + matches!(self.theme.as_deref(), Some("light")) + } + pub fn render_options(&self) -> Result { let theme = if self.highlight { - let theme_mode = if self.light_theme { "light" } else { "dark" }; + let theme_mode = if self.light_theme() { "light" } else { "dark" }; let theme_filename = format!("{theme_mode}.tmTheme"); let theme_path = Self::local_path(&theme_filename); if theme_path.exists() { @@ -1907,7 +1912,7 @@ impl Config { .with_context(|| format!("Invalid theme at '{}'", theme_path.display()))?; Some(theme) } else { - let theme = if self.light_theme { + let theme = if self.light_theme() { bincode::deserialize_from(LIGHT_THEME).expect("Invalid builtin light theme") } else { bincode::deserialize_from(DARK_THEME).expect("Invalid builtin dark theme") @@ -2335,12 +2340,16 @@ impl Config { if *NO_COLOR { self.highlight = false; } - if let Some(Some(v)) = read_env_bool(&get_env_name("light_theme")) { - self.light_theme = v; - } else if !self.light_theme { - if let Ok(v) = env::var("COLORFGBG") { - if let Some(v) = light_theme_from_colorfgbg(&v) { - self.light_theme = v + if self.highlight && self.theme.is_none() { + if let Some(v) = read_env_value::(&get_env_name("theme")) { + self.theme = v; + } else if *IS_STDOUT_TERMINAL { + if let Ok(color_scheme) = color_scheme(QueryOptions::default()) { + let theme = match color_scheme { + ColorScheme::Dark => "dark", + ColorScheme::Light => "light", + }; + self.theme = Some(theme.into()); } } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6c5b08a..1f2a592 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -86,24 +86,6 @@ pub fn estimate_token_length(text: &str) -> usize { output.ceil() as usize } -pub fn light_theme_from_colorfgbg(colorfgbg: &str) -> Option { - let parts: Vec<_> = colorfgbg.split(';').collect(); - let bg = match parts.len() { - 2 => &parts[1], - 3 => &parts[2], - _ => { - return None; - } - }; - let bg = bg.parse::().ok()?; - let (r, g, b) = ansi_colours::rgb_from_ansi256(bg); - - let v = 0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32; - - let light = v > 128.0; - Some(light) -} - pub fn strip_think_tag(text: &str) -> Cow { THINK_TAG_RE.replace_all(text, "") } -- cgit v1.2.3