Merge pull request #3 from xxanqw:fix-no-colors-on-windows
Enable ANSI support and improve color handling on Windows
This commit is contained in:
5 files changed
+251
-76
No files matched your search
@@ -80,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
$issContent = @'
|
$issContent = @'
|
||||||
[Setup]
|
[Setup]
|
||||||
AppName=Mikusays
|
AppName=mikusays
|
||||||
AppVersion=__VERSION__
|
AppVersion=__VERSION__
|
||||||
AppCopyright=2025 xxanqw
|
AppCopyright=2025 xxanqw
|
||||||
AppPublisher=xxanqw
|
AppPublisher=xxanqw
|
||||||
|
|||||||
Generated
+2
-1
@@ -252,12 +252,13 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mikusays"
|
name = "mikusays"
|
||||||
version = "0.1.4"
|
version = "0.1.4-2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"rand",
|
"rand",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mikusays"
|
name = "mikusays"
|
||||||
version = "0.1.4"
|
version = "0.1.4-2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A \"cowsay\" clone with Hatsune Miku ASCII art and speech bubbles."
|
description = "A \"cowsay\" clone with Hatsune Miku ASCII art and speech bubbles."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -31,3 +31,7 @@ crossterm = "0.29"
|
|||||||
unicode-width = "0.2.2"
|
unicode-width = "0.2.2"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
clap = { version = "4.5.53", features = ["derive"] }
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
# sysinfo removed; using env var heuristics instead
|
||||||
|
|
||||||
|
# Windows console API to enable virtual terminal processing (ANSI)
|
||||||
|
winapi = { version = "0.3", features = ["consoleapi", "wincon", "processenv", "handleapi", "winbase"] }
|
||||||
+138
-60
@@ -1,4 +1,5 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
// No external sysinfo dependency — use environment heuristics instead for Windows shell detection.
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -374,47 +375,22 @@ fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert RGB to ANSI 16 color code
|
/// Convert RGB to ANSI 16 color code
|
||||||
fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> u8 {
|
pub(crate) fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> u8 {
|
||||||
// Calculate brightness
|
// 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 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;
|
let is_bright = brightness > 128;
|
||||||
|
|
||||||
// Determine base color
|
// Determine base color using simple thresholding on each channel, producing an index 0..7
|
||||||
let base_color = if r > g && r > b {
|
let r_on = r > 128;
|
||||||
// Red dominant
|
let g_on = g > 128;
|
||||||
if r > 192 && g < 64 && b < 64 {
|
let b_on = b > 128;
|
||||||
1 // Red
|
let base_idx = (r_on as u8) + (g_on as u8) * 2 + (b_on as u8) * 4; // 0..7
|
||||||
} 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// SGR color codes: 30..37 for normal, 90..97 for bright
|
||||||
if is_bright {
|
if is_bright {
|
||||||
base_color + 8 // Bright colors are 8-15
|
90 + base_idx
|
||||||
} else {
|
} else {
|
||||||
base_color // Normal colors are 0-7
|
30 + base_idx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,45 +433,131 @@ fn ansi_to_rgb(code: u8) -> (u8, u8, u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect the best available color mode for the terminal
|
// (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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detect the parent shell name on Windows, using the sysinfo crate.
|
||||||
pub fn detect_color_mode() -> ColorMode {
|
pub fn detect_color_mode() -> ColorMode {
|
||||||
if std::env::var("NO_COLOR").is_ok() {
|
if std::env::var("NO_COLOR").is_ok() {
|
||||||
return ColorMode::NoColor;
|
return ColorMode::NoColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(colorterm) = std::env::var("COLORTERM")
|
// Early checks
|
||||||
&& (colorterm.contains("truecolor") || colorterm.contains("24bit"))
|
if std::env::var("COLORTERM")
|
||||||
|
.is_ok_and(|colorterm| colorterm.contains("truecolor") || colorterm.contains("24bit"))
|
||||||
{
|
{
|
||||||
return ColorMode::Truecolor;
|
return ColorMode::Truecolor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(term) = std::env::var("TERM") {
|
if std::env::var("TERM").is_ok_and(|term| term.contains("256color")) {
|
||||||
if term.contains("256color") {
|
return ColorMode::Ansi256;
|
||||||
return ColorMode::Ansi256;
|
}
|
||||||
}
|
if std::env::var("TERM").is_ok_and(|term| term.contains("color")) {
|
||||||
if term.contains("color") {
|
return ColorMode::Ansi16;
|
||||||
return ColorMode::Ansi16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallback
|
// Default fallback
|
||||||
ColorMode::Ansi16
|
#[cfg(windows)]
|
||||||
}
|
let mut mode = ColorMode::Ansi16;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let mode = ColorMode::Ansi16;
|
||||||
|
|
||||||
/// Linear interpolation between two colors in HSV space
|
// If running on Windows, try to enable ANSI (virtual terminal processing)
|
||||||
pub fn lerp_color_hsv(start: &Color, end: &Color, t: f32) -> Color {
|
#[cfg(windows)]
|
||||||
let (h1, s1, v1) = rgb_to_hsv(start.to_rgb());
|
{
|
||||||
let (h2, s2, v2) = rgb_to_hsv(end.to_rgb());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle hue interpolation correctly (shortest path around the color wheel)
|
mode
|
||||||
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 RGB to HSV
|
/// Convert RGB to HSV
|
||||||
@@ -526,6 +588,22 @@ fn rgb_to_hsv((r, g, b): (u8, u8, u8)) -> (f32, f32, f32) {
|
|||||||
(h, s, v)
|
(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
|
/// Convert HSV to RGB
|
||||||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||||||
let c = v * s;
|
let c = v * s;
|
||||||
|
|||||||
+105
-13
@@ -1,6 +1,9 @@
|
|||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
|
use crossterm::QueueableCommand;
|
||||||
|
use crossterm::style::{Print, ResetColor, SetForegroundColor};
|
||||||
use crossterm::terminal::size;
|
use crossterm::terminal::size;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::io::{Write, stdout};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
@@ -55,6 +58,9 @@ pub struct Args {
|
|||||||
/// Disable all coloring (respects NO_COLOR environment variable)
|
/// Disable all coloring (respects NO_COLOR environment variable)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub no_color: bool,
|
pub no_color: bool,
|
||||||
|
/// Print color detection/debug info and exit
|
||||||
|
#[arg(long)]
|
||||||
|
pub color_debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -66,6 +72,8 @@ pub struct ColorConfig {
|
|||||||
pub single_color: Option<Color>,
|
pub single_color: Option<Color>,
|
||||||
pub gradient: Option<(Color, Color)>,
|
pub gradient: Option<(Color, Color)>,
|
||||||
pub color_cache: Vec<Color>,
|
pub color_cache: Vec<Color>,
|
||||||
|
pub ansi_enabled: bool,
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_color_args(args: &Args) -> Result<ColorConfig, Box<dyn Error>> {
|
pub fn parse_color_args(args: &Args) -> Result<ColorConfig, Box<dyn Error>> {
|
||||||
@@ -157,6 +165,12 @@ pub fn parse_color_args(args: &Args) -> Result<ColorConfig, Box<dyn Error>> {
|
|||||||
std::process::exit(1);
|
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 && color::enable_windows_ansi();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let ansi_enabled = false;
|
||||||
|
|
||||||
Ok(ColorConfig {
|
Ok(ColorConfig {
|
||||||
mode: color_mode,
|
mode: color_mode,
|
||||||
rainbow: args.rainbow,
|
rainbow: args.rainbow,
|
||||||
@@ -165,6 +179,8 @@ pub fn parse_color_args(args: &Args) -> Result<ColorConfig, Box<dyn Error>> {
|
|||||||
single_color,
|
single_color,
|
||||||
gradient,
|
gradient,
|
||||||
color_cache: Vec::new(),
|
color_cache: Vec::new(),
|
||||||
|
ansi_enabled,
|
||||||
|
debug: args.color_debug,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,30 +441,38 @@ fn draw_miku_says(
|
|||||||
|
|
||||||
// Apply coloring to Miku art lines
|
// Apply coloring to Miku art lines
|
||||||
if color_config.mode != ColorMode::NoColor && !color_config.color_cache.is_empty() {
|
if color_config.mode != ColorMode::NoColor && !color_config.color_cache.is_empty() {
|
||||||
let mut colored_line = String::new();
|
// Use crossterm queueing to color characters so it's platform-agnostic
|
||||||
colored_line.push_str(&horizontal_padding);
|
let mut stdout = stdout();
|
||||||
|
stdout.queue(Print(horizontal_padding.clone())).ok();
|
||||||
|
|
||||||
for c in line.chars() {
|
for c in line.chars() {
|
||||||
if c.is_whitespace() {
|
if c.is_whitespace() {
|
||||||
colored_line.push(c);
|
stdout.queue(Print(c)).ok();
|
||||||
} else if color_index < color_config.color_cache.len() {
|
} else if color_index < color_config.color_cache.len() {
|
||||||
let color = &color_config.color_cache[color_index];
|
let color = &color_config.color_cache[color_index];
|
||||||
let ansi_code = color.to_ansi_fg(color_config.mode);
|
let ct_color = color::to_crossterm_color(color);
|
||||||
colored_line.push_str(&ansi_code);
|
stdout.queue(SetForegroundColor(ct_color)).ok();
|
||||||
colored_line.push(c);
|
stdout.queue(Print(c)).ok();
|
||||||
colored_line.push_str("\x1b[0m"); // Reset
|
stdout.queue(ResetColor).ok();
|
||||||
color_index += 1;
|
color_index += 1;
|
||||||
} else {
|
} else {
|
||||||
colored_line.push(c);
|
stdout.queue(Print(c)).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}", colored_line);
|
stdout.queue(Print("\n")).ok();
|
||||||
|
stdout.flush().ok();
|
||||||
|
continue;
|
||||||
} else if let Some(single_color) = &color_config.single_color {
|
} else if let Some(single_color) = &color_config.single_color {
|
||||||
// Apply single solid color
|
// Apply single solid color using crossterm
|
||||||
let ansi_code = single_color.to_ansi_fg(color_config.mode);
|
let mut stdout = stdout();
|
||||||
let reset_code = "\x1b[0m";
|
let ct_color = color::to_crossterm_color(single_color);
|
||||||
println!("{}{}{}{}", horizontal_padding, ansi_code, line, reset_code);
|
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 {
|
} else {
|
||||||
// No coloring
|
// No coloring
|
||||||
println!("{}{}", horizontal_padding, line);
|
println!("{}{}", horizontal_padding, line);
|
||||||
@@ -481,6 +505,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Parse color-related arguments
|
// Parse color-related arguments
|
||||||
let mut color_config = parse_color_args(&args)?;
|
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={}",
|
||||||
|
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)?;
|
draw_miku_says(&text, style, &mut color_config)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -574,6 +630,7 @@ mod tests {
|
|||||||
color: None,
|
color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
no_color: false,
|
no_color: false,
|
||||||
|
color_debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = parse_color_args(&args).unwrap();
|
let config = parse_color_args(&args).unwrap();
|
||||||
@@ -598,6 +655,7 @@ mod tests {
|
|||||||
color: None,
|
color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
no_color: true,
|
no_color: true,
|
||||||
|
color_debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = parse_color_args(&args).unwrap();
|
let config = parse_color_args(&args).unwrap();
|
||||||
@@ -645,6 +703,8 @@ mod tests {
|
|||||||
single_color: None,
|
single_color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled
|
color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled
|
||||||
|
ansi_enabled: false,
|
||||||
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test with 0 characters
|
// Test with 0 characters
|
||||||
@@ -666,6 +726,8 @@ mod tests {
|
|||||||
single_color: None,
|
single_color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled
|
color_cache: vec![Color::Hex("#000000".to_string())], // Pre-filled
|
||||||
|
ansi_enabled: false,
|
||||||
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = Color::Hex("#FF0000".to_string());
|
let start = Color::Hex("#FF0000".to_string());
|
||||||
@@ -690,6 +752,8 @@ mod tests {
|
|||||||
single_color: None,
|
single_color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
color_cache: Vec::new(),
|
color_cache: Vec::new(),
|
||||||
|
ansi_enabled: false,
|
||||||
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
generate_rainbow_cache(&mut config, 10);
|
generate_rainbow_cache(&mut config, 10);
|
||||||
@@ -701,6 +765,14 @@ mod tests {
|
|||||||
assert_ne!(first_color, last_color);
|
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]
|
#[test]
|
||||||
fn test_generate_gradient_cache() {
|
fn test_generate_gradient_cache() {
|
||||||
let mut config = ColorConfig {
|
let mut config = ColorConfig {
|
||||||
@@ -711,6 +783,8 @@ mod tests {
|
|||||||
single_color: None,
|
single_color: None,
|
||||||
gradient: None,
|
gradient: None,
|
||||||
color_cache: Vec::new(),
|
color_cache: Vec::new(),
|
||||||
|
ansi_enabled: false,
|
||||||
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = Color::Hex("#FF0000".to_string()); // Red
|
let start = Color::Hex("#FF0000".to_string()); // Red
|
||||||
@@ -725,4 +799,22 @@ mod tests {
|
|||||||
let last_color = &config.color_cache[9];
|
let last_color = &config.color_cache[9];
|
||||||
assert_ne!(first_color, last_color);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user