const MIN_COLS: u16 = 80;
const MIN_ROWS: u16 = 24;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct TerminalInfo {
pub cols: u16,
pub rows: u16,
pub term: String,
}
pub fn detect_terminal() -> TerminalInfo {
let (cols, rows) = crossterm::terminal::size().unwrap_or((MIN_COLS, MIN_ROWS));
let term = std::env::var("TERM").unwrap_or_else(|_| "unknown".to_string());
TerminalInfo { cols, rows, term }
}
pub fn validate_minimum_size(info: &TerminalInfo) -> Result<(), String> {
if info.cols < MIN_COLS && info.rows < MIN_ROWS {
return Err(format!(
"terminal too small: {}x{} — minimum is {MIN_COLS}x{MIN_ROWS}",
info.cols, info.rows
));
}
if info.cols < MIN_COLS {
return Err(format!(
"terminal too narrow: {} columns — minimum is {MIN_COLS}",
info.cols
));
}
if info.rows < MIN_ROWS {
return Err(format!(
"terminal too short: {} rows — minimum is {MIN_ROWS}",
info.rows
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn info(cols: u16, rows: u16) -> TerminalInfo {
TerminalInfo {
cols,
rows,
term: "xterm-256color".to_string(),
}
}
#[test]
fn validates_80x24_passes() {
assert!(validate_minimum_size(&info(80, 24)).is_ok());
}
#[test]
fn validates_larger_passes() {
assert!(validate_minimum_size(&info(120, 40)).is_ok());
}
#[test]
fn validates_79x24_fails() {
let result = validate_minimum_size(&info(79, 24));
assert!(result.is_err());
assert!(result.unwrap_err().contains("narrow"));
}
#[test]
fn validates_80x23_fails() {
let result = validate_minimum_size(&info(80, 23));
assert!(result.is_err());
assert!(result.unwrap_err().contains("short"));
}
#[test]
fn validates_both_too_small() {
let result = validate_minimum_size(&info(40, 10));
assert!(result.is_err());
assert!(result.unwrap_err().contains("too small"));
}
#[test]
fn detect_terminal_returns_info() {
let info = detect_terminal();
assert!(!info.term.is_empty());
}
}