modern_terminal/core/
console.rs

1pub struct Console<'a, W>
2where
3    W: std::io::Write,
4{
5    options: crate::core::render::Options,
6    writer:  &'a mut W,
7}
8
9impl<'a, W> Console<'a, W>
10where
11    W: std::io::Write,
12{
13    pub fn render<R>(
14        &mut self,
15        component: &R,
16    ) -> std::io::Result<()>
17    where
18        R: crate::core::render::Render,
19    {
20        for segment in component.render(&self.options).iter() {
21            self.writer.write(segment.as_bytes())?;
22            self.writer.write(b"\n")?;
23        }
24
25        Ok(())
26    }
27}
28
29impl<'a, W> Console<'a, W>
30where
31    W: std::io::Write + std::os::unix::io::AsRawFd,
32{
33    pub fn from_fd(writer: &mut W) -> Console<W> {
34        let is_tty = is_tty(writer);
35        let tty_size = tty_size(writer);
36
37        Console {
38            options: crate::core::render::Options {
39                is_tty,
40                columns: Some(tty_size.0),
41                rows: Some(tty_size.1),
42                storage: detect_storage(),
43            },
44            writer,
45        }
46    }
47}
48
49impl<'a, W> Console<'a, W>
50where
51    W: std::io::Write,
52{
53    pub fn from_writer(
54        writer: &mut W,
55        options: crate::core::render::Options,
56    ) -> Console<W> {
57        Console { options, writer }
58    }
59}
60
61fn detect_storage() -> Option<crate::core::color::storage::Storage> {
62    if let Ok(term) = std::env::var("COLORTERM") {
63        let term = term.trim().to_lowercase();
64        if &term == "24bit" || &term == "truecolor" {
65            return Some(crate::core::color::storage::Storage::Bits24);
66        }
67    }
68
69    if let Ok(term) = std::env::var("TERM") {
70        if let Some((_, term)) = term.trim().to_lowercase().rsplit_once("-") {
71            if term == "dumb" || term == "unknown" {
72                return None;
73            }
74            else if term == "16color" {
75                return Some(crate::core::color::storage::Storage::Bits4);
76            }
77            else if term == "256color" {
78                return Some(crate::core::color::storage::Storage::Bits8);
79            }
80        }
81    }
82
83    return None;
84}
85
86fn is_tty<W>(writer: &W) -> bool
87where
88    W: std::io::Write + std::os::unix::io::AsRawFd,
89{
90    let fd = writer.as_raw_fd();
91    unsafe { libc::isatty(fd) != 0 }
92}
93
94fn tty_size<W>(writer: &W) -> (usize, usize)
95where
96    W: std::io::Write + std::os::unix::io::AsRawFd,
97{
98    let fd = writer.as_raw_fd();
99    let mut size = libc::winsize {
100        ws_row:    0,
101        ws_col:    0,
102        ws_xpixel: 0,
103        ws_ypixel: 0,
104    };
105
106    if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ.into(), &mut size) } != -1
107        && size.ws_col > 0
108        && size.ws_row > 0
109    {
110        (size.ws_col.into(), size.ws_row.into())
111    }
112    else {
113        (
114            crate::core::render::DEFAULT_COLUMNS,
115            crate::core::render::DEFAULT_ROWS,
116        )
117    }
118}
119
120#[cfg(test)]
121mod test_console {
122    use super::Console;
123
124    #[test]
125    fn from_fd() {
126        let mut writer = std::io::stdout();
127        Console::from_fd(&mut writer);
128    }
129
130    #[test]
131    fn from_writer() {
132        let options = crate::core::render::Options {
133            columns: Some(crate::core::render::DEFAULT_COLUMNS),
134            is_tty:  false,
135            storage: None,
136            rows:    Some(crate::core::render::DEFAULT_ROWS),
137        };
138        let mut writer = std::io::Cursor::new(Vec::new());
139        let console = Console::from_writer(&mut writer, options);
140        let mut writer = std::io::Cursor::new(Vec::new());
141        let expected = Console { options, writer: &mut writer };
142        assert_eq!(console.options, expected.options);
143    }
144}