1use std::fmt::{Debug, Display};
2use std::io::{self, Read, Write};
3use std::sync::{Arc, Mutex, RwLock};
4
5#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
6use std::os::fd::{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)]
41struct 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 TermFeatures<'_> {
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() {
340 if let Some(ch) = chars.pop() {
341 slf.clear_chars(crate::utils::char_width(ch))?;
342 }
343 }
344 slf.flush()?;
345 }
346 Key::Char(chr) => {
347 chars.push(chr);
348 let mut bytes_char = [0; 4];
349 chr.encode_utf8(&mut bytes_char);
350 slf.write_str(chr.encode_utf8(&mut bytes_char))?;
351 slf.flush()?;
352 }
353 Key::Enter => {
354 slf.write_through(format!("\n{}", initial).as_bytes())?;
355 break;
356 }
357 _ => (),
358 }
359 }
360 Ok(chars.iter().skip(prefix_len).collect::<String>())
361 }
362 let ret = read_line_internal(self, initial);
363
364 *self.inner.prompt.write().unwrap() = String::new();
365 ret
366 }
367
368 pub fn read_secure_line(&self) -> io::Result<String> {
374 if !self.is_tty {
375 return Ok("".into());
376 }
377 match read_secure() {
378 Ok(rv) => {
379 self.write_line("")?;
380 Ok(rv)
381 }
382 Err(err) => Err(err),
383 }
384 }
385
386 pub fn flush(&self) -> io::Result<()> {
392 if let Some(ref buffer) = self.inner.buffer {
393 let mut buffer = buffer.lock().unwrap();
394 if !buffer.is_empty() {
395 self.write_through(&buffer[..])?;
396 buffer.clear();
397 }
398 }
399 Ok(())
400 }
401
402 #[inline]
404 pub fn is_term(&self) -> bool {
405 self.is_tty
406 }
407
408 #[inline]
410 pub fn features(&self) -> TermFeatures<'_> {
411 TermFeatures(self)
412 }
413
414 #[inline]
416 pub fn size(&self) -> (u16, u16) {
417 self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
418 }
419
420 #[inline]
424 pub fn size_checked(&self) -> Option<(u16, u16)> {
425 terminal_size(self)
426 }
427
428 #[inline]
430 pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
431 move_cursor_to(self, x, y)
432 }
433
434 #[inline]
439 pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
440 move_cursor_up(self, n)
441 }
442
443 #[inline]
448 pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
449 move_cursor_down(self, n)
450 }
451
452 #[inline]
457 pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
458 move_cursor_left(self, n)
459 }
460
461 #[inline]
466 pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
467 move_cursor_right(self, n)
468 }
469
470 #[inline]
474 pub fn clear_line(&self) -> io::Result<()> {
475 clear_line(self)
476 }
477
478 pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
482 self.move_cursor_up(n)?;
483 for _ in 0..n {
484 self.clear_line()?;
485 self.move_cursor_down(1)?;
486 }
487 self.move_cursor_up(n)?;
488 Ok(())
489 }
490
491 #[inline]
495 pub fn clear_screen(&self) -> io::Result<()> {
496 clear_screen(self)
497 }
498
499 #[inline]
502 pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
503 clear_to_end_of_screen(self)
504 }
505
506 #[inline]
508 pub fn clear_chars(&self, n: usize) -> io::Result<()> {
509 clear_chars(self, n)
510 }
511
512 pub fn set_title<T: Display>(&self, title: T) {
514 if !self.is_tty {
515 return;
516 }
517 set_title(title);
518 }
519
520 #[inline]
522 pub fn show_cursor(&self) -> io::Result<()> {
523 show_cursor(self)
524 }
525
526 #[inline]
528 pub fn hide_cursor(&self) -> io::Result<()> {
529 hide_cursor(self)
530 }
531
532 #[cfg(all(windows, feature = "windows-console-colors"))]
535 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
536 if self.is_msys_tty || !self.is_tty {
537 self.write_through_common(bytes)
538 } else {
539 match self.inner.target {
540 TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
541 TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
542 }
543 }
544 }
545
546 #[cfg(not(all(windows, feature = "windows-console-colors")))]
547 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
548 self.write_through_common(bytes)
549 }
550
551 pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
552 match self.inner.target {
553 TermTarget::Stdout => {
554 io::stdout().write_all(bytes)?;
555 io::stdout().flush()?;
556 }
557 TermTarget::Stderr => {
558 io::stderr().write_all(bytes)?;
559 io::stderr().flush()?;
560 }
561 #[cfg(unix)]
562 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
563 let mut write = write.lock().unwrap();
564 write.write_all(bytes)?;
565 write.flush()?;
566 }
567 }
568 Ok(())
569 }
570}
571
572#[inline]
578pub fn user_attended() -> bool {
579 Term::stdout().features().is_attended()
580}
581
582#[inline]
588pub fn user_attended_stderr() -> bool {
589 Term::stderr().features().is_attended()
590}
591
592#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
593impl AsRawFd for Term {
594 fn as_raw_fd(&self) -> RawFd {
595 match self.inner.target {
596 TermTarget::Stdout => libc::STDOUT_FILENO,
597 TermTarget::Stderr => libc::STDERR_FILENO,
598 #[cfg(unix)]
599 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
600 write.lock().unwrap().as_raw_fd()
601 }
602 }
603 }
604}
605
606#[cfg(windows)]
607impl AsRawHandle for Term {
608 fn as_raw_handle(&self) -> RawHandle {
609 use windows_sys::Win32::System::Console::{
610 GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE,
611 };
612
613 unsafe {
614 GetStdHandle(match self.inner.target {
615 TermTarget::Stdout => STD_OUTPUT_HANDLE,
616 TermTarget::Stderr => STD_ERROR_HANDLE,
617 }) as RawHandle
618 }
619 }
620}
621
622impl Write for Term {
623 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
624 match self.inner.buffer {
625 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
626 None => self.write_through(buf),
627 }?;
628 Ok(buf.len())
629 }
630
631 fn flush(&mut self) -> io::Result<()> {
632 Term::flush(self)
633 }
634}
635
636impl Write for &Term {
637 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
638 match self.inner.buffer {
639 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
640 None => self.write_through(buf),
641 }?;
642 Ok(buf.len())
643 }
644
645 fn flush(&mut self) -> io::Result<()> {
646 Term::flush(self)
647 }
648}
649
650impl Read for Term {
651 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
652 io::stdin().read(buf)
653 }
654}
655
656impl Read for &Term {
657 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
658 io::stdin().read(buf)
659 }
660}
661
662#[cfg(all(unix, not(target_arch = "wasm32")))]
663pub(crate) use crate::unix_term::*;
664#[cfg(target_arch = "wasm32")]
665pub(crate) use crate::wasm_term::*;
666#[cfg(windows)]
667pub(crate) use crate::windows_term::*;