use std::io::IsTerminal;
use crate::color::ColorSystem;
struct EnvSettings {
no_color: Option<String>,
colorterm: Option<String>,
term: Option<String>,
#[cfg(windows)]
wt_session: Option<String>,
}
fn read_env_settings() -> EnvSettings {
EnvSettings {
no_color: std::env::var("NO_COLOR").ok(),
colorterm: std::env::var("COLORTERM").ok(),
term: std::env::var("TERM").ok(),
#[cfg(windows)]
wt_session: std::env::var("WT_SESSION").ok(),
}
}
#[must_use]
pub fn get_terminal_size() -> Option<(usize, usize)> {
crossterm::terminal::size()
.ok()
.map(|(w, h)| (w as usize, h as usize))
}
#[must_use]
pub fn get_terminal_width() -> usize {
get_terminal_size().map_or(80, |(w, _)| w)
}
#[must_use]
pub fn get_terminal_height() -> usize {
get_terminal_size().map_or(24, |(_, h)| h)
}
#[must_use]
pub fn is_terminal() -> bool {
if force_color_forces_terminal(std::env::var("FORCE_COLOR").ok().as_deref()) {
return true;
}
std::io::stdout().is_terminal()
}
#[must_use]
pub fn is_stderr_terminal() -> bool {
if force_color_forces_terminal(std::env::var("FORCE_COLOR").ok().as_deref()) {
return true;
}
std::io::stderr().is_terminal()
}
fn force_color_forces_terminal(force_color: Option<&str>) -> bool {
let Some(force_color) = force_color else {
return false;
};
let force_color = force_color.trim();
!force_color.is_empty() && force_color != "0"
}
#[must_use]
pub fn is_dumb_terminal() -> bool {
std::env::var("TERM").ok().is_some_and(|term| {
let term = term.to_lowercase();
term == "dumb" || term == "unknown"
})
}
#[must_use]
pub fn detect_color_system() -> Option<ColorSystem> {
detect_color_system_with(&read_env_settings(), is_terminal())
}
#[must_use]
pub(crate) fn detect_color_system_forced(is_tty: bool) -> Option<ColorSystem> {
detect_color_system_with(&read_env_settings(), is_tty)
}
fn detect_color_system_with(
env: &EnvSettings,
#[allow(unused_variables)] is_tty: bool,
) -> Option<ColorSystem> {
if env
.no_color
.as_deref()
.is_some_and(|value| !value.is_empty())
{
return None;
}
if let Some(colorterm) = env.colorterm.as_ref() {
let colorterm = colorterm.trim().to_lowercase();
if colorterm == "truecolor" || colorterm == "24bit" {
return Some(ColorSystem::TrueColor);
}
}
let term = env
.term
.as_ref()
.map(|value| value.trim().to_lowercase())
.unwrap_or_default();
if term == "dumb" || term == "unknown" {
return None;
}
let colors = term.rsplit('-').next().unwrap_or("");
match colors {
"kitty" | "256color" => return Some(ColorSystem::EightBit),
"16color" => return Some(ColorSystem::Standard),
_ => {}
}
#[cfg(windows)]
{
if env.wt_session.is_some() {
return Some(ColorSystem::TrueColor);
}
return Some(ColorSystem::TrueColor);
}
#[cfg(not(windows))]
if is_tty {
Some(ColorSystem::Standard)
} else {
None
}
}
pub fn enable_raw_mode() -> std::io::Result<()> {
crossterm::terminal::enable_raw_mode()
}
pub fn disable_raw_mode() -> std::io::Result<()> {
crossterm::terminal::disable_raw_mode()
}
pub mod control {
use std::io::Write;
pub fn clear_screen<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{
ExecutableCommand,
terminal::{Clear, ClearType},
};
writer.execute(Clear(ClearType::All))?;
Ok(())
}
pub fn clear_line<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{
ExecutableCommand,
terminal::{Clear, ClearType},
};
writer.execute(Clear(ClearType::CurrentLine))?;
Ok(())
}
pub fn cursor_home<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveTo};
writer.execute(MoveTo(0, 0))?;
Ok(())
}
pub fn cursor_move_to<W: Write>(writer: &mut W, x: u16, y: u16) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveTo};
writer.execute(MoveTo(x, y))?;
Ok(())
}
pub fn cursor_up<W: Write>(writer: &mut W, n: u16) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveUp};
writer.execute(MoveUp(n))?;
Ok(())
}
pub fn cursor_down<W: Write>(writer: &mut W, n: u16) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveDown};
writer.execute(MoveDown(n))?;
Ok(())
}
pub fn cursor_forward<W: Write>(writer: &mut W, n: u16) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveRight};
writer.execute(MoveRight(n))?;
Ok(())
}
pub fn cursor_backward<W: Write>(writer: &mut W, n: u16) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::MoveLeft};
writer.execute(MoveLeft(n))?;
Ok(())
}
pub fn hide_cursor<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::Hide};
writer.execute(Hide)?;
Ok(())
}
pub fn show_cursor<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, cursor::Show};
writer.execute(Show)?;
Ok(())
}
pub fn enable_alt_screen<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, terminal::EnterAlternateScreen};
writer.execute(EnterAlternateScreen)?;
Ok(())
}
pub fn disable_alt_screen<W: Write>(writer: &mut W) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, terminal::LeaveAlternateScreen};
writer.execute(LeaveAlternateScreen)?;
Ok(())
}
pub fn set_title<W: Write>(writer: &mut W, title: &str) -> std::io::Result<()> {
use crossterm::{ExecutableCommand, terminal::SetTitle};
writer.execute(SetTitle(title))?;
Ok(())
}
pub fn bell<W: Write>(writer: &mut W) -> std::io::Result<()> {
write!(writer, "\x07")?;
writer.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_env(
no_color: Option<&str>,
colorterm: Option<&str>,
term: Option<&str>,
) -> EnvSettings {
EnvSettings {
no_color: no_color.map(String::from),
colorterm: colorterm.map(String::from),
term: term.map(String::from),
#[cfg(windows)]
wt_session: None,
}
}
#[test]
fn test_detect_color_system() {
let _ = detect_color_system();
}
#[test]
fn test_is_terminal() {
let _ = is_terminal();
}
#[test]
fn test_force_color_forces_terminal() {
assert!(!force_color_forces_terminal(None));
assert!(!force_color_forces_terminal(Some("")));
assert!(!force_color_forces_terminal(Some(" ")));
assert!(!force_color_forces_terminal(Some("0")));
assert!(!force_color_forces_terminal(Some(" 0 ")));
assert!(force_color_forces_terminal(Some("1")));
assert!(force_color_forces_terminal(Some("true")));
}
#[test]
fn test_is_stderr_terminal() {
let _ = is_stderr_terminal();
}
#[test]
fn test_get_terminal_size() {
let _ = get_terminal_size();
}
#[test]
fn test_get_terminal_width() {
let width = get_terminal_width();
assert!(width > 0);
}
#[test]
fn test_get_terminal_height() {
let height = get_terminal_height();
assert!(height > 0);
}
#[test]
fn test_no_color_disables_colors() {
let settings = make_env(Some("1"), Some("truecolor"), Some("xterm-256color"));
assert_eq!(detect_color_system_with(&settings, true), None);
}
#[test]
fn test_no_color_empty_string_ignored() {
let settings = make_env(Some(""), Some("truecolor"), None);
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_no_color_takes_precedence_over_colorterm() {
let settings = make_env(Some("1"), Some("truecolor"), None);
assert_eq!(detect_color_system_with(&settings, true), None);
}
#[test]
fn test_colorterm_truecolor() {
let settings = make_env(None, Some("truecolor"), None);
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_colorterm_24bit() {
let settings = make_env(None, Some("24bit"), None);
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_colorterm_case_insensitive() {
let settings = make_env(None, Some("TRUECOLOR"), None);
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_colorterm_unknown_value() {
let settings = make_env(None, Some("unknown"), Some("xterm-256color"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::EightBit)
);
}
#[test]
fn test_term_dumb() {
let settings = make_env(None, None, Some("dumb"));
assert_eq!(detect_color_system_with(&settings, true), None);
}
#[test]
fn test_term_dumb_case_insensitive() {
let settings = make_env(None, None, Some("DUMB"));
assert_eq!(detect_color_system_with(&settings, true), None);
}
#[test]
fn test_term_unknown() {
let settings = make_env(None, None, Some("unknown"));
assert_eq!(detect_color_system_with(&settings, true), None);
}
#[test]
fn test_term_256color() {
let settings = make_env(None, None, Some("xterm-256color"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::EightBit)
);
}
#[test]
fn test_term_256_variant() {
let settings = make_env(None, None, Some("screen-256"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
fn test_term_16color() {
let settings = make_env(None, None, Some("xterm-16color"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
fn test_term_xterm() {
let settings = make_env(None, None, Some("xterm"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
fn test_term_xterm_color() {
let settings = make_env(None, None, Some("xterm-color"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
fn test_term_vt100() {
let settings = make_env(None, None, Some("vt100"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
#[allow(unused_variables)]
fn test_term_linux() {
let settings = make_env(None, None, Some("linux"));
#[cfg(not(windows))]
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
#[allow(unused_variables)]
fn test_no_env_vars_tty_true() {
let settings = make_env(None, None, None);
#[cfg(not(windows))]
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::Standard)
);
}
#[test]
#[allow(unused_variables)]
fn test_no_env_vars_tty_false() {
let settings = make_env(None, None, None);
#[cfg(not(windows))]
assert_eq!(detect_color_system_with(&settings, false), None);
}
#[cfg(windows)]
#[test]
fn test_windows_terminal_detected() {
let settings = EnvSettings {
no_color: None,
force_color: None,
colorterm: None,
term: None,
wt_session: Some("1".to_string()),
};
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[cfg(windows)]
#[test]
fn test_windows_default_truecolor() {
let settings = EnvSettings {
no_color: None,
force_color: None,
colorterm: None,
term: None,
wt_session: None,
};
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_colorterm_takes_precedence_over_term() {
let settings = make_env(None, Some("truecolor"), Some("xterm"));
assert_eq!(
detect_color_system_with(&settings, true),
Some(ColorSystem::TrueColor)
);
}
#[test]
fn test_all_env_vars_empty() {
let settings = make_env(None, None, None);
let _ = detect_color_system_with(&settings, true);
let _ = detect_color_system_with(&settings, false);
}
}