1use alloc::sync::Arc;
2use core::fmt::{Debug, Display};
3use std::io::{self, Read, Write};
4#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
5use std::os::fd::{AsRawFd, RawFd};
6#[cfg(windows)]
7use std::os::windows::io::{AsRawHandle, RawHandle};
8use std::sync::{Mutex, RwLock};
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 pub fn true_colors_supported(&self) -> bool {
83 is_a_true_color_terminal(self.0)
84 }
85
86 #[inline]
91 pub fn is_msys_tty(&self) -> bool {
92 #[cfg(windows)]
93 {
94 msys_tty_on(self.0)
95 }
96 #[cfg(not(windows))]
97 {
98 false
99 }
100 }
101
102 #[inline]
104 pub fn wants_emoji(&self) -> bool {
105 self.is_attended() && wants_emoji()
106 }
107
108 #[inline]
110 pub fn family(&self) -> TermFamily {
111 if !self.is_attended() {
112 return TermFamily::File;
113 }
114 #[cfg(windows)]
115 {
116 TermFamily::WindowsConsole
117 }
118 #[cfg(all(unix, not(target_arch = "wasm32")))]
119 {
120 TermFamily::UnixTerm
121 }
122 #[cfg(target_arch = "wasm32")]
123 {
124 TermFamily::Dummy
125 }
126 }
127}
128
129#[derive(Clone, Debug)]
134pub struct Term {
135 inner: Arc<TermInner>,
136 pub(crate) is_msys_tty: bool,
137 pub(crate) is_tty: bool,
138}
139
140impl Term {
141 fn with_inner(inner: TermInner) -> Term {
142 let mut term = Term {
143 inner: Arc::new(inner),
144 is_msys_tty: false,
145 is_tty: false,
146 };
147
148 term.is_msys_tty = term.features().is_msys_tty();
149 term.is_tty = term.features().is_attended();
150 term
151 }
152
153 #[inline]
155 pub fn stdout() -> Term {
156 Term::with_inner(TermInner {
157 target: TermTarget::Stdout,
158 buffer: None,
159 prompt: RwLock::new(String::new()),
160 prompt_guard: Mutex::new(()),
161 })
162 }
163
164 #[inline]
166 pub fn stderr() -> Term {
167 Term::with_inner(TermInner {
168 target: TermTarget::Stderr,
169 buffer: None,
170 prompt: RwLock::new(String::new()),
171 prompt_guard: Mutex::new(()),
172 })
173 }
174
175 pub fn buffered_stdout() -> Term {
177 Term::with_inner(TermInner {
178 target: TermTarget::Stdout,
179 buffer: Some(Mutex::new(vec![])),
180 prompt: RwLock::new(String::new()),
181 prompt_guard: Mutex::new(()),
182 })
183 }
184
185 pub fn buffered_stderr() -> Term {
187 Term::with_inner(TermInner {
188 target: TermTarget::Stderr,
189 buffer: Some(Mutex::new(vec![])),
190 prompt: RwLock::new(String::new()),
191 prompt_guard: Mutex::new(()),
192 })
193 }
194
195 #[cfg(unix)]
197 pub fn read_write_pair<R, W>(read: R, write: W) -> Term
198 where
199 R: Read + Debug + AsRawFd + Send + 'static,
200 W: Write + Debug + AsRawFd + Send + 'static,
201 {
202 Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
203 }
204
205 #[cfg(unix)]
207 pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
208 where
209 R: Read + Debug + AsRawFd + Send + 'static,
210 W: Write + Debug + AsRawFd + Send + 'static,
211 {
212 Term::with_inner(TermInner {
213 target: TermTarget::ReadWritePair(ReadWritePair {
214 read: Arc::new(Mutex::new(read)),
215 write: Arc::new(Mutex::new(write)),
216 style,
217 }),
218 buffer: None,
219 prompt: RwLock::new(String::new()),
220 prompt_guard: Mutex::new(()),
221 })
222 }
223
224 #[inline]
226 pub fn style(&self) -> Style {
227 match self.inner.target {
228 TermTarget::Stderr => Style::new().for_stderr(),
229 TermTarget::Stdout => Style::new().for_stdout(),
230 #[cfg(unix)]
231 TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
232 }
233 }
234
235 #[inline]
237 pub fn target(&self) -> TermTarget {
238 self.inner.target.clone()
239 }
240
241 #[doc(hidden)]
242 pub fn write_str(&self, s: &str) -> io::Result<()> {
243 match self.inner.buffer {
244 Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()),
245 None => self.write_through(s.as_bytes()),
246 }
247 }
248
249 pub fn write_line(&self, s: &str) -> io::Result<()> {
251 let prompt = self.inner.prompt.read().unwrap();
252 if !prompt.is_empty() {
253 self.clear_line()?;
254 }
255 match self.inner.buffer {
256 Some(ref mutex) => {
257 let mut buffer = mutex.lock().unwrap();
258 buffer.extend_from_slice(s.as_bytes());
259 buffer.push(b'\n');
260 buffer.extend_from_slice(prompt.as_bytes());
261 Ok(())
262 }
263 None => self.write_through(format!("{}\n{}", s, prompt.as_str()).as_bytes()),
264 }
265 }
266
267 pub fn read_char(&self) -> io::Result<char> {
273 if !self.is_tty {
274 return Err(io::Error::new(
275 io::ErrorKind::NotConnected,
276 "Not a terminal",
277 ));
278 }
279 loop {
280 match self.read_key()? {
281 Key::Char(c) => {
282 return Ok(c);
283 }
284 Key::Enter => {
285 return Ok('\n');
286 }
287 _ => {}
288 }
289 }
290 }
291
292 pub fn read_key(&self) -> io::Result<Key> {
297 if !self.is_tty {
298 Ok(Key::Unknown)
299 } else {
300 read_single_key(false)
301 }
302 }
303
304 pub fn read_key_raw(&self) -> io::Result<Key> {
305 if !self.is_tty {
306 Ok(Key::Unknown)
307 } else {
308 read_single_key(true)
309 }
310 }
311
312 pub fn read_line(&self) -> io::Result<String> {
317 self.read_line_initial_text("")
318 }
319
320 pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
327 if !self.is_tty {
328 return Ok("".into());
329 }
330 *self.inner.prompt.write().unwrap() = initial.to_string();
331 let _guard = self.inner.prompt_guard.lock().unwrap();
333
334 self.write_str(initial)?;
335
336 fn read_line_internal(slf: &Term, initial: &str) -> io::Result<String> {
337 let prefix_len = initial.len();
338
339 let mut chars: Vec<char> = initial.chars().collect();
340
341 loop {
342 match slf.read_key()? {
343 Key::Backspace => {
344 if prefix_len < chars.len() {
345 if let Some(ch) = chars.pop() {
346 slf.clear_chars(crate::utils::char_width(ch))?;
347 }
348 }
349 slf.flush()?;
350 }
351 Key::Char(chr) => {
352 chars.push(chr);
353 let mut bytes_char = [0; 4];
354 chr.encode_utf8(&mut bytes_char);
355 slf.write_str(chr.encode_utf8(&mut bytes_char))?;
356 slf.flush()?;
357 }
358 Key::Enter => {
359 slf.write_through(format!("\n{initial}").as_bytes())?;
360 break;
361 }
362 _ => (),
363 }
364 }
365 Ok(chars.iter().skip(prefix_len).collect::<String>())
366 }
367 let ret = read_line_internal(self, initial);
368
369 *self.inner.prompt.write().unwrap() = String::new();
370 ret
371 }
372
373 pub fn read_secure_line(&self) -> io::Result<String> {
379 if !self.is_tty {
380 return Ok("".into());
381 }
382 match read_secure() {
383 Ok(rv) => {
384 self.write_line("")?;
385 Ok(rv)
386 }
387 Err(err) => Err(err),
388 }
389 }
390
391 pub fn flush(&self) -> io::Result<()> {
397 if let Some(ref buffer) = self.inner.buffer {
398 let mut buffer = buffer.lock().unwrap();
399 if !buffer.is_empty() {
400 self.write_through(&buffer[..])?;
401 buffer.clear();
402 }
403 }
404 Ok(())
405 }
406
407 #[inline]
409 pub fn is_term(&self) -> bool {
410 self.is_tty
411 }
412
413 #[inline]
415 pub fn features(&self) -> TermFeatures<'_> {
416 TermFeatures(self)
417 }
418
419 #[inline]
421 pub fn size(&self) -> (u16, u16) {
422 self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
423 }
424
425 #[inline]
429 pub fn size_checked(&self) -> Option<(u16, u16)> {
430 terminal_size(self)
431 }
432
433 #[inline]
435 pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
436 move_cursor_to(self, x, y)
437 }
438
439 #[inline]
444 pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
445 move_cursor_up(self, n)
446 }
447
448 #[inline]
453 pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
454 move_cursor_down(self, n)
455 }
456
457 #[inline]
462 pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
463 move_cursor_left(self, n)
464 }
465
466 #[inline]
471 pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
472 move_cursor_right(self, n)
473 }
474
475 #[inline]
479 pub fn clear_line(&self) -> io::Result<()> {
480 clear_line(self)
481 }
482
483 pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
487 self.move_cursor_up(n)?;
488 for _ in 0..n {
489 self.clear_line()?;
490 self.move_cursor_down(1)?;
491 }
492 self.move_cursor_up(n)?;
493 Ok(())
494 }
495
496 #[inline]
500 pub fn clear_screen(&self) -> io::Result<()> {
501 clear_screen(self)
502 }
503
504 #[inline]
507 pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
508 clear_to_end_of_screen(self)
509 }
510
511 #[inline]
513 pub fn clear_chars(&self, n: usize) -> io::Result<()> {
514 clear_chars(self, n)
515 }
516
517 pub fn set_title<T: Display>(&self, title: T) {
519 if !self.is_tty {
520 return;
521 }
522 set_title(title);
523 }
524
525 #[inline]
527 pub fn show_cursor(&self) -> io::Result<()> {
528 show_cursor(self)
529 }
530
531 #[inline]
533 pub fn hide_cursor(&self) -> io::Result<()> {
534 hide_cursor(self)
535 }
536
537 #[cfg(all(windows, feature = "windows-console-colors"))]
540 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
541 if self.is_msys_tty || !self.is_tty {
542 self.write_through_common(bytes)
543 } else {
544 match self.inner.target {
545 TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
546 TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
547 }
548 }
549 }
550
551 #[cfg(not(all(windows, feature = "windows-console-colors")))]
552 fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
553 self.write_through_common(bytes)
554 }
555
556 pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
557 match self.inner.target {
558 TermTarget::Stdout => {
559 io::stdout().write_all(bytes)?;
560 io::stdout().flush()?;
561 }
562 TermTarget::Stderr => {
563 io::stderr().write_all(bytes)?;
564 io::stderr().flush()?;
565 }
566 #[cfg(unix)]
567 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
568 let mut write = write.lock().unwrap();
569 write.write_all(bytes)?;
570 write.flush()?;
571 }
572 }
573 Ok(())
574 }
575}
576
577#[inline]
583pub fn user_attended() -> bool {
584 Term::stdout().features().is_attended()
585}
586
587#[inline]
593pub fn user_attended_stderr() -> bool {
594 Term::stderr().features().is_attended()
595}
596
597#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
598impl AsRawFd for Term {
599 fn as_raw_fd(&self) -> RawFd {
600 match self.inner.target {
601 TermTarget::Stdout => libc::STDOUT_FILENO,
602 TermTarget::Stderr => libc::STDERR_FILENO,
603 #[cfg(unix)]
604 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
605 write.lock().unwrap().as_raw_fd()
606 }
607 }
608 }
609}
610
611#[cfg(windows)]
612impl AsRawHandle for Term {
613 fn as_raw_handle(&self) -> RawHandle {
614 use windows_sys::Win32::System::Console::{
615 GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE,
616 };
617
618 unsafe {
619 GetStdHandle(match self.inner.target {
620 TermTarget::Stdout => STD_OUTPUT_HANDLE,
621 TermTarget::Stderr => STD_ERROR_HANDLE,
622 }) as RawHandle
623 }
624 }
625}
626
627impl Write for Term {
628 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
629 match self.inner.buffer {
630 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
631 None => self.write_through(buf),
632 }?;
633 Ok(buf.len())
634 }
635
636 fn flush(&mut self) -> io::Result<()> {
637 Term::flush(self)
638 }
639}
640
641impl Write for &Term {
642 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
643 match self.inner.buffer {
644 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
645 None => self.write_through(buf),
646 }?;
647 Ok(buf.len())
648 }
649
650 fn flush(&mut self) -> io::Result<()> {
651 Term::flush(self)
652 }
653}
654
655impl Read for Term {
656 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
657 io::stdin().read(buf)
658 }
659}
660
661impl Read for &Term {
662 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
663 io::stdin().read(buf)
664 }
665}
666
667#[cfg(all(unix, not(target_arch = "wasm32")))]
668pub(crate) use crate::unix_term::*;
669#[cfg(target_arch = "wasm32")]
670pub(crate) use crate::wasm_term::*;
671#[cfg(windows)]
672pub(crate) use crate::windows_term::*;