use super::TerminalMode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TerminalCapabilities {
pub width: u16,
pub height: u16,
pub unicode: bool,
pub ansi_color: bool,
pub true_color: bool,
pub is_tty: bool,
}
impl Default for TerminalCapabilities {
fn default() -> Self {
Self {
width: 80,
height: 24,
unicode: true,
ansi_color: true,
true_color: false,
is_tty: true,
}
}
}
impl TerminalCapabilities {
pub fn detect() -> Self {
use std::env;
use std::io::{stdout, IsTerminal};
let is_tty = stdout().is_terminal();
let (width, height) = Self::get_size();
let lang = env::var("LANG").unwrap_or_default();
let unicode = lang.contains("UTF") || lang.contains("utf");
let term = env::var("TERM").unwrap_or_default();
let ansi_color = !term.is_empty() && term != "dumb";
let colorterm = env::var("COLORTERM").unwrap_or_default();
let true_color = colorterm == "truecolor" || colorterm == "24bit";
Self { width, height, unicode, ansi_color, true_color, is_tty }
}
pub(crate) fn get_size() -> (u16, u16) {
use std::env;
if let (Ok(cols), Ok(rows)) = (env::var("COLUMNS"), env::var("LINES")) {
if let (Ok(c), Ok(r)) = (cols.parse(), rows.parse()) {
return (c, r);
}
}
#[cfg(unix)]
{
use std::io::{stdout, IsTerminal};
if stdout().is_terminal() {
#[repr(C)]
struct WinSize {
ws_row: u16,
ws_col: u16,
ws_xpixel: u16,
ws_ypixel: u16,
}
extern "C" {
fn ioctl(fd: i32, request: u64, ...) -> i32;
}
const TIOCGWINSZ: u64 = 0x5413; let mut ws = WinSize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
#[allow(unsafe_code)]
if unsafe { ioctl(1, TIOCGWINSZ, &mut ws) } == 0 && ws.ws_col > 0 {
return (ws.ws_col, ws.ws_row);
}
}
}
(80, 24)
}
pub fn recommended_mode(&self) -> TerminalMode {
if !self.is_tty {
TerminalMode::Ascii
} else if self.true_color {
TerminalMode::Ansi
} else if self.unicode {
TerminalMode::Unicode
} else {
TerminalMode::Ascii
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_terminal_capabilities_default() {
let caps = TerminalCapabilities::default();
assert_eq!(caps.width, 80);
assert_eq!(caps.height, 24);
assert!(caps.unicode);
assert!(caps.ansi_color);
assert!(!caps.true_color);
assert!(caps.is_tty);
}
#[test]
fn test_terminal_capabilities_clone() {
let caps = TerminalCapabilities::default();
let cloned = caps;
assert_eq!(caps.width, cloned.width);
assert_eq!(caps.unicode, cloned.unicode);
}
#[test]
fn test_terminal_capabilities_eq() {
let caps1 = TerminalCapabilities::default();
let caps2 = TerminalCapabilities::default();
assert_eq!(caps1, caps2);
let caps3 = TerminalCapabilities { width: 120, ..Default::default() };
assert_ne!(caps1, caps3);
}
#[test]
fn test_terminal_capabilities_debug() {
let caps = TerminalCapabilities::default();
let debug = format!("{caps:?}");
assert!(debug.contains("TerminalCapabilities"));
assert!(debug.contains("width: 80"));
}
#[test]
fn test_recommended_mode_not_tty() {
let caps = TerminalCapabilities { is_tty: false, ..Default::default() };
assert_eq!(caps.recommended_mode(), TerminalMode::Ascii);
}
#[test]
fn test_recommended_mode_true_color() {
let caps = TerminalCapabilities {
is_tty: true,
true_color: true,
unicode: true,
..Default::default()
};
assert_eq!(caps.recommended_mode(), TerminalMode::Ansi);
}
#[test]
fn test_recommended_mode_unicode() {
let caps = TerminalCapabilities {
is_tty: true,
true_color: false,
unicode: true,
..Default::default()
};
assert_eq!(caps.recommended_mode(), TerminalMode::Unicode);
}
#[test]
fn test_recommended_mode_ascii_fallback() {
let caps = TerminalCapabilities {
is_tty: true,
true_color: false,
unicode: false,
ansi_color: false,
..Default::default()
};
assert_eq!(caps.recommended_mode(), TerminalMode::Ascii);
}
#[test]
fn test_detect_returns_valid_capabilities() {
let caps = TerminalCapabilities::detect();
assert!(caps.width > 0);
assert!(caps.height > 0);
}
#[test]
fn test_get_size_returns_valid_size() {
let (width, height) = TerminalCapabilities::get_size();
assert!(width > 0);
assert!(height > 0);
}
}