use std::io::IsTerminal;
use crate::render::{Charset, ColorMode};
use crate::theme::Theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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 portable(width: usize, height: usize) -> Frame {
Frame {
width,
height,
charset: Charset::Quadrants,
color: ColorMode::Plain,
theme: Theme::DARK,
}
}
pub fn detect() -> Frame {
Frame::detect_for(&std::io::stdout())
}
pub fn detect_for(destination: &impl IsTerminal) -> 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(destination.is_terminal()),
theme: Theme::detect(),
}
}
}
fn variable(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|value| !value.is_empty())
}
fn detect_charset() -> Charset {
detect_charset_with(variable)
}
fn detect_charset_with(mut variable: impl FnMut(&str) -> Option<String>) -> Charset {
if let Some(charset) = variable("MALEVICH_CHARSET").and_then(|value| named_charset(&value)) {
return 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) {
if !locale.to_ascii_lowercase().contains("utf") {
return Charset::Ascii;
}
break;
}
}
Charset::Quadrants
}
fn named_charset(value: &str) -> Option<Charset> {
Some(match value.trim().to_ascii_lowercase().as_str() {
"ascii" => Charset::Ascii,
"half" | "halfblock" | "halfblocks" => Charset::HalfBlocks,
"quad" | "quadrant" | "quadrants" => Charset::Quadrants,
"sextant" | "sextants" => Charset::Sextants,
"octant" | "octants" => Charset::Octants,
"braille" => Charset::Braille,
"auto" => return None,
_ => return None,
})
}
fn detect_color(is_terminal: bool) -> ColorMode {
if variable("NO_COLOR").is_some() {
return ColorMode::Plain;
}
let forced = variable("CLICOLOR_FORCE").is_some_and(|value| value != "0");
if !forced && !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
}
#[cfg(test)]
#[path = "tests/frame_tests.rs"]
mod tests;