forked from eminence/terminal-size
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunix.rs
111 lines (99 loc) · 3.53 KB
/
unix.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use super::{Height, Width};
use std::os::unix::io::RawFd;
use rustix::fd::{BorrowedFd, AsRawFd};
/// Returns the size of the terminal defaulting to STDOUT, if available.
///
/// If STDOUT is not a tty, returns `None`
pub fn terminal_size() -> Option<(Width, Height)> {
terminal_size_using_fd(std::io::stdout().as_raw_fd())
}
/// Returns the size of the terminal using the given file descriptor, if available.
///
/// If the given file descriptor is not a tty, returns `None`
pub fn terminal_size_using_fd(fd: RawFd) -> Option<(Width, Height)> {
use rustix::termios::{isatty, tcgetwinsize};
// TODO: Once I/O safety is stabilized, the enlosing function here should
// be unsafe due to taking a `RawFd`. We should then move the main
// logic here into a new function which takes a `BorrowedFd` and is safe.
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
if !isatty(fd) {
return None;
}
let winsize = tcgetwinsize(fd).ok()?;
let rows = winsize.ws_row;
let cols = winsize.ws_col;
if rows > 0 && cols > 0 {
Some((Width(cols), Height(rows)))
} else {
None
}
}
#[test]
/// Compare with the output of `stty size`
fn compare_with_stty() {
use std::process::Command;
use std::process::Stdio;
let (rows, cols) = if cfg!(target_os = "illumos") {
// illumos stty(1) does not accept a device argument, instead using
// stdin unconditionally:
let output = Command::new("stty")
.stdin(Stdio::inherit())
.output()
.unwrap();
assert!(output.status.success());
// stdout includes the row and columns thus: "rows = 80; columns = 24;"
let vals = String::from_utf8(output.stdout)
.unwrap()
.lines()
.map(|line| {
// Split each line on semicolons to get "k = v" strings:
line.split(';')
.map(str::trim)
.map(str::to_string)
.collect::<Vec<_>>()
})
.flatten()
.filter_map(|term| {
// split each "k = v" string and look for rows/columns:
match term.splitn(2, " = ").collect::<Vec<_>>().as_slice() {
["rows", n] | ["columns", n] => Some(n.parse().unwrap()),
_ => None,
}
})
.collect::<Vec<_>>();
(vals[0], vals[1])
} else {
let output = if cfg!(target_os = "linux") {
Command::new("stty")
.arg("size")
.arg("-F")
.arg("/dev/stderr")
.stderr(Stdio::inherit())
.output()
.unwrap()
} else {
Command::new("stty")
.arg("-f")
.arg("/dev/stderr")
.arg("size")
.stderr(Stdio::inherit())
.output()
.unwrap()
};
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
// stdout is "rows cols"
let mut data = stdout.split_whitespace();
println!("{}", stdout);
let rows = u16::from_str_radix(data.next().unwrap(), 10).unwrap();
let cols = u16::from_str_radix(data.next().unwrap(), 10).unwrap();
(rows, cols)
};
println!("{} {}", rows, cols);
if let Some((Width(w), Height(h))) = terminal_size() {
assert_eq!(rows, h);
assert_eq!(cols, w);
} else {
panic!("terminal_size() return None");
}
}