1use crate::prelude::*;
2use crossterm::style;
3use crossterm::terminal;
4
5use std::process::Command;
6
7const FALLBACK_WIDTH: u16 = 80;
8
9fn width_with_shell_out() -> Result<u16> {
10 let output = if cfg!(target_os = "macos") {
11 Command::new("stty")
12 .arg("-f")
13 .arg("/dev/stderr")
14 .arg("size")
15 .stderr(Stdio::inherit())
16 .output()?
17 } else {
18 Command::new("stty")
19 .arg("size")
20 .arg("-F")
21 .arg("/dev/stderr")
22 .stderr(Stdio::inherit())
23 .output()?
24 };
25
26 if let Some(0) = output.status.code() {
27 let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
28 let mut data = stdout.split_whitespace();
29 data.next();
30 return data
31 .next()
32 .expect("Not enough data")
33 .parse::<u16>()
34 .map_err(|_| anyhow!("Invalid width"));
35 }
36
37 Err(anyhow!("Invalid status code"))
38}
39
40pub fn width() -> u16 {
41 if let Ok((w, _)) = terminal::size() {
42 w
43 } else {
44 width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
45 }
46}
47
48pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
49 style::Color::parse_ansi(&format!("5;{ansi}"))
50}
51
52#[derive(Debug, Clone)]
53pub struct Color(pub style::Color);
54
55impl FromStr for Color {
56 type Err = &'static str;
57
58 fn from_str(ansi: &str) -> Result<Self, Self::Err> {
59 if let Some(c) = parse_ansi(ansi) {
60 Ok(Color(c))
61 } else {
62 Err("Invalid color")
63 }
64 }
65}