use std::io::IsTerminal;
use crate::render::{Charset, ColorMode};
use crate::theme::Theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Frame {
pub width: usize,
pub height: usize,
pub charset: Charset,
pub color: ColorMode,
pub theme: Theme,
}
impl Frame {
pub fn plain(width: usize, height: usize) -> Frame {
Frame {
width,
height,
charset: Charset::Braille,
color: ColorMode::Plain,
theme: Theme::DARK,
}
}
pub fn detect() -> Frame {
let (width, height) = match terminal_size::terminal_size() {
Some((terminal_size::Width(w), terminal_size::Height(h))) => {
(w as usize, (h as usize / 3).clamp(8, 24))
}
None => (80, 16),
};
Frame {
width,
height,
charset: detect_charset(),
color: detect_color(),
theme: Theme::detect(),
}
}
}
fn variable(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|value| !value.is_empty())
}
fn detect_charset() -> Charset {
if variable("TERM").as_deref() == Some("dumb") {
return Charset::Ascii;
}
for name in ["LC_ALL", "LC_CTYPE", "LANG"] {
if let Some(locale) = variable(name) {
return if locale.to_ascii_lowercase().contains("utf") {
Charset::Braille
} else {
Charset::Ascii
};
}
}
Charset::Braille
}
fn detect_color() -> ColorMode {
if variable("NO_COLOR").is_some() {
return ColorMode::Plain;
}
let forced = variable("CLICOLOR_FORCE").is_some_and(|value| value != "0");
if !forced && !std::io::stdout().is_terminal() {
return ColorMode::Plain;
}
let term = variable("TERM").unwrap_or_default();
if term == "dumb" {
return ColorMode::Plain;
}
let colorterm = variable("COLORTERM").unwrap_or_default();
if colorterm == "truecolor" || colorterm == "24bit" {
return ColorMode::TrueColor;
}
if term.contains("256color") {
return ColorMode::Ansi256;
}
ColorMode::Ansi16
}