modern_terminal/core/
console.rs1pub 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}