diff --git a/Cargo.lock b/Cargo.lock index 7c72f4b92..8cfa6107a 100644 Binary files a/Cargo.lock and b/Cargo.lock differ diff --git a/Cargo.toml b/Cargo.toml index c579773e3..a5a850e09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ incremental = true codegen-units = 256 [workspace.dependencies] +ansi_colours = "1.2.3" anstyle = "1.0.10" anyhow = "1.0.94" bstr = "1.11.3" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d1c6172bd..1353f815b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,6 +28,7 @@ harness = false wasm = ["tree-sitter/wasm", "tree-sitter-loader/wasm"] [dependencies] +ansi_colours.workspace = true anstyle.workspace = true anyhow.workspace = true bstr.workspace = true diff --git a/cli/src/highlight.rs b/cli/src/highlight.rs index cfdd2f76c..8611374dc 100644 --- a/cli/src/highlight.rs +++ b/cli/src/highlight.rs @@ -5,10 +5,11 @@ use std::{ io::{self, Write as _}, path::{self, Path, PathBuf}, str, - sync::{atomic::AtomicUsize, Arc, LazyLock}, + sync::{atomic::AtomicUsize, Arc}, time::Instant, }; +use ansi_colours::{ansi256_from_rgb, rgb_from_ansi256}; use anstyle::{Ansi256Color, AnsiColor, Color, Effects, RgbColor}; use anyhow::Result; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -44,9 +45,6 @@ pub const HTML_FOOTER: &str = " "; -static CSS_STYLES_BY_COLOR_ID: LazyLock> = - LazyLock::new(|| serde_json::from_str(include_str!("../vendor/xterm-colors.json")).unwrap()); - #[derive(Debug, Default)] pub struct Style { pub ansi: anstyle::Style, @@ -224,9 +222,8 @@ fn parse_style(style: &mut Style, json: Value) { if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color() { if !terminal_supports_truecolor() { - style.ansi = style - .ansi - .fg_color(Some(closest_xterm_color(red, green, blue))); + let ansi256 = Color::Ansi256(Ansi256Color(ansi256_from_rgb((red, green, blue)))); + style.ansi = style.ansi.fg_color(Some(ansi256)); } } } @@ -303,7 +300,8 @@ fn write_color(buffer: &mut String, color: Color) { _ => unreachable!(), }, Color::Ansi256(Ansi256Color(n)) => { - write!(buffer, "color: {}", CSS_STYLES_BY_COLOR_ID[n as usize]).unwrap(); + let (r, g, b) = rgb_from_ansi256(n); + write!(buffer, "color: #{r:02x}{g:02x}{b:02x}").unwrap(); } Color::Rgb(RgbColor(r, g, b)) => write!(buffer, "color: #{r:02x}{g:02x}{b:02x}").unwrap(), } @@ -314,30 +312,6 @@ fn terminal_supports_truecolor() -> bool { .is_ok_and(|truecolor| truecolor == "truecolor" || truecolor == "24bit") } -fn closest_xterm_color(red: u8, green: u8, blue: u8) -> Color { - use std::cmp::{max, min}; - - let colors = CSS_STYLES_BY_COLOR_ID - .iter() - .enumerate() - .map(|(color_id, hex)| (color_id as u8, hex_string_to_rgb(hex).unwrap())); - - // Get the xterm color with the minimum Euclidean distance to the target color - // i.e. distance = √ (r2 - r1)² + (g2 - g1)² + (b2 - b1)² - let distances = colors.map(|(color_id, (r, g, b))| { - let r_delta = (max(r, red) - min(r, red)) as u32; - let g_delta = (max(g, green) - min(g, green)) as u32; - let b_delta = (max(b, blue) - min(b, blue)) as u32; - let distance = r_delta.pow(2) + g_delta.pow(2) + b_delta.pow(2); - // don't need to actually take the square root for the sake of comparison - (color_id, distance) - }); - - Color::Ansi256(Ansi256Color( - distances.min_by(|(_, d1), (_, d2)| d1.cmp(d2)).unwrap().0, - )) -} - pub struct HighlightOptions { pub theme: Theme, pub check: bool, @@ -518,12 +492,12 @@ mod tests { ); assert_eq!(style.css, Some("color: #26a69a".to_string())); - // junglegreen gets approximated as darkcyan when the terminal does not support it + // junglegreen gets approximated as cadetblue when the terminal does not support it env::set_var("COLORTERM", ""); parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string())); assert_eq!( style.ansi.get_fg_color(), - Some(Color::Ansi256(Ansi256Color(36))) + Some(Color::Ansi256(Ansi256Color(72))) ); assert_eq!(style.css, Some("color: #26a69a".to_string())); diff --git a/cli/vendor/xterm-colors.json b/cli/vendor/xterm-colors.json deleted file mode 100644 index 479944967..000000000 --- a/cli/vendor/xterm-colors.json +++ /dev/null @@ -1,258 +0,0 @@ -[ - "#000000", - "#800000", - "#008000", - "#808000", - "#000080", - "#800080", - "#008080", - "#c0c0c0", - "#808080", - "#ff0000", - "#00ff00", - "#ffff00", - "#0000ff", - "#ff00ff", - "#00ffff", - "#ffffff", - "#000000", - "#00005f", - "#000087", - "#0000af", - "#0000d7", - "#0000ff", - "#005f00", - "#005f5f", - "#005f87", - "#005faf", - "#005fd7", - "#005fff", - "#008700", - "#00875f", - "#008787", - "#0087af", - "#0087d7", - "#0087ff", - "#00af00", - "#00af5f", - "#00af87", - "#00afaf", - "#00afd7", - "#00afff", - "#00d700", - "#00d75f", - "#00d787", - "#00d7af", - "#00d7d7", - "#00d7ff", - "#00ff00", - "#00ff5f", - "#00ff87", - "#00ffaf", - "#00ffd7", - "#00ffff", - "#5f0000", - "#5f005f", - "#5f0087", - "#5f00af", - "#5f00d7", - "#5f00ff", - "#5f5f00", - "#5f5f5f", - "#5f5f87", - "#5f5faf", - "#5f5fd7", - "#5f5fff", - "#5f8700", - "#5f875f", - "#5f8787", - "#5f87af", - "#5f87d7", - "#5f87ff", - "#5faf00", - "#5faf5f", - "#5faf87", - "#5fafaf", - "#5fafd7", - "#5fafff", - "#5fd700", - "#5fd75f", - "#5fd787", - "#5fd7af", - "#5fd7d7", - "#5fd7ff", - "#5fff00", - "#5fff5f", - "#5fff87", - "#5fffaf", - "#5fffd7", - "#5fffff", - "#870000", - "#87005f", - "#870087", - "#8700af", - "#8700d7", - "#8700ff", - "#875f00", - "#875f5f", - "#875f87", - "#875faf", - "#875fd7", - "#875fff", - "#878700", - "#87875f", - "#878787", - "#8787af", - "#8787d7", - "#8787ff", - "#87af00", - "#87af5f", - "#87af87", - "#87afaf", - "#87afd7", - "#87afff", - "#87d700", - "#87d75f", - "#87d787", - "#87d7af", - "#87d7d7", - "#87d7ff", - "#87ff00", - "#87ff5f", - "#87ff87", - "#87ffaf", - "#87ffd7", - "#87ffff", - "#af0000", - "#af005f", - "#af0087", - "#af00af", - "#af00d7", - "#af00ff", - "#af5f00", - "#af5f5f", - "#af5f87", - "#af5faf", - "#af5fd7", - "#af5fff", - "#af8700", - "#af875f", - "#af8787", - "#af87af", - "#af87d7", - "#af87ff", - "#afaf00", - "#afaf5f", - "#afaf87", - "#afafaf", - "#afafd7", - "#afafff", - "#afd700", - "#afd75f", - "#afd787", - "#afd7af", - "#afd7d7", - "#afd7ff", - "#afff00", - "#afff5f", - "#afff87", - "#afffaf", - "#afffd7", - "#afffff", - "#d70000", - "#d7005f", - "#d70087", - "#d700af", - "#d700d7", - "#d700ff", - "#d75f00", - "#d75f5f", - "#d75f87", - "#d75faf", - "#d75fd7", - "#d75fff", - "#d78700", - "#d7875f", - "#d78787", - "#d787af", - "#d787d7", - "#d787ff", - "#d7af00", - "#d7af5f", - "#d7af87", - "#d7afaf", - "#d7afd7", - "#d7afff", - "#d7d700", - "#d7d75f", - "#d7d787", - "#d7d7af", - "#d7d7d7", - "#d7d7ff", - "#d7ff00", - "#d7ff5f", - "#d7ff87", - "#d7ffaf", - "#d7ffd7", - "#d7ffff", - "#ff0000", - "#ff005f", - "#ff0087", - "#ff00af", - "#ff00d7", - "#ff00ff", - "#ff5f00", - "#ff5f5f", - "#ff5f87", - "#ff5faf", - "#ff5fd7", - "#ff5fff", - "#ff8700", - "#ff875f", - "#ff8787", - "#ff87af", - "#ff87d7", - "#ff87ff", - "#ffaf00", - "#ffaf5f", - "#ffaf87", - "#ffafaf", - "#ffafd7", - "#ffafff", - "#ffd700", - "#ffd75f", - "#ffd787", - "#ffd7af", - "#ffd7d7", - "#ffd7ff", - "#ffff00", - "#ffff5f", - "#ffff87", - "#ffffaf", - "#ffffd7", - "#ffffff", - "#080808", - "#121212", - "#1c1c1c", - "#262626", - "#303030", - "#3a3a3a", - "#444444", - "#4e4e4e", - "#585858", - "#626262", - "#6c6c6c", - "#767676", - "#808080", - "#8a8a8a", - "#949494", - "#9e9e9e", - "#a8a8a8", - "#b2b2b2", - "#bcbcbc", - "#c6c6c6", - "#d0d0d0", - "#dadada", - "#e4e4e4", - "#eeeeee" -]