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