diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/term/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/term/src')
-rw-r--r-- | vendor/term/src/lib.rs | 392 | ||||
-rw-r--r-- | vendor/term/src/terminfo/mod.rs | 400 | ||||
-rw-r--r-- | vendor/term/src/terminfo/parm.rs | 741 | ||||
-rw-r--r-- | vendor/term/src/terminfo/parser/compiled.rs | 204 | ||||
-rw-r--r-- | vendor/term/src/terminfo/parser/names.rs | 553 | ||||
-rw-r--r-- | vendor/term/src/terminfo/searcher.rs | 93 | ||||
-rw-r--r-- | vendor/term/src/win.rs | 434 |
7 files changed, 2817 insertions, 0 deletions
diff --git a/vendor/term/src/lib.rs b/vendor/term/src/lib.rs new file mode 100644 index 000000000..d23d873ca --- /dev/null +++ b/vendor/term/src/lib.rs @@ -0,0 +1,392 @@ +// Copyright 2013-2019 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Terminal formatting library. +//! +//! This crate provides the `Terminal` trait, which abstracts over an [ANSI +//! Terminal][ansi] to provide color printing, among other things. There are two +//! implementations, the `TerminfoTerminal`, which uses control characters from +//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console +//! API][win]. +//! +//! # Usage +//! +//! This crate is [on crates.io](https://crates.io/crates/term) and can be +//! used by adding `term` to the dependencies in your project's `Cargo.toml`. +//! +//! ```toml +//! [dependencies] +//! +//! term = "*" +//! ``` +//! +//! and this to your crate root: +//! +//! ```rust +//! extern crate term; +//! ``` +//! +//! # Examples +//! +//! ```no_run +//! extern crate term; +//! use std::io::prelude::*; +//! +//! fn main() { +//! let mut t = term::stdout().unwrap(); +//! +//! t.fg(term::color::GREEN).unwrap(); +//! write!(t, "hello, ").unwrap(); +//! +//! t.fg(term::color::RED).unwrap(); +//! writeln!(t, "world!").unwrap(); +//! +//! t.reset().unwrap(); +//! } +//! ``` +//! +//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code +//! [win]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682010%28v=vs.85%29.aspx +//! [ti]: https://en.wikipedia.org/wiki/Terminfo + +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://stebalien.github.io/doc/term/term/", + test(attr(deny(warnings))) +)] +#![deny(missing_docs)] +#![cfg_attr(test, deny(warnings))] +#![allow(clippy::redundant_field_names)] + +use std::io::prelude::*; + +pub use crate::terminfo::TerminfoTerminal; +#[cfg(windows)] +pub use win::{WinConsole, WinConsoleInfo}; + +use std::io::{self, Stderr, Stdout}; + +pub mod terminfo; + +#[cfg(windows)] +mod win; + +/// Alias for stdout terminals. +pub type StdoutTerminal = dyn Terminal<Output = Stdout> + Send; +/// Alias for stderr terminals. +pub type StderrTerminal = dyn Terminal<Output = Stderr> + Send; + +#[cfg(not(windows))] +/// Return a Terminal wrapping stdout, or None if a terminal couldn't be +/// opened. +pub fn stdout() -> Option<Box<StdoutTerminal>> { + TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>) +} + +#[cfg(windows)] +/// Return a Terminal wrapping stdout, or None if a terminal couldn't be +/// opened. +pub fn stdout() -> Option<Box<StdoutTerminal>> { + TerminfoTerminal::new(io::stdout()) + .map(|t| Box::new(t) as Box<StdoutTerminal>) + .or_else(|| { + WinConsole::new(io::stdout()) + .ok() + .map(|t| Box::new(t) as Box<StdoutTerminal>) + }) +} + +#[cfg(not(windows))] +/// Return a Terminal wrapping stderr, or None if a terminal couldn't be +/// opened. +pub fn stderr() -> Option<Box<StderrTerminal>> { + TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>) +} + +#[cfg(windows)] +/// Return a Terminal wrapping stderr, or None if a terminal couldn't be +/// opened. +pub fn stderr() -> Option<Box<StderrTerminal>> { + TerminfoTerminal::new(io::stderr()) + .map(|t| Box::new(t) as Box<StderrTerminal>) + .or_else(|| { + WinConsole::new(io::stderr()) + .ok() + .map(|t| Box::new(t) as Box<StderrTerminal>) + }) +} + +/// Terminal color definitions +#[allow(missing_docs)] +pub mod color { + /// Number for a terminal color + pub type Color = u32; + + pub const BLACK: Color = 0; + pub const RED: Color = 1; + pub const GREEN: Color = 2; + pub const YELLOW: Color = 3; + pub const BLUE: Color = 4; + pub const MAGENTA: Color = 5; + pub const CYAN: Color = 6; + pub const WHITE: Color = 7; + + pub const BRIGHT_BLACK: Color = 8; + pub const BRIGHT_RED: Color = 9; + pub const BRIGHT_GREEN: Color = 10; + pub const BRIGHT_YELLOW: Color = 11; + pub const BRIGHT_BLUE: Color = 12; + pub const BRIGHT_MAGENTA: Color = 13; + pub const BRIGHT_CYAN: Color = 14; + pub const BRIGHT_WHITE: Color = 15; +} + +/// Terminal attributes for use with term.attr(). +/// +/// Most attributes can only be turned on and must be turned off with term.reset(). +/// The ones that can be turned off explicitly take a boolean value. +/// Color is also represented as an attribute for convenience. +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] +pub enum Attr { + /// Bold (or possibly bright) mode + Bold, + /// Dim mode, also called faint or half-bright. Often not supported + Dim, + /// Italics mode. Often not supported + Italic(bool), + /// Underline mode + Underline(bool), + /// Blink mode + Blink, + /// Standout mode. Often implemented as Reverse, sometimes coupled with Bold + Standout(bool), + /// Reverse mode, inverts the foreground and background colors + Reverse, + /// Secure mode, also called invis mode. Hides the printed text + Secure, + /// Convenience attribute to set the foreground color + ForegroundColor(color::Color), + /// Convenience attribute to set the background color + BackgroundColor(color::Color), +} + +/// An error arising from interacting with the terminal. +#[derive(Debug)] +pub enum Error { + /// Indicates an error from any underlying IO + Io(io::Error), + /// Indicates an error during terminfo parsing + TerminfoParsing(terminfo::Error), + /// Indicates an error expanding a parameterized string from the terminfo database + ParameterizedExpansion(terminfo::parm::Error), + /// Indicates that the terminal does not support the requested operation. + NotSupported, + /// Indicates that the `TERM` environment variable was unset, and thus we were unable to detect + /// which terminal we should be using. + TermUnset, + /// Indicates that we were unable to find a terminfo entry for the requested terminal. + TerminfoEntryNotFound, + /// Indicates that the cursor could not be moved to the requested position. + CursorDestinationInvalid, + /// Indicates that the terminal does not support displaying the requested color. + /// + /// This is like `NotSupported`, but more specific. + ColorOutOfRange, + #[doc(hidden)] + /// Please don't match against this - if you do, we can't promise we won't break your crate + /// with a semver-compliant version bump. + __Nonexhaustive, +} + +// manually implemented because std::io::Error does not implement Eq/PartialEq +impl std::cmp::PartialEq for Error { + fn eq(&self, other: &Error) -> bool { + use crate::Error::*; + match *self { + Io(_) => false, + TerminfoParsing(ref inner1) => match *other { + TerminfoParsing(ref inner2) => inner1 == inner2, + _ => false, + }, + ParameterizedExpansion(ref inner1) => match *other { + ParameterizedExpansion(ref inner2) => inner1 == inner2, + _ => false, + }, + NotSupported => match *other { + NotSupported => true, + _ => false, + }, + TermUnset => match *other { + TermUnset => true, + _ => false, + }, + TerminfoEntryNotFound => match *other { + TerminfoEntryNotFound => true, + _ => false, + }, + CursorDestinationInvalid => match *other { + CursorDestinationInvalid => true, + _ => false, + }, + ColorOutOfRange => match *other { + ColorOutOfRange => true, + _ => false, + }, + __Nonexhaustive => match *other { + __Nonexhaustive => true, + _ => false, + }, + } + } +} + +/// The canonical `Result` type using this crate's Error type. +pub type Result<T> = std::result::Result<T, Error>; + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use crate::Error::*; + match *self { + Io(ref io) => io.fmt(f), + TerminfoParsing(ref e) => e.fmt(f), + ParameterizedExpansion(ref e) => e.fmt(f), + NotSupported => f.write_str("operation not supported by the terminal"), + TermUnset => { + f.write_str("TERM environment variable unset, unable to detect a terminal") + } + TerminfoEntryNotFound => { + f.write_str("could not find a terminfo entry for this terminal") + } + CursorDestinationInvalid => f.write_str("could not move cursor to requested position"), + ColorOutOfRange => f.write_str("color not supported by the terminal"), + __Nonexhaustive => f.write_str("placeholder variant that shouldn't be used"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Error::Io(ref io) => Some(io), + Error::TerminfoParsing(ref e) => Some(e), + Error::ParameterizedExpansion(ref e) => Some(e), + _ => None, + } + } +} + +impl From<Error> for io::Error { + fn from(err: Error) -> io::Error { + let kind = match err { + Error::Io(ref e) => e.kind(), + _ => io::ErrorKind::Other, + }; + io::Error::new(kind, err) + } +} + +impl std::convert::From<io::Error> for Error { + fn from(val: io::Error) -> Self { + Error::Io(val) + } +} + +impl std::convert::From<terminfo::Error> for Error { + fn from(val: terminfo::Error) -> Self { + Error::TerminfoParsing(val) + } +} + +impl std::convert::From<terminfo::parm::Error> for Error { + fn from(val: terminfo::parm::Error) -> Self { + Error::ParameterizedExpansion(val) + } +} + +/// A terminal with similar capabilities to an ANSI Terminal +/// (foreground/background colors etc). +pub trait Terminal: Write { + /// The terminal's output writer type. + type Output: Write; + + /// Sets the foreground color to the given color. + /// + /// If the color is a bright color, but the terminal only supports 8 colors, + /// the corresponding normal color will be used instead. + /// + /// Returns `Ok(())` if the color change code was sent to the terminal, or `Err(e)` if there + /// was an error. + fn fg(&mut self, color: color::Color) -> Result<()>; + + /// Sets the background color to the given color. + /// + /// If the color is a bright color, but the terminal only supports 8 colors, + /// the corresponding normal color will be used instead. + /// + /// Returns `Ok(())` if the color change code was sent to the terminal, or `Err(e)` if there + /// was an error. + fn bg(&mut self, color: color::Color) -> Result<()>; + + /// Sets the given terminal attribute, if supported. Returns `Ok(())` if the attribute is + /// supported and was sent to the terminal, or `Err(e)` if there was an error or the attribute + /// wasn't supported. + fn attr(&mut self, attr: Attr) -> Result<()>; + + /// Returns whether the given terminal attribute is supported. + fn supports_attr(&self, attr: Attr) -> bool; + + /// Resets all terminal attributes and colors to their defaults. + /// + /// Returns `Ok(())` if the reset code was printed, or `Err(e)` if there was an error. + /// + /// *Note: This does not flush.* + /// + /// That means the reset command may get buffered so, if you aren't planning on doing anything + /// else that might flush stdout's buffer (e.g. writing a line of text), you should flush after + /// calling reset. + fn reset(&mut self) -> Result<()>; + + /// Returns true if reset is supported. + fn supports_reset(&self) -> bool; + + /// Returns true if color is fully supported. + /// + /// If this function returns `true`, `bg`, `fg`, and `reset` will never + /// return `Err(Error::NotSupported)`. + fn supports_color(&self) -> bool; + + /// Moves the cursor up one line. + /// + /// Returns `Ok(())` if the cursor movement code was printed, or `Err(e)` if there was an + /// error. + fn cursor_up(&mut self) -> Result<()>; + + /// Deletes the text from the cursor location to the end of the line. + /// + /// Returns `Ok(())` if the deletion code was printed, or `Err(e)` if there was an error. + fn delete_line(&mut self) -> Result<()>; + + /// Moves the cursor to the left edge of the current line. + /// + /// Returns `Ok(true)` if the deletion code was printed, or `Err(e)` if there was an error. + fn carriage_return(&mut self) -> Result<()>; + + /// Gets an immutable reference to the stream inside + fn get_ref(&self) -> &Self::Output; + + /// Gets a mutable reference to the stream inside + fn get_mut(&mut self) -> &mut Self::Output; + + /// Returns the contained stream, destroying the `Terminal` + fn into_inner(self) -> Self::Output + where + Self: Sized; +} diff --git a/vendor/term/src/terminfo/mod.rs b/vendor/term/src/terminfo/mod.rs new file mode 100644 index 000000000..804c8dc01 --- /dev/null +++ b/vendor/term/src/terminfo/mod.rs @@ -0,0 +1,400 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Terminfo database interface. + +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::io::BufReader; +use std::path::Path; + +#[cfg(windows)] +use crate::win; + +use self::parm::{expand, Param, Variables}; +use self::parser::compiled::parse; +use self::searcher::get_dbpath_for_term; +use self::Error::*; +use crate::color; +use crate::Attr; +use crate::Result; +use crate::Terminal; + +/// Returns true if the named terminal supports basic ANSI escape codes. +fn is_ansi(name: &str) -> bool { + // SORTED! We binary search this. + static ANSI_TERM_PREFIX: &[&str] = &[ + "Eterm", "ansi", "eterm", "iterm", "konsole", "linux", "mrxvt", "msyscon", "rxvt", + "screen", "tmux", "xterm", + ]; + match ANSI_TERM_PREFIX.binary_search(&name) { + Ok(_) => true, + Err(0) => false, + Err(idx) => name.starts_with(ANSI_TERM_PREFIX[idx - 1]), + } +} + +/// A parsed terminfo database entry. +#[derive(Debug, Clone)] +pub struct TermInfo { + /// Names for the terminal + pub names: Vec<String>, + /// Map of capability name to boolean value + pub bools: HashMap<&'static str, bool>, + /// Map of capability name to numeric value + pub numbers: HashMap<&'static str, u32>, + /// Map of capability name to raw (unexpanded) string + pub strings: HashMap<&'static str, Vec<u8>>, +} + +impl TermInfo { + /// Create a `TermInfo` based on current environment. + pub fn from_env() -> Result<TermInfo> { + let term_var = env::var("TERM").ok(); + let term_name = term_var.as_ref().map(|s| &**s).or_else(|| { + env::var("MSYSCON").ok().and_then(|s| { + if s == "mintty.exe" { + Some("msyscon") + } else { + None + } + }) + }); + + #[cfg(windows)] + { + if term_name.is_none() && win::supports_ansi() { + // Microsoft people seem to be fine with pretending to be xterm: + // https://github.com/Microsoft/WSL/issues/1446 + // The basic ANSI fallback terminal will be uses. + return TermInfo::from_name("xterm"); + } + } + + if let Some(term_name) = term_name { + TermInfo::from_name(term_name) + } else { + Err(crate::Error::TermUnset) + } + } + + /// Create a `TermInfo` for the named terminal. + pub fn from_name(name: &str) -> Result<TermInfo> { + if let Some(path) = get_dbpath_for_term(name) { + match TermInfo::from_path(&path) { + Ok(term) => return Ok(term), + // Skip IO Errors (e.g., permission denied). + Err(crate::Error::Io(_)) => {} + // Don't ignore malformed terminfo databases. + Err(e) => return Err(e), + } + } + // Basic ANSI fallback terminal. + if is_ansi(name) { + let mut strings = HashMap::new(); + strings.insert("sgr0", b"\x1B[0m".to_vec()); + strings.insert("bold", b"\x1B[1m".to_vec()); + strings.insert("setaf", b"\x1B[3%p1%dm".to_vec()); + strings.insert("setab", b"\x1B[4%p1%dm".to_vec()); + + let mut numbers = HashMap::new(); + numbers.insert("colors", 8); + + Ok(TermInfo { + names: vec![name.to_owned()], + bools: HashMap::new(), + numbers: numbers, + strings: strings, + }) + } else { + Err(crate::Error::TerminfoEntryNotFound) + } + } + + /// Parse the given `TermInfo`. + pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo> { + Self::_from_path(path.as_ref()) + } + // Keep the metadata small + // (That is, this uses a &Path so that this function need not be instantiated + // for every type + // which implements AsRef<Path>. One day, if/when rustc is a bit smarter, it + // might do this for + // us. Alas. ) + fn _from_path(path: &Path) -> Result<TermInfo> { + let file = File::open(path).map_err(crate::Error::Io)?; + let mut reader = BufReader::new(file); + parse(&mut reader, false) + } + + /// Retrieve a capability `cmd` and expand it with `params`, writing result to `out`. + pub fn apply_cap(&self, cmd: &str, params: &[Param], out: &mut dyn io::Write) -> Result<()> { + match self.strings.get(cmd) { + Some(cmd) => match expand(cmd, params, &mut Variables::new()) { + Ok(s) => { + out.write_all(&s)?; + Ok(()) + } + Err(e) => Err(e.into()), + }, + None => Err(crate::Error::NotSupported), + } + } + + /// Write the reset string to `out`. + pub fn reset(&self, out: &mut dyn io::Write) -> Result<()> { + // are there any terminals that have color/attrs and not sgr0? + // Try falling back to sgr, then op + let cmd = match [ + ("sgr0", &[] as &[Param]), + ("sgr", &[Param::Number(0)]), + ("op", &[]), + ] + .iter() + .filter_map(|&(cap, params)| self.strings.get(cap).map(|c| (c, params))) + .next() + { + Some((op, params)) => match expand(op, params, &mut Variables::new()) { + Ok(cmd) => cmd, + Err(e) => return Err(e.into()), + }, + None => return Err(crate::Error::NotSupported), + }; + out.write_all(&cmd)?; + Ok(()) + } +} + +#[derive(Debug, Eq, PartialEq)] +/// An error from parsing a terminfo entry +pub enum Error { + /// The "magic" number at the start of the file was wrong. + /// + /// It should be `0x11A` (16bit numbers) or `0x21e` (32bit numbers) + BadMagic(u16), + /// The names in the file were not valid UTF-8. + /// + /// In theory these should only be ASCII, but to work with the Rust `str` type, we treat them + /// as UTF-8. This is valid, except when a terminfo file decides to be invalid. This hasn't + /// been encountered in the wild. + NotUtf8(::std::str::Utf8Error), + /// The names section of the file was empty + ShortNames, + /// More boolean parameters are present in the file than this crate knows how to interpret. + TooManyBools, + /// More number parameters are present in the file than this crate knows how to interpret. + TooManyNumbers, + /// More string parameters are present in the file than this crate knows how to interpret. + TooManyStrings, + /// The length of some field was not >= -1. + InvalidLength, + /// The names table was missing a trailing null terminator. + NamesMissingNull, + /// The strings table was missing a trailing null terminator. + StringsMissingNull, +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + BadMagic(v) => write!(f, "bad magic number {:x} in terminfo header", v), + ShortNames => f.write_str("no names exposed, need at least one"), + TooManyBools => f.write_str("more boolean properties than libterm knows about"), + TooManyNumbers => f.write_str("more number properties than libterm knows about"), + TooManyStrings => f.write_str("more string properties than libterm knows about"), + InvalidLength => f.write_str("invalid length field value, must be >= -1"), + NotUtf8(ref e) => e.fmt(f), + NamesMissingNull => f.write_str("names table missing NUL terminator"), + StringsMissingNull => f.write_str("string table missing NUL terminator"), + } + } +} + +impl ::std::convert::From<::std::string::FromUtf8Error> for Error { + fn from(v: ::std::string::FromUtf8Error) -> Self { + NotUtf8(v.utf8_error()) + } +} + +impl ::std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + NotUtf8(ref e) => Some(e), + _ => None, + } + } +} + +pub mod searcher; + +/// `TermInfo` format parsing. +pub mod parser { + //! ncurses-compatible compiled terminfo format parsing (term(5)) + pub mod compiled; + mod names; +} +pub mod parm; + +fn cap_for_attr(attr: Attr) -> &'static str { + match attr { + Attr::Bold => "bold", + Attr::Dim => "dim", + Attr::Italic(true) => "sitm", + Attr::Italic(false) => "ritm", + Attr::Underline(true) => "smul", + Attr::Underline(false) => "rmul", + Attr::Blink => "blink", + Attr::Standout(true) => "smso", + Attr::Standout(false) => "rmso", + Attr::Reverse => "rev", + Attr::Secure => "invis", + Attr::ForegroundColor(_) => "setaf", + Attr::BackgroundColor(_) => "setab", + } +} + +/// A Terminal that knows how many colors it supports, with a reference to its +/// parsed Terminfo database record. +#[derive(Clone, Debug)] +pub struct TerminfoTerminal<T> { + num_colors: u32, + out: T, + ti: TermInfo, +} + +impl<T: Write> Terminal for TerminfoTerminal<T> { + type Output = T; + fn fg(&mut self, color: color::Color) -> Result<()> { + let color = self.dim_if_necessary(color); + if self.num_colors > color { + return self + .ti + .apply_cap("setaf", &[Param::Number(color as i32)], &mut self.out); + } + Err(crate::Error::ColorOutOfRange) + } + + fn bg(&mut self, color: color::Color) -> Result<()> { + let color = self.dim_if_necessary(color); + if self.num_colors > color { + return self + .ti + .apply_cap("setab", &[Param::Number(color as i32)], &mut self.out); + } + Err(crate::Error::ColorOutOfRange) + } + + fn attr(&mut self, attr: Attr) -> Result<()> { + match attr { + Attr::ForegroundColor(c) => self.fg(c), + Attr::BackgroundColor(c) => self.bg(c), + _ => self.ti.apply_cap(cap_for_attr(attr), &[], &mut self.out), + } + } + + fn supports_attr(&self, attr: Attr) -> bool { + match attr { + Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0, + _ => { + let cap = cap_for_attr(attr); + self.ti.strings.get(cap).is_some() + } + } + } + + fn reset(&mut self) -> Result<()> { + self.ti.reset(&mut self.out) + } + + fn supports_reset(&self) -> bool { + ["sgr0", "sgr", "op"] + .iter() + .any(|&cap| self.ti.strings.get(cap).is_some()) + } + + fn supports_color(&self) -> bool { + self.num_colors > 0 && self.supports_reset() + } + + fn cursor_up(&mut self) -> Result<()> { + self.ti.apply_cap("cuu1", &[], &mut self.out) + } + + fn delete_line(&mut self) -> Result<()> { + self.ti.apply_cap("el", &[], &mut self.out) + } + + fn carriage_return(&mut self) -> Result<()> { + self.ti.apply_cap("cr", &[], &mut self.out) + } + + fn get_ref(&self) -> &T { + &self.out + } + + fn get_mut(&mut self) -> &mut T { + &mut self.out + } + + fn into_inner(self) -> T + where + Self: Sized, + { + self.out + } +} + +impl<T: Write> TerminfoTerminal<T> { + /// Create a new TerminfoTerminal with the given TermInfo and Write. + pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> { + let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab") + { + terminfo.numbers.get("colors").map_or(0, |&n| n) + } else { + 0 + }; + + TerminfoTerminal { + out: out, + ti: terminfo, + num_colors: nc as u32, + } + } + + /// Create a new TerminfoTerminal for the current environment with the given Write. + /// + /// Returns `None` when the terminfo cannot be found or parsed. + pub fn new(out: T) -> Option<TerminfoTerminal<T>> { + TermInfo::from_env() + .map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)) + .ok() + } + + fn dim_if_necessary(&self, color: color::Color) -> color::Color { + if color >= self.num_colors && color >= 8 && color < 16 { + color - 8 + } else { + color + } + } +} + +impl<T: Write> Write for TerminfoTerminal<T> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.out.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.out.flush() + } +} diff --git a/vendor/term/src/terminfo/parm.rs b/vendor/term/src/terminfo/parm.rs new file mode 100644 index 000000000..a7f0b5087 --- /dev/null +++ b/vendor/term/src/terminfo/parm.rs @@ -0,0 +1,741 @@ +// Copyright 2019 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Parameterized string expansion + +use self::Param::*; +use self::States::*; + +use std::iter::repeat; + +#[derive(Clone, Copy, PartialEq)] +enum States { + Nothing, + Delay, + Percent, + SetVar, + GetVar, + PushParam, + CharConstant, + CharClose, + IntConstant(i32), + FormatPattern(Flags, FormatState), + SeekIfElse(usize), + SeekIfElsePercent(usize), + SeekIfEnd(usize), + SeekIfEndPercent(usize), +} + +#[derive(Copy, PartialEq, Clone)] +enum FormatState { + Flags, + Width, + Precision, +} + +/// Types of parameters a capability can use +#[allow(missing_docs)] +#[derive(Clone)] +pub enum Param { + Number(i32), + Words(String), +} + +impl Default for Param { + fn default() -> Self { + Param::Number(0) + } +} + +/// An error from interpreting a parameterized string. +#[derive(Debug, Eq, PartialEq)] +pub enum Error { + /// Data was requested from the stack, but the stack didn't have enough elements. + StackUnderflow, + /// The type of the element(s) on top of the stack did not match the type that the operator + /// wanted. + TypeMismatch, + /// An unrecognized format option was used. + UnrecognizedFormatOption(char), + /// An invalid variable name was used. + InvalidVariableName(char), + /// An invalid parameter index was used. + InvalidParameterIndex(char), + /// A malformed character constant was used. + MalformedCharacterConstant, + /// An integer constant was too large (overflowed an i32) + IntegerConstantOverflow, + /// A malformed integer constant was used. + MalformedIntegerConstant, + /// A format width constant was too large (overflowed a usize) + FormatWidthOverflow, + /// A format precision constant was too large (overflowed a usize) + FormatPrecisionOverflow, +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + use self::Error::*; + match *self { + StackUnderflow => f.write_str("not enough elements on the stack"), + TypeMismatch => f.write_str("type mismatch"), + UnrecognizedFormatOption(_) => f.write_str("unrecognized format option"), + InvalidVariableName(_) => f.write_str("invalid variable name"), + InvalidParameterIndex(_) => f.write_str("invalid parameter index"), + MalformedCharacterConstant => f.write_str("malformed character constant"), + IntegerConstantOverflow => f.write_str("integer constant computation overflowed"), + MalformedIntegerConstant => f.write_str("malformed integer constant"), + FormatWidthOverflow => f.write_str("format width constant computation overflowed"), + FormatPrecisionOverflow => { + f.write_str("format precision constant computation overflowed") + } + } + } +} + +impl ::std::error::Error for Error {} + +/// Container for static and dynamic variable arrays +#[derive(Default)] +pub struct Variables { + /// Static variables A-Z + sta_vars: [Param; 26], + /// Dynamic variables a-z + dyn_vars: [Param; 26], +} + +impl Variables { + /// Return a new zero-initialized Variables + pub fn new() -> Variables { + Default::default() + } +} + +/// Expand a parameterized capability +/// +/// # Arguments +/// * `cap` - string to expand +/// * `params` - vector of params for %p1 etc +/// * `vars` - Variables struct for %Pa etc +/// +/// To be compatible with ncurses, `vars` should be the same between calls to `expand` for +/// multiple capabilities for the same terminal. +pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<u8>, Error> { + let mut state = Nothing; + + // expanded cap will only rarely be larger than the cap itself + let mut output = Vec::with_capacity(cap.len()); + + let mut stack: Vec<Param> = Vec::new(); + + // Copy parameters into a local vector for mutability + let mut mparams = [ + Number(0), + Number(0), + Number(0), + Number(0), + Number(0), + Number(0), + Number(0), + Number(0), + Number(0), + ]; + for (dst, src) in mparams.iter_mut().zip(params.iter()) { + *dst = (*src).clone(); + } + + for &c in cap.iter() { + let cur = c as char; + let mut old_state = state; + match state { + Nothing => { + if cur == '%' { + state = Percent; + } else if cur == '$' { + state = Delay; + } else { + output.push(c); + } + } + Delay => { + old_state = Nothing; + if cur == '>' { + state = Nothing; + } + } + Percent => { + match cur { + '%' => { + output.push(c); + state = Nothing + } + 'c' => { + match stack.pop() { + // if c is 0, use 0200 (128) for ncurses compatibility + Some(Number(0)) => output.push(128u8), + // Don't check bounds. ncurses just casts and truncates. + Some(Number(c)) => output.push(c as u8), + Some(_) => return Err(Error::TypeMismatch), + None => return Err(Error::StackUnderflow), + } + } + 'p' => state = PushParam, + 'P' => state = SetVar, + 'g' => state = GetVar, + '\'' => state = CharConstant, + '{' => state = IntConstant(0), + 'l' => match stack.pop() { + Some(Words(s)) => stack.push(Number(s.len() as i32)), + Some(_) => return Err(Error::TypeMismatch), + None => return Err(Error::StackUnderflow), + }, + '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => { + match (stack.pop(), stack.pop()) { + (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur { + '+' => x + y, + '-' => x - y, + '*' => x * y, + '/' => x / y, + '|' => x | y, + '&' => x & y, + '^' => x ^ y, + 'm' => x % y, + _ => unreachable!("logic error"), + })), + (Some(_), Some(_)) => return Err(Error::TypeMismatch), + _ => return Err(Error::StackUnderflow), + } + } + '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) { + (Some(Number(y)), Some(Number(x))) => stack.push(Number( + if match cur { + '=' => x == y, + '<' => x < y, + '>' => x > y, + 'A' => x > 0 && y > 0, + 'O' => x > 0 || y > 0, + _ => unreachable!("logic error"), + } { + 1 + } else { + 0 + }, + )), + (Some(_), Some(_)) => return Err(Error::TypeMismatch), + _ => return Err(Error::StackUnderflow), + }, + '!' | '~' => match stack.pop() { + Some(Number(x)) => stack.push(Number(match cur { + '!' if x > 0 => 0, + '!' => 1, + '~' => !x, + _ => unreachable!("logic error"), + })), + Some(_) => return Err(Error::TypeMismatch), + None => return Err(Error::StackUnderflow), + }, + 'i' => match (&mparams[0], &mparams[1]) { + (&Number(x), &Number(y)) => { + mparams[0] = Number(x + 1); + mparams[1] = Number(y + 1); + } + (_, _) => return Err(Error::TypeMismatch), + }, + + // printf-style support for %doxXs + 'd' | 'o' | 'x' | 'X' | 's' => { + if let Some(arg) = stack.pop() { + let flags = Flags::default(); + let res = format(arg, FormatOp::from_char(cur), flags)?; + output.extend(res); + } else { + return Err(Error::StackUnderflow); + } + } + ':' | '#' | ' ' | '.' | '0'..='9' => { + let mut flags = Flags::default(); + let mut fstate = FormatState::Flags; + match cur { + ':' => (), + '#' => flags.alternate = true, + ' ' => flags.space = true, + '.' => fstate = FormatState::Precision, + '0'..='9' => { + flags.width = cur as usize - '0' as usize; + fstate = FormatState::Width; + } + _ => unreachable!("logic error"), + } + state = FormatPattern(flags, fstate); + } + + // conditionals + '?' | ';' => (), + 't' => match stack.pop() { + Some(Number(0)) => state = SeekIfElse(0), + Some(Number(_)) => (), + Some(_) => return Err(Error::TypeMismatch), + None => return Err(Error::StackUnderflow), + }, + 'e' => state = SeekIfEnd(0), + c => return Err(Error::UnrecognizedFormatOption(c)), + } + } + PushParam => { + // params are 1-indexed + stack.push( + mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err(Error::InvalidParameterIndex(cur)), + }] + .clone(), + ); + } + SetVar => { + if cur >= 'A' && cur <= 'Z' { + if let Some(arg) = stack.pop() { + let idx = (cur as u8) - b'A'; + vars.sta_vars[idx as usize] = arg; + } else { + return Err(Error::StackUnderflow); + } + } else if cur >= 'a' && cur <= 'z' { + if let Some(arg) = stack.pop() { + let idx = (cur as u8) - b'a'; + vars.dyn_vars[idx as usize] = arg; + } else { + return Err(Error::StackUnderflow); + } + } else { + return Err(Error::InvalidVariableName(cur)); + } + } + GetVar => { + if cur >= 'A' && cur <= 'Z' { + let idx = (cur as u8) - b'A'; + stack.push(vars.sta_vars[idx as usize].clone()); + } else if cur >= 'a' && cur <= 'z' { + let idx = (cur as u8) - b'a'; + stack.push(vars.dyn_vars[idx as usize].clone()); + } else { + return Err(Error::InvalidVariableName(cur)); + } + } + CharConstant => { + stack.push(Number(i32::from(c))); + state = CharClose; + } + CharClose => { + if cur != '\'' { + return Err(Error::MalformedCharacterConstant); + } + } + IntConstant(i) => { + if cur == '}' { + stack.push(Number(i)); + state = Nothing; + } else if let Some(digit) = cur.to_digit(10) { + match i + .checked_mul(10) + .and_then(|i_ten| i_ten.checked_add(digit as i32)) + { + Some(i) => { + state = IntConstant(i); + old_state = Nothing; + } + None => return Err(Error::IntegerConstantOverflow), + } + } else { + return Err(Error::MalformedIntegerConstant); + } + } + FormatPattern(ref mut flags, ref mut fstate) => { + old_state = Nothing; + match (*fstate, cur) { + (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => { + if let Some(arg) = stack.pop() { + let res = format(arg, FormatOp::from_char(cur), *flags)?; + output.extend(res); + // will cause state to go to Nothing + old_state = FormatPattern(*flags, *fstate); + } else { + return Err(Error::StackUnderflow); + } + } + (FormatState::Flags, '#') => { + flags.alternate = true; + } + (FormatState::Flags, '-') => { + flags.left = true; + } + (FormatState::Flags, '+') => { + flags.sign = true; + } + (FormatState::Flags, ' ') => { + flags.space = true; + } + (FormatState::Flags, '0'..='9') => { + flags.width = cur as usize - '0' as usize; + *fstate = FormatState::Width; + } + (FormatState::Width, '0'..='9') => { + flags.width = match flags + .width + .checked_mul(10) + .and_then(|w| w.checked_add(cur as usize - '0' as usize)) + { + Some(width) => width, + None => return Err(Error::FormatWidthOverflow), + } + } + (FormatState::Width, '.') | (FormatState::Flags, '.') => { + *fstate = FormatState::Precision; + } + (FormatState::Precision, '0'..='9') => { + flags.precision = match flags + .precision + .checked_mul(10) + .and_then(|w| w.checked_add(cur as usize - '0' as usize)) + { + Some(precision) => precision, + None => return Err(Error::FormatPrecisionOverflow), + } + } + _ => return Err(Error::UnrecognizedFormatOption(cur)), + } + } + SeekIfElse(level) => { + if cur == '%' { + state = SeekIfElsePercent(level); + } + old_state = Nothing; + } + SeekIfElsePercent(level) => { + if cur == ';' { + if level == 0 { + state = Nothing; + } else { + state = SeekIfElse(level - 1); + } + } else if cur == 'e' && level == 0 { + state = Nothing; + } else if cur == '?' { + state = SeekIfElse(level + 1); + } else { + state = SeekIfElse(level); + } + } + SeekIfEnd(level) => { + if cur == '%' { + state = SeekIfEndPercent(level); + } + old_state = Nothing; + } + SeekIfEndPercent(level) => { + if cur == ';' { + if level == 0 { + state = Nothing; + } else { + state = SeekIfEnd(level - 1); + } + } else if cur == '?' { + state = SeekIfEnd(level + 1); + } else { + state = SeekIfEnd(level); + } + } + } + if state == old_state { + state = Nothing; + } + } + Ok(output) +} + +#[derive(Copy, PartialEq, Clone, Default)] +struct Flags { + width: usize, + precision: usize, + alternate: bool, + left: bool, + sign: bool, + space: bool, +} + +#[derive(Copy, Clone)] +enum FormatOp { + Digit, + Octal, + Hex, + HEX, + String, +} + +impl FormatOp { + fn from_char(c: char) -> FormatOp { + use self::FormatOp::*; + match c { + 'd' => Digit, + 'o' => Octal, + 'x' => Hex, + 'X' => HEX, + 's' => String, + _ => panic!("bad FormatOp char"), + } + } +} + +fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, Error> { + use self::FormatOp::*; + let mut s = match val { + Number(d) => { + match op { + Digit => { + if flags.sign { + format!("{:+01$}", d, flags.precision) + } else if d < 0 { + // C doesn't take sign into account in precision calculation. + format!("{:01$}", d, flags.precision + 1) + } else if flags.space { + format!(" {:01$}", d, flags.precision) + } else { + format!("{:01$}", d, flags.precision) + } + } + Octal => { + if flags.alternate { + // Leading octal zero counts against precision. + format!("0{:01$o}", d, flags.precision.saturating_sub(1)) + } else { + format!("{:01$o}", d, flags.precision) + } + } + Hex => { + if flags.alternate && d != 0 { + format!("0x{:01$x}", d, flags.precision) + } else { + format!("{:01$x}", d, flags.precision) + } + } + HEX => { + if flags.alternate && d != 0 { + format!("0X{:01$X}", d, flags.precision) + } else { + format!("{:01$X}", d, flags.precision) + } + } + String => return Err(Error::TypeMismatch), + } + .into_bytes() + } + Words(s) => match op { + String => { + let mut s = s.into_bytes(); + if flags.precision > 0 && flags.precision < s.len() { + s.truncate(flags.precision); + } + s + } + _ => return Err(Error::TypeMismatch), + }, + }; + if flags.width > s.len() { + let n = flags.width - s.len(); + if flags.left { + s.extend(repeat(b' ').take(n)); + } else { + let mut s_ = Vec::with_capacity(flags.width); + s_.extend(repeat(b' ').take(n)); + s_.extend(s.into_iter()); + s = s_; + } + } + Ok(s) +} + +#[cfg(test)] +mod test { + use super::Param::{self, Number, Words}; + use super::{expand, Variables}; + use std::result::Result::Ok; + + #[test] + fn test_basic_setabf() { + let s = b"\\E[48;5;%p1%dm"; + assert_eq!( + expand(s, &[Number(1)], &mut Variables::new()).unwrap(), + "\\E[48;5;1m".bytes().collect::<Vec<_>>() + ); + } + + #[test] + fn test_multiple_int_constants() { + assert_eq!( + expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(), + "21".bytes().collect::<Vec<_>>() + ); + } + + #[test] + fn test_op_i() { + let mut vars = Variables::new(); + assert_eq!( + expand( + b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d", + &[Number(1), Number(2), Number(3)], + &mut vars + ), + Ok("123233".bytes().collect::<Vec<_>>()) + ); + assert_eq!( + expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars), + Ok("0011".bytes().collect::<Vec<_>>()) + ); + } + + #[test] + fn test_param_stack_failure_conditions() { + let mut varstruct = Variables::new(); + let vars = &mut varstruct; + fn get_res( + fmt: &str, + cap: &str, + params: &[Param], + vars: &mut Variables, + ) -> Result<Vec<u8>, super::Error> { + let mut u8v: Vec<_> = fmt.bytes().collect(); + u8v.extend(cap.as_bytes().iter().cloned()); + expand(&u8v, params, vars) + } + + let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"]; + for &cap in &caps { + let res = get_res("", cap, &[], vars); + assert!( + res.is_err(), + "Op {} succeeded incorrectly with 0 stack entries", + cap + ); + let p = if cap == "%s" || cap == "%l" { + Words("foo".to_owned()) + } else { + Number(97) + }; + let res = get_res("%p1", cap, &[p], vars); + assert!( + res.is_ok(), + "Op {} failed with 1 stack entry: {}", + cap, + res.err().unwrap() + ); + } + let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"]; + for &cap in &caps { + let res = expand(cap.as_bytes(), &[], vars); + assert!( + res.is_err(), + "Binop {} succeeded incorrectly with 0 stack entries", + cap + ); + let res = get_res("%{1}", cap, &[], vars); + assert!( + res.is_err(), + "Binop {} succeeded incorrectly with 1 stack entry", + cap + ); + let res = get_res("%{1}%{2}", cap, &[], vars); + assert!( + res.is_ok(), + "Binop {} failed with 2 stack entries: {}", + cap, + res.err().unwrap() + ); + } + } + + #[test] + fn test_push_bad_param() { + assert!(expand(b"%pa", &[], &mut Variables::new()).is_err()); + } + + #[test] + fn test_comparison_ops() { + let v = [ + ('<', [1u8, 0u8, 0u8]), + ('=', [0u8, 1u8, 0u8]), + ('>', [0u8, 0u8, 1u8]), + ]; + for &(op, bs) in &v { + let s = format!("%{{1}}%{{2}}%{}%d", op); + let res = expand(s.as_bytes(), &[], &mut Variables::new()); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), vec![b'0' + bs[0]]); + let s = format!("%{{1}}%{{1}}%{}%d", op); + let res = expand(s.as_bytes(), &[], &mut Variables::new()); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), vec![b'0' + bs[1]]); + let s = format!("%{{2}}%{{1}}%{}%d", op); + let res = expand(s.as_bytes(), &[], &mut Variables::new()); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), vec![b'0' + bs[2]]); + } + } + + #[test] + fn test_conditionals() { + let mut vars = Variables::new(); + let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"; + let res = expand(s, &[Number(1)], &mut vars); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>()); + let res = expand(s, &[Number(8)], &mut vars); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>()); + let res = expand(s, &[Number(42)], &mut vars); + assert!(res.is_ok(), res.err().unwrap()); + assert_eq!(res.unwrap(), "\\E[38;5;42m".bytes().collect::<Vec<_>>()); + } + + #[test] + fn test_format() { + let mut varstruct = Variables::new(); + let vars = &mut varstruct; + assert_eq!( + expand( + b"%p1%s%p2%2s%p3%2s%p4%.2s", + &[ + Words("foo".to_owned()), + Words("foo".to_owned()), + Words("f".to_owned()), + Words("foo".to_owned()) + ], + vars + ), + Ok("foofoo ffo".bytes().collect::<Vec<_>>()) + ); + assert_eq!( + expand(b"%p1%:-4.2s", &[Words("foo".to_owned())], vars), + Ok("fo ".bytes().collect::<Vec<_>>()) + ); + + assert_eq!( + expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars), + Ok("1001 1+1".bytes().collect::<Vec<_>>()) + ); + assert_eq!( + expand( + b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", + &[Number(15), Number(27)], + vars + ), + Ok("17017 001b0X001B".bytes().collect::<Vec<_>>()) + ); + } +} diff --git a/vendor/term/src/terminfo/parser/compiled.rs b/vendor/term/src/terminfo/parser/compiled.rs new file mode 100644 index 000000000..d84e21b00 --- /dev/null +++ b/vendor/term/src/terminfo/parser/compiled.rs @@ -0,0 +1,204 @@ +// Copyright 2019 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! ncurses-compatible compiled terminfo format parsing (term(5)) + +use std::collections::HashMap; +use std::io; +use std::io::prelude::*; + +use crate::terminfo::Error::*; +use crate::terminfo::TermInfo; +use crate::Result; + +pub use crate::terminfo::parser::names::*; + +// These are the orders ncurses uses in its compiled format (as of 5.9). Not +// sure if portable. + +fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u32> { + let mut buf = [0; 2]; + r.read_exact(&mut buf) + .map(|()| u32::from(u16::from_le_bytes(buf))) +} + +fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> { + let mut buf = [0; 4]; + r.read_exact(&mut buf).map(|()| u32::from_le_bytes(buf)) +} + +fn read_byte(r: &mut dyn io::Read) -> io::Result<u8> { + match r.bytes().next() { + Some(s) => s, + None => Err(io::Error::new(io::ErrorKind::Other, "end of file")), + } +} + +/// Parse a compiled terminfo entry, using long capability names if `longnames` +/// is true +pub fn parse(file: &mut dyn io::Read, longnames: bool) -> Result<TermInfo> { + let (bnames, snames, nnames) = if longnames { + (boolfnames, stringfnames, numfnames) + } else { + (boolnames, stringnames, numnames) + }; + + // Check magic number + let mut buf = [0; 2]; + file.read_exact(&mut buf)?; + let magic = u16::from_le_bytes(buf); + + let read_number = match magic { + 0x011A => read_le_u16, + 0x021e => read_le_u32, + _ => return Err(BadMagic(magic).into()), + }; + + // According to the spec, these fields must be >= -1 where -1 means that the + // feature is not + // supported. Using 0 instead of -1 works because we skip sections with length + // 0. + macro_rules! read_nonneg { + () => {{ + match read_le_u16(file)? as i16 { + n if n >= 0 => n as usize, + -1 => 0, + _ => return Err(InvalidLength.into()), + } + }}; + } + + let names_bytes = read_nonneg!(); + let bools_bytes = read_nonneg!(); + let numbers_count = read_nonneg!(); + let string_offsets_count = read_nonneg!(); + let string_table_bytes = read_nonneg!(); + + if names_bytes == 0 { + return Err(ShortNames.into()); + } + + if bools_bytes > boolnames.len() { + return Err(TooManyBools.into()); + } + + if numbers_count > numnames.len() { + return Err(TooManyNumbers.into()); + } + + if string_offsets_count > stringnames.len() { + return Err(TooManyStrings.into()); + } + + // don't read NUL + let mut bytes = Vec::new(); + file.take((names_bytes - 1) as u64) + .read_to_end(&mut bytes)?; + let names_str = match String::from_utf8(bytes) { + Ok(s) => s, + Err(e) => return Err(NotUtf8(e.utf8_error()).into()), + }; + + let term_names: Vec<String> = names_str.split('|').map(|s| s.to_owned()).collect(); + // consume NUL + if read_byte(file)? != b'\0' { + return Err(NamesMissingNull.into()); + } + + let bools_map = (0..bools_bytes) + .filter_map(|i| match read_byte(file) { + Err(e) => Some(Err(e)), + Ok(1) => Some(Ok((bnames[i], true))), + Ok(_) => None, + }) + .collect::<io::Result<HashMap<_, _>>>()?; + + if (bools_bytes + names_bytes) % 2 == 1 { + read_byte(file)?; // compensate for padding + } + + let numbers_map = (0..numbers_count) + .filter_map(|i| match read_number(file) { + Ok(0xFFFF) => None, + Ok(n) => Some(Ok((nnames[i], n))), + Err(e) => Some(Err(e)), + }) + .collect::<io::Result<HashMap<_, _>>>()?; + + let string_map: HashMap<&str, Vec<u8>> = if string_offsets_count > 0 { + let string_offsets = (0..string_offsets_count) + .map(|_| { + let mut buf = [0; 2]; + file.read_exact(&mut buf).map(|()| u16::from_le_bytes(buf)) + }) + .collect::<io::Result<Vec<_>>>()?; + + let mut string_table = Vec::new(); + file.take(string_table_bytes as u64) + .read_to_end(&mut string_table)?; + + string_offsets + .into_iter() + .enumerate() + .filter(|&(_, offset)| { + // non-entry + offset != 0xFFFF + }) + .map(|(i, offset)| { + let offset = offset as usize; + + let name = if snames[i] == "_" { + stringfnames[i] + } else { + snames[i] + }; + + if offset == 0xFFFE { + // undocumented: FFFE indicates cap@, which means the capability + // is not present + // unsure if the handling for this is correct + return Ok((name, Vec::new())); + } + + // Find the offset of the NUL we want to go to + let nulpos = string_table[offset..string_table_bytes] + .iter() + .position(|&b| b == 0); + match nulpos { + Some(len) => Ok((name, string_table[offset..offset + len].to_vec())), + None => Err(crate::Error::TerminfoParsing(StringsMissingNull)), + } + }) + .collect::<Result<HashMap<_, _>>>()? + } else { + HashMap::new() + }; + + // And that's all there is to it + Ok(TermInfo { + names: term_names, + bools: bools_map, + numbers: numbers_map, + strings: string_map, + }) +} + +#[cfg(test)] +mod test { + + use super::{boolfnames, boolnames, numfnames, numnames, stringfnames, stringnames}; + + #[test] + fn test_veclens() { + assert_eq!(boolfnames.len(), boolnames.len()); + assert_eq!(numfnames.len(), numnames.len()); + assert_eq!(stringfnames.len(), stringnames.len()); + } +} diff --git a/vendor/term/src/terminfo/parser/names.rs b/vendor/term/src/terminfo/parser/names.rs new file mode 100644 index 000000000..c706b941a --- /dev/null +++ b/vendor/term/src/terminfo/parser/names.rs @@ -0,0 +1,553 @@ +#![allow(non_upper_case_globals, missing_docs)] +#![cfg_attr(rustfmt, rustfmt_skip)] + +pub static boolfnames: &[&str] = &["auto_left_margin", + "auto_right_margin", + "no_esc_ctlc", + "ceol_standout_glitch", + "eat_newline_glitch", + "erase_overstrike", + "generic_type", + "hard_copy", + "has_meta_key", + "has_status_line", + "insert_null_glitch", + "memory_above", + "memory_below", + "move_insert_mode", + "move_standout_mode", + "over_strike", + "status_line_esc_ok", + "dest_tabs_magic_smso", + "tilde_glitch", + "transparent_underline", + "xon_xoff", + "needs_xon_xoff", + "prtr_silent", + "hard_cursor", + "non_rev_rmcup", + "no_pad_char", + "non_dest_scroll_region", + "can_change", + "back_color_erase", + "hue_lightness_saturation", + "col_addr_glitch", + "cr_cancels_micro_mode", + "has_print_wheel", + "row_addr_glitch", + "semi_auto_right_margin", + "cpi_changes_res", + "lpi_changes_res", + "backspaces_with_bs", + "crt_no_scrolling", + "no_correctly_working_cr", + "gnu_has_meta_key", + "linefeed_is_newline", + "has_hardware_tabs", + "return_does_clr_eol"]; + +pub static boolnames: &[&str] = + &["bw", "am", "xsb", "xhp", "xenl", "eo", "gn", "hc", "km", "hs", "in", "db", "da", "mir", + "msgr", "os", "eslok", "xt", "hz", "ul", "xon", "nxon", "mc5i", "chts", "nrrmc", "npc", + "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy", "xvpa", "sam", "cpix", "lpix", + "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"]; + +pub static numfnames: &[&str] = &["columns", + "init_tabs", + "lines", + "lines_of_memory", + "magic_cookie_glitch", + "padding_baud_rate", + "virtual_terminal", + "width_status_line", + "num_labels", + "label_height", + "label_width", + "max_attributes", + "maximum_windows", + "max_colors", + "max_pairs", + "no_color_video", + "buffer_capacity", + "dot_vert_spacing", + "dot_horz_spacing", + "max_micro_address", + "max_micro_jump", + "micro_col_size", + "micro_line_size", + "number_of_pins", + "output_res_char", + "output_res_line", + "output_res_horz_inch", + "output_res_vert_inch", + "print_rate", + "wide_char_size", + "buttons", + "bit_image_entwining", + "bit_image_type", + "magic_cookie_glitch_ul", + "carriage_return_delay", + "new_line_delay", + "backspace_delay", + "horizontal_tab_delay", + "number_of_function_keys"]; + +pub static numnames: &[&str] = + &["cols", "it", "lines", "lm", "xmc", "pb", "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", + "colors", "pairs", "ncv", "bufsz", "spinv", "spinh", "maddr", "mjump", "mcs", "mls", + "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs", "btns", "bitwin", "bitype", "UTug", + "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"]; + +pub static stringfnames: &[&str] = &["back_tab", + "bell", + "carriage_return", + "change_scroll_region", + "clear_all_tabs", + "clear_screen", + "clr_eol", + "clr_eos", + "column_address", + "command_character", + "cursor_address", + "cursor_down", + "cursor_home", + "cursor_invisible", + "cursor_left", + "cursor_mem_address", + "cursor_normal", + "cursor_right", + "cursor_to_ll", + "cursor_up", + "cursor_visible", + "delete_character", + "delete_line", + "dis_status_line", + "down_half_line", + "enter_alt_charset_mode", + "enter_blink_mode", + "enter_bold_mode", + "enter_ca_mode", + "enter_delete_mode", + "enter_dim_mode", + "enter_insert_mode", + "enter_secure_mode", + "enter_protected_mode", + "enter_reverse_mode", + "enter_standout_mode", + "enter_underline_mode", + "erase_chars", + "exit_alt_charset_mode", + "exit_attribute_mode", + "exit_ca_mode", + "exit_delete_mode", + "exit_insert_mode", + "exit_standout_mode", + "exit_underline_mode", + "flash_screen", + "form_feed", + "from_status_line", + "init_1string", + "init_2string", + "init_3string", + "init_file", + "insert_character", + "insert_line", + "insert_padding", + "key_backspace", + "key_catab", + "key_clear", + "key_ctab", + "key_dc", + "key_dl", + "key_down", + "key_eic", + "key_eol", + "key_eos", + "key_f0", + "key_f1", + "key_f10", + "key_f2", + "key_f3", + "key_f4", + "key_f5", + "key_f6", + "key_f7", + "key_f8", + "key_f9", + "key_home", + "key_ic", + "key_il", + "key_left", + "key_ll", + "key_npage", + "key_ppage", + "key_right", + "key_sf", + "key_sr", + "key_stab", + "key_up", + "keypad_local", + "keypad_xmit", + "lab_f0", + "lab_f1", + "lab_f10", + "lab_f2", + "lab_f3", + "lab_f4", + "lab_f5", + "lab_f6", + "lab_f7", + "lab_f8", + "lab_f9", + "meta_off", + "meta_on", + "newline", + "pad_char", + "parm_dch", + "parm_delete_line", + "parm_down_cursor", + "parm_ich", + "parm_index", + "parm_insert_line", + "parm_left_cursor", + "parm_right_cursor", + "parm_rindex", + "parm_up_cursor", + "pkey_key", + "pkey_local", + "pkey_xmit", + "print_screen", + "prtr_off", + "prtr_on", + "repeat_char", + "reset_1string", + "reset_2string", + "reset_3string", + "reset_file", + "restore_cursor", + "row_address", + "save_cursor", + "scroll_forward", + "scroll_reverse", + "set_attributes", + "set_tab", + "set_window", + "tab", + "to_status_line", + "underline_char", + "up_half_line", + "init_prog", + "key_a1", + "key_a3", + "key_b2", + "key_c1", + "key_c3", + "prtr_non", + "char_padding", + "acs_chars", + "plab_norm", + "key_btab", + "enter_xon_mode", + "exit_xon_mode", + "enter_am_mode", + "exit_am_mode", + "xon_character", + "xoff_character", + "ena_acs", + "label_on", + "label_off", + "key_beg", + "key_cancel", + "key_close", + "key_command", + "key_copy", + "key_create", + "key_end", + "key_enter", + "key_exit", + "key_find", + "key_help", + "key_mark", + "key_message", + "key_move", + "key_next", + "key_open", + "key_options", + "key_previous", + "key_print", + "key_redo", + "key_reference", + "key_refresh", + "key_replace", + "key_restart", + "key_resume", + "key_save", + "key_suspend", + "key_undo", + "key_sbeg", + "key_scancel", + "key_scommand", + "key_scopy", + "key_screate", + "key_sdc", + "key_sdl", + "key_select", + "key_send", + "key_seol", + "key_sexit", + "key_sfind", + "key_shelp", + "key_shome", + "key_sic", + "key_sleft", + "key_smessage", + "key_smove", + "key_snext", + "key_soptions", + "key_sprevious", + "key_sprint", + "key_sredo", + "key_sreplace", + "key_sright", + "key_srsume", + "key_ssave", + "key_ssuspend", + "key_sundo", + "req_for_input", + "key_f11", + "key_f12", + "key_f13", + "key_f14", + "key_f15", + "key_f16", + "key_f17", + "key_f18", + "key_f19", + "key_f20", + "key_f21", + "key_f22", + "key_f23", + "key_f24", + "key_f25", + "key_f26", + "key_f27", + "key_f28", + "key_f29", + "key_f30", + "key_f31", + "key_f32", + "key_f33", + "key_f34", + "key_f35", + "key_f36", + "key_f37", + "key_f38", + "key_f39", + "key_f40", + "key_f41", + "key_f42", + "key_f43", + "key_f44", + "key_f45", + "key_f46", + "key_f47", + "key_f48", + "key_f49", + "key_f50", + "key_f51", + "key_f52", + "key_f53", + "key_f54", + "key_f55", + "key_f56", + "key_f57", + "key_f58", + "key_f59", + "key_f60", + "key_f61", + "key_f62", + "key_f63", + "clr_bol", + "clear_margins", + "set_left_margin", + "set_right_margin", + "label_format", + "set_clock", + "display_clock", + "remove_clock", + "create_window", + "goto_window", + "hangup", + "dial_phone", + "quick_dial", + "tone", + "pulse", + "flash_hook", + "fixed_pause", + "wait_tone", + "user0", + "user1", + "user2", + "user3", + "user4", + "user5", + "user6", + "user7", + "user8", + "user9", + "orig_pair", + "orig_colors", + "initialize_color", + "initialize_pair", + "set_color_pair", + "set_foreground", + "set_background", + "change_char_pitch", + "change_line_pitch", + "change_res_horz", + "change_res_vert", + "define_char", + "enter_doublewide_mode", + "enter_draft_quality", + "enter_italics_mode", + "enter_leftward_mode", + "enter_micro_mode", + "enter_near_letter_quality", + "enter_normal_quality", + "enter_shadow_mode", + "enter_subscript_mode", + "enter_superscript_mode", + "enter_upward_mode", + "exit_doublewide_mode", + "exit_italics_mode", + "exit_leftward_mode", + "exit_micro_mode", + "exit_shadow_mode", + "exit_subscript_mode", + "exit_superscript_mode", + "exit_upward_mode", + "micro_column_address", + "micro_down", + "micro_left", + "micro_right", + "micro_row_address", + "micro_up", + "order_of_pins", + "parm_down_micro", + "parm_left_micro", + "parm_right_micro", + "parm_up_micro", + "select_char_set", + "set_bottom_margin", + "set_bottom_margin_parm", + "set_left_margin_parm", + "set_right_margin_parm", + "set_top_margin", + "set_top_margin_parm", + "start_bit_image", + "start_char_set_def", + "stop_bit_image", + "stop_char_set_def", + "subscript_characters", + "superscript_characters", + "these_cause_cr", + "zero_motion", + "char_set_names", + "key_mouse", + "mouse_info", + "req_mouse_pos", + "get_mouse", + "set_a_foreground", + "set_a_background", + "pkey_plab", + "device_type", + "code_set_init", + "set0_des_seq", + "set1_des_seq", + "set2_des_seq", + "set3_des_seq", + "set_lr_margin", + "set_tb_margin", + "bit_image_repeat", + "bit_image_newline", + "bit_image_carriage_return", + "color_names", + "define_bit_image_region", + "end_bit_image_region", + "set_color_band", + "set_page_length", + "display_pc_char", + "enter_pc_charset_mode", + "exit_pc_charset_mode", + "enter_scancode_mode", + "exit_scancode_mode", + "pc_term_options", + "scancode_escape", + "alt_scancode_esc", + "enter_horizontal_hl_mode", + "enter_left_hl_mode", + "enter_low_hl_mode", + "enter_right_hl_mode", + "enter_top_hl_mode", + "enter_vertical_hl_mode", + "set_a_attributes", + "set_pglen_inch", + "termcap_init2", + "termcap_reset", + "linefeed_if_not_lf", + "backspace_if_not_bs", + "other_non_function_keys", + "arrow_key_map", + "acs_ulcorner", + "acs_llcorner", + "acs_urcorner", + "acs_lrcorner", + "acs_ltee", + "acs_rtee", + "acs_btee", + "acs_ttee", + "acs_hline", + "acs_vline", + "acs_plus", + "memory_lock", + "memory_unlock", + "box_chars_1"]; + +pub static stringnames: &[&str] = + &["cbt", "bel", "cr", "csr", "tbc", "clear", "el", "ed", "hpa", "cmdch", "cup", "cud1", + "home", "civis", "cub1", "mrcup", "cnorm", "cuf1", "ll", "cuu1", "cvvis", "dch1", "dl1", + "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc", "dim", "smir", "invis", "prot", + "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc", "rmir", "rmso", "rmul", + "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip", "kbs", "ktbc", "kclr", + "kctab", "kdch1", "kdl1", "kcud1", "krmir", "kel", "ked", "kf0", "kf1", "kf10", "kf2", + "kf3", "kf4", "kf5", "kf6", "kf7", "kf8", "kf9", "khome", "kich1", "kil1", "kcub1", "kll", + "knp", "kpp", "kcuf1", "kind", "kri", "khts", "kcuu1", "rmkx", "smkx", "lf0", "lf1", "lf10", + "lf2", "lf3", "lf4", "lf5", "lf6", "lf7", "lf8", "lf9", "rmm", "smm", "nel", "pad", "dch", + "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey", "pfloc", "pfx", + "mc0", "mc4", "mc5", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind", "ri", + "sgr", "hts", "wind", "ht", "tsl", "uc", "hu", "iprog", "ka1", "ka3", "kb2", "kc1", "kc3", + "mc5p", "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", + "enacs", "smln", "rmln", "kbeg", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "kend", "kent", + "kext", "kfnd", "khlp", "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", + "krdo", "kref", "krfr", "krpl", "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", + "kCMD", "kCPY", "kCRT", "kDC", "kDL", "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", + "kHOM", "kIC", "kLFT", "kMSG", "kMOV", "kNXT", "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", + "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "kf11", "kf12", "kf13", "kf14", "kf15", + "kf16", "kf17", "kf18", "kf19", "kf20", "kf21", "kf22", "kf23", "kf24", "kf25", "kf26", + "kf27", "kf28", "kf29", "kf30", "kf31", "kf32", "kf33", "kf34", "kf35", "kf36", "kf37", + "kf38", "kf39", "kf40", "kf41", "kf42", "kf43", "kf44", "kf45", "kf46", "kf47", "kf48", + "kf49", "kf50", "kf51", "kf52", "kf53", "kf54", "kf55", "kf56", "kf57", "kf58", "kf59", + "kf60", "kf61", "kf62", "kf63", "el1", "mgc", "smgl", "smgr", "fln", "sclk", "dclk", + "rmclk", "cwin", "wingo", "hup", "dial", "qdial", "tone", "pulse", "hook", "pause", "wait", + "u0", "u1", "u2", "u3", "u4", "u5", "u6", "u7", "u8", "u9", "op", "oc", "initc", "initp", + "scp", "setf", "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", + "smicm", "snlq", "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", + "rshm", "rsubm", "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", + "porder", "mcud", "mcub", "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", + "smgtp", "sbim", "scsd", "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", + "minfo", "reqmp", "getm", "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", + "s3ds", "smglr", "smgtb", "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", + "slines", "dispc", "smpch", "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", + "elhlm", "elohlm", "erhlm", "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", + "OTbs", "OTko", "OTma", "OTG2", "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", + "OTGH", "OTGV", "OTGC", "meml", "memu", "box1"]; diff --git a/vendor/term/src/terminfo/searcher.rs b/vendor/term/src/terminfo/searcher.rs new file mode 100644 index 000000000..fca3061f4 --- /dev/null +++ b/vendor/term/src/terminfo/searcher.rs @@ -0,0 +1,93 @@ +// Copyright 2019 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! ncurses-compatible database discovery +//! +//! Does not support hashed database, only filesystem! + +use std::env; +use std::fs; +use std::path::PathBuf; + +use dirs_next as dirs; + +/// Return path to database entry for `term` +pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> { + let mut dirs_to_search = Vec::new(); + let first_char = match term.chars().next() { + Some(c) => c, + None => return None, + }; + + // Find search directory + // The terminfo manual says: + // + // > If the environment variable TERMINFO is set, it is interpreted + // > as the pathname of a directory containing the compiled description + // > you are working on. Only that directory is searched. + // + // However, the ncurses manual says: + // + // > If the environment variable TERMINFO is defined, any program using + // > curses checks for a local terminal definition before checking in + // > the standard place. + // + // Given that ncurses is the defacto standard, we follow the ncurses manual. + if let Some(dir) = env::var_os("TERMINFO") { + dirs_to_search.push(PathBuf::from(dir)); + } + + if let Ok(dirs) = env::var("TERMINFO_DIRS") { + for i in dirs.split(':') { + if i == "" { + dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); + } else { + dirs_to_search.push(PathBuf::from(i)); + } + } + } else { + // Found nothing in TERMINFO_DIRS, use the default paths: + // According to /etc/terminfo/README, after looking at + // ~/.terminfo, ncurses will search /etc/terminfo, then + // /lib/terminfo, and eventually /usr/share/terminfo. + // On Haiku the database can be found at /boot/system/data/terminfo + if let Some(mut homedir) = dirs::home_dir() { + homedir.push(".terminfo"); + dirs_to_search.push(homedir) + } + + dirs_to_search.push(PathBuf::from("/etc/terminfo")); + dirs_to_search.push(PathBuf::from("/lib/terminfo")); + dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); + dirs_to_search.push(PathBuf::from("/boot/system/data/terminfo")); + } + + // Look for the terminal in all of the search directories + for mut p in dirs_to_search { + if fs::metadata(&p).is_ok() { + p.push(&first_char.to_string()); + p.push(&term); + if fs::metadata(&p).is_ok() { + return Some(p); + } + p.pop(); + p.pop(); + + // on some installations the dir is named after the hex of the char + // (e.g. OS X) + p.push(&format!("{:x}", first_char as usize)); + p.push(term); + if fs::metadata(&p).is_ok() { + return Some(p); + } + } + } + None +} diff --git a/vendor/term/src/win.rs b/vendor/term/src/win.rs new file mode 100644 index 000000000..48ece2db2 --- /dev/null +++ b/vendor/term/src/win.rs @@ -0,0 +1,434 @@ +// Copyright 2013-2019 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Windows console handling + +// FIXME (#13400): this is only a tiny fraction of the Windows console api + +use crate::color; +use crate::Attr; +use crate::Error; +use crate::Result; +use crate::Terminal; +use std::io; +use std::io::prelude::*; +use std::ops::Deref; +use std::ptr; + +use winapi::shared::minwindef::{DWORD, WORD}; +use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; +use winapi::um::fileapi::{CreateFileA, OPEN_EXISTING}; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::wincon::FillConsoleOutputAttribute; +use winapi::um::wincon::{ + FillConsoleOutputCharacterW, GetConsoleScreenBufferInfo, CONSOLE_SCREEN_BUFFER_INFO, COORD, +}; +use winapi::um::wincon::{SetConsoleCursorPosition, SetConsoleTextAttribute}; +use winapi::um::wincon::{BACKGROUND_INTENSITY, ENABLE_VIRTUAL_TERMINAL_PROCESSING}; +use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE}; + +/// Console info which can be used by a Terminal implementation +/// which uses the Win32 Console API. +pub struct WinConsoleInfo { + def_foreground: color::Color, + def_background: color::Color, + foreground: color::Color, + background: color::Color, + reverse: bool, + secure: bool, + standout: bool, +} + +/// A Terminal implementation which uses the Win32 Console API. +pub struct WinConsole<T> { + buf: T, + info: WinConsoleInfo, +} + +fn color_to_bits(color: color::Color) -> u16 { + // magic numbers from mingw-w64's wincon.h + + let bits = match color % 8 { + color::BLACK => 0, + color::BLUE => 0x1, + color::GREEN => 0x2, + color::RED => 0x4, + color::YELLOW => 0x2 | 0x4, + color::MAGENTA => 0x1 | 0x4, + color::CYAN => 0x1 | 0x2, + color::WHITE => 0x1 | 0x2 | 0x4, + _ => unreachable!(), + }; + + if color >= 8 { + bits | 0x8 + } else { + bits + } +} + +fn bits_to_color(bits: u16) -> color::Color { + let color = match bits & 0x7 { + 0 => color::BLACK, + 0x1 => color::BLUE, + 0x2 => color::GREEN, + 0x4 => color::RED, + 0x6 => color::YELLOW, + 0x5 => color::MAGENTA, + 0x3 => color::CYAN, + 0x7 => color::WHITE, + _ => unreachable!(), + }; + + color | (bits as u32 & 0x8) // copy the hi-intensity bit +} + +struct HandleWrapper { + inner: HANDLE, +} + +impl HandleWrapper { + fn new(h: HANDLE) -> HandleWrapper { + HandleWrapper { inner: h } + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + if self.inner != INVALID_HANDLE_VALUE { + unsafe { + CloseHandle(self.inner); + } + } + } +} + +impl Deref for HandleWrapper { + type Target = HANDLE; + fn deref(&self) -> &HANDLE { + &self.inner + } +} + +/// Just get a handle to the current console buffer whatever it is +fn conout() -> io::Result<HandleWrapper> { + let name = b"CONOUT$\0"; + let handle = unsafe { + CreateFileA( + name.as_ptr() as *const i8, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, + ptr::null_mut(), + OPEN_EXISTING, + 0, + ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(HandleWrapper::new(handle)) + } +} + +unsafe fn set_flag(handle: HANDLE, flag: DWORD) -> io::Result<()> { + let mut curr_mode: DWORD = 0; + if GetConsoleMode(handle, &mut curr_mode) == 0 { + return Err(io::Error::last_os_error()); + } + + if SetConsoleMode(handle, curr_mode | flag) == 0 { + return Err(io::Error::last_os_error()); + } + return Ok(()); +} + +/// Check if console supports ansi codes (should succeed on Windows 10) +pub fn supports_ansi() -> bool { + conout() + .and_then(|handle| unsafe { set_flag(*handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING) }) + .is_ok() +} + +// This test will only pass if it is running in an actual console, probably +#[test] +fn test_conout() { + assert!(conout().is_ok()) +} + +#[rustversion::before(1.36)] +unsafe fn get_console_screen_buffer_info(handle: HANDLE) -> io::Result<CONSOLE_SCREEN_BUFFER_INFO> { + let mut buffer_info = ::std::mem::uninitialized(); + if GetConsoleScreenBufferInfo(handle, &mut buffer_info) == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(buffer_info) + } +} +#[rustversion::since(1.36)] +unsafe fn get_console_screen_buffer_info(handle: HANDLE) -> io::Result<CONSOLE_SCREEN_BUFFER_INFO> { + let mut buffer_info = ::std::mem::MaybeUninit::uninit(); + if GetConsoleScreenBufferInfo(handle, buffer_info.as_mut_ptr()) == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(buffer_info.assume_init()) + } +} + +// This test will only pass if it is running in an actual console, probably +#[test] +fn test_get_console_screen_buffer_info() { + let handle = conout().unwrap(); + unsafe { + let buffer_info = get_console_screen_buffer_info(*handle); + assert!(buffer_info.is_ok()); + } +} + +impl WinConsoleInfo { + /// Returns `Err` whenever console info cannot be retrieved for some + /// reason. + pub fn from_env() -> io::Result<WinConsoleInfo> { + let fg; + let bg; + let handle = conout()?; + unsafe { + let buffer_info = get_console_screen_buffer_info(*handle)?; + fg = bits_to_color(buffer_info.wAttributes); + bg = bits_to_color(buffer_info.wAttributes >> 4); + } + Ok(WinConsoleInfo { + def_foreground: fg, + def_background: bg, + foreground: fg, + background: bg, + reverse: false, + secure: false, + standout: false, + }) + } +} + +impl<T: Write + Send> WinConsole<T> { + fn apply(&mut self) -> io::Result<()> { + let out = conout()?; + let _unused = self.buf.flush(); + + let (mut fg, bg) = if self.info.reverse { + (self.info.background, self.info.foreground) + } else { + (self.info.foreground, self.info.background) + }; + + if self.info.secure { + fg = bg; + } + + let mut accum: WORD = 0; + + accum |= color_to_bits(fg); + accum |= color_to_bits(bg) << 4; + + if self.info.standout { + accum |= BACKGROUND_INTENSITY; + } else { + accum &= BACKGROUND_INTENSITY ^ 0xFF; + } + + unsafe { + SetConsoleTextAttribute(*out, accum); + } + Ok(()) + } + + /// Create a new WinConsole with the given WinConsoleInfo and out + pub fn new_with_consoleinfo(out: T, info: WinConsoleInfo) -> WinConsole<T> { + WinConsole { buf: out, info } + } + + /// Returns `Err` whenever the terminal cannot be created for some + /// reason. + pub fn new(out: T) -> io::Result<WinConsole<T>> { + let info = WinConsoleInfo::from_env()?; + Ok(Self::new_with_consoleinfo(out, info)) + } +} + +impl<T: Write> Write for WinConsole<T> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.buf.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.flush() + } +} + +impl<T: Write + Send> Terminal for WinConsole<T> { + type Output = T; + + fn fg(&mut self, color: color::Color) -> Result<()> { + self.info.foreground = color; + self.apply()?; + + Ok(()) + } + + fn bg(&mut self, color: color::Color) -> Result<()> { + self.info.background = color; + self.apply()?; + + Ok(()) + } + + fn attr(&mut self, attr: Attr) -> Result<()> { + match attr { + Attr::ForegroundColor(f) => { + self.info.foreground = f; + self.apply()?; + Ok(()) + } + Attr::BackgroundColor(b) => { + self.info.background = b; + self.apply()?; + Ok(()) + } + Attr::Reverse => { + self.info.reverse = true; + self.apply()?; + Ok(()) + } + Attr::Secure => { + self.info.secure = true; + self.apply()?; + Ok(()) + } + Attr::Standout(v) => { + self.info.standout = v; + self.apply()?; + Ok(()) + } + _ => Err(Error::NotSupported), + } + } + + fn supports_attr(&self, attr: Attr) -> bool { + match attr { + Attr::ForegroundColor(_) + | Attr::BackgroundColor(_) + | Attr::Standout(_) + | Attr::Reverse + | Attr::Secure => true, + _ => false, + } + } + + fn reset(&mut self) -> Result<()> { + self.info.foreground = self.info.def_foreground; + self.info.background = self.info.def_background; + self.info.reverse = false; + self.info.secure = false; + self.info.standout = false; + self.apply()?; + + Ok(()) + } + + fn supports_reset(&self) -> bool { + true + } + + fn supports_color(&self) -> bool { + true + } + + fn cursor_up(&mut self) -> Result<()> { + let _unused = self.buf.flush(); + let handle = conout()?; + unsafe { + let buffer_info = get_console_screen_buffer_info(*handle)?; + let (x, y) = ( + buffer_info.dwCursorPosition.X, + buffer_info.dwCursorPosition.Y, + ); + if y == 0 { + // Even though this might want to be a CursorPositionInvalid, on Unix there + // is no checking to see if the cursor is already on the first line. + // I'm not sure what the ideal behavior is, but I think it'd be silly to have + // cursor_up fail in this case. + Ok(()) + } else { + let pos = COORD { X: x, Y: y - 1 }; + if SetConsoleCursorPosition(*handle, pos) != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } + } + } + } + + fn delete_line(&mut self) -> Result<()> { + let _unused = self.buf.flush(); + let handle = conout()?; + unsafe { + let buffer_info = get_console_screen_buffer_info(*handle)?; + let pos = buffer_info.dwCursorPosition; + let size = buffer_info.dwSize; + let num = (size.X - pos.X) as DWORD; + let mut written = 0; + // 0x0020u16 is ' ' (space) in UTF-16 (same as ascii) + if FillConsoleOutputCharacterW(*handle, 0x0020, num, pos, &mut written) == 0 { + return Err(io::Error::last_os_error().into()); + } + if FillConsoleOutputAttribute(*handle, 0, num, pos, &mut written) == 0 { + return Err(io::Error::last_os_error().into()); + } + // Similar reasoning for not failing as in cursor_up -- it doesn't even make + // sense to + // me that these APIs could have written 0, unless the terminal is width zero. + Ok(()) + } + } + + fn carriage_return(&mut self) -> Result<()> { + let _unused = self.buf.flush(); + let handle = conout()?; + unsafe { + let buffer_info = get_console_screen_buffer_info(*handle)?; + let COORD { X: x, Y: y } = buffer_info.dwCursorPosition; + if x == 0 { + Err(Error::CursorDestinationInvalid) + } else { + let pos = COORD { X: 0, Y: y }; + if SetConsoleCursorPosition(*handle, pos) != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } + } + } + } + + fn get_ref<'a>(&'a self) -> &'a T { + &self.buf + } + + fn get_mut<'a>(&'a mut self) -> &'a mut T { + &mut self.buf + } + + fn into_inner(self) -> T + where + Self: Sized, + { + self.buf + } +} |