From 517df2bb0006b16d24f8b9b25020a793354ab294 Mon Sep 17 00:00:00 2001 From: xxanqw Date: Sat, 29 Nov 2025 23:42:12 +0200 Subject: Enable ANSI support on Windows and fallback to crossterm colors if necessary --- src/color.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 67 +++++++++++++++++++++++++++--------- 2 files changed, 158 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/color.rs b/src/color.rs index 1b1b201..20fbb38 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,5 @@ 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; @@ -478,10 +479,115 @@ pub fn detect_color_mode() -> ColorMode { } } - // Default fallback + // If running on Windows, try to enable ANSI (virtual terminal processing) + #[cfg(windows)] + { + if enable_windows_ansi() { + // After enabling ANSI, reuse heuristic + if let Ok(colorterm) = std::env::var("COLORTERM") + && (colorterm.contains("truecolor") || colorterm.contains("24bit")) + { + return ColorMode::Truecolor; + } + + if let Ok(term) = std::env::var("TERM") { + if term.contains("256color") { + return ColorMode::Ansi256; + } + if term.contains("color") { + return ColorMode::Ansi16; + } + } + + return ColorMode::Ansi16; + } else { + // If we couldn't enable ANSI, detect shell and adjust gracefully + if let Some(shell) = detect_windows_shell() { + let shell = shell.to_lowercase(); + if shell.contains("cmd.exe") { + // cmd historically doesn't support ANSI; fallback to Ansi16 + return ColorMode::Ansi16; + } + if shell.contains("powershell") || shell.contains("pwsh") { + // Try to detect term capability + if let Ok(term) = std::env::var("TERM") { + if term.contains("256color") { + return ColorMode::Ansi256; + } + } + // Default to Ansi16 when in older hosts + return ColorMode::Ansi16; + } + } + // Generic fallback if no shell detected + return ColorMode::Ansi16; + } + } + + // Default fallback for non-Windows ColorMode::Ansi16 } +/// 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 { + use std::ptr; + 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 the sysinfo crate. +#[cfg(windows)] +pub fn detect_windows_shell() -> Option { + // Heuristic checks using common env vars + // PowerShell sets PSModulePath; pwsh may also set POWERSHELL_* 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() { + // Some versions of pwsh may expose env vars; use generic pwsh hint + return Some("pwsh".to_string()); + } + + // COMSPEC usually points to cmd.exe on Windows + if let Ok(comspec) = std::env::var("COMSPEC") { + if comspec.to_lowercase().ends_with("cmd.exe") { + return Some("cmd.exe".to_string()); + } + } + + // Windows Terminal sets WT_SESSION, but it's not a shell; just return terminal info + if std::env::var("WT_SESSION").is_ok() { + return Some("windows_terminal".to_string()); + } + + None +} + /// 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()); diff --git a/src/main.rs b/src/main.rs index 023f7f3..49ac18d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,11 @@ use clap::{Parser, ValueEnum}; use crossterm::terminal::size; +#[cfg(windows)] +use crossterm::style::{SetForegroundColor, ResetColor, Print}; +#[cfg(windows)] +use crossterm::QueueableCommand; +#[cfg(windows)] +use std::io::stdout; use std::error::Error; use std::str::FromStr; use unicode_width::UnicodeWidthStr; @@ -428,22 +434,51 @@ fn draw_miku_says( let mut colored_line = String::new(); colored_line.push_str(&horizontal_padding); - for c in line.chars() { - if c.is_whitespace() { - colored_line.push(c); - } else if color_index < color_config.color_cache.len() { - let color = &color_config.color_cache[color_index]; - let ansi_code = color.to_ansi_fg(color_config.mode); - colored_line.push_str(&ansi_code); - colored_line.push(c); - colored_line.push_str("\x1b[0m"); // Reset - color_index += 1; - } else { - colored_line.push(c); - } - } - - println!("{}", colored_line); + // If on Windows and ANSI wasn't enabled, fallback to crossterm color APIs + #[cfg(windows)] + { + if !color::enable_windows_ansi() { + let mut stdout = stdout(); + stdout.queue(Print(horizontal_padding.clone())).ok(); + + for c in line.chars() { + if c.is_whitespace() { + stdout.queue(Print(c)).ok(); + } else if color_index < color_config.color_cache.len() { + let color = &color_config.color_cache[color_index]; + let ct_color = color::to_crossterm_color(color); + stdout.queue(SetForegroundColor(ct_color)).ok(); + stdout.queue(Print(c)).ok(); + stdout.queue(ResetColor).ok(); + color_index += 1; + } else { + stdout.queue(Print(c)).ok(); + } + } + + stdout.queue(Print("\n")).ok(); + stdout.flush().ok(); + // Skip the ANSI branch below + continue; + } + } + + for c in line.chars() { + if c.is_whitespace() { + colored_line.push(c); + } else if color_index < color_config.color_cache.len() { + let color = &color_config.color_cache[color_index]; + let ansi_code = color.to_ansi_fg(color_config.mode); + colored_line.push_str(&ansi_code); + colored_line.push(c); + colored_line.push_str("\x1b[0m"); // Reset + color_index += 1; + } else { + colored_line.push(c); + } + } + + println!("{}", colored_line); } else if let Some(single_color) = &color_config.single_color { // Apply single solid color let ansi_code = single_color.to_ansi_fg(color_config.mode); -- cgit v1.3-2-g0d8e From 77d0b7659ca23881f3a867de10bf8fa7bbecfd41 Mon Sep 17 00:00:00 2001 From: xxanqw Date: Sun, 30 Nov 2025 00:22:20 +0200 Subject: Refactor color detection and ANSI handling for improved platform compatibility --- src/color.rs | 174 +++++++++++++++++++++++++++++------------------------------ src/main.rs | 92 ++++++++++++------------------- 2 files changed, 118 insertions(+), 148 deletions(-) (limited to 'src') diff --git a/src/color.rs b/src/color.rs index 20fbb38..f0ab080 100644 --- a/src/color.rs +++ b/src/color.rs @@ -458,81 +458,13 @@ fn ansi_to_rgb(code: u8) -> (u8, u8, u8) { } } -/// Detect the best available color mode for the terminal -pub fn detect_color_mode() -> ColorMode { - if std::env::var("NO_COLOR").is_ok() { - return ColorMode::NoColor; - } - - if let Ok(colorterm) = std::env::var("COLORTERM") - && (colorterm.contains("truecolor") || colorterm.contains("24bit")) - { - return ColorMode::Truecolor; - } - - if let Ok(term) = std::env::var("TERM") { - if term.contains("256color") { - return ColorMode::Ansi256; - } - if term.contains("color") { - return ColorMode::Ansi16; - } - } - - // If running on Windows, try to enable ANSI (virtual terminal processing) - #[cfg(windows)] - { - if enable_windows_ansi() { - // After enabling ANSI, reuse heuristic - if let Ok(colorterm) = std::env::var("COLORTERM") - && (colorterm.contains("truecolor") || colorterm.contains("24bit")) - { - return ColorMode::Truecolor; - } - - if let Ok(term) = std::env::var("TERM") { - if term.contains("256color") { - return ColorMode::Ansi256; - } - if term.contains("color") { - return ColorMode::Ansi16; - } - } - - return ColorMode::Ansi16; - } else { - // If we couldn't enable ANSI, detect shell and adjust gracefully - if let Some(shell) = detect_windows_shell() { - let shell = shell.to_lowercase(); - if shell.contains("cmd.exe") { - // cmd historically doesn't support ANSI; fallback to Ansi16 - return ColorMode::Ansi16; - } - if shell.contains("powershell") || shell.contains("pwsh") { - // Try to detect term capability - if let Ok(term) = std::env::var("TERM") { - if term.contains("256color") { - return ColorMode::Ansi256; - } - } - // Default to Ansi16 when in older hosts - return ColorMode::Ansi16; - } - } - // Generic fallback if no shell detected - return ColorMode::Ansi16; - } - } - - // Default fallback for non-Windows - ColorMode::Ansi16 -} +// (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 { - use std::ptr; + // 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; @@ -559,49 +491,95 @@ pub fn enable_windows_ansi() -> bool { true } -/// Detect the parent shell name on Windows, using the sysinfo crate. +/// Detect the parent shell name on Windows, using environment hints. #[cfg(windows)] pub fn detect_windows_shell() -> Option { // Heuristic checks using common env vars - // PowerShell sets PSModulePath; pwsh may also set POWERSHELL_* 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() { - // Some versions of pwsh may expose env vars; use generic pwsh hint + if std::env::var("PWshPROFILE").is_ok() || std::env::var("PWSH_VERSION").is_ok() { return Some("pwsh".to_string()); } - - // COMSPEC usually points to cmd.exe on Windows if let Ok(comspec) = std::env::var("COMSPEC") { if comspec.to_lowercase().ends_with("cmd.exe") { return Some("cmd.exe".to_string()); } } - - // Windows Terminal sets WT_SESSION, but it's not a shell; just return terminal info 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 { None } -/// 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()); +/// Convert Color to a crossterm Color (helper for use across platforms) +pub fn to_crossterm_color(color: &Color) -> crossterm::style::Color { + let (r, g, b) = color.to_rgb(); + crossterm::style::Color::Rgb { r, g, b } +} - // 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; +/// 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; + } - let s = s1 + (s2 - s1) * t; - let v = v1 + (v2 - v1) * t; + // Early checks + if std::env::var("COLORTERM").is_ok_and(|colorterm| { + colorterm.contains("truecolor") || colorterm.contains("24bit") + }) { + return ColorMode::Truecolor; + } - let (r, g, b) = hsv_to_rgb(h, s, v); - Color::Truecolor(r, g, b) + 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 + 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 @@ -632,6 +610,22 @@ fn rgb_to_hsv((r, g, b): (u8, u8, u8)) -> (f32, f32, f32) { (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; diff --git a/src/main.rs b/src/main.rs index 49ac18d..c50f37c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,9 @@ use clap::{Parser, ValueEnum}; -use crossterm::terminal::size; -#[cfg(windows)] -use crossterm::style::{SetForegroundColor, ResetColor, Print}; -#[cfg(windows)] use crossterm::QueueableCommand; -#[cfg(windows)] -use std::io::stdout; +use crossterm::style::{Print, ResetColor, SetForegroundColor}; +use crossterm::terminal::size; use std::error::Error; +use std::io::{Write, stdout}; use std::str::FromStr; use unicode_width::UnicodeWidthStr; @@ -431,59 +428,38 @@ fn draw_miku_says( // Apply coloring to Miku art lines if color_config.mode != ColorMode::NoColor && !color_config.color_cache.is_empty() { - let mut colored_line = String::new(); - colored_line.push_str(&horizontal_padding); - - // If on Windows and ANSI wasn't enabled, fallback to crossterm color APIs - #[cfg(windows)] - { - if !color::enable_windows_ansi() { - let mut stdout = stdout(); - stdout.queue(Print(horizontal_padding.clone())).ok(); - - for c in line.chars() { - if c.is_whitespace() { - stdout.queue(Print(c)).ok(); - } else if color_index < color_config.color_cache.len() { - let color = &color_config.color_cache[color_index]; - let ct_color = color::to_crossterm_color(color); - stdout.queue(SetForegroundColor(ct_color)).ok(); - stdout.queue(Print(c)).ok(); - stdout.queue(ResetColor).ok(); - color_index += 1; - } else { - stdout.queue(Print(c)).ok(); - } - } - - stdout.queue(Print("\n")).ok(); - stdout.flush().ok(); - // Skip the ANSI branch below - continue; - } - } - - for c in line.chars() { - if c.is_whitespace() { - colored_line.push(c); - } else if color_index < color_config.color_cache.len() { - let color = &color_config.color_cache[color_index]; - let ansi_code = color.to_ansi_fg(color_config.mode); - colored_line.push_str(&ansi_code); - colored_line.push(c); - colored_line.push_str("\x1b[0m"); // Reset - color_index += 1; - } else { - colored_line.push(c); - } - } - - println!("{}", colored_line); + // Use crossterm queueing to color characters so it's platform-agnostic + let mut stdout = stdout(); + stdout.queue(Print(horizontal_padding.clone())).ok(); + + for c in line.chars() { + if c.is_whitespace() { + stdout.queue(Print(c)).ok(); + } else if color_index < color_config.color_cache.len() { + let color = &color_config.color_cache[color_index]; + let ct_color = color::to_crossterm_color(color); + stdout.queue(SetForegroundColor(ct_color)).ok(); + stdout.queue(Print(c)).ok(); + stdout.queue(ResetColor).ok(); + color_index += 1; + } else { + stdout.queue(Print(c)).ok(); + } + } + + stdout.queue(Print("\n")).ok(); + stdout.flush().ok(); + continue; } else if let Some(single_color) = &color_config.single_color { - // Apply single solid color - let ansi_code = single_color.to_ansi_fg(color_config.mode); - let reset_code = "\x1b[0m"; - println!("{}{}{}{}", horizontal_padding, ansi_code, line, reset_code); + // Apply single solid color using crossterm + let mut stdout = stdout(); + let ct_color = color::to_crossterm_color(single_color); + stdout.queue(Print(horizontal_padding.clone())).ok(); + stdout.queue(SetForegroundColor(ct_color)).ok(); + stdout.queue(Print(line)).ok(); + stdout.queue(ResetColor).ok(); + stdout.queue(Print("\n")).ok(); + stdout.flush().ok(); } else { // No coloring println!("{}{}", horizontal_padding, line); -- cgit v1.3-2-g0d8e From 28e212e0ac79ce54526815437c2428deed252079 Mon Sep 17 00:00:00 2001 From: xxanqw Date: Sun, 30 Nov 2025 00:33:44 +0200 Subject: Add color debugging feature and improve RGB to ANSI conversion --- src/color.rs | 45 ++++++++++--------------------------------- src/main.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/color.rs b/src/color.rs index f0ab080..ba79353 100644 --- a/src/color.rs +++ b/src/color.rs @@ -375,47 +375,22 @@ fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> u8 { } /// Convert RGB to ANSI 16 color code -fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> u8 { - // Calculate brightness +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; - - // Determine if it's bright or dark let is_bright = brightness > 128; - // Determine base color - let base_color = if r > g && r > b { - // Red dominant - if r > 192 && g < 64 && b < 64 { - 1 // Red - } else if r > g && r > b { - 3 // Yellow - } else { - 7 // White - } - } else if g > r && g > b { - // Green dominant - if g > 192 && r < 64 && b < 64 { - 2 // Green - } else if g > b { - 3 // Yellow - } else { - 6 // Cyan - } - } else { - // Blue dominant - if b > 192 && r < 64 && g < 64 { - 4 // Blue - } else if b > r { - 6 // Cyan - } else { - 5 // Magenta - } - }; + // 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 { - base_color + 8 // Bright colors are 8-15 + 90 + base_idx } else { - base_color // Normal colors are 0-7 + 30 + base_idx } } diff --git a/src/main.rs b/src/main.rs index c50f37c..90fc061 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,6 +58,9 @@ pub struct Args { /// Disable all coloring (respects NO_COLOR environment variable) #[arg(long)] pub no_color: bool, + /// Print color detection/debug info and exit + #[arg(long)] + pub color_debug: bool, } #[derive(Debug)] @@ -69,6 +72,8 @@ pub struct ColorConfig { pub single_color: Option, pub gradient: Option<(Color, Color)>, pub color_cache: Vec, + pub ansi_enabled: bool, + pub debug: bool, } pub fn parse_color_args(args: &Args) -> Result> { @@ -160,6 +165,12 @@ pub fn parse_color_args(args: &Args) -> Result> { std::process::exit(1); } + // Determine if ANSI is enabled (Windows toggle helper) — don't enable if --no-color + #[cfg(windows)] + let ansi_enabled = !args.no_color && enable_windows_ansi(); + #[cfg(not(windows))] + let ansi_enabled = false; + Ok(ColorConfig { mode: color_mode, rainbow: args.rainbow, @@ -168,6 +179,8 @@ pub fn parse_color_args(args: &Args) -> Result> { single_color, gradient, color_cache: Vec::new(), + ansi_enabled, + debug: args.color_debug, }) } @@ -492,6 +505,19 @@ fn main() -> Result<(), Box> { // Parse color-related arguments let mut color_config = parse_color_args(&args)?; + // If color debug is requested, print information and exit + if args.color_debug { + eprintln!("Color Debug: mode={:?}, ansi_enabled={}, rainbow={}, saturation={}, brightness={}", + color_config.mode, color_config.ansi_enabled, color_config.rainbow, color_config.saturation, color_config.brightness); + if let Some(single) = &color_config.single_color { + eprintln!("Single color: {:?}", single); + } + if let Some((start, end)) = &color_config.gradient { + eprintln!("Gradient: {:?} -> {:?}", start, end); + } + return Ok(()); + } + draw_miku_says(&text, style, &mut color_config)?; Ok(()) @@ -585,6 +611,7 @@ mod tests { color: None, gradient: None, no_color: false, + color_debug: false, }; let config = parse_color_args(&args).unwrap(); @@ -609,6 +636,7 @@ mod tests { color: None, gradient: None, no_color: true, + color_debug: false, }; let config = parse_color_args(&args).unwrap(); @@ -656,6 +684,8 @@ mod tests { single_color: None, gradient: None, color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled + ansi_enabled: false, + debug: false, }; // Test with 0 characters @@ -677,6 +707,8 @@ mod tests { single_color: None, gradient: None, color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled + ansi_enabled: false, + debug: false, }; let start = Color::Hex("#FF0000".to_string()); @@ -701,6 +733,8 @@ mod tests { single_color: None, gradient: None, color_cache: Vec::new(), + ansi_enabled: false, + debug: false, }; generate_rainbow_cache(&mut config, 10); @@ -712,6 +746,14 @@ mod tests { assert_ne!(first_color, last_color); } + #[test] + fn test_rgb_to_ansi16_mapping() { + // Validate mapping for basic colors + assert_eq!(crate::color::rgb_to_ansi16(255, 0, 0), 31); + assert_eq!(crate::color::rgb_to_ansi16(255, 255, 255), 97); + assert_eq!(crate::color::rgb_to_ansi16(0, 0, 0), 30); + } + #[test] fn test_generate_gradient_cache() { let mut config = ColorConfig { @@ -722,6 +764,8 @@ mod tests { single_color: None, gradient: None, color_cache: Vec::new(), + ansi_enabled: false, + debug: false, }; let start = Color::Hex("#FF0000".to_string()); // Red @@ -736,4 +780,22 @@ mod tests { let last_color = &config.color_cache[9]; assert_ne!(first_color, last_color); } + + #[test] + fn test_parse_color_debug_flag() { + let args = Args { + text: Some("test".to_string()), + style: None, + list: false, + rainbow: false, + saturation: 100, + brightness: 50, + color: None, + gradient: None, + no_color: false, + color_debug: true, + }; + let config = parse_color_args(&args).unwrap(); + assert!(config.debug); + } } -- cgit v1.3-2-g0d8e From d0350658ac3df17e1b4661cbbb103063721d5f31 Mon Sep 17 00:00:00 2001 From: xxanqw Date: Sun, 30 Nov 2025 00:36:35 +0200 Subject: Fix Windows ANSI detection and enhance color debugging output --- src/color.rs | 3 +++ src/main.rs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/color.rs b/src/color.rs index ba79353..809c66f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -520,6 +520,9 @@ pub fn detect_color_mode() -> ColorMode { } // 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) diff --git a/src/main.rs b/src/main.rs index 90fc061..23683b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,7 +167,7 @@ pub fn parse_color_args(args: &Args) -> Result> { // Determine if ANSI is enabled (Windows toggle helper) — don't enable if --no-color #[cfg(windows)] - let ansi_enabled = !args.no_color && enable_windows_ansi(); + let ansi_enabled = !args.no_color && color::enable_windows_ansi(); #[cfg(not(windows))] let ansi_enabled = false; @@ -505,6 +505,19 @@ fn main() -> Result<(), Box> { // Parse color-related arguments let mut color_config = parse_color_args(&args)?; + if color_config.debug { + println!("Detected color mode: {:?}", color_config.mode); + println!("ANSI enabled: {}", color_config.ansi_enabled); + println!("NO_COLOR: {:?}", std::env::var("NO_COLOR").ok()); + println!("COLORTERM: {:?}", std::env::var("COLORTERM").ok()); + println!("TERM: {:?}", std::env::var("TERM").ok()); + #[cfg(windows)] + { + println!("Windows shell hint: {:?}", color::detect_windows_shell()); + } + return Ok(()); + } + // If color debug is requested, print information and exit if args.color_debug { eprintln!("Color Debug: mode={:?}, ansi_enabled={}, rainbow={}, saturation={}, brightness={}", -- cgit v1.3-2-g0d8e From 511553a4a2edc051f8a9f0826432147aac4b0032 Mon Sep 17 00:00:00 2001 From: xxanqw Date: Sun, 30 Nov 2025 00:39:08 +0200 Subject: cargo fmt --- src/color.rs | 6 +++--- src/main.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/color.rs b/src/color.rs index 809c66f..548c222 100644 --- a/src/color.rs +++ b/src/color.rs @@ -506,9 +506,9 @@ pub fn detect_color_mode() -> ColorMode { } // Early checks - if std::env::var("COLORTERM").is_ok_and(|colorterm| { - colorterm.contains("truecolor") || colorterm.contains("24bit") - }) { + if std::env::var("COLORTERM") + .is_ok_and(|colorterm| colorterm.contains("truecolor") || colorterm.contains("24bit")) + { return ColorMode::Truecolor; } diff --git a/src/main.rs b/src/main.rs index 23683b4..8a50a54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -520,8 +520,14 @@ fn main() -> Result<(), Box> { // If color debug is requested, print information and exit if args.color_debug { - eprintln!("Color Debug: mode={:?}, ansi_enabled={}, rainbow={}, saturation={}, brightness={}", - color_config.mode, color_config.ansi_enabled, color_config.rainbow, color_config.saturation, color_config.brightness); + eprintln!( + "Color Debug: mode={:?}, ansi_enabled={}, rainbow={}, saturation={}, brightness={}", + color_config.mode, + color_config.ansi_enabled, + color_config.rainbow, + color_config.saturation, + color_config.brightness + ); if let Some(single) = &color_config.single_color { eprintln!("Single color: {:?}", single); } -- cgit v1.3-2-g0d8e