1 use std::env;
2 use std::fmt::Display;
3 use std::fs;
4 use std::io;
5 use std::io::{BufRead, BufReader};
6 use std::os::unix::io::AsRawFd;
7 use std::str;
8
9 use crate::kb::Key;
10 use crate::term::Term;
11
12 pub use crate::common_term::*;
13
14 pub const DEFAULT_WIDTH: u16 = 80;
15
16 #[inline]
is_a_terminal(out: &Term) -> bool17 pub fn is_a_terminal(out: &Term) -> bool {
18 unsafe { libc::isatty(out.as_raw_fd()) != 0 }
19 }
20
is_a_color_terminal(out: &Term) -> bool21 pub fn is_a_color_terminal(out: &Term) -> bool {
22 if !is_a_terminal(out) {
23 return false;
24 }
25
26 if env::var("NO_COLOR").is_ok() {
27 return false;
28 }
29
30 match env::var("TERM") {
31 Ok(term) => term != "dumb",
32 Err(_) => false,
33 }
34 }
35
c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()>36 pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
37 let res = f();
38 if res != 0 {
39 Err(io::Error::last_os_error())
40 } else {
41 Ok(())
42 }
43 }
44
45 #[inline]
terminal_size(out: &Term) -> Option<(u16, u16)>46 pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
47 terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0))
48 }
49
read_secure() -> io::Result<String>50 pub fn read_secure() -> io::Result<String> {
51 let f_tty;
52 let fd = unsafe {
53 if libc::isatty(libc::STDIN_FILENO) == 1 {
54 f_tty = None;
55 libc::STDIN_FILENO
56 } else {
57 let f = fs::File::open("/dev/tty")?;
58 let fd = f.as_raw_fd();
59 f_tty = Some(BufReader::new(f));
60 fd
61 }
62 };
63
64 let mut termios = core::mem::MaybeUninit::uninit();
65 c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
66 let mut termios = unsafe { termios.assume_init() };
67 let original = termios;
68 termios.c_lflag &= !libc::ECHO;
69 c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &termios) })?;
70 let mut rv = String::new();
71
72 let read_rv = if let Some(mut f) = f_tty {
73 f.read_line(&mut rv)
74 } else {
75 io::stdin().read_line(&mut rv)
76 };
77
78 c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?;
79
80 read_rv.map(|_| {
81 let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
82 rv.truncate(len);
83 rv
84 })
85 }
86
read_single_char(fd: i32) -> io::Result<Option<char>>87 fn read_single_char(fd: i32) -> io::Result<Option<char>> {
88 let mut pollfd = libc::pollfd {
89 fd,
90 events: libc::POLLIN,
91 revents: 0,
92 };
93
94 // timeout of zero means that it will not block
95 let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
96 if ret < 0 {
97 return Err(io::Error::last_os_error());
98 }
99
100 let is_ready = pollfd.revents & libc::POLLIN != 0;
101
102 if is_ready {
103 // if there is something to be read, take 1 byte from it
104 let mut buf: [u8; 1] = [0];
105
106 read_bytes(fd, &mut buf, 1)?;
107 Ok(Some(buf[0] as char))
108 } else {
109 //there is nothing to be read
110 Ok(None)
111 }
112 }
113
114 // Similar to libc::read. Read count bytes into slice buf from descriptor fd.
115 // If successful, return the number of bytes read.
116 // Will return an error if nothing was read, i.e when called at end of file.
read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8>117 fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
118 let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
119 if read < 0 {
120 Err(io::Error::last_os_error())
121 } else if read == 0 {
122 Err(io::Error::new(
123 io::ErrorKind::UnexpectedEof,
124 "Reached end of file",
125 ))
126 } else if buf[0] == b'\x03' {
127 Err(io::Error::new(
128 io::ErrorKind::Interrupted,
129 "read interrupted",
130 ))
131 } else {
132 Ok(read as u8)
133 }
134 }
135
read_single_key() -> io::Result<Key>136 pub fn read_single_key() -> io::Result<Key> {
137 let tty_f;
138 let fd = unsafe {
139 if libc::isatty(libc::STDIN_FILENO) == 1 {
140 libc::STDIN_FILENO
141 } else {
142 tty_f = fs::File::open("/dev/tty")?;
143 tty_f.as_raw_fd()
144 }
145 };
146 let mut termios = core::mem::MaybeUninit::uninit();
147 c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
148 let mut termios = unsafe { termios.assume_init() };
149 let original = termios;
150 unsafe { libc::cfmakeraw(&mut termios) };
151 c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
152
153 let rv = match read_single_char(fd)? {
154 Some('\x1b') => {
155 // Escape was read, keep reading in case we find a familiar key
156 if let Some(c1) = read_single_char(fd)? {
157 if c1 == '[' {
158 if let Some(c2) = read_single_char(fd)? {
159 match c2 {
160 'A' => Ok(Key::ArrowUp),
161 'B' => Ok(Key::ArrowDown),
162 'C' => Ok(Key::ArrowRight),
163 'D' => Ok(Key::ArrowLeft),
164 'H' => Ok(Key::Home),
165 'F' => Ok(Key::End),
166 'Z' => Ok(Key::BackTab),
167 _ => {
168 let c3 = read_single_char(fd)?;
169 if let Some(c3) = c3 {
170 if c3 == '~' {
171 match c2 {
172 '1' => Ok(Key::Home), // tmux
173 '2' => Ok(Key::Insert),
174 '3' => Ok(Key::Del),
175 '4' => Ok(Key::End), // tmux
176 '5' => Ok(Key::PageUp),
177 '6' => Ok(Key::PageDown),
178 '7' => Ok(Key::Home), // xrvt
179 '8' => Ok(Key::End), // xrvt
180 _ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
181 }
182 } else {
183 Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
184 }
185 } else {
186 // \x1b[ and 1 more char
187 Ok(Key::UnknownEscSeq(vec![c1, c2]))
188 }
189 }
190 }
191 } else {
192 // \x1b[ and no more input
193 Ok(Key::UnknownEscSeq(vec![c1]))
194 }
195 } else {
196 // char after escape is not [
197 Ok(Key::UnknownEscSeq(vec![c1]))
198 }
199 } else {
200 //nothing after escape
201 Ok(Key::Escape)
202 }
203 }
204 Some(c) => {
205 let byte = c as u8;
206 let mut buf: [u8; 4] = [byte, 0, 0, 0];
207
208 if byte & 224u8 == 192u8 {
209 // a two byte unicode character
210 read_bytes(fd, &mut buf[1..], 1)?;
211 Ok(key_from_utf8(&buf[..2]))
212 } else if byte & 240u8 == 224u8 {
213 // a three byte unicode character
214 read_bytes(fd, &mut buf[1..], 2)?;
215 Ok(key_from_utf8(&buf[..3]))
216 } else if byte & 248u8 == 240u8 {
217 // a four byte unicode character
218 read_bytes(fd, &mut buf[1..], 3)?;
219 Ok(key_from_utf8(&buf[..4]))
220 } else {
221 Ok(match c {
222 '\n' | '\r' => Key::Enter,
223 '\x7f' => Key::Backspace,
224 '\t' => Key::Tab,
225 '\x01' => Key::Home, // Control-A (home)
226 '\x05' => Key::End, // Control-E (end)
227 '\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
228 _ => Key::Char(c),
229 })
230 }
231 }
232 None => {
233 // there is no subsequent byte ready to be read, block and wait for input
234
235 let mut pollfd = libc::pollfd {
236 fd,
237 events: libc::POLLIN,
238 revents: 0,
239 };
240
241 // negative timeout means that it will block indefinitely
242 let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
243 if ret < 0 {
244 return Err(io::Error::last_os_error());
245 }
246
247 read_single_key()
248 }
249 };
250 c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
251
252 // if the user hit ^C we want to signal SIGINT to outselves.
253 if let Err(ref err) = rv {
254 if err.kind() == io::ErrorKind::Interrupted {
255 unsafe {
256 libc::raise(libc::SIGINT);
257 }
258 }
259 }
260
261 rv
262 }
263
key_from_utf8(buf: &[u8]) -> Key264 pub fn key_from_utf8(buf: &[u8]) -> Key {
265 if let Ok(s) = str::from_utf8(buf) {
266 if let Some(c) = s.chars().next() {
267 return Key::Char(c);
268 }
269 }
270 Key::Unknown
271 }
272
273 #[cfg(not(target_os = "macos"))]
274 static IS_LANG_UTF8: once_cell::sync::Lazy<bool> =
275 once_cell::sync::Lazy::new(|| match std::env::var("LANG") {
276 Ok(lang) => lang.to_uppercase().ends_with("UTF-8"),
277 _ => false,
278 });
279
280 #[cfg(target_os = "macos")]
wants_emoji() -> bool281 pub fn wants_emoji() -> bool {
282 true
283 }
284
285 #[cfg(not(target_os = "macos"))]
wants_emoji() -> bool286 pub fn wants_emoji() -> bool {
287 *IS_LANG_UTF8
288 }
289
set_title<T: Display>(title: T)290 pub fn set_title<T: Display>(title: T) {
291 print!("\x1b]0;{}\x07", title);
292 }
293