diff options
| -rw-r--r-- | Cargo.lock | 25 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/config/mod.rs | 14 | ||||
| -rw-r--r-- | src/utils/mod.rs | 20 | ||||
| -rw-r--r-- | src/utils/termbg.rs | 257 |
5 files changed, 51 insertions, 268 deletions
@@ -30,6 +30,7 @@ dependencies = [ name = "aichat" version = "0.9.0-rc2" dependencies = [ + "ansi_colours", "anyhow", "arboard", "async-trait", @@ -78,6 +79,15 @@ dependencies = [ ] [[package]] +name = "ansi_colours" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80" +dependencies = [ + "rgb", +] + +[[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -252,6 +262,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1357,6 +1373,15 @@ dependencies = [ ] [[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] name = "ring" version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -23,7 +23,7 @@ reedline = "0.21.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" serde_yaml = "0.9.17" -tokio = { version = "1.26.0", features = ["rt", "time", "macros", "signal", "io-std"] } +tokio = { version = "1.26.0", features = ["rt", "time", "macros", "signal"] } crossbeam = "0.8.2" crossterm = "0.26.1" chrono = "0.4.23" @@ -39,6 +39,7 @@ nu-ansi-term = "0.47.0" arboard = { version = "3.2.0", default-features = false } async-trait = "0.1.74" textwrap = "0.16.0" +ansi_colours = "1.2.2" [dependencies.reqwest] version = "0.11.14" diff --git a/src/config/mod.rs b/src/config/mod.rs index 0af432c..ed256e6 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,7 +10,7 @@ use crate::client::openai::{OpenAIClient, OpenAIConfig}; use crate::client::{all_clients, create_client_config, list_models, ClientConfig, ModelInfo}; use crate::config::message::num_tokens_from_messages; use crate::render::RenderOptions; -use crate::utils::{get_env_name, now}; +use crate::utils::{get_env_name, light_theme_from_colorfgbg, now}; use anyhow::{anyhow, bail, Context, Result}; use inquire::{Confirm, Select, Text}; @@ -592,13 +592,11 @@ impl Config { if let Ok(value) = env::var(get_env_name("light_theme")) { set_bool(&mut self.light_theme, &value); return Ok(()); - } - #[cfg(not(target_os = "windows"))] - if let Ok(crate::utils::termbg::Theme::Light) = - crate::utils::termbg::theme(std::time::Duration::from_millis(200)) - { - self.light_theme = true; - } + } else if let Ok(value) = env::var("COLORFGBG") { + if let Some(light) = light_theme_from_colorfgbg(&value) { + self.light_theme = light + } + }; Ok(()) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index fa26dcf..699f8a0 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,3 @@ -#[cfg(not(target_os = "windows"))] -pub(crate) mod termbg; mod tiktoken; use self::tiktoken::cl100k_base; @@ -45,3 +43,21 @@ pub fn split_text(text: &str) -> Result<Vec<String>, anyhow::Error> { let data: Result<Vec<String>, _> = tokens.into_iter().map(|v| bpe.decode(&[v])).collect(); data } + +pub fn light_theme_from_colorfgbg(colorfgbg: &str) -> Option<bool> { + let parts: Vec<_> = colorfgbg.split(';').collect(); + let bg = match parts.len() { + 2 => &parts[1], + 3 => &parts[2], + _ => { + return None; + } + }; + let bg = bg.parse::<u8>().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) +} diff --git a/src/utils/termbg.rs b/src/utils/termbg.rs deleted file mode 100644 index 2322227..0000000 --- a/src/utils/termbg.rs +++ /dev/null @@ -1,257 +0,0 @@ -//! Terminal background color detection -/// Fork from https://github.com/dalance/termbg/blob/v0.4.3/src/lib.rs -use anyhow::{anyhow, Error, Result}; -use crossterm::terminal; -use std::env; -use std::io::{self, Read, Write}; -use std::time::Duration; - -/// Terminal -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Terminal { - Screen, - Tmux, - XtermCompatible, - VSCode, - Emacs, -} - -/// 16bit RGB color -#[derive(Copy, Clone, Debug)] -pub struct Rgb { - pub r: u16, - pub g: u16, - pub b: u16, -} - -/// Background theme -#[derive(Copy, Clone, Debug)] -pub enum Theme { - Light, - Dark, -} - -/// get detected termnial -pub fn terminal() -> Terminal { - if let Ok(term_program) = env::var("TERM_PROGRAM") { - if term_program == "vscode" { - return Terminal::VSCode; - } - } - - if env::var("INSIDE_EMACS").is_ok() { - return Terminal::Emacs; - } - - if env::var("TMUX").is_ok() { - Terminal::Tmux - } else { - let is_screen = if let Ok(term) = env::var("TERM") { - term.starts_with("screen") - } else { - false - }; - if is_screen { - Terminal::Screen - } else { - Terminal::XtermCompatible - } - } -} - -/// get background color by `RGB` -pub fn rgb(timeout: Duration) -> Result<Rgb> { - let term = terminal(); - let rgb = match term { - Terminal::VSCode => Err(unsupported_err()), - Terminal::Emacs => Err(unsupported_err()), - _ => from_xterm(term, timeout), - }; - check_rgb(rgb) -} - -/// get background color by `Theme` -pub fn theme(timeout: Duration) -> Result<Theme> { - let rgb = rgb(timeout)?; - - // ITU-R BT.601 - let y = rgb.r as f64 * 0.299 + rgb.g as f64 * 0.587 + rgb.b as f64 * 0.114; - - if y > 32768.0 { - Ok(Theme::Light) - } else { - Ok(Theme::Dark) - } -} - -fn from_xterm(term: Terminal, timeout: Duration) -> Result<Rgb> { - // Query by XTerm control sequence - let query = if term == Terminal::Tmux { - "\x1bPtmux;\x1b\x1b]11;?\x07\x1b\\\x03" - } else if term == Terminal::Screen { - "\x1bP\x1b]11;?\x07\x1b\\\x03" - } else { - "\x1b]11;?\x1b\\" - }; - - let mut stderr = io::stderr(); - terminal::enable_raw_mode()?; - write!(stderr, "{}", query)?; - stderr.flush()?; - - let mut stdin = io::stdin(); - - let (tx, rx) = std::sync::mpsc::channel(); - - std::thread::spawn(move || { - let mut buffer = Vec::new(); - let mut buf = [0; 1]; - let mut start = false; - loop { - let _ = stdin.read_exact(&mut buf); - // response terminated by BEL(0x7) - if start && (buf[0] == 0x7) { - break; - } - // response terminated by ST(0x1b 0x5c) - if start && (buf[0] == 0x1b) { - // consume last 0x5c - let _ = stdin.read_exact(&mut buf); - debug_assert_eq!(buf[0], 0x5c); - break; - } - if start { - buffer.push(buf[0]); - } - if buf[0] == b':' { - start = true; - } - } - // Ignore send error because timeout may be occured - let _ = tx.send(buffer); - }); - - let buffer = rx.recv_timeout(timeout); - - terminal::disable_raw_mode()?; - - let buffer = buffer?; - - let s = String::from_utf8_lossy(&buffer); - let (r, g, b) = decode_x11_color(&s)?; - Ok(Rgb { r, g, b }) -} - -fn decode_x11_color(s: &str) -> Result<(u16, u16, u16)> { - fn decode_hex(s: &str) -> Result<u16> { - let len = s.len() as u32; - let mut ret = u16::from_str_radix(s, 16).map_err(|_| parse_err(s))?; - ret <<= (4 - len) * 4; - Ok(ret) - } - - let rgb: Vec<_> = s.split('/').collect(); - - let r = rgb.first().ok_or_else(|| parse_err(s))?; - let g = rgb.get(1).ok_or_else(|| parse_err(s))?; - let b = rgb.get(2).ok_or_else(|| parse_err(s))?; - let r = decode_hex(r)?; - let g = decode_hex(g)?; - let b = decode_hex(b)?; - - Ok((r, g, b)) -} - -fn check_rgb(prev: Result<Rgb>) -> Result<Rgb> { - if prev.is_ok() { - return prev; - } - let fallback = from_env_colorfgbg(); - if fallback.is_ok() { - return fallback; - } - prev -} - -fn from_env_colorfgbg() -> Result<Rgb> { - let var = env::var("COLORFGBG").map_err(|_| unsupported_err())?; - let fgbg: Vec<_> = var.split(';').collect(); - let bg = fgbg.get(1).ok_or(unsupported_err())?; - let bg = bg.parse::<u8>().map_err(|_| parse_err(&var))?; - - // rxvt default color table - let (r, g, b) = match bg { - // black - 0 => (0, 0, 0), - // red - 1 => (205, 0, 0), - // green - 2 => (0, 205, 0), - // yellow - 3 => (205, 205, 0), - // blue - 4 => (0, 0, 238), - // magenta - 5 => (205, 0, 205), - - // cyan - 6 => (0, 205, 205), - // white - 7 => (229, 229, 229), - // bright black - 8 => (127, 127, 127), - // bright red - 9 => (255, 0, 0), - // bright green - 10 => (0, 255, 0), - // bright yellow - 11 => (255, 255, 0), - // bright blue - 12 => (92, 92, 255), - // bright magenta - 13 => (255, 0, 255), - // bright cyan - 14 => (0, 255, 255), - - // bright white - 15 => (255, 255, 255), - _ => (0, 0, 0), - }; - - Ok(Rgb { - r: r * 256, - g: g * 256, - b: b * 256, - }) -} - -fn unsupported_err() -> Error { - anyhow!("Unsupported terminal") -} - -fn parse_err(value: &str) -> Error { - anyhow!("Failed to parse {value}") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_decode_x11_color() { - let s = "0000/0000/0000"; - assert_eq!((0, 0, 0), decode_x11_color(s).unwrap()); - - let s = "1111/2222/3333"; - assert_eq!((0x1111, 0x2222, 0x3333), decode_x11_color(s).unwrap()); - - let s = "111/222/333"; - assert_eq!((0x1110, 0x2220, 0x3330), decode_x11_color(s).unwrap()); - - let s = "11/22/33"; - assert_eq!((0x1100, 0x2200, 0x3300), decode_x11_color(s).unwrap()); - - let s = "1/2/3"; - assert_eq!((0x1000, 0x2000, 0x3000), decode_x11_color(s).unwrap()); - } -} |
