1use std::fmt::{Debug, Display};
2use std::io::{self, Read, Write};
3use std::sync::{Arc, Mutex};
4
5#[cfg(unix)]
6use std::os::unix::io::{AsRawFd, RawFd};
7#[cfg(windows)]
8use std::os::windows::io::{AsRawHandle, RawHandle};
9
10use crate::{kb::Key, utils::Style};
11
12#[cfg(unix)]
13trait TermWrite: Write + Debug + AsRawFd + Send {}
14#[cfg(unix)]
15impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}
16
17#[cfg(unix)]
18trait TermRead: Read + Debug + AsRawFd + Send {}
19#[cfg(unix)]
20impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}
21
22#[cfg(unix)]
23#[derive(Debug, Clone)]
24pub struct ReadWritePair {
25 #[allow(unused)]
26 read: Arc<Mutex<dyn TermRead>>,
27 write: Arc<Mutex<dyn TermWrite>>,
28 style: Style,
29}
30
31/// Where the term is writing.
32#[derive(Debug, Clone)]
33pub enum TermTarget {
34 Stdout,
35 Stderr,
36 #[cfg(unix)]
37 ReadWritePair(ReadWritePair),
38}
39
40#[derive(Debug)]
41pub struct TermInner {
42 target: TermTarget,
43 buffer: Option<Mutex<Vec<u8>>>,
44}
45
46/// The family of the terminal.
47#[derive(Debug, Copy, Clone, PartialEq, Eq)]
48pub enum TermFamily {
49 /// Redirected to a file or file like thing.
50 File,
51 /// A standard unix terminal.
52 UnixTerm,
53 /// A cmd.exe like windows console.
54 WindowsConsole,
55 /// A dummy terminal (for instance on wasm)
56 Dummy,
57}
58
59/// Gives access to the terminal features.
60#[derive(Debug, Clone)]
61pub struct TermFeatures<'a>(&'a Term);
62
63impl<'a> TermFeatures<'a> {
64 /// Check if this is a real user attended terminal (`isatty`)
65 #[inline]
66 pub fn is_attended(&self) -> bool {
67 is_a_terminal(self.0)
68 }
69
70 /// Check if colors are supported by this terminal.
71 ///
72 /// This does not check if colors are enabled. Currently all terminals
73 /// are considered to support colors
74 #[inline]
75 pub fn colors_supported(&self) -> bool {
76 is_a_color_terminal(self.0)
77 }
78
79 /// Check if this terminal is an msys terminal.
80 ///
81 /// This is sometimes useful to disable features that are known to not
82 /// work on msys terminals or require special handling.
83 #[inline]
84 pub fn is_msys_tty(&self) -> bool {
85 #[cfg(windows)]
86 {
87 msys_tty_on(self.0)
88 }
89 #[cfg(not(windows))]
90 {
91 false
92 }
93 }
94
95 /// Check if this terminal wants emojis.
96 #[inline]
97 pub fn wants_emoji(&self) -> bool {
98 self.is_attended() && wants_emoji()
99 }
100
101 /// Return the family of the terminal.
102 #[inline]
103 pub fn family(&self) -> TermFamily {
104 if !self.is_attended() {
105 return TermFamily::File;
106 }
107 #[cfg(windows)]
108 {
109 TermFamily::WindowsConsole
110 }
111 #[cfg(unix)]
112 {
113 TermFamily::UnixTerm
114 }
115 #[cfg(target_arch = "wasm32")]
116 {
117 TermFamily::Dummy
118 }
119 }
120}
121
122/// Abstraction around a terminal.
123///
124/// A terminal can be cloned. If a buffer is used it's shared across all
125/// clones which means it largely acts as a handle.
126#[derive(Clone, Debug)]
127pub struct Term {
128 inner: Arc<TermInner>,
129 pub(crate) is_msys_tty: bool,
130 pub(crate) is_tty: bool,
131}
132
133impl Term {
134 fn with_inner(inner: TermInner) -> Term {
135 let mut term = Term {
136 inner: Arc::new(inner),
137 is_msys_tty: false,
138 is_tty: false,
139 };
140
141 term.is_msys_tty = term.features().is_msys_tty();
142 term.is_tty = term.features().is_attended();
143 term
144 }
145
146 /// Return a new unbuffered terminal.
147 #[inline]
148 pub fn stdout() -> Term {
149 Term::with_inner(TermInner {
150 target: TermTarget::Stdout,
151 buffer: None,
152 })
153 }
154
155 /// Return a new unbuffered terminal to stderr.
156 #[inline]
157 pub fn stderr() -> Term {
158 Term::with_inner(TermInner {
159 target: TermTarget::Stderr,
160 buffer: None,
161 })
162 }
163
164 /// Return a new buffered terminal.
165 pub fn buffered_stdout() -> Term {
166 Term::with_inner(TermInner {
167 target: TermTarget::Stdout,
168 buffer: Some(Mutex::new(vec![])),
169 })
170 }
171
172 /// Return a new buffered terminal to stderr.
173 pub fn buffered_stderr() -> Term {
174 Term::with_inner(TermInner {
175 target: TermTarget::Stderr,
176 buffer: Some(Mutex::new(vec![])),
177 })
178 }
179
180 /// Return a terminal for the given Read/Write pair styled like stderr.
181 #[cfg(unix)]
182 pub fn read_write_pair<R, W>(read: R, write: W) -> Term
183 where
184 R: Read + Debug + AsRawFd + Send + 'static,
185 W: Write + Debug + AsRawFd + Send + 'static,
186 {
187 Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
188 }
189
190 /// Return a terminal for the given Read/Write pair.
191 #[cfg(unix)]
192 pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
193 where
194 R: Read + Debug + AsRawFd + Send + 'static,
195 W: Write + Debug + AsRawFd + Send + 'static,
196 {
197 Term::with_inner(TermInner {
198 target: TermTarget::ReadWritePair(ReadWritePair {
199 read: Arc::new(Mutex::new(read)),
200 write: Arc::new(Mutex::new(write)),
201 style,
202 }),
203 buffer: None,
204 })
205 }
206
207 /// Return the style for this terminal.
208 #[inline]
209 pub fn style(&self) -> Style {
210 match self.inner.target {
211 TermTarget::Stderr => Style::new().for_stderr(),
212 TermTarget::Stdout => Style::new().for_stdout(),
213 #[cfg(unix)]
214 TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
215 }
216 }
217
218 /// Return the target of this terminal.
219 #[inline]
220 pub fn target(&self) -> TermTarget {
221 self.inner.target.clone()
222 }
223
224 #[doc(hidden)]
225 pub fn write_str(&self, s: &str) -> io::Result<()> {
226 match self.inner.buffer {
227 Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()),
228 None => self.write_through(s.as_bytes()),
229 }
230 }
231
232 /// Write a string to the terminal and add a newline.
233 pub fn write_line(&self, s: &str) -> io::Result<()> {
234 match self.inner.buffer {
235 Some(ref mutex) => {
236 let mut buffer = mutex.lock().unwrap();
237 buffer.extend_from_slice(s.as_bytes());
238 buffer.push(b'\n');
239 Ok(())
240 }
241 None => self.write_through(format!("{}\n", s).as_bytes()),
242 }
243 }
244
245 /// Read a single character from the terminal.
246 ///
247 /// This does not echo the character and blocks until a single character
248 /// or complete key chord is entered. If the terminal is not user attended
249 /// the return value will be an error.
250 pub fn read_char(&self) -> io::Result<char> {
251 if !self.is_tty {
252 return Err(io::Error::new(
253 io::ErrorKind::NotConnected,
254 "Not a terminal",
255 ));
256 }
257 loop {
258 match self.read_key()? {
259 Key::Char(c) => {
260 return Ok(c);
261 }
262 Key::Enter => {
263 return Ok('\n');
264 }
265 _ => {}
266 }
267 }
268 }
269
270 /// Read a single key form the terminal.
271 ///
272 /// This does not echo anything. If the terminal is not user attended
273 /// the return value will always be the unknown key.
274 pub fn read_key(&self) -> io::Result<Key> {
275 if !self.is_tty {
276 Ok(Key::Unknown)
277 } else {
278 read_single_key()
279 }
280 }
281
282 /// Read one line of input.
283 ///
284 /// This does not include the trailing newline. If the terminal is not
285 /// user attended the return value will always be an empty string.
286 pub fn read_line(&self) -> io::Result<String> {
287 if !self.is_tty {
288 return Ok("".into());
289 }
290 let mut rv = String::new();
291 io::stdin().read_line(&mut rv)?;
292 let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
293 rv.truncate(len);
294 Ok(rv)
295 }
296
297 /// Read one line of input with initial text.
298 ///
299 /// This does not include the trailing newline. If the terminal is not
300 /// user attended the return value will always be an empty string.
301 pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
302 if !self.is_tty {
303 return Ok("".into());
304 }
305 self.write_str(initial)?;
306
307 let mut chars: Vec<char> = initial.chars().collect();
308
309 loop {
310 match self.read_key()? {
311 Key::Backspace => {
312 if chars.pop().is_some() {
313 self.clear_chars(1)?;
314 }
315 self.flush()?;
316 }
317 Key::Char(chr) => {
318 chars.push(chr);
319 let mut bytes_char = [0; 4];
320 chr.encode_utf8(&mut bytes_char);
321 self.write_str(chr.encode_utf8(&mut bytes_char))?;
322 self.flush()?;
323 }
324 Key::Enter => {
325 self.write_line("")?;
326 break;
327 }
328 _ => (),
329 }
330 }
331 Ok(chars.iter().collect::<String>())
332 }
333
334 /// Read a line of input securely.
335 ///
336 /// This is similar to `read_line` but will not echo the output. This
337 /// also switches the terminal into a different mode where not all
338 /// characters might be accepted.
339 pub fn read_secure_line(&self) -> io::Result<String> {
340 if !self.is_tty {
341 return Ok("".into());
342 }
343 match read_secure() {
344 Ok(rv) => {
345 self.write_line("")?;
346 Ok(rv)
347 }
348 Err(err) => Err(err),
349 }
350 }
351
352 /// Flush internal buffers.
353 ///
354 /// This forces the contents of the internal buffer to be written to
355 /// the terminal. This is unnecessary for unbuffered terminals which
356 /// will automatically flush.
357 pub fn flush(&self) -> io::Result<()> {
358 if let Some(ref buffer) = self.inner.buffer {
359 let mut buffer = buffer.lock().unwrap();
360 if !buffer.is_empty() {
361 self.write_through(&buffer[..])?;
362 buffer.clear();
363 }
364 }
365 Ok(())
366 }
367
368 /// Check if the terminal is indeed a terminal.
369 #[inline]
370 pub fn is_term(&self) -> bool {
371 self.is_tty
372 }
373
374 /// Check for common terminal features.
375 #[inline]
376 pub fn features(&self) -> TermFeatures<'_> {
377 TermFeatures(self)
378 }
379
380 /// Return the terminal size in rows and columns or gets sensible defaults.
381 #[inline]
382 pub fn size(&self) -> (u16, u16) {
383 self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
384 }
385
386 /// Return the terminal size in rows and columns.
387 ///
388 /// If the size cannot be reliably determined `None` is returned.
389 #[inline]
390 pub fn size_checked(&self) -> Option<(u16, u16)> {
391 terminal_size(self)
392 }
393
394 /// Move the cursor to row `x` and column `y`. Values are 0-based.
395 #[inline]
396 pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
397 move_cursor_to(self, x, y)
398 }
399
400 /// Move the cursor up by `n` lines, if possible.
401 ///
402 /// If there are less than `n` lines above the current cursor position,
403 /// the cursor is moved to the top line of the terminal (i.e., as far up as possible).
404 #[inline]
405 pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
406 move_cursor_up(self, n)
407 }
408
409 /// Move the cursor down by `n` lines, if possible.
410 ///
411 /// If there are less than `n` lines below the current cursor position,
412 /// the cursor is moved to the bottom line of the terminal (i.e., as far down as possible).
413 #[inline]
414 pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
415 move_cursor_down(self, n)
416 }
417
418 /// Move the cursor `n` characters to the left, if possible.
419 ///
420 /// If there are fewer than `n` characters to the left of the current cursor position,
421 /// the cursor is moved to the beginning of the line (i.e., as far to the left as possible).
422 #[inline]
423 pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
424 move_cursor_left(self, n)
425 }
426
427 /// Move the cursor `n` characters to the right.
428 ///
429 /// If there are fewer than `n` characters to the right of the current cursor position,
430 /// the cursor is moved to the end of the current line (i.e., as far to the right as possible).
431 #[inline]
432 pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
433 move_cursor_right(self, n)
434 }
435
436 /// Clear the current line.
437 ///
438 /// Position the cursor at the beginning of the current line.
439 #[inline]
440 pub fn clear_line(&self) -> io::Result<()> {
441 clear_line(self)
442 }
443
444 /// Clear the last `n` lines before the current line.
445 ///
446 /// Position the cursor at the beginning of the first line that was cleared.
447 pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
448 self.move_cursor_up(n)?;
449 for _ in 0..n {
450 self.clear_line()?;
451 self.move_cursor_down(1)?;
452 }
453 self.move_cursor_up(n)?;
454 Ok(())
455 }
456
457 /// Clear the entire screen.
458 ///
459 /// Move the cursor to the upper left corner of the screen.
460 #[inline]
461 pub fn clear_screen(&self) -> io::Result<()> {
462 clear_screen(self)
463 }
464
465 /// Clear everything from the current cursor position to the end of the screen.
466 /// The cursor stays in its position.
467 #[inline]
468 pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
469 clear_to_end_of_screen(self)
470 }
471
472 /// Clear the last `n` characters of the current line.
473 #[inline]
474 pub fn clear_chars(&self, n: usize) -> io::Result<()> {
475 clear_chars(self, n)
476 }
477
478 /// Set the terminal title.
479 pub fn set_title<T: Display>(&self, title: T) {
480 if !self.is_tty {
481 return;
482 }
483 set_title(title);
484 }
485
486 /// Make the cursor visible again.
487 #[inline]
488 pub fn show_cursor(&self) -> io::Result<()> {
489 show_cursor(self)
490 }
491
492 /// Hide the cursor.
493 #[inline]
494 pub fn hide_cursor(&self) -> io::Result<()> {
495 hide_cursor(self)
496 }
497
498 // helpers
499
500 #[cfg(all(windows, feature = "windows-console-colors"))]
501 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
502 if self.is_msys_tty || !self.is_tty {
503 self.write_through_common(bytes)
504 } else {
505 match self.inner.target {
506 TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
507 TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
508 }
509 }
510 }
511
512 #[cfg(not(all(windows, feature = "windows-console-colors")))]
513 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
514 self.write_through_common(bytes)
515 }
516
517 pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
518 match self.inner.target {
519 TermTarget::Stdout => {
520 io::stdout().write_all(bytes)?;
521 io::stdout().flush()?;
522 }
523 TermTarget::Stderr => {
524 io::stderr().write_all(bytes)?;
525 io::stderr().flush()?;
526 }
527 #[cfg(unix)]
528 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
529 let mut write = write.lock().unwrap();
530 write.write_all(bytes)?;
531 write.flush()?;
532 }
533 }
534 Ok(())
535 }
536}
537
538/// A fast way to check if the application has a user attended for stdout.
539///
540/// This means that stdout is connected to a terminal instead of a
541/// file or redirected by other means. This is a shortcut for
542/// checking the `is_attended` feature on the stdout terminal.
543#[inline]
544pub fn user_attended() -> bool {
545 Term::stdout().features().is_attended()
546}
547
548/// A fast way to check if the application has a user attended for stderr.
549///
550/// This means that stderr is connected to a terminal instead of a
551/// file or redirected by other means. This is a shortcut for
552/// checking the `is_attended` feature on the stderr terminal.
553#[inline]
554pub fn user_attended_stderr() -> bool {
555 Term::stderr().features().is_attended()
556}
557
558#[cfg(unix)]
559impl AsRawFd for Term {
560 fn as_raw_fd(&self) -> RawFd {
561 match self.inner.target {
562 TermTarget::Stdout => libc::STDOUT_FILENO,
563 TermTarget::Stderr => libc::STDERR_FILENO,
564 TermTarget::ReadWritePair(ReadWritePair { ref write: &Arc>, .. }) => {
565 write.lock().unwrap().as_raw_fd()
566 }
567 }
568 }
569}
570
571#[cfg(windows)]
572impl AsRawHandle for Term {
573 fn as_raw_handle(&self) -> RawHandle {
574 use windows_sys::Win32::System::Console::{
575 GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE,
576 };
577
578 unsafe {
579 GetStdHandle(match self.inner.target {
580 TermTarget::Stdout => STD_OUTPUT_HANDLE,
581 TermTarget::Stderr => STD_ERROR_HANDLE,
582 }) as RawHandle
583 }
584 }
585}
586
587impl Write for Term {
588 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
589 match self.inner.buffer {
590 Some(ref buffer: &Mutex>) => buffer.lock().unwrap().write_all(buf),
591 None => self.write_through(bytes:buf),
592 }?;
593 Ok(buf.len())
594 }
595
596 fn flush(&mut self) -> io::Result<()> {
597 Term::flush(self)
598 }
599}
600
601impl<'a> Write for &'a Term {
602 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
603 match self.inner.buffer {
604 Some(ref buffer: &Mutex>) => buffer.lock().unwrap().write_all(buf),
605 None => self.write_through(bytes:buf),
606 }?;
607 Ok(buf.len())
608 }
609
610 fn flush(&mut self) -> io::Result<()> {
611 Term::flush(self)
612 }
613}
614
615impl Read for Term {
616 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
617 io::stdin().read(buf)
618 }
619}
620
621impl<'a> Read for &'a Term {
622 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
623 io::stdin().read(buf)
624 }
625}
626
627#[cfg(unix)]
628pub use crate::unix_term::*;
629#[cfg(target_arch = "wasm32")]
630pub use crate::wasm_term::*;
631#[cfg(windows)]
632pub use crate::windows_term::*;
633