summaryrefslogtreecommitdiffstats
path: root/vendor/console/src/term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src/term.rs')
-rw-r--r--vendor/console/src/term.rs632
1 files changed, 632 insertions, 0 deletions
diff --git a/vendor/console/src/term.rs b/vendor/console/src/term.rs
new file mode 100644
index 000000000..0a4025850
--- /dev/null
+++ b/vendor/console/src/term.rs
@@ -0,0 +1,632 @@
+use std::fmt::{Debug, Display};
+use std::io::{self, Read, Write};
+use std::sync::{Arc, Mutex};
+
+#[cfg(unix)]
+use std::os::unix::io::{AsRawFd, RawFd};
+#[cfg(windows)]
+use std::os::windows::io::{AsRawHandle, RawHandle};
+
+use crate::{kb::Key, utils::Style};
+
+#[cfg(unix)]
+trait TermWrite: Write + Debug + AsRawFd + Send {}
+#[cfg(unix)]
+impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}
+
+#[cfg(unix)]
+trait TermRead: Read + Debug + AsRawFd + Send {}
+#[cfg(unix)]
+impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}
+
+#[cfg(unix)]
+#[derive(Debug, Clone)]
+pub struct ReadWritePair {
+ #[allow(unused)]
+ read: Arc<Mutex<dyn TermRead>>,
+ write: Arc<Mutex<dyn TermWrite>>,
+ style: Style,
+}
+
+/// Where the term is writing.
+#[derive(Debug, Clone)]
+pub enum TermTarget {
+ Stdout,
+ Stderr,
+ #[cfg(unix)]
+ ReadWritePair(ReadWritePair),
+}
+
+#[derive(Debug)]
+pub struct TermInner {
+ target: TermTarget,
+ buffer: Option<Mutex<Vec<u8>>>,
+}
+
+/// The family of the terminal.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum TermFamily {
+ /// Redirected to a file or file like thing.
+ File,
+ /// A standard unix terminal.
+ UnixTerm,
+ /// A cmd.exe like windows console.
+ WindowsConsole,
+ /// A dummy terminal (for instance on wasm)
+ Dummy,
+}
+
+/// Gives access to the terminal features.
+#[derive(Debug, Clone)]
+pub struct TermFeatures<'a>(&'a Term);
+
+impl<'a> TermFeatures<'a> {
+ /// Check if this is a real user attended terminal (`isatty`)
+ #[inline]
+ pub fn is_attended(&self) -> bool {
+ is_a_terminal(self.0)
+ }
+
+ /// Check if colors are supported by this terminal.
+ ///
+ /// This does not check if colors are enabled. Currently all terminals
+ /// are considered to support colors
+ #[inline]
+ pub fn colors_supported(&self) -> bool {
+ is_a_color_terminal(self.0)
+ }
+
+ /// Check if this terminal is an msys terminal.
+ ///
+ /// This is sometimes useful to disable features that are known to not
+ /// work on msys terminals or require special handling.
+ #[inline]
+ pub fn is_msys_tty(&self) -> bool {
+ #[cfg(windows)]
+ {
+ msys_tty_on(self.0)
+ }
+ #[cfg(not(windows))]
+ {
+ false
+ }
+ }
+
+ /// Check if this terminal wants emojis.
+ #[inline]
+ pub fn wants_emoji(&self) -> bool {
+ self.is_attended() && wants_emoji()
+ }
+
+ /// Return the family of the terminal.
+ #[inline]
+ pub fn family(&self) -> TermFamily {
+ if !self.is_attended() {
+ return TermFamily::File;
+ }
+ #[cfg(windows)]
+ {
+ TermFamily::WindowsConsole
+ }
+ #[cfg(unix)]
+ {
+ TermFamily::UnixTerm
+ }
+ #[cfg(target_arch = "wasm32")]
+ {
+ TermFamily::Dummy
+ }
+ }
+}
+
+/// Abstraction around a terminal.
+///
+/// A terminal can be cloned. If a buffer is used it's shared across all
+/// clones which means it largely acts as a handle.
+#[derive(Clone, Debug)]
+pub struct Term {
+ inner: Arc<TermInner>,
+ pub(crate) is_msys_tty: bool,
+ pub(crate) is_tty: bool,
+}
+
+impl Term {
+ fn with_inner(inner: TermInner) -> Term {
+ let mut term = Term {
+ inner: Arc::new(inner),
+ is_msys_tty: false,
+ is_tty: false,
+ };
+
+ term.is_msys_tty = term.features().is_msys_tty();
+ term.is_tty = term.features().is_attended();
+ term
+ }
+
+ /// Return a new unbuffered terminal.
+ #[inline]
+ pub fn stdout() -> Term {
+ Term::with_inner(TermInner {
+ target: TermTarget::Stdout,
+ buffer: None,
+ })
+ }
+
+ /// Return a new unbuffered terminal to stderr.
+ #[inline]
+ pub fn stderr() -> Term {
+ Term::with_inner(TermInner {
+ target: TermTarget::Stderr,
+ buffer: None,
+ })
+ }
+
+ /// Return a new buffered terminal.
+ pub fn buffered_stdout() -> Term {
+ Term::with_inner(TermInner {
+ target: TermTarget::Stdout,
+ buffer: Some(Mutex::new(vec![])),
+ })
+ }
+
+ /// Return a new buffered terminal to stderr.
+ pub fn buffered_stderr() -> Term {
+ Term::with_inner(TermInner {
+ target: TermTarget::Stderr,
+ buffer: Some(Mutex::new(vec![])),
+ })
+ }
+
+ /// Return a terminal for the given Read/Write pair styled like stderr.
+ #[cfg(unix)]
+ pub fn read_write_pair<R, W>(read: R, write: W) -> Term
+ where
+ R: Read + Debug + AsRawFd + Send + 'static,
+ W: Write + Debug + AsRawFd + Send + 'static,
+ {
+ Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
+ }
+
+ /// Return a terminal for the given Read/Write pair.
+ #[cfg(unix)]
+ pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
+ where
+ R: Read + Debug + AsRawFd + Send + 'static,
+ W: Write + Debug + AsRawFd + Send + 'static,
+ {
+ Term::with_inner(TermInner {
+ target: TermTarget::ReadWritePair(ReadWritePair {
+ read: Arc::new(Mutex::new(read)),
+ write: Arc::new(Mutex::new(write)),
+ style,
+ }),
+ buffer: None,
+ })
+ }
+
+ /// Return the style for this terminal.
+ #[inline]
+ pub fn style(&self) -> Style {
+ match self.inner.target {
+ TermTarget::Stderr => Style::new().for_stderr(),
+ TermTarget::Stdout => Style::new().for_stdout(),
+ #[cfg(unix)]
+ TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
+ }
+ }
+
+ /// Return the target of this terminal.
+ #[inline]
+ pub fn target(&self) -> TermTarget {
+ self.inner.target.clone()
+ }
+
+ #[doc(hidden)]
+ pub fn write_str(&self, s: &str) -> io::Result<()> {
+ match self.inner.buffer {
+ Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()),
+ None => self.write_through(s.as_bytes()),
+ }
+ }
+
+ /// Write a string to the terminal and add a newline.
+ pub fn write_line(&self, s: &str) -> io::Result<()> {
+ match self.inner.buffer {
+ Some(ref mutex) => {
+ let mut buffer = mutex.lock().unwrap();
+ buffer.extend_from_slice(s.as_bytes());
+ buffer.push(b'\n');
+ Ok(())
+ }
+ None => self.write_through(format!("{}\n", s).as_bytes()),
+ }
+ }
+
+ /// Read a single character from the terminal.
+ ///
+ /// This does not echo the character and blocks until a single character
+ /// or complete key chord is entered. If the terminal is not user attended
+ /// the return value will be an error.
+ pub fn read_char(&self) -> io::Result<char> {
+ if !self.is_tty {
+ return Err(io::Error::new(
+ io::ErrorKind::NotConnected,
+ "Not a terminal",
+ ));
+ }
+ loop {
+ match self.read_key()? {
+ Key::Char(c) => {
+ return Ok(c);
+ }
+ Key::Enter => {
+ return Ok('\n');
+ }
+ _ => {}
+ }
+ }
+ }
+
+ /// Read a single key form the terminal.
+ ///
+ /// This does not echo anything. If the terminal is not user attended
+ /// the return value will always be the unknown key.
+ pub fn read_key(&self) -> io::Result<Key> {
+ if !self.is_tty {
+ Ok(Key::Unknown)
+ } else {
+ read_single_key()
+ }
+ }
+
+ /// Read one line of input.
+ ///
+ /// This does not include the trailing newline. If the terminal is not
+ /// user attended the return value will always be an empty string.
+ pub fn read_line(&self) -> io::Result<String> {
+ if !self.is_tty {
+ return Ok("".into());
+ }
+ let mut rv = String::new();
+ io::stdin().read_line(&mut rv)?;
+ let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
+ rv.truncate(len);
+ Ok(rv)
+ }
+
+ /// Read one line of input with initial text.
+ ///
+ /// This does not include the trailing newline. If the terminal is not
+ /// user attended the return value will always be an empty string.
+ pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
+ if !self.is_tty {
+ return Ok("".into());
+ }
+ self.write_str(initial)?;
+
+ let mut chars: Vec<char> = initial.chars().collect();
+
+ loop {
+ match self.read_key()? {
+ Key::Backspace => {
+ if chars.pop().is_some() {
+ self.clear_chars(1)?;
+ }
+ self.flush()?;
+ }
+ Key::Char(chr) => {
+ chars.push(chr);
+ let mut bytes_char = [0; 4];
+ chr.encode_utf8(&mut bytes_char);
+ self.write_str(chr.encode_utf8(&mut bytes_char))?;
+ self.flush()?;
+ }
+ Key::Enter => {
+ self.write_line("")?;
+ break;
+ }
+ _ => (),
+ }
+ }
+ Ok(chars.iter().collect::<String>())
+ }
+
+ /// Read a line of input securely.
+ ///
+ /// This is similar to `read_line` but will not echo the output. This
+ /// also switches the terminal into a different mode where not all
+ /// characters might be accepted.
+ pub fn read_secure_line(&self) -> io::Result<String> {
+ if !self.is_tty {
+ return Ok("".into());
+ }
+ match read_secure() {
+ Ok(rv) => {
+ self.write_line("")?;
+ Ok(rv)
+ }
+ Err(err) => Err(err),
+ }
+ }
+
+ /// Flush internal buffers.
+ ///
+ /// This forces the contents of the internal buffer to be written to
+ /// the terminal. This is unnecessary for unbuffered terminals which
+ /// will automatically flush.
+ pub fn flush(&self) -> io::Result<()> {
+ if let Some(ref buffer) = self.inner.buffer {
+ let mut buffer = buffer.lock().unwrap();
+ if !buffer.is_empty() {
+ self.write_through(&buffer[..])?;
+ buffer.clear();
+ }
+ }
+ Ok(())
+ }
+
+ /// Check if the terminal is indeed a terminal.
+ #[inline]
+ pub fn is_term(&self) -> bool {
+ self.is_tty
+ }
+
+ /// Check for common terminal features.
+ #[inline]
+ pub fn features(&self) -> TermFeatures<'_> {
+ TermFeatures(self)
+ }
+
+ /// Return the terminal size in rows and columns or gets sensible defaults.
+ #[inline]
+ pub fn size(&self) -> (u16, u16) {
+ self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
+ }
+
+ /// Return the terminal size in rows and columns.
+ ///
+ /// If the size cannot be reliably determined `None` is returned.
+ #[inline]
+ pub fn size_checked(&self) -> Option<(u16, u16)> {
+ terminal_size(self)
+ }
+
+ /// Move the cursor to row `x` and column `y`. Values are 0-based.
+ #[inline]
+ pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
+ move_cursor_to(self, x, y)
+ }
+
+ /// Move the cursor up by `n` lines, if possible.
+ ///
+ /// If there are less than `n` lines above the current cursor position,
+ /// the cursor is moved to the top line of the terminal (i.e., as far up as possible).
+ #[inline]
+ pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
+ move_cursor_up(self, n)
+ }
+
+ /// Move the cursor down by `n` lines, if possible.
+ ///
+ /// If there are less than `n` lines below the current cursor position,
+ /// the cursor is moved to the bottom line of the terminal (i.e., as far down as possible).
+ #[inline]
+ pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
+ move_cursor_down(self, n)
+ }
+
+ /// Move the cursor `n` characters to the left, if possible.
+ ///
+ /// If there are fewer than `n` characters to the left of the current cursor position,
+ /// the cursor is moved to the beginning of the line (i.e., as far to the left as possible).
+ #[inline]
+ pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
+ move_cursor_left(self, n)
+ }
+
+ /// Move the cursor `n` characters to the right.
+ ///
+ /// If there are fewer than `n` characters to the right of the current cursor position,
+ /// the cursor is moved to the end of the current line (i.e., as far to the right as possible).
+ #[inline]
+ pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
+ move_cursor_right(self, n)
+ }
+
+ /// Clear the current line.
+ ///
+ /// Position the cursor at the beginning of the current line.
+ #[inline]
+ pub fn clear_line(&self) -> io::Result<()> {
+ clear_line(self)
+ }
+
+ /// Clear the last `n` lines before the current line.
+ ///
+ /// Position the cursor at the beginning of the first line that was cleared.
+ pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
+ self.move_cursor_up(n)?;
+ for _ in 0..n {
+ self.clear_line()?;
+ self.move_cursor_down(1)?;
+ }
+ self.move_cursor_up(n)?;
+ Ok(())
+ }
+
+ /// Clear the entire screen.
+ ///
+ /// Move the cursor to the upper left corner of the screen.
+ #[inline]
+ pub fn clear_screen(&self) -> io::Result<()> {
+ clear_screen(self)
+ }
+
+ /// Clear everything from the current cursor position to the end of the screen.
+ /// The cursor stays in its position.
+ #[inline]
+ pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
+ clear_to_end_of_screen(self)
+ }
+
+ /// Clear the last `n` characters of the current line.
+ #[inline]
+ pub fn clear_chars(&self, n: usize) -> io::Result<()> {
+ clear_chars(self, n)
+ }
+
+ /// Set the terminal title.
+ pub fn set_title<T: Display>(&self, title: T) {
+ if !self.is_tty {
+ return;
+ }
+ set_title(title);
+ }
+
+ /// Make the cursor visible again.
+ #[inline]
+ pub fn show_cursor(&self) -> io::Result<()> {
+ show_cursor(self)
+ }
+
+ /// Hide the cursor.
+ #[inline]
+ pub fn hide_cursor(&self) -> io::Result<()> {
+ hide_cursor(self)
+ }
+
+ // helpers
+
+ #[cfg(all(windows, feature = "windows-console-colors"))]
+ fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
+ if self.is_msys_tty || !self.is_tty {
+ self.write_through_common(bytes)
+ } else {
+ match self.inner.target {
+ TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
+ TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
+ }
+ }
+ }
+
+ #[cfg(not(all(windows, feature = "windows-console-colors")))]
+ fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
+ self.write_through_common(bytes)
+ }
+
+ pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
+ match self.inner.target {
+ TermTarget::Stdout => {
+ io::stdout().write_all(bytes)?;
+ io::stdout().flush()?;
+ }
+ TermTarget::Stderr => {
+ io::stderr().write_all(bytes)?;
+ io::stderr().flush()?;
+ }
+ #[cfg(unix)]
+ TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
+ let mut write = write.lock().unwrap();
+ write.write_all(bytes)?;
+ write.flush()?;
+ }
+ }
+ Ok(())
+ }
+}
+
+/// A fast way to check if the application has a user attended for stdout.
+///
+/// This means that stdout is connected to a terminal instead of a
+/// file or redirected by other means. This is a shortcut for
+/// checking the `is_attended` feature on the stdout terminal.
+#[inline]
+pub fn user_attended() -> bool {
+ Term::stdout().features().is_attended()
+}
+
+/// A fast way to check if the application has a user attended for stderr.
+///
+/// This means that stderr is connected to a terminal instead of a
+/// file or redirected by other means. This is a shortcut for
+/// checking the `is_attended` feature on the stderr terminal.
+#[inline]
+pub fn user_attended_stderr() -> bool {
+ Term::stderr().features().is_attended()
+}
+
+#[cfg(unix)]
+impl AsRawFd for Term {
+ fn as_raw_fd(&self) -> RawFd {
+ match self.inner.target {
+ TermTarget::Stdout => libc::STDOUT_FILENO,
+ TermTarget::Stderr => libc::STDERR_FILENO,
+ TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
+ write.lock().unwrap().as_raw_fd()
+ }
+ }
+ }
+}
+
+#[cfg(windows)]
+impl AsRawHandle for Term {
+ fn as_raw_handle(&self) -> RawHandle {
+ use windows_sys::Win32::System::Console::{
+ GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE,
+ };
+
+ unsafe {
+ GetStdHandle(match self.inner.target {
+ TermTarget::Stdout => STD_OUTPUT_HANDLE,
+ TermTarget::Stderr => STD_ERROR_HANDLE,
+ }) as RawHandle
+ }
+ }
+}
+
+impl Write for Term {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ match self.inner.buffer {
+ Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
+ None => self.write_through(buf),
+ }?;
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Term::flush(self)
+ }
+}
+
+impl<'a> Write for &'a Term {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ match self.inner.buffer {
+ Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
+ None => self.write_through(buf),
+ }?;
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Term::flush(self)
+ }
+}
+
+impl Read for Term {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ io::stdin().read(buf)
+ }
+}
+
+impl<'a> Read for &'a Term {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ io::stdin().read(buf)
+ }
+}
+
+#[cfg(unix)]
+pub use crate::unix_term::*;
+#[cfg(target_arch = "wasm32")]
+pub use crate::wasm_term::*;
+#[cfg(windows)]
+pub use crate::windows_term::*;