diff options
| author | david <[email protected]> | 2026-10-05 23:08:45 -0300 |
|---|---|---|
| committer | david <[email protected]> | 2026-10-05 23:08:45 -0300 |
| commit | cc232721ad21e4f714467c56dfe064207e77db61 (patch) | |
| tree | 300bbf25ba353ad3d6fb582ce75d6d6d54de0289 /src/color.rs | |
| parent | 0324a92037f1c830fa1c9e440024211d423dbea5 (diff) | |
| download | mikusays-c-cc232721ad21e4f714467c56dfe064207e77db61.tar.gz mikusays-c-cc232721ad21e4f714467c56dfe064207e77db61.zip | |
rewrote basic functionality to C. there are no colors just yet
Diffstat (limited to 'src/color.rs')
| -rw-r--r-- | src/color.rs | 617 |
1 files changed, 0 insertions, 617 deletions
diff --git a/src/color.rs b/src/color.rs deleted file mode 100644 index 74497ab..0000000 --- a/src/color.rs +++ /dev/null @@ -1,617 +0,0 @@ -use clap::ValueEnum; -// No external sysinfo dependency — use environment heuristics instead for Windows shell detection. -use std::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug, Clone, PartialEq)] -pub enum Color { - Named(NamedColor), - Hex(String), - Rgb(u8, u8, u8), - Hsl(u16, u8, u8), // h: 0-360, s: 0-100, l: 0-100 - Ansi(u8), // ANSI color code (0-255) - Truecolor(u8, u8, u8), // 24-bit RGB -} - -#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] -pub enum NamedColor { - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - White, - BrightBlack, - BrightRed, - BrightGreen, - BrightYellow, - BrightBlue, - BrightMagenta, - BrightCyan, - BrightWhite, -} - -#[derive(Debug)] -pub enum ColorParseError { - InvalidFormat, - InvalidHex, - InvalidRgb, - InvalidHsl, - UnknownNamedColor, -} - -impl fmt::Display for ColorParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ColorParseError::InvalidFormat => write!(f, "Invalid color format"), - ColorParseError::InvalidHex => write!(f, "Invalid hex color code"), - ColorParseError::InvalidRgb => write!(f, "Invalid RGB format. Expected format: R,G,B"), - ColorParseError::InvalidHsl => { - write!(f, "Invalid HSL format. Expected format: hsl(H,S%,L%)") - } - ColorParseError::UnknownNamedColor => write!(f, "Unknown named color"), - } - } -} - -impl Error for ColorParseError {} - -impl FromStr for Color { - type Err = ColorParseError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - let s_lower = s.to_lowercase(); - - // Try named color first - if let Ok(named) = NamedColor::from_str(s, true) { - return Ok(Color::Named(named)); - } - - // Try hex color (#RRGGBB or #RGB) - if s.starts_with('#') { - return parse_hex_color(s); - } - - // Try HSL (hsl(180,100%,50%)) - if s_lower.starts_with("hsl(") && s.ends_with(')') { - return parse_hsl_color(s); - } - - // Try RGB tuple (255,0,128) - if s.contains(',') { - return parse_rgb_color(s); - } - - // Try ANSI code (number between 0-255) - if let Ok(code) = s.parse::<u8>() { - return Ok(Color::Ansi(code)); - } - - Err(ColorParseError::InvalidFormat) - } -} - -impl FromStr for NamedColor { - type Err = ColorParseError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - Self::from_str(s, false) - } -} - -impl NamedColor { - fn from_str(s: &str, ignore_case: bool) -> Result<Self, ColorParseError> { - let s = if ignore_case { - s.to_lowercase() - } else { - s.to_string() - }; - - match s.as_str() { - "black" => Ok(NamedColor::Black), - "red" => Ok(NamedColor::Red), - "green" => Ok(NamedColor::Green), - "yellow" => Ok(NamedColor::Yellow), - "blue" => Ok(NamedColor::Blue), - "magenta" => Ok(NamedColor::Magenta), - "cyan" => Ok(NamedColor::Cyan), - "white" => Ok(NamedColor::White), - "brightblack" | "bright_black" => Ok(NamedColor::BrightBlack), - "brightred" | "bright_red" => Ok(NamedColor::BrightRed), - "brightgreen" | "bright_green" => Ok(NamedColor::BrightGreen), - "brightyellow" | "bright_yellow" => Ok(NamedColor::BrightYellow), - "brightblue" | "bright_blue" => Ok(NamedColor::BrightBlue), - "brightmagenta" | "bright_magenta" => Ok(NamedColor::BrightMagenta), - "brightcyan" | "bright_cyan" => Ok(NamedColor::BrightCyan), - "brightwhite" | "bright_white" => Ok(NamedColor::BrightWhite), - _ => Err(ColorParseError::UnknownNamedColor), - } - } -} - -fn parse_hex_color(s: &str) -> Result<Color, ColorParseError> { - let hex = s.trim_start_matches('#'); - - match hex.len() { - 3 => { - // #RGB format - let r = u8::from_str_radix(&hex[0..1].repeat(2), 16) - .map_err(|_| ColorParseError::InvalidHex)?; - let g = u8::from_str_radix(&hex[1..2].repeat(2), 16) - .map_err(|_| ColorParseError::InvalidHex)?; - let b = u8::from_str_radix(&hex[2..3].repeat(2), 16) - .map_err(|_| ColorParseError::InvalidHex)?; - Ok(Color::Hex(format!("#{:02x}{:02x}{:02x}", r, g, b))) - } - 6 => { - // #RRGGBB format - let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| ColorParseError::InvalidHex)?; - let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| ColorParseError::InvalidHex)?; - let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| ColorParseError::InvalidHex)?; - Ok(Color::Hex(format!("#{:02x}{:02x}{:02x}", r, g, b))) - } - _ => Err(ColorParseError::InvalidHex), - } -} - -fn parse_rgb_color(s: &str) -> Result<Color, ColorParseError> { - let parts: Vec<&str> = s.split(',').map(|p| p.trim()).collect(); - if parts.len() != 3 { - return Err(ColorParseError::InvalidRgb); - } - - let r = parts[0] - .parse::<u8>() - .map_err(|_| ColorParseError::InvalidRgb)?; - let g = parts[1] - .parse::<u8>() - .map_err(|_| ColorParseError::InvalidRgb)?; - let b = parts[2] - .parse::<u8>() - .map_err(|_| ColorParseError::InvalidRgb)?; - - Ok(Color::Rgb(r, g, b)) -} - -fn parse_hsl_color(s: &str) -> Result<Color, ColorParseError> { - let content = s.trim_start_matches("hsl(").trim_end_matches(')'); - let parts: Vec<&str> = content.split(',').map(|p| p.trim()).collect(); - - if parts.len() != 3 { - return Err(ColorParseError::InvalidHsl); - } - - let h = parts[0] - .trim_end_matches(|c: char| !c.is_ascii_digit()) - .parse::<u16>() - .map_err(|_| ColorParseError::InvalidHsl)?; - let s = parts[1] - .trim_end_matches('%') - .parse::<u8>() - .map_err(|_| ColorParseError::InvalidHsl)?; - let l = parts[2] - .trim_end_matches('%') - .parse::<u8>() - .map_err(|_| ColorParseError::InvalidHsl)?; - - if h > 360 || s > 100 || l > 100 { - return Err(ColorParseError::InvalidHsl); - } - - Ok(Color::Hsl(h, s, l)) -} - -impl Color { - /// Convert color to RGB tuple - pub fn to_rgb(&self) -> (u8, u8, u8) { - match self { - Color::Named(named) => named.to_rgb(), - Color::Hex(hex) => { - let hex = hex.trim_start_matches('#'); - match hex.len() { - 3 => { - // #RGB format - let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0); - let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0); - let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0); - (r, g, b) - } - 6 => { - // #RRGGBB format - let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0); - let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0); - let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0); - (r, g, b) - } - _ => (0, 0, 0), - } - } - Color::Rgb(r, g, b) => (*r, *g, *b), - Color::Hsl(h, s, l) => hsl_to_rgb(*h, *s, *l), - Color::Ansi(code) => ansi_to_rgb(*code), - Color::Truecolor(r, g, b) => (*r, *g, *b), - } - } - - /// Convert color to ANSI escape code for foreground - pub fn to_ansi_fg(&self, color_mode: ColorMode) -> String { - match color_mode { - ColorMode::Truecolor => self.to_truecolor_fg(), - ColorMode::Ansi256 => self.to_ansi256_fg(), - ColorMode::Ansi16 => self.to_ansi16_fg(), - ColorMode::NoColor => "".to_string(), - } - } - - fn to_truecolor_fg(&self) -> String { - let (r, g, b) = self.to_rgb(); - format!("\x1b[38;2;{};{};{}m", r, g, b) - } - - fn to_ansi256_fg(&self) -> String { - let (r, g, b) = self.to_rgb(); - let code = rgb_to_ansi256(r, g, b); - format!("\x1b[38;5;{}m", code) - } - - fn to_ansi16_fg(&self) -> String { - let (r, g, b) = self.to_rgb(); - let code = rgb_to_ansi16(r, g, b); - format!("\x1b[{}m", code) - } -} - -impl NamedColor { - pub fn to_rgb(self) -> (u8, u8, u8) { - match self { - NamedColor::Black => (0, 0, 0), - NamedColor::Red => (197, 15, 31), - NamedColor::Green => (19, 161, 14), - NamedColor::Yellow => (193, 156, 0), - NamedColor::Blue => (0, 55, 218), - NamedColor::Magenta => (136, 23, 152), - NamedColor::Cyan => (58, 150, 221), - NamedColor::White => (204, 204, 204), - NamedColor::BrightBlack => (118, 118, 118), - NamedColor::BrightRed => (231, 72, 86), - NamedColor::BrightGreen => (22, 198, 12), - NamedColor::BrightYellow => (249, 241, 165), - NamedColor::BrightBlue => (59, 120, 255), - NamedColor::BrightMagenta => (180, 0, 158), - NamedColor::BrightCyan => (97, 214, 214), - NamedColor::BrightWhite => (242, 242, 242), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ColorMode { - Truecolor, - Ansi256, - Ansi16, - NoColor, -} - -/// Convert HSL to RGB -fn hsl_to_rgb(h: u16, s: u8, l: u8) -> (u8, u8, u8) { - let h = h as f32 / 360.0; - let s = s as f32 / 100.0; - let l = l as f32 / 100.0; - - let (r, g, b) = if s == 0.0 { - (l, l, l) - } else { - let hue_to_rgb = |p: f32, q: f32, t: f32| -> f32 { - let mut t = t; - if t < 0.0 { - t += 1.0; - } - if t > 1.0 { - t -= 1.0; - } - if t < 1.0 / 6.0 { - return p + (q - p) * 6.0 * t; - } - if t < 1.0 / 2.0 { - return q; - } - if t < 2.0 / 3.0 { - return p + (q - p) * (2.0 / 3.0 - t) * 6.0; - } - p - }; - - let q = if l < 0.5 { - l * (1.0 + s) - } else { - l + s - l * s - }; - let p = 2.0 * l - q; - - ( - hue_to_rgb(p, q, h + 1.0 / 3.0), - hue_to_rgb(p, q, h), - hue_to_rgb(p, q, h - 1.0 / 3.0), - ) - }; - - ( - (r * 255.0).round() as u8, - (g * 255.0).round() as u8, - (b * 255.0).round() as u8, - ) -} - -/// Convert RGB to ANSI 256 color code -fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> u8 { - // Calculate grayscale value - let gray = (r as f32 * 0.299 + g as f32 * 0.587 + b as f32 * 0.114).round() as u8; - - // Check if it's a grayscale color - if r == g && g == b { - // Grayscale ramp (232-255) - if gray <= 8 { - 16 - } else if gray >= 248 { - 231 - } else { - ((gray as f32 - 8.0) / 247.0 * 24.0).round() as u8 + 232 - } - } else { - // Color cube (16-231) - let r = (r as f32 / 255.0 * 5.0).round() as u8; - let g = (g as f32 / 255.0 * 5.0).round() as u8; - let b = (b as f32 / 255.0 * 5.0).round() as u8; - - 16 + (r * 36) + (g * 6) + b - } -} - -/// Convert RGB to ANSI 16 color code -pub(crate) fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> u8 { - // Calculate brightness (perceived luminance) - let brightness = (r as f32 * 0.299 + g as f32 * 0.587 + b as f32 * 0.114).round() as u8; - let is_bright = brightness > 128; - - // Determine base color using simple thresholding on each channel, producing an index 0..7 - let r_on = r > 128; - let g_on = g > 128; - let b_on = b > 128; - let base_idx = (r_on as u8) + (g_on as u8) * 2 + (b_on as u8) * 4; // 0..7 - - // SGR color codes: 30..37 for normal, 90..97 for bright - if is_bright { - 90 + base_idx - } else { - 30 + base_idx - } -} - -/// Convert ANSI color code to RGB -fn ansi_to_rgb(code: u8) -> (u8, u8, u8) { - match code { - // Standard 16 colors - 0 => (0, 0, 0), // Black - 1 => (128, 0, 0), // Red - 2 => (0, 128, 0), // Green - 3 => (128, 128, 0), // Yellow - 4 => (0, 0, 128), // Blue - 5 => (128, 0, 128), // Magenta - 6 => (0, 128, 128), // Cyan - 7 => (192, 192, 192), // White - 8 => (128, 128, 128), // Bright Black - 9 => (255, 0, 0), // Bright Red - 10 => (0, 255, 0), // Bright Green - 11 => (255, 255, 0), // Bright Yellow - 12 => (0, 0, 255), // Bright Blue - 13 => (255, 0, 255), // Bright Magenta - 14 => (0, 255, 255), // Bright Cyan - 15 => (255, 255, 255), // Bright White - - // 256-color mode - 16..=231 => { - // Color cube - let code = code - 16; - let r = ((code / 36) % 6) * 51; - let g = ((code / 6) % 6) * 51; - let b = (code % 6) * 51; - (r, g, b) - } - - 232..=255 => { - // Grayscale ramp - let gray = (code - 232) * 10 + 8; - (gray, gray, gray) - } - } -} - -// (Removed older detect_color_mode to keep a single refactored implementation) - -/// Try to enable ANSI (virtual terminal processing) on Windows consoles. -/// Returns true if enabling succeeded or isn't necessary, false otherwise. -#[cfg(windows)] -pub fn enable_windows_ansi() -> bool { - // no ptr usage required; removed unused import - use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; - use winapi::um::handleapi::INVALID_HANDLE_VALUE; - use winapi::um::processenv::GetStdHandle; - use winapi::um::winbase::STD_OUTPUT_HANDLE; - use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; - - unsafe { - let stdout = GetStdHandle(STD_OUTPUT_HANDLE); - if stdout == INVALID_HANDLE_VALUE { - return false; - } - - let mut mode: u32 = 0; - if GetConsoleMode(stdout, &mut mode) == 0 { - return false; - } - - let new_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if SetConsoleMode(stdout, new_mode) == 0 { - return false; - } - } - - true -} - -/// Detect the parent shell name on Windows, using environment hints. -#[cfg(windows)] -pub fn detect_windows_shell() -> Option<String> { - // Heuristic checks using common env vars - if std::env::var("PSModulePath").is_ok() { - return Some("powershell".to_string()); - } - if std::env::var("PWshPROFILE").is_ok() || std::env::var("PWSH_VERSION").is_ok() { - return Some("pwsh".to_string()); - } - if let Ok(comspec) = std::env::var("COMSPEC") { - if comspec.to_lowercase().ends_with("cmd.exe") { - return Some("cmd.exe".to_string()); - } - } - if std::env::var("WT_SESSION").is_ok() { - return Some("windows_terminal".to_string()); - } - None -} - -#[cfg(not(windows))] -#[allow(dead_code)] -pub fn detect_windows_shell() -> Option<String> { - None -} - -/// Detect the parent shell name on Windows, using the sysinfo crate. -pub fn detect_color_mode() -> ColorMode { - if std::env::var("NO_COLOR").is_ok() { - return ColorMode::NoColor; - } - - // Early checks - if std::env::var("COLORTERM") - .is_ok_and(|colorterm| colorterm.contains("truecolor") || colorterm.contains("24bit")) - { - return ColorMode::Truecolor; - } - - if std::env::var("TERM").is_ok_and(|term| term.contains("256color")) { - return ColorMode::Ansi256; - } - if std::env::var("TERM").is_ok_and(|term| term.contains("color")) { - return ColorMode::Ansi16; - } - - // Default fallback - #[cfg(windows)] - let mut mode = ColorMode::Ansi16; - #[cfg(not(windows))] - let mode = ColorMode::Ansi16; - - // If running on Windows, try to enable ANSI (virtual terminal processing) - #[cfg(windows)] - { - if enable_windows_ansi() { - // Re-check for a better mode - if let Ok(colorterm) = std::env::var("COLORTERM") { - if colorterm.contains("truecolor") || colorterm.contains("24bit") { - mode = ColorMode::Truecolor; - } - } - if let Ok(term) = std::env::var("TERM") { - if term.contains("256color") { - mode = ColorMode::Ansi256; - } - } - } else if let Some(shell) = detect_windows_shell() { - let shell = shell.to_lowercase(); - if shell.contains("cmd.exe") { - // cmd historically doesn't support ANSI; use 16 colors fallback - mode = ColorMode::Ansi16; - } else if shell.contains("powershell") || shell.contains("pwsh") { - if let Ok(term) = std::env::var("TERM") { - if term.contains("256color") { - mode = ColorMode::Ansi256; - } - } - } else { - mode = ColorMode::Ansi16; - } - } - } - - mode -} - -/// Convert RGB to HSV -fn rgb_to_hsv((r, g, b): (u8, u8, u8)) -> (f32, f32, f32) { - let r = r as f32 / 255.0; - let g = g as f32 / 255.0; - let b = b as f32 / 255.0; - - let max = r.max(g).max(b); - let min = r.min(g).min(b); - let delta = max - min; - - let h = if delta == 0.0 { - 0.0 - } else if max == r { - 60.0 * (((g - b) / delta) % 6.0) - } else if max == g { - 60.0 * (((b - r) / delta) + 2.0) - } else { - 60.0 * (((r - g) / delta) + 4.0) - }; - - let h = if h < 0.0 { h + 360.0 } else { h }; - - let s = if max == 0.0 { 0.0 } else { delta / max }; - let v = max; - - (h, s, v) -} - -/// Linear interpolation between two colors in HSV space -pub fn lerp_color_hsv(start: &Color, end: &Color, t: f32) -> Color { - let (h1, s1, v1) = rgb_to_hsv(start.to_rgb()); - let (h2, s2, v2) = rgb_to_hsv(end.to_rgb()); - - // Handle hue interpolation correctly (shortest path around the color wheel) - let h_diff = (h2 - h1 + 180.0) % 360.0 - 180.0; - let h = (h1 + h_diff * t + 360.0) % 360.0; - - let s = s1 + (s2 - s1) * t; - let v = v1 + (v2 - v1) * t; - - let (r, g, b) = hsv_to_rgb(h, s, v); - Color::Truecolor(r, g, b) -} - -/// Convert HSV to RGB -fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) { - let c = v * s; - let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); - let m = v - c; - - let (r, g, b) = match h { - h if (0.0..60.0).contains(&h) => (c, x, 0.0), - h if (60.0..120.0).contains(&h) => (x, c, 0.0), - h if (120.0..180.0).contains(&h) => (0.0, c, x), - h if (180.0..240.0).contains(&h) => (0.0, x, c), - h if (240.0..300.0).contains(&h) => (x, 0.0, c), - _ => (c, 0.0, x), - }; - - ( - ((r + m) * 255.0).round() as u8, - ((g + m) * 255.0).round() as u8, - ((b + m) * 255.0).round() as u8, - ) -} |
