1use alloc::sync::Arc;
2use core::fmt::{Debug, Display};
3use std::env;
4use std::io::{self, Read, Write};
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};
9use std::sync::{Mutex, RwLock};
10
11use crate::{kb::Key, utils::Style};
12
13#[cfg(unix)]
14trait TermWrite: Write + Debug + AsRawFd + Send {}
15#[cfg(unix)]
16impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}
17
18#[cfg(unix)]
19trait TermRead: Read + Debug + AsRawFd + Send {}
20#[cfg(unix)]
21impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}
22
23#[cfg(unix)]
24#[derive(Debug, Clone)]
25pub struct ReadWritePair {
26 #[allow(unused)]
27 read: Arc<Mutex<dyn TermRead>>,
28 write: Arc<Mutex<dyn TermWrite>>,
29 style: Style,
30}
31
32#[derive(Debug, Clone)]
34pub enum TermTarget {
35 Stdout,
36 Stderr,
37 #[cfg(unix)]
38 ReadWritePair(ReadWritePair),
39}
40
41#[derive(Debug)]
42struct TermInner {
43 target: TermTarget,
44 buffer: Option<Mutex<Vec<u8>>>,
45 prompt: RwLock<String>,
46 prompt_guard: Mutex<()>,
47}
48
49impl TermInner {
50 fn new(target: TermTarget) -> Self {
51 Self::with_buffer(target, None)
52 }
53
54 fn new_buffered(target: TermTarget) -> Self {
55 Self::with_buffer(target, Some(vec![]))
56 }
57
58 fn with_buffer(target: TermTarget, buffer: Option<Vec<u8>>) -> Self {
59 Self {
60 target,
61 buffer: buffer.map(Mutex::new),
62 prompt: RwLock::new(String::new()),
63 prompt_guard: Mutex::new(()),
64 }
65 }
66}
67
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
70pub enum TermFamily {
71 File,
73 UnixTerm,
75 WindowsConsole,
77 Dummy,
79}
80
81#[derive(Debug, Clone)]
83pub struct TermFeatures<'a>(&'a Term);
84
85impl TermFeatures<'_> {
86 #[inline]
88 pub fn is_attended(&self) -> bool {
89 is_a_terminal(self.0)
90 }
91
92 #[inline]
97 pub fn colors_supported(&self) -> bool {
98 is_a_color_terminal(self.0)
99 }
100
101 pub fn true_colors_supported(&self) -> bool {
103 is_a_true_color_terminal(self.0)
104 }
105
106 #[inline]
111 pub fn is_msys_tty(&self) -> bool {
112 #[cfg(windows)]
113 {
114 msys_tty_on(self.0)
115 }
116 #[cfg(not(windows))]
117 {
118 false
119 }
120 }
121
122 #[inline]
124 pub fn wants_emoji(&self) -> bool {
125 self.is_attended() && wants_emoji()
126 }
127
128 #[inline]
130 pub fn family(&self) -> TermFamily {
131 if !self.is_attended() {
132 return TermFamily::File;
133 }
134 #[cfg(windows)]
135 {
136 TermFamily::WindowsConsole
137 }
138 #[cfg(all(unix, not(target_arch = "wasm32")))]
139 {
140 TermFamily::UnixTerm
141 }
142 #[cfg(target_arch = "wasm32")]
143 {
144 TermFamily::Dummy
145 }
146 }
147}
148
149#[derive(Clone, Debug)]
154pub struct Term {
155 inner: Arc<TermInner>,
156 pub(crate) is_msys_tty: bool,
157 pub(crate) is_tty: bool,
158}
159
160impl Term {
161 fn with_inner(inner: TermInner) -> Term {
162 let mut term = Term {
163 inner: Arc::new(inner),
164 is_msys_tty: false,
165 is_tty: false,
166 };
167
168 term.is_msys_tty = term.features().is_msys_tty();
169 term.is_tty = term.features().is_attended();
170 term
171 }
172
173 #[inline]
175 pub fn stdout() -> Term {
176 Term::with_inner(TermInner::new(TermTarget::Stdout))
177 }
178
179 #[inline]
181 pub fn stderr() -> Term {
182 Term::with_inner(TermInner::new(TermTarget::Stderr))
183 }
184
185 pub fn buffered_stdout() -> Term {
187 Term::with_inner(TermInner::new_buffered(TermTarget::Stdout))
188 }
189
190 pub fn buffered_stderr() -> Term {
192 Term::with_inner(TermInner::new_buffered(TermTarget::Stderr))
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::new(TermTarget::ReadWritePair(ReadWritePair {
213 read: Arc::new(Mutex::new(read)),
214 write: Arc::new(Mutex::new(write)),
215 style,
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]
579pub fn is_dumb() -> bool {
580 #[cfg(windows)]
581 let default = false;
582
583 #[cfg(not(windows))]
584 let default = true;
585
586 match env::var("TERM") {
587 Ok(term) => term == "dumb",
588 Err(_) => default,
589 }
590}
591
592#[inline]
598pub fn user_attended() -> bool {
599 Term::stdout().features().is_attended()
600}
601
602#[inline]
608pub fn user_attended_stderr() -> bool {
609 Term::stderr().features().is_attended()
610}
611
612#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
613impl AsRawFd for Term {
614 fn as_raw_fd(&self) -> RawFd {
615 match self.inner.target {
616 TermTarget::Stdout => libc::STDOUT_FILENO,
617 TermTarget::Stderr => libc::STDERR_FILENO,
618 #[cfg(unix)]
619 TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
620 write.lock().unwrap().as_raw_fd()
621 }
622 }
623 }
624}
625
626#[cfg(windows)]
627impl AsRawHandle for Term {
628 fn as_raw_handle(&self) -> RawHandle {
629 use windows_sys::Win32::System::Console::{
630 GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE,
631 };
632
633 unsafe {
634 GetStdHandle(match self.inner.target {
635 TermTarget::Stdout => STD_OUTPUT_HANDLE,
636 TermTarget::Stderr => STD_ERROR_HANDLE,
637 }) as RawHandle
638 }
639 }
640}
641
642impl Write for Term {
643 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
644 match self.inner.buffer {
645 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
646 None => self.write_through(buf),
647 }?;
648 Ok(buf.len())
649 }
650
651 fn flush(&mut self) -> io::Result<()> {
652 Term::flush(self)
653 }
654}
655
656impl Write for &Term {
657 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
658 match self.inner.buffer {
659 Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
660 None => self.write_through(buf),
661 }?;
662 Ok(buf.len())
663 }
664
665 fn flush(&mut self) -> io::Result<()> {
666 Term::flush(self)
667 }
668}
669
670impl Read for Term {
671 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
672 io::stdin().read(buf)
673 }
674}
675
676impl Read for &Term {
677 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
678 io::stdin().read(buf)
679 }
680}
681
682#[cfg(all(unix, not(target_arch = "wasm32")))]
683pub(crate) use crate::unix_term::*;
684#[cfg(target_arch = "wasm32")]
685pub(crate) use crate::wasm_term::*;
686#[cfg(windows)]
687pub(crate) use crate::windows_term::*;