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#[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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
50pub enum TermFamily {
51 File,
53 UnixTerm,
55 WindowsConsole,
57 Dummy,
59}
60
61#[derive(Debug, Clone)]
63pub struct TermFeatures<'a>(&'a Term);
64
65impl<'a> TermFeatures<'a> {
66 #[inline]
68 pub fn is_attended(&self) -> bool {
69 is_a_terminal(self.0)
70 }
71
72 #[inline]
77 pub fn colors_supported(&self) -> bool {
78 is_a_color_terminal(self.0)
79 }
80
81 #[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 #[inline]
99 pub fn wants_emoji(&self) -> bool {
100 self.is_attended() && wants_emoji()
101 }
102
103 #[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#[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 #[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 #[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 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 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 #[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 #[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 #[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 #[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 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 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 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 pub fn read_line(&self) -> io::Result<String> {
312 self.read_line_initial_text("")
313 }
314
315 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 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 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 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 #[inline]
402 pub fn is_term(&self) -> bool {
403 self.is_tty
404 }
405
406 #[inline]
408 pub fn features(&self) -> TermFeatures<'_> {
409 TermFeatures(self)
410 }
411
412 #[inline]
414 pub fn size(&self) -> (u16, u16) {
415 self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
416 }
417
418 #[inline]
422 pub fn size_checked(&self) -> Option<(u16, u16)> {
423 terminal_size(self)
424 }
425
426 #[inline]
428 pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
429 move_cursor_to(self, x, y)
430 }
431
432 #[inline]
437 pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
438 move_cursor_up(self, n)
439 }
440
441 #[inline]
446 pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
447 move_cursor_down(self, n)
448 }
449
450 #[inline]
455 pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
456 move_cursor_left(self, n)
457 }
458
459 #[inline]
464 pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
465 move_cursor_right(self, n)
466 }
467
468 #[inline]
472 pub fn clear_line(&self) -> io::Result<()> {
473 clear_line(self)
474 }
475
476 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 #[inline]
493 pub fn clear_screen(&self) -> io::Result<()> {
494 clear_screen(self)
495 }
496
497 #[inline]
500 pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
501 clear_to_end_of_screen(self)
502 }
503
504 #[inline]
506 pub fn clear_chars(&self, n: usize) -> io::Result<()> {
507 clear_chars(self, n)
508 }
509
510 pub fn set_title<T: Display>(&self, title: T) {
512 if !self.is_tty {
513 return;
514 }
515 set_title(title);
516 }
517
518 #[inline]
520 pub fn show_cursor(&self) -> io::Result<()> {
521 show_cursor(self)
522 }
523
524 #[inline]
526 pub fn hide_cursor(&self) -> io::Result<()> {
527 hide_cursor(self)
528 }
529
530 #[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#[inline]
576pub fn user_attended() -> bool {
577 Term::stdout().features().is_attended()
578}
579
580#[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, .. }) => {
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) => buffer.lock().unwrap().write_all(buf),
623 None => self.write_through(buf),
624 }?;
625 Ok(buf.len())
626 }
627
628 fn flush(&mut self) -> io::Result<()> {
629 Term::flush(self)
630 }
631}
632
633impl<'a> Write for &'a Term {
634 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
635 match self.inner.buffer {
636 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
637 None => self.write_through(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<'a> Read for &'a 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::*;