pulldown_cmark_mdcat/terminal/
size.rs1use std::cmp::Ordering;
10
11#[derive(Debug, Copy, Clone)]
18pub struct PixelSize {
19 pub x: u32,
21 pub y: u32,
23}
24
25impl PixelSize {
26 pub fn from_xy((x, y): (u32, u32)) -> Self {
28 Self { x, y }
29 }
30}
31
32impl PartialEq for PixelSize {
33 fn eq(&self, other: &Self) -> bool {
34 matches!(self.partial_cmp(other), Some(Ordering::Equal))
35 }
36}
37
38impl PartialOrd for PixelSize {
39 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
40 if self.x == other.x && self.y == other.y {
41 Some(Ordering::Equal)
42 } else if self.x < other.x && self.y < other.y {
43 Some(Ordering::Less)
44 } else if self.x > other.x && self.y > other.y {
45 Some(Ordering::Greater)
46 } else {
47 None
48 }
49 }
50}
51
52#[derive(Debug, Copy, Clone, PartialEq)]
54pub struct TerminalSize {
55 pub columns: u16,
57 pub rows: u16,
59 pub pixels: Option<PixelSize>,
61 pub cell: Option<PixelSize>,
63}
64
65impl Default for TerminalSize {
66 fn default() -> Self {
67 TerminalSize {
68 columns: 80,
69 rows: 24,
70 pixels: None,
71 cell: None,
72 }
73 }
74}
75
76#[cfg(unix)]
77mod implementation {
78 use rustix::termios::{tcgetwinsize, Winsize};
79 use tracing::{event, Level};
80
81 use crate::TerminalSize;
82 use std::fs::File;
83 use std::io::Result;
84 use std::path::Path;
85
86 use super::PixelSize;
87
88 fn ctermid() -> &'static Path {
93 Path::new("/dev/tty")
94 }
95
96 fn from_cterm() -> Result<Winsize> {
97 let tty = File::open(ctermid())?;
98 tcgetwinsize(&tty).map_err(Into::into)
99 }
100
101 pub fn from_terminal() -> Option<TerminalSize> {
109 let winsize = from_cterm()
110 .map_err(|error| {
111 event!(
112 Level::ERROR,
113 "Failed to read terminal size from controlling terminal: {}",
114 error
115 );
116 error
117 })
118 .ok()?;
119 if winsize.ws_row == 0 || winsize.ws_col == 0 {
120 event!(
121 Level::WARN,
122 "Invalid terminal size returned, columns or rows were 0: {:?}",
123 winsize
124 );
125 None
126 } else {
127 let mut terminal_size = TerminalSize {
128 columns: winsize.ws_col,
129 rows: winsize.ws_row,
130 pixels: None,
131 cell: None,
132 };
133 if winsize.ws_xpixel != 0 && winsize.ws_ypixel != 0 {
134 let pixels = PixelSize {
135 x: winsize.ws_xpixel as u32,
136 y: winsize.ws_ypixel as u32,
137 };
138 terminal_size.pixels = Some(pixels);
139 terminal_size.cell = Some(PixelSize {
140 x: pixels.x / terminal_size.columns as u32,
141 y: pixels.y / terminal_size.rows as u32,
142 });
143 };
144 Some(terminal_size)
145 }
146 }
147}
148
149#[cfg(windows)]
150mod implementation {
151 use terminal_size::{terminal_size, Height, Width};
152
153 use super::TerminalSize;
154
155 pub fn from_terminal() -> Option<TerminalSize> {
156 terminal_size().map(|(Width(columns), Height(rows))| TerminalSize {
157 rows,
158 columns,
159 pixels: None,
160 cell: None,
161 })
162 }
163}
164
165impl TerminalSize {
166 pub fn from_env() -> Option<Self> {
170 let columns = std::env::var("COLUMNS")
171 .ok()
172 .and_then(|value| value.parse::<u16>().ok());
173 let rows = std::env::var("LINES")
174 .ok()
175 .and_then(|value| value.parse::<u16>().ok());
176
177 match (columns, rows) {
178 (Some(columns), Some(rows)) => Some(Self {
179 columns,
180 rows,
181 pixels: None,
182 cell: None,
183 }),
184 _ => None,
185 }
186 }
187
188 pub fn from_terminal() -> Option<Self> {
196 implementation::from_terminal()
197 }
198
199 pub fn detect() -> Option<Self> {
204 Self::from_terminal().or_else(Self::from_env)
205 }
206
207 pub fn with_max_columns(&self, max_columns: u16) -> Self {
211 let pixels = match (self.pixels, self.cell) {
212 (Some(pixels), Some(cell)) => Some(PixelSize {
213 x: cell.x * max_columns as u32,
214 y: pixels.y,
215 }),
216 _ => None,
217 };
218 Self {
219 columns: max_columns,
220 rows: self.rows,
221 pixels,
222 cell: self.cell,
223 }
224 }
225}