aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml6
-rw-r--r--src/color.rs108
-rw-r--r--src/main.rs65
4 files changed, 164 insertions, 18 deletions
diff --git a/Cargo.lock b/Cargo.lock
index beaaa6b..7b96e0a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -252,12 +252,13 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "mikusays"
-version = "0.1.4"
+version = "0.1.4-2"
dependencies = [
"clap",
"crossterm",
"rand",
"unicode-width",
+ "winapi",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index e61489c..e7bdafc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mikusays"
-version = "0.1.4"
+version = "0.1.4-2"
edition = "2024"
description = "A \"cowsay\" clone with Hatsune Miku ASCII art and speech bubbles."
license = "MIT"
@@ -31,3 +31,7 @@ crossterm = "0.29"
unicode-width = "0.2.2"
rand = "0.9.2"
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"] }
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<String> {
+ // 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);
- }
- }
+ // 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();
- println!("{}", colored_line);
+ 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);