navi/common/
terminal.rs

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}