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 } -/// 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()); +#[cfg(not(windows))] +#[allow(dead_code)] +pub fn detect_windows_shell() -> Option { + None +} - // 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; +/// 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 } +} - let s = s1 + (s2 - s1) * t; - let v = v1 + (v2 - v1) * t; +/// 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 (r, g, b) = hsv_to_rgb(h, s, v); - Color::Truecolor(r, g, b) + // 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 + 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); + // Use crossterm queueing to color characters so it's platform-agnostic + let mut stdout = stdout(); + stdout.queue(Print(horizontal_padding.clone())).ok(); - // 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(); + } + } - 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); + 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);