]> git.lizzy.rs Git - rust.git/commitdiff
Move `library/term/src` to `library/test/src/term/`.
authorCharles Lew <crlf0710@gmail.com>
Sun, 18 Jul 2021 06:42:14 +0000 (14:42 +0800)
committerCharles Lew <crlf0710@gmail.com>
Sun, 18 Jul 2021 12:49:18 +0000 (20:49 +0800)
20 files changed:
library/term/Cargo.toml [deleted file]
library/term/src/lib.rs [deleted file]
library/term/src/terminfo/mod.rs [deleted file]
library/term/src/terminfo/parm.rs [deleted file]
library/term/src/terminfo/parm/tests.rs [deleted file]
library/term/src/terminfo/parser/compiled.rs [deleted file]
library/term/src/terminfo/parser/compiled/tests.rs [deleted file]
library/term/src/terminfo/searcher.rs [deleted file]
library/term/src/terminfo/searcher/tests.rs [deleted file]
library/term/src/win.rs [deleted file]
library/test/Cargo.toml
library/test/src/term/mod.rs [new file with mode: 0644]
library/test/src/term/terminfo/mod.rs [new file with mode: 0644]
library/test/src/term/terminfo/parm.rs [new file with mode: 0644]
library/test/src/term/terminfo/parm/tests.rs [new file with mode: 0644]
library/test/src/term/terminfo/parser/compiled.rs [new file with mode: 0644]
library/test/src/term/terminfo/parser/compiled/tests.rs [new file with mode: 0644]
library/test/src/term/terminfo/searcher.rs [new file with mode: 0644]
library/test/src/term/terminfo/searcher/tests.rs [new file with mode: 0644]
library/test/src/term/win.rs [new file with mode: 0644]

diff --git a/library/term/Cargo.toml b/library/term/Cargo.toml
deleted file mode 100644 (file)
index ddf85b5..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-[package]
-authors = ["The Rust Project Developers"]
-name = "term"
-version = "0.0.0"
-edition = "2018"
-
-[dependencies]
-core = { path = "../core" }
-std = { path = "../std" }
diff --git a/library/term/src/lib.rs b/library/term/src/lib.rs
deleted file mode 100644 (file)
index 943b276..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-//! 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].
-//!
-//! # Examples
-//!
-//! ```no_run
-//! # #![feature(rustc_private)]
-//! 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();
-//!
-//!     assert!(t.reset().unwrap());
-//! }
-//! ```
-//!
-//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
-//! [win]: https://docs.microsoft.com/en-us/windows/console/character-mode-applications
-//! [ti]: https://en.wikipedia.org/wiki/Terminfo
-
-#![doc(html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))]
-#![deny(missing_docs)]
-#![cfg_attr(windows, feature(libc))]
-
-use std::io::prelude::*;
-use std::io::{self, Stderr, Stdout};
-
-pub use terminfo::TerminfoTerminal;
-#[cfg(windows)]
-pub use win::WinConsole;
-
-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))]
-/// Returns 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)]
-/// Returns 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))]
-/// Returns 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)]
-/// Returns 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, 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),
-}
-
-/// 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(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
-    /// if there was an I/O error.
-    fn fg(&mut self, color: color::Color) -> io::Result<bool>;
-
-    /// 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(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
-    /// if there was an I/O error.
-    fn bg(&mut self, color: color::Color) -> io::Result<bool>;
-
-    /// Sets the given terminal attribute, if supported. Returns `Ok(true)`
-    /// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
-    /// there was an I/O error.
-    fn attr(&mut self, attr: Attr) -> io::Result<bool>;
-
-    /// Returns `true` if the given terminal attribute is supported.
-    fn supports_attr(&self, attr: Attr) -> bool;
-
-    /// Resets all terminal attributes and colors to their defaults.
-    ///
-    /// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
-    /// was an I/O 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) -> io::Result<bool>;
-
-    /// 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/library/term/src/terminfo/mod.rs b/library/term/src/terminfo/mod.rs
deleted file mode 100644 (file)
index fec59aa..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-//! Terminfo database interface.
-
-use std::collections::HashMap;
-use std::env;
-use std::error;
-use std::fmt;
-use std::fs::File;
-use std::io::{self, prelude::*, BufReader};
-use std::path::Path;
-
-use crate::color;
-use crate::Attr;
-use crate::Terminal;
-
-use parm::{expand, Param, Variables};
-use parser::compiled::{msys_terminfo, parse};
-use searcher::get_dbpath_for_term;
-
-/// A parsed terminfo database entry.
-#[derive(Debug)]
-pub struct TermInfo {
-    /// Names for the terminal
-    pub names: Vec<String>,
-    /// Map of capability name to boolean value
-    pub bools: HashMap<String, bool>,
-    /// Map of capability name to numeric value
-    pub numbers: HashMap<String, u32>,
-    /// Map of capability name to raw (unexpanded) string
-    pub strings: HashMap<String, Vec<u8>>,
-}
-
-/// A terminfo creation error.
-#[derive(Debug)]
-pub enum Error {
-    /// TermUnset Indicates that the environment doesn't include enough information to find
-    /// the terminfo entry.
-    TermUnset,
-    /// MalformedTerminfo indicates that parsing the terminfo entry failed.
-    MalformedTerminfo(String),
-    /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
-    IoError(io::Error),
-}
-
-impl error::Error for Error {
-    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
-        use Error::*;
-        match self {
-            IoError(e) => Some(e),
-            _ => None,
-        }
-    }
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use Error::*;
-        match *self {
-            TermUnset => Ok(()),
-            MalformedTerminfo(ref e) => e.fmt(f),
-            IoError(ref e) => e.fmt(f),
-        }
-    }
-}
-
-impl TermInfo {
-    /// Creates a TermInfo based on current environment.
-    pub fn from_env() -> Result<TermInfo, Error> {
-        let term = match env::var("TERM") {
-            Ok(name) => TermInfo::from_name(&name),
-            Err(..) => return Err(Error::TermUnset),
-        };
-
-        if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
-            // msys terminal
-            Ok(msys_terminfo())
-        } else {
-            term
-        }
-    }
-
-    /// Creates a TermInfo for the named terminal.
-    pub fn from_name(name: &str) -> Result<TermInfo, Error> {
-        get_dbpath_for_term(name)
-            .ok_or_else(|| {
-                Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
-            })
-            .and_then(|p| TermInfo::from_path(&(*p)))
-    }
-
-    /// Parse the given TermInfo.
-    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
-        Self::_from_path(path.as_ref())
-    }
-    // Keep the metadata small
-    fn _from_path(path: &Path) -> Result<TermInfo, Error> {
-        let file = File::open(path).map_err(Error::IoError)?;
-        let mut reader = BufReader::new(file);
-        parse(&mut reader, false).map_err(Error::MalformedTerminfo)
-    }
-}
-
-pub mod searcher;
-
-/// TermInfo format parsing.
-pub mod parser {
-    //! ncurses-compatible compiled terminfo format parsing (term(5))
-    pub mod compiled;
-}
-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.
-pub struct TerminfoTerminal<T> {
-    num_colors: u32,
-    out: T,
-    ti: TermInfo,
-}
-
-impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
-    type Output = T;
-    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
-        let color = self.dim_if_necessary(color);
-        if self.num_colors > color {
-            return self.apply_cap("setaf", &[Param::Number(color as i32)]);
-        }
-        Ok(false)
-    }
-
-    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
-        let color = self.dim_if_necessary(color);
-        if self.num_colors > color {
-            return self.apply_cap("setab", &[Param::Number(color as i32)]);
-        }
-        Ok(false)
-    }
-
-    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
-        match attr {
-            Attr::ForegroundColor(c) => self.fg(c),
-            Attr::BackgroundColor(c) => self.bg(c),
-            _ => self.apply_cap(cap_for_attr(attr), &[]),
-        }
-    }
-
-    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) -> io::Result<bool> {
-        // are there any terminals that have color/attrs and not sgr0?
-        // Try falling back to sgr, then op
-        let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
-            Some(op) => match expand(&op, &[], &mut Variables::new()) {
-                Ok(cmd) => cmd,
-                Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
-            },
-            None => return Ok(false),
-        };
-        self.out.write_all(&cmd).and(Ok(true))
-    }
-
-    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 + Send> TerminfoTerminal<T> {
-    /// Creates 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, ti: terminfo, num_colors: nc }
-    }
-
-    /// Creates 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 }
-    }
-
-    fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
-        match self.ti.strings.get(cmd) {
-            Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
-                Ok(s) => self.out.write_all(&s).and(Ok(true)),
-                Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
-            },
-            None => Ok(false),
-        }
-    }
-}
-
-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/library/term/src/terminfo/parm.rs b/library/term/src/terminfo/parm.rs
deleted file mode 100644 (file)
index 2e4e917..0000000
+++ /dev/null
@@ -1,559 +0,0 @@
-//! Parameterized string expansion
-
-use self::Param::*;
-use self::States::*;
-
-use std::iter::repeat;
-
-#[cfg(test)]
-mod tests;
-
-#[derive(Clone, Copy, PartialEq)]
-enum States {
-    Nothing,
-    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 {
-    Words(String),
-    Number(i32),
-}
-
-/// Container for static and dynamic variable arrays
-pub struct Variables {
-    /// Static variables A-Z
-    sta_va: [Param; 26],
-    /// Dynamic variables a-z
-    dyn_va: [Param; 26],
-}
-
-impl Variables {
-    /// Returns a new zero-initialized Variables
-    pub fn new() -> Variables {
-        Variables {
-            sta_va: [
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-            ],
-            dyn_va: [
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-                Number(0),
-            ],
-        }
-    }
-}
-
-/// 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>, String> {
-    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 {
-                    output.push(c);
-                }
-            }
-            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("a non-char was used with %c".to_string()),
-                            None => return Err("stack is empty".to_string()),
-                        }
-                    }
-                    '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("a non-str was used with %l".to_string()),
-                        None => return Err("stack is empty".to_string()),
-                    },
-                    '+' | '-' | '/' | '*' | '^' | '&' | '|' | '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!("All cases handled"),
-                            })),
-                            (Some(_), Some(_)) => {
-                                return Err(format!("non-numbers on stack with {}", cur));
-                            }
-                            _ => return Err("stack is empty".to_string()),
-                        }
-                    }
-                    '=' | '>' | '<' | '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!(),
-                            } {
-                                1
-                            } else {
-                                0
-                            },
-                        )),
-                        (Some(_), Some(_)) => {
-                            return Err(format!("non-numbers on stack with {}", cur));
-                        }
-                        _ => return Err("stack is empty".to_string()),
-                    },
-                    '!' | '~' => match stack.pop() {
-                        Some(Number(x)) => stack.push(Number(match cur {
-                            '!' if x > 0 => 0,
-                            '!' => 1,
-                            '~' => !x,
-                            _ => unreachable!(),
-                        })),
-                        Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
-                        None => return Err("stack is empty".to_string()),
-                    },
-                    'i' => match (&mparams[0], &mparams[1]) {
-                        (&Number(x), &Number(y)) => {
-                            mparams[0] = Number(x + 1);
-                            mparams[1] = Number(y + 1);
-                        }
-                        _ => return Err("first two params not numbers with %i".to_string()),
-                    },
-
-                    // printf-style support for %doxXs
-                    'd' | 'o' | 'x' | 'X' | 's' => {
-                        if let Some(arg) = stack.pop() {
-                            let flags = Flags::new();
-                            let res = format(arg, FormatOp::from_char(cur), flags)?;
-                            output.extend(res.iter().cloned());
-                        } else {
-                            return Err("stack is empty".to_string());
-                        }
-                    }
-                    ':' | '#' | ' ' | '.' | '0'..='9' => {
-                        let mut flags = Flags::new();
-                        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!(),
-                        }
-                        state = FormatPattern(flags, fstate);
-                    }
-
-                    // conditionals
-                    '?' => (),
-                    't' => match stack.pop() {
-                        Some(Number(0)) => state = SeekIfElse(0),
-                        Some(Number(_)) => (),
-                        Some(_) => return Err("non-number on stack with conditional".to_string()),
-                        None => return Err("stack is empty".to_string()),
-                    },
-                    'e' => state = SeekIfEnd(0),
-                    ';' => (),
-                    _ => return Err(format!("unrecognized format option {}", cur)),
-                }
-            }
-            PushParam => {
-                // params are 1-indexed
-                stack.push(
-                    mparams[match cur.to_digit(10) {
-                        Some(d) => d as usize - 1,
-                        None => return Err("bad param number".to_string()),
-                    }]
-                    .clone(),
-                );
-            }
-            SetVar => {
-                if cur >= 'A' && cur <= 'Z' {
-                    if let Some(arg) = stack.pop() {
-                        let idx = (cur as u8) - b'A';
-                        vars.sta_va[idx as usize] = arg;
-                    } else {
-                        return Err("stack is empty".to_string());
-                    }
-                } else if cur >= 'a' && cur <= 'z' {
-                    if let Some(arg) = stack.pop() {
-                        let idx = (cur as u8) - b'a';
-                        vars.dyn_va[idx as usize] = arg;
-                    } else {
-                        return Err("stack is empty".to_string());
-                    }
-                } else {
-                    return Err("bad variable name in %P".to_string());
-                }
-            }
-            GetVar => {
-                if cur >= 'A' && cur <= 'Z' {
-                    let idx = (cur as u8) - b'A';
-                    stack.push(vars.sta_va[idx as usize].clone());
-                } else if cur >= 'a' && cur <= 'z' {
-                    let idx = (cur as u8) - b'a';
-                    stack.push(vars.dyn_va[idx as usize].clone());
-                } else {
-                    return Err("bad variable name in %g".to_string());
-                }
-            }
-            CharConstant => {
-                stack.push(Number(c as i32));
-                state = CharClose;
-            }
-            CharClose => {
-                if cur != '\'' {
-                    return Err("malformed character constant".to_string());
-                }
-            }
-            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("int constant too large".to_string()),
-                    }
-                } else {
-                    return Err("bad int constant".to_string());
-                }
-            }
-            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.iter().cloned());
-                            // will cause state to go to Nothing
-                            old_state = FormatPattern(*flags, *fstate);
-                        } else {
-                            return Err("stack is empty".to_string());
-                        }
-                    }
-                    (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::Flags, '.') => {
-                        *fstate = FormatState::Precision;
-                    }
-                    (FormatState::Width, '0'..='9') => {
-                        let old = flags.width;
-                        flags.width = flags.width * 10 + (cur as usize - '0' as usize);
-                        if flags.width < old {
-                            return Err("format width overflow".to_string());
-                        }
-                    }
-                    (FormatState::Width, '.') => {
-                        *fstate = FormatState::Precision;
-                    }
-                    (FormatState::Precision, '0'..='9') => {
-                        let old = flags.precision;
-                        flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
-                        if flags.precision < old {
-                            return Err("format precision overflow".to_string());
-                        }
-                    }
-                    _ => return Err("invalid format specifier".to_string()),
-                }
-            }
-            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)]
-struct Flags {
-    width: usize,
-    precision: usize,
-    alternate: bool,
-    left: bool,
-    sign: bool,
-    space: bool,
-}
-
-impl Flags {
-    fn new() -> Flags {
-        Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
-    }
-}
-
-#[derive(Copy, Clone)]
-enum FormatOp {
-    Digit,
-    Octal,
-    LowerHex,
-    UpperHex,
-    String,
-}
-
-impl FormatOp {
-    fn from_char(c: char) -> FormatOp {
-        match c {
-            'd' => FormatOp::Digit,
-            'o' => FormatOp::Octal,
-            'x' => FormatOp::LowerHex,
-            'X' => FormatOp::UpperHex,
-            's' => FormatOp::String,
-            _ => panic!("bad FormatOp char"),
-        }
-    }
-    fn to_char(self) -> char {
-        match self {
-            FormatOp::Digit => 'd',
-            FormatOp::Octal => 'o',
-            FormatOp::LowerHex => 'x',
-            FormatOp::UpperHex => 'X',
-            FormatOp::String => 's',
-        }
-    }
-}
-
-fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
-    let mut s = match val {
-        Number(d) => {
-            match op {
-                FormatOp::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)
-                    }
-                }
-                FormatOp::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)
-                    }
-                }
-                FormatOp::LowerHex => {
-                    if flags.alternate && d != 0 {
-                        format!("0x{:01$x}", d, flags.precision)
-                    } else {
-                        format!("{:01$x}", d, flags.precision)
-                    }
-                }
-                FormatOp::UpperHex => {
-                    if flags.alternate && d != 0 {
-                        format!("0X{:01$X}", d, flags.precision)
-                    } else {
-                        format!("{:01$X}", d, flags.precision)
-                    }
-                }
-                FormatOp::String => return Err("non-number on stack with %s".to_string()),
-            }
-            .into_bytes()
-        }
-        Words(s) => match op {
-            FormatOp::String => {
-                let mut s = s.into_bytes();
-                if flags.precision > 0 && flags.precision < s.len() {
-                    s.truncate(flags.precision);
-                }
-                s
-            }
-            _ => return Err(format!("non-string on stack with %{}", op.to_char())),
-        },
-    };
-    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)
-}
diff --git a/library/term/src/terminfo/parm/tests.rs b/library/term/src/terminfo/parm/tests.rs
deleted file mode 100644 (file)
index 1cc0967..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-use super::*;
-
-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>, String> {
-        let mut u8v: Vec<_> = fmt.bytes().collect();
-        u8v.extend(cap.as_bytes().iter().map(|&b| b));
-        expand(&u8v, params, vars)
-    }
-
-    let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
-    for &cap in caps.iter() {
-        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_string()) } else { Number(97) };
-        let res = get_res("%p1", cap, &[p], vars);
-        assert!(res.is_ok(), "Op {} failed with 1 stack entry: {}", cap, res.unwrap_err());
-    }
-    let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
-    for &cap in caps.iter() {
-        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.unwrap_err());
-    }
-}
-
-#[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.iter() {
-        let s = format!("%{{1}}%{{2}}%{}%d", op);
-        let res = expand(s.as_bytes(), &[], &mut Variables::new());
-        assert!(res.is_ok(), "{}", res.unwrap_err());
-        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.unwrap_err());
-        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.unwrap_err());
-        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.unwrap_err());
-    assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>());
-    let res = expand(s, &[Number(8)], &mut vars);
-    assert!(res.is_ok(), "{}", res.unwrap_err());
-    assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
-    let res = expand(s, &[Number(42)], &mut vars);
-    assert!(res.is_ok(), "{}", res.unwrap_err());
-    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_string()),
-                Words("foo".to_string()),
-                Words("f".to_string()),
-                Words("foo".to_string())
-            ],
-            vars
-        ),
-        Ok("foofoo ffo".bytes().collect::<Vec<_>>())
-    );
-    assert_eq!(
-        expand(b"%p1%:-4.2s", &[Words("foo".to_string())], 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/library/term/src/terminfo/parser/compiled.rs b/library/term/src/terminfo/parser/compiled.rs
deleted file mode 100644 (file)
index fbc5aeb..0000000
+++ /dev/null
@@ -1,336 +0,0 @@
-#![allow(non_upper_case_globals, missing_docs)]
-
-//! ncurses-compatible compiled terminfo format parsing (term(5))
-
-use super::super::TermInfo;
-use std::collections::HashMap;
-use std::io;
-use std::io::prelude::*;
-
-#[cfg(test)]
-mod tests;
-
-// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
-
-#[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"];
-
-#[rustfmt::skip]
-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"];
-
-#[rustfmt::skip]
-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"];
-
-#[rustfmt::skip]
-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"];
-
-#[rustfmt::skip]
-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"];
-
-#[rustfmt::skip]
-pub static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
-    "_", "_", "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", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
-    "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
-    "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
-    "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
-    "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
-    "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
-    "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
-    "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "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", "_", "_",
-    "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
-    "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
-    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
-    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
-    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
-    "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
-    "_", "_", "_", "_", "_", "_", "_", "_", "_", "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"];
-
-fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
-    let mut b = [0; 2];
-    r.read_exact(&mut b)?;
-    Ok((b[0] as u16) | ((b[1] as u16) << 8))
-}
-
-fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> {
-    let mut b = [0; 4];
-    r.read_exact(&mut b)?;
-    Ok((b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24))
-}
-
-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, String> {
-    macro_rules! t( ($e:expr) => (
-        match $e {
-            Ok(e) => e,
-            Err(e) => return Err(e.to_string())
-        }
-    ) );
-
-    let (bnames, snames, nnames) = if longnames {
-        (boolfnames, stringfnames, numfnames)
-    } else {
-        (boolnames, stringnames, numnames)
-    };
-
-    // Check magic number
-    let magic = t!(read_le_u16(file));
-
-    let extended = match magic {
-        0o0432 => false,
-        0o01036 => true,
-        _ => return Err(format!("invalid magic number, found {:o}", magic)),
-    };
-
-    // 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 t!(read_le_u16(file)) as i16 {
-                n if n >= 0 => n as usize,
-                -1 => 0,
-                _ => return Err("incompatible file: length fields must be  >= -1".to_string()),
-            }
-        }};
-    }
-
-    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("incompatible file: names field must be at least 1 byte wide".to_string());
-    }
-
-    if bools_bytes > boolnames.len() {
-        return Err("incompatible file: more booleans than expected".to_string());
-    }
-
-    if numbers_count > numnames.len() {
-        return Err("incompatible file: more numbers than expected".to_string());
-    }
-
-    if string_offsets_count > stringnames.len() {
-        return Err("incompatible file: more string offsets than expected".to_string());
-    }
-
-    // don't read NUL
-    let mut bytes = Vec::new();
-    t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
-    let names_str = match String::from_utf8(bytes) {
-        Ok(s) => s,
-        Err(_) => return Err("input not utf-8".to_string()),
-    };
-
-    let term_names: Vec<String> = names_str.split('|').map(|s| s.to_string()).collect();
-    // consume NUL
-    if t!(read_byte(file)) != b'\0' {
-        return Err("incompatible file: missing null terminator for names section".to_string());
-    }
-
-    let bools_map: HashMap<String, bool> = t! {
-        (0..bools_bytes).filter_map(|i| match read_byte(file) {
-            Err(e) => Some(Err(e)),
-            Ok(1) => Some(Ok((bnames[i].to_string(), true))),
-            Ok(_) => None
-        }).collect()
-    };
-
-    if (bools_bytes + names_bytes) % 2 == 1 {
-        t!(read_byte(file)); // compensate for padding
-    }
-
-    let numbers_map: HashMap<String, u32> = t! {
-        (0..numbers_count).filter_map(|i| {
-            let number = if extended { read_le_u32(file) } else { read_le_u16(file).map(Into::into) };
-
-            match number {
-                Ok(0xFFFF) => None,
-                Ok(n) => Some(Ok((nnames[i].to_string(), n))),
-                Err(e) => Some(Err(e))
-            }
-        }).collect()
-    };
-
-    let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
-        let string_offsets: Vec<u16> =
-            t!((0..string_offsets_count).map(|_| read_le_u16(file)).collect());
-
-        let mut string_table = Vec::new();
-        t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
-
-        t!(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.to_string(), 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.to_string(), string_table[offset..offset + len].to_vec()))
-                    }
-                    None => Err("invalid file: missing NUL in string_table".to_string()),
-                }
-            })
-            .collect())
-    } 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 })
-}
-
-/// Creates a dummy TermInfo struct for msys terminals
-pub fn msys_terminfo() -> TermInfo {
-    let mut strings = HashMap::new();
-    strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
-    strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
-    strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
-    strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
-
-    let mut numbers = HashMap::new();
-    numbers.insert("colors".to_string(), 8);
-
-    TermInfo {
-        names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
-        bools: HashMap::new(),
-        numbers,
-        strings,
-    }
-}
diff --git a/library/term/src/terminfo/parser/compiled/tests.rs b/library/term/src/terminfo/parser/compiled/tests.rs
deleted file mode 100644 (file)
index 8a9187b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-use super::*;
-
-#[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/library/term/src/terminfo/searcher.rs b/library/term/src/terminfo/searcher.rs
deleted file mode 100644 (file)
index 5499e24..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-//! ncurses-compatible database discovery.
-//!
-//! Does not support hashed database, only filesystem!
-
-use std::env;
-use std::fs;
-use std::path::PathBuf;
-
-#[cfg(test)]
-mod tests;
-
-/// Return path to database entry for `term`
-#[allow(deprecated)]
-pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
-    let mut dirs_to_search = Vec::new();
-    let first_char = term.chars().next()?;
-
-    // Find search directory
-    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) = env::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., macOS)
-            p.push(&format!("{:x}", first_char as usize));
-            p.push(term);
-            if fs::metadata(&p).is_ok() {
-                return Some(p);
-            }
-        }
-    }
-    None
-}
diff --git a/library/term/src/terminfo/searcher/tests.rs b/library/term/src/terminfo/searcher/tests.rs
deleted file mode 100644 (file)
index 4227a58..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-use super::*;
-
-#[test]
-#[ignore = "buildbots don't have ncurses installed and I can't mock everything I need"]
-fn test_get_dbpath_for_term() {
-    // woefully inadequate test coverage
-    // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's)
-    use std::env;
-    // FIXME (#9639): This needs to handle non-utf8 paths
-    fn x(t: &str) -> String {
-        let p = get_dbpath_for_term(t).expect("no terminfo entry found");
-        p.to_str().unwrap().to_string()
-    }
-    assert!(x("screen") == "/usr/share/terminfo/s/screen");
-    assert!(get_dbpath_for_term("") == None);
-    env::set_var("TERMINFO_DIRS", ":");
-    assert!(x("screen") == "/usr/share/terminfo/s/screen");
-    env::remove_var("TERMINFO_DIRS");
-}
diff --git a/library/term/src/win.rs b/library/term/src/win.rs
deleted file mode 100644 (file)
index c24cf95..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-//! Windows console handling
-
-// FIXME (#13400): this is only a tiny fraction of the Windows console api
-
-use std::io;
-use std::io::prelude::*;
-
-use crate::color;
-use crate::Attr;
-use crate::Terminal;
-
-/// A Terminal implementation that uses the Win32 Console API.
-pub struct WinConsole<T> {
-    buf: T,
-    def_foreground: color::Color,
-    def_background: color::Color,
-    foreground: color::Color,
-    background: color::Color,
-}
-
-type SHORT = i16;
-type WORD = u16;
-type DWORD = u32;
-type BOOL = i32;
-type HANDLE = *mut u8;
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct SMALL_RECT {
-    Left: SHORT,
-    Top: SHORT,
-    Right: SHORT,
-    Bottom: SHORT,
-}
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct COORD {
-    X: SHORT,
-    Y: SHORT,
-}
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct CONSOLE_SCREEN_BUFFER_INFO {
-    dwSize: COORD,
-    dwCursorPosition: COORD,
-    wAttributes: WORD,
-    srWindow: SMALL_RECT,
-    dwMaximumWindowSize: COORD,
-}
-
-#[allow(non_snake_case)]
-#[link(name = "kernel32")]
-extern "system" {
-    fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
-    fn GetStdHandle(which: DWORD) -> HANDLE;
-    fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
-}
-
-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 | (u32::from(bits) & 0x8) // copy the hi-intensity bit
-}
-
-impl<T: Write + Send + 'static> WinConsole<T> {
-    fn apply(&mut self) {
-        let _unused = self.buf.flush();
-        let mut accum: WORD = 0;
-        accum |= color_to_bits(self.foreground);
-        accum |= color_to_bits(self.background) << 4;
-
-        unsafe {
-            // Magic -11 means stdout, from
-            // https://docs.microsoft.com/en-us/windows/console/getstdhandle
-            //
-            // You may be wondering, "but what about stderr?", and the answer
-            // to that is that setting terminal attributes on the stdout
-            // handle also sets them for stderr, since they go to the same
-            // terminal! Admittedly, this is fragile, since stderr could be
-            // redirected to a different console. This is good enough for
-            // rustc though. See #13400.
-            let out = GetStdHandle(-11i32 as DWORD);
-            SetConsoleTextAttribute(out, accum);
-        }
-    }
-
-    /// Returns `None` whenever the terminal cannot be created for some reason.
-    pub fn new(out: T) -> io::Result<WinConsole<T>> {
-        use std::mem::MaybeUninit;
-
-        let fg;
-        let bg;
-        unsafe {
-            let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
-            if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), buffer_info.as_mut_ptr())
-                != 0
-            {
-                let buffer_info = buffer_info.assume_init();
-                fg = bits_to_color(buffer_info.wAttributes);
-                bg = bits_to_color(buffer_info.wAttributes >> 4);
-            } else {
-                fg = color::WHITE;
-                bg = color::BLACK;
-            }
-        }
-        Ok(WinConsole {
-            buf: out,
-            def_foreground: fg,
-            def_background: bg,
-            foreground: fg,
-            background: bg,
-        })
-    }
-}
-
-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 + 'static> Terminal for WinConsole<T> {
-    type Output = T;
-
-    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
-        self.foreground = color;
-        self.apply();
-
-        Ok(true)
-    }
-
-    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
-        self.background = color;
-        self.apply();
-
-        Ok(true)
-    }
-
-    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
-        match attr {
-            Attr::ForegroundColor(f) => {
-                self.foreground = f;
-                self.apply();
-                Ok(true)
-            }
-            Attr::BackgroundColor(b) => {
-                self.background = b;
-                self.apply();
-                Ok(true)
-            }
-            _ => Ok(false),
-        }
-    }
-
-    fn supports_attr(&self, attr: Attr) -> bool {
-        // it claims support for underscore and reverse video, but I can't get
-        // it to do anything -cmr
-        match attr {
-            Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
-            _ => false,
-        }
-    }
-
-    fn reset(&mut self) -> io::Result<bool> {
-        self.foreground = self.def_foreground;
-        self.background = self.def_background;
-        self.apply();
-
-        Ok(true)
-    }
-
-    fn get_ref(&self) -> &T {
-        &self.buf
-    }
-
-    fn get_mut(&mut self) -> &mut T {
-        &mut self.buf
-    }
-
-    fn into_inner(self) -> T
-    where
-        Self: Sized,
-    {
-        self.buf
-    }
-}
index 226557430df210a23a1b24d309aa81a8d316ab41..479d86354c04cd24f84bcb938183e6630c31bd7a 100644 (file)
@@ -10,7 +10,6 @@ crate-type = ["dylib", "rlib"]
 [dependencies]
 cfg-if = { version = "0.1.8", features = ['rustc-dep-of-std'] }
 getopts = { version = "0.2.21", features = ['rustc-dep-of-std'] }
-term = { path = "../term" }
 std = { path = "../std" }
 core = { path = "../core" }
 libc = { version = "0.2", default-features = false }
diff --git a/library/test/src/term/mod.rs b/library/test/src/term/mod.rs
new file mode 100644 (file)
index 0000000..943b276
--- /dev/null
@@ -0,0 +1,194 @@
+//! 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].
+//!
+//! # Examples
+//!
+//! ```no_run
+//! # #![feature(rustc_private)]
+//! 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();
+//!
+//!     assert!(t.reset().unwrap());
+//! }
+//! ```
+//!
+//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
+//! [win]: https://docs.microsoft.com/en-us/windows/console/character-mode-applications
+//! [ti]: https://en.wikipedia.org/wiki/Terminfo
+
+#![doc(html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))]
+#![deny(missing_docs)]
+#![cfg_attr(windows, feature(libc))]
+
+use std::io::prelude::*;
+use std::io::{self, Stderr, Stdout};
+
+pub use terminfo::TerminfoTerminal;
+#[cfg(windows)]
+pub use win::WinConsole;
+
+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))]
+/// Returns 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)]
+/// Returns 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))]
+/// Returns 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)]
+/// Returns 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, 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),
+}
+
+/// 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(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
+    /// if there was an I/O error.
+    fn fg(&mut self, color: color::Color) -> io::Result<bool>;
+
+    /// 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(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
+    /// if there was an I/O error.
+    fn bg(&mut self, color: color::Color) -> io::Result<bool>;
+
+    /// Sets the given terminal attribute, if supported. Returns `Ok(true)`
+    /// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
+    /// there was an I/O error.
+    fn attr(&mut self, attr: Attr) -> io::Result<bool>;
+
+    /// Returns `true` if the given terminal attribute is supported.
+    fn supports_attr(&self, attr: Attr) -> bool;
+
+    /// Resets all terminal attributes and colors to their defaults.
+    ///
+    /// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
+    /// was an I/O 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) -> io::Result<bool>;
+
+    /// 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/library/test/src/term/terminfo/mod.rs b/library/test/src/term/terminfo/mod.rs
new file mode 100644 (file)
index 0000000..fec59aa
--- /dev/null
@@ -0,0 +1,245 @@
+//! Terminfo database interface.
+
+use std::collections::HashMap;
+use std::env;
+use std::error;
+use std::fmt;
+use std::fs::File;
+use std::io::{self, prelude::*, BufReader};
+use std::path::Path;
+
+use crate::color;
+use crate::Attr;
+use crate::Terminal;
+
+use parm::{expand, Param, Variables};
+use parser::compiled::{msys_terminfo, parse};
+use searcher::get_dbpath_for_term;
+
+/// A parsed terminfo database entry.
+#[derive(Debug)]
+pub struct TermInfo {
+    /// Names for the terminal
+    pub names: Vec<String>,
+    /// Map of capability name to boolean value
+    pub bools: HashMap<String, bool>,
+    /// Map of capability name to numeric value
+    pub numbers: HashMap<String, u32>,
+    /// Map of capability name to raw (unexpanded) string
+    pub strings: HashMap<String, Vec<u8>>,
+}
+
+/// A terminfo creation error.
+#[derive(Debug)]
+pub enum Error {
+    /// TermUnset Indicates that the environment doesn't include enough information to find
+    /// the terminfo entry.
+    TermUnset,
+    /// MalformedTerminfo indicates that parsing the terminfo entry failed.
+    MalformedTerminfo(String),
+    /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
+    IoError(io::Error),
+}
+
+impl error::Error for Error {
+    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+        use Error::*;
+        match self {
+            IoError(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use Error::*;
+        match *self {
+            TermUnset => Ok(()),
+            MalformedTerminfo(ref e) => e.fmt(f),
+            IoError(ref e) => e.fmt(f),
+        }
+    }
+}
+
+impl TermInfo {
+    /// Creates a TermInfo based on current environment.
+    pub fn from_env() -> Result<TermInfo, Error> {
+        let term = match env::var("TERM") {
+            Ok(name) => TermInfo::from_name(&name),
+            Err(..) => return Err(Error::TermUnset),
+        };
+
+        if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
+            // msys terminal
+            Ok(msys_terminfo())
+        } else {
+            term
+        }
+    }
+
+    /// Creates a TermInfo for the named terminal.
+    pub fn from_name(name: &str) -> Result<TermInfo, Error> {
+        get_dbpath_for_term(name)
+            .ok_or_else(|| {
+                Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
+            })
+            .and_then(|p| TermInfo::from_path(&(*p)))
+    }
+
+    /// Parse the given TermInfo.
+    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
+        Self::_from_path(path.as_ref())
+    }
+    // Keep the metadata small
+    fn _from_path(path: &Path) -> Result<TermInfo, Error> {
+        let file = File::open(path).map_err(Error::IoError)?;
+        let mut reader = BufReader::new(file);
+        parse(&mut reader, false).map_err(Error::MalformedTerminfo)
+    }
+}
+
+pub mod searcher;
+
+/// TermInfo format parsing.
+pub mod parser {
+    //! ncurses-compatible compiled terminfo format parsing (term(5))
+    pub mod compiled;
+}
+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.
+pub struct TerminfoTerminal<T> {
+    num_colors: u32,
+    out: T,
+    ti: TermInfo,
+}
+
+impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
+    type Output = T;
+    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+        let color = self.dim_if_necessary(color);
+        if self.num_colors > color {
+            return self.apply_cap("setaf", &[Param::Number(color as i32)]);
+        }
+        Ok(false)
+    }
+
+    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
+        let color = self.dim_if_necessary(color);
+        if self.num_colors > color {
+            return self.apply_cap("setab", &[Param::Number(color as i32)]);
+        }
+        Ok(false)
+    }
+
+    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
+        match attr {
+            Attr::ForegroundColor(c) => self.fg(c),
+            Attr::BackgroundColor(c) => self.bg(c),
+            _ => self.apply_cap(cap_for_attr(attr), &[]),
+        }
+    }
+
+    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) -> io::Result<bool> {
+        // are there any terminals that have color/attrs and not sgr0?
+        // Try falling back to sgr, then op
+        let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
+            Some(op) => match expand(&op, &[], &mut Variables::new()) {
+                Ok(cmd) => cmd,
+                Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
+            },
+            None => return Ok(false),
+        };
+        self.out.write_all(&cmd).and(Ok(true))
+    }
+
+    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 + Send> TerminfoTerminal<T> {
+    /// Creates 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, ti: terminfo, num_colors: nc }
+    }
+
+    /// Creates 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 }
+    }
+
+    fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
+        match self.ti.strings.get(cmd) {
+            Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
+                Ok(s) => self.out.write_all(&s).and(Ok(true)),
+                Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
+            },
+            None => Ok(false),
+        }
+    }
+}
+
+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/library/test/src/term/terminfo/parm.rs b/library/test/src/term/terminfo/parm.rs
new file mode 100644 (file)
index 0000000..2e4e917
--- /dev/null
@@ -0,0 +1,559 @@
+//! Parameterized string expansion
+
+use self::Param::*;
+use self::States::*;
+
+use std::iter::repeat;
+
+#[cfg(test)]
+mod tests;
+
+#[derive(Clone, Copy, PartialEq)]
+enum States {
+    Nothing,
+    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 {
+    Words(String),
+    Number(i32),
+}
+
+/// Container for static and dynamic variable arrays
+pub struct Variables {
+    /// Static variables A-Z
+    sta_va: [Param; 26],
+    /// Dynamic variables a-z
+    dyn_va: [Param; 26],
+}
+
+impl Variables {
+    /// Returns a new zero-initialized Variables
+    pub fn new() -> Variables {
+        Variables {
+            sta_va: [
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+            ],
+            dyn_va: [
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+                Number(0),
+            ],
+        }
+    }
+}
+
+/// 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>, String> {
+    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 {
+                    output.push(c);
+                }
+            }
+            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("a non-char was used with %c".to_string()),
+                            None => return Err("stack is empty".to_string()),
+                        }
+                    }
+                    '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("a non-str was used with %l".to_string()),
+                        None => return Err("stack is empty".to_string()),
+                    },
+                    '+' | '-' | '/' | '*' | '^' | '&' | '|' | '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!("All cases handled"),
+                            })),
+                            (Some(_), Some(_)) => {
+                                return Err(format!("non-numbers on stack with {}", cur));
+                            }
+                            _ => return Err("stack is empty".to_string()),
+                        }
+                    }
+                    '=' | '>' | '<' | '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!(),
+                            } {
+                                1
+                            } else {
+                                0
+                            },
+                        )),
+                        (Some(_), Some(_)) => {
+                            return Err(format!("non-numbers on stack with {}", cur));
+                        }
+                        _ => return Err("stack is empty".to_string()),
+                    },
+                    '!' | '~' => match stack.pop() {
+                        Some(Number(x)) => stack.push(Number(match cur {
+                            '!' if x > 0 => 0,
+                            '!' => 1,
+                            '~' => !x,
+                            _ => unreachable!(),
+                        })),
+                        Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
+                        None => return Err("stack is empty".to_string()),
+                    },
+                    'i' => match (&mparams[0], &mparams[1]) {
+                        (&Number(x), &Number(y)) => {
+                            mparams[0] = Number(x + 1);
+                            mparams[1] = Number(y + 1);
+                        }
+                        _ => return Err("first two params not numbers with %i".to_string()),
+                    },
+
+                    // printf-style support for %doxXs
+                    'd' | 'o' | 'x' | 'X' | 's' => {
+                        if let Some(arg) = stack.pop() {
+                            let flags = Flags::new();
+                            let res = format(arg, FormatOp::from_char(cur), flags)?;
+                            output.extend(res.iter().cloned());
+                        } else {
+                            return Err("stack is empty".to_string());
+                        }
+                    }
+                    ':' | '#' | ' ' | '.' | '0'..='9' => {
+                        let mut flags = Flags::new();
+                        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!(),
+                        }
+                        state = FormatPattern(flags, fstate);
+                    }
+
+                    // conditionals
+                    '?' => (),
+                    't' => match stack.pop() {
+                        Some(Number(0)) => state = SeekIfElse(0),
+                        Some(Number(_)) => (),
+                        Some(_) => return Err("non-number on stack with conditional".to_string()),
+                        None => return Err("stack is empty".to_string()),
+                    },
+                    'e' => state = SeekIfEnd(0),
+                    ';' => (),
+                    _ => return Err(format!("unrecognized format option {}", cur)),
+                }
+            }
+            PushParam => {
+                // params are 1-indexed
+                stack.push(
+                    mparams[match cur.to_digit(10) {
+                        Some(d) => d as usize - 1,
+                        None => return Err("bad param number".to_string()),
+                    }]
+                    .clone(),
+                );
+            }
+            SetVar => {
+                if cur >= 'A' && cur <= 'Z' {
+                    if let Some(arg) = stack.pop() {
+                        let idx = (cur as u8) - b'A';
+                        vars.sta_va[idx as usize] = arg;
+                    } else {
+                        return Err("stack is empty".to_string());
+                    }
+                } else if cur >= 'a' && cur <= 'z' {
+                    if let Some(arg) = stack.pop() {
+                        let idx = (cur as u8) - b'a';
+                        vars.dyn_va[idx as usize] = arg;
+                    } else {
+                        return Err("stack is empty".to_string());
+                    }
+                } else {
+                    return Err("bad variable name in %P".to_string());
+                }
+            }
+            GetVar => {
+                if cur >= 'A' && cur <= 'Z' {
+                    let idx = (cur as u8) - b'A';
+                    stack.push(vars.sta_va[idx as usize].clone());
+                } else if cur >= 'a' && cur <= 'z' {
+                    let idx = (cur as u8) - b'a';
+                    stack.push(vars.dyn_va[idx as usize].clone());
+                } else {
+                    return Err("bad variable name in %g".to_string());
+                }
+            }
+            CharConstant => {
+                stack.push(Number(c as i32));
+                state = CharClose;
+            }
+            CharClose => {
+                if cur != '\'' {
+                    return Err("malformed character constant".to_string());
+                }
+            }
+            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("int constant too large".to_string()),
+                    }
+                } else {
+                    return Err("bad int constant".to_string());
+                }
+            }
+            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.iter().cloned());
+                            // will cause state to go to Nothing
+                            old_state = FormatPattern(*flags, *fstate);
+                        } else {
+                            return Err("stack is empty".to_string());
+                        }
+                    }
+                    (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::Flags, '.') => {
+                        *fstate = FormatState::Precision;
+                    }
+                    (FormatState::Width, '0'..='9') => {
+                        let old = flags.width;
+                        flags.width = flags.width * 10 + (cur as usize - '0' as usize);
+                        if flags.width < old {
+                            return Err("format width overflow".to_string());
+                        }
+                    }
+                    (FormatState::Width, '.') => {
+                        *fstate = FormatState::Precision;
+                    }
+                    (FormatState::Precision, '0'..='9') => {
+                        let old = flags.precision;
+                        flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
+                        if flags.precision < old {
+                            return Err("format precision overflow".to_string());
+                        }
+                    }
+                    _ => return Err("invalid format specifier".to_string()),
+                }
+            }
+            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)]
+struct Flags {
+    width: usize,
+    precision: usize,
+    alternate: bool,
+    left: bool,
+    sign: bool,
+    space: bool,
+}
+
+impl Flags {
+    fn new() -> Flags {
+        Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
+    }
+}
+
+#[derive(Copy, Clone)]
+enum FormatOp {
+    Digit,
+    Octal,
+    LowerHex,
+    UpperHex,
+    String,
+}
+
+impl FormatOp {
+    fn from_char(c: char) -> FormatOp {
+        match c {
+            'd' => FormatOp::Digit,
+            'o' => FormatOp::Octal,
+            'x' => FormatOp::LowerHex,
+            'X' => FormatOp::UpperHex,
+            's' => FormatOp::String,
+            _ => panic!("bad FormatOp char"),
+        }
+    }
+    fn to_char(self) -> char {
+        match self {
+            FormatOp::Digit => 'd',
+            FormatOp::Octal => 'o',
+            FormatOp::LowerHex => 'x',
+            FormatOp::UpperHex => 'X',
+            FormatOp::String => 's',
+        }
+    }
+}
+
+fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
+    let mut s = match val {
+        Number(d) => {
+            match op {
+                FormatOp::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)
+                    }
+                }
+                FormatOp::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)
+                    }
+                }
+                FormatOp::LowerHex => {
+                    if flags.alternate && d != 0 {
+                        format!("0x{:01$x}", d, flags.precision)
+                    } else {
+                        format!("{:01$x}", d, flags.precision)
+                    }
+                }
+                FormatOp::UpperHex => {
+                    if flags.alternate && d != 0 {
+                        format!("0X{:01$X}", d, flags.precision)
+                    } else {
+                        format!("{:01$X}", d, flags.precision)
+                    }
+                }
+                FormatOp::String => return Err("non-number on stack with %s".to_string()),
+            }
+            .into_bytes()
+        }
+        Words(s) => match op {
+            FormatOp::String => {
+                let mut s = s.into_bytes();
+                if flags.precision > 0 && flags.precision < s.len() {
+                    s.truncate(flags.precision);
+                }
+                s
+            }
+            _ => return Err(format!("non-string on stack with %{}", op.to_char())),
+        },
+    };
+    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)
+}
diff --git a/library/test/src/term/terminfo/parm/tests.rs b/library/test/src/term/terminfo/parm/tests.rs
new file mode 100644 (file)
index 0000000..1cc0967
--- /dev/null
@@ -0,0 +1,138 @@
+use super::*;
+
+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>, String> {
+        let mut u8v: Vec<_> = fmt.bytes().collect();
+        u8v.extend(cap.as_bytes().iter().map(|&b| b));
+        expand(&u8v, params, vars)
+    }
+
+    let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
+    for &cap in caps.iter() {
+        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_string()) } else { Number(97) };
+        let res = get_res("%p1", cap, &[p], vars);
+        assert!(res.is_ok(), "Op {} failed with 1 stack entry: {}", cap, res.unwrap_err());
+    }
+    let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
+    for &cap in caps.iter() {
+        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.unwrap_err());
+    }
+}
+
+#[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.iter() {
+        let s = format!("%{{1}}%{{2}}%{}%d", op);
+        let res = expand(s.as_bytes(), &[], &mut Variables::new());
+        assert!(res.is_ok(), "{}", res.unwrap_err());
+        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.unwrap_err());
+        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.unwrap_err());
+        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.unwrap_err());
+    assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>());
+    let res = expand(s, &[Number(8)], &mut vars);
+    assert!(res.is_ok(), "{}", res.unwrap_err());
+    assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
+    let res = expand(s, &[Number(42)], &mut vars);
+    assert!(res.is_ok(), "{}", res.unwrap_err());
+    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_string()),
+                Words("foo".to_string()),
+                Words("f".to_string()),
+                Words("foo".to_string())
+            ],
+            vars
+        ),
+        Ok("foofoo ffo".bytes().collect::<Vec<_>>())
+    );
+    assert_eq!(
+        expand(b"%p1%:-4.2s", &[Words("foo".to_string())], 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/library/test/src/term/terminfo/parser/compiled.rs b/library/test/src/term/terminfo/parser/compiled.rs
new file mode 100644 (file)
index 0000000..fbc5aeb
--- /dev/null
@@ -0,0 +1,336 @@
+#![allow(non_upper_case_globals, missing_docs)]
+
+//! ncurses-compatible compiled terminfo format parsing (term(5))
+
+use super::super::TermInfo;
+use std::collections::HashMap;
+use std::io;
+use std::io::prelude::*;
+
+#[cfg(test)]
+mod tests;
+
+// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
+
+#[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"];
+
+#[rustfmt::skip]
+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"];
+
+#[rustfmt::skip]
+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"];
+
+#[rustfmt::skip]
+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"];
+
+#[rustfmt::skip]
+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"];
+
+#[rustfmt::skip]
+pub static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
+    "_", "_", "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", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
+    "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
+    "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
+    "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
+    "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
+    "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
+    "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "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", "_", "_",
+    "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
+    "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "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"];
+
+fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
+    let mut b = [0; 2];
+    r.read_exact(&mut b)?;
+    Ok((b[0] as u16) | ((b[1] as u16) << 8))
+}
+
+fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> {
+    let mut b = [0; 4];
+    r.read_exact(&mut b)?;
+    Ok((b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24))
+}
+
+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, String> {
+    macro_rules! t( ($e:expr) => (
+        match $e {
+            Ok(e) => e,
+            Err(e) => return Err(e.to_string())
+        }
+    ) );
+
+    let (bnames, snames, nnames) = if longnames {
+        (boolfnames, stringfnames, numfnames)
+    } else {
+        (boolnames, stringnames, numnames)
+    };
+
+    // Check magic number
+    let magic = t!(read_le_u16(file));
+
+    let extended = match magic {
+        0o0432 => false,
+        0o01036 => true,
+        _ => return Err(format!("invalid magic number, found {:o}", magic)),
+    };
+
+    // 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 t!(read_le_u16(file)) as i16 {
+                n if n >= 0 => n as usize,
+                -1 => 0,
+                _ => return Err("incompatible file: length fields must be  >= -1".to_string()),
+            }
+        }};
+    }
+
+    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("incompatible file: names field must be at least 1 byte wide".to_string());
+    }
+
+    if bools_bytes > boolnames.len() {
+        return Err("incompatible file: more booleans than expected".to_string());
+    }
+
+    if numbers_count > numnames.len() {
+        return Err("incompatible file: more numbers than expected".to_string());
+    }
+
+    if string_offsets_count > stringnames.len() {
+        return Err("incompatible file: more string offsets than expected".to_string());
+    }
+
+    // don't read NUL
+    let mut bytes = Vec::new();
+    t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
+    let names_str = match String::from_utf8(bytes) {
+        Ok(s) => s,
+        Err(_) => return Err("input not utf-8".to_string()),
+    };
+
+    let term_names: Vec<String> = names_str.split('|').map(|s| s.to_string()).collect();
+    // consume NUL
+    if t!(read_byte(file)) != b'\0' {
+        return Err("incompatible file: missing null terminator for names section".to_string());
+    }
+
+    let bools_map: HashMap<String, bool> = t! {
+        (0..bools_bytes).filter_map(|i| match read_byte(file) {
+            Err(e) => Some(Err(e)),
+            Ok(1) => Some(Ok((bnames[i].to_string(), true))),
+            Ok(_) => None
+        }).collect()
+    };
+
+    if (bools_bytes + names_bytes) % 2 == 1 {
+        t!(read_byte(file)); // compensate for padding
+    }
+
+    let numbers_map: HashMap<String, u32> = t! {
+        (0..numbers_count).filter_map(|i| {
+            let number = if extended { read_le_u32(file) } else { read_le_u16(file).map(Into::into) };
+
+            match number {
+                Ok(0xFFFF) => None,
+                Ok(n) => Some(Ok((nnames[i].to_string(), n))),
+                Err(e) => Some(Err(e))
+            }
+        }).collect()
+    };
+
+    let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
+        let string_offsets: Vec<u16> =
+            t!((0..string_offsets_count).map(|_| read_le_u16(file)).collect());
+
+        let mut string_table = Vec::new();
+        t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
+
+        t!(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.to_string(), 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.to_string(), string_table[offset..offset + len].to_vec()))
+                    }
+                    None => Err("invalid file: missing NUL in string_table".to_string()),
+                }
+            })
+            .collect())
+    } 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 })
+}
+
+/// Creates a dummy TermInfo struct for msys terminals
+pub fn msys_terminfo() -> TermInfo {
+    let mut strings = HashMap::new();
+    strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
+    strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
+    strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
+    strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
+
+    let mut numbers = HashMap::new();
+    numbers.insert("colors".to_string(), 8);
+
+    TermInfo {
+        names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
+        bools: HashMap::new(),
+        numbers,
+        strings,
+    }
+}
diff --git a/library/test/src/term/terminfo/parser/compiled/tests.rs b/library/test/src/term/terminfo/parser/compiled/tests.rs
new file mode 100644 (file)
index 0000000..8a9187b
--- /dev/null
@@ -0,0 +1,8 @@
+use super::*;
+
+#[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/library/test/src/term/terminfo/searcher.rs b/library/test/src/term/terminfo/searcher.rs
new file mode 100644 (file)
index 0000000..5499e24
--- /dev/null
@@ -0,0 +1,69 @@
+//! ncurses-compatible database discovery.
+//!
+//! Does not support hashed database, only filesystem!
+
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+#[cfg(test)]
+mod tests;
+
+/// Return path to database entry for `term`
+#[allow(deprecated)]
+pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
+    let mut dirs_to_search = Vec::new();
+    let first_char = term.chars().next()?;
+
+    // Find search directory
+    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) = env::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., macOS)
+            p.push(&format!("{:x}", first_char as usize));
+            p.push(term);
+            if fs::metadata(&p).is_ok() {
+                return Some(p);
+            }
+        }
+    }
+    None
+}
diff --git a/library/test/src/term/terminfo/searcher/tests.rs b/library/test/src/term/terminfo/searcher/tests.rs
new file mode 100644 (file)
index 0000000..4227a58
--- /dev/null
@@ -0,0 +1,19 @@
+use super::*;
+
+#[test]
+#[ignore = "buildbots don't have ncurses installed and I can't mock everything I need"]
+fn test_get_dbpath_for_term() {
+    // woefully inadequate test coverage
+    // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's)
+    use std::env;
+    // FIXME (#9639): This needs to handle non-utf8 paths
+    fn x(t: &str) -> String {
+        let p = get_dbpath_for_term(t).expect("no terminfo entry found");
+        p.to_str().unwrap().to_string()
+    }
+    assert!(x("screen") == "/usr/share/terminfo/s/screen");
+    assert!(get_dbpath_for_term("") == None);
+    env::set_var("TERMINFO_DIRS", ":");
+    assert!(x("screen") == "/usr/share/terminfo/s/screen");
+    env::remove_var("TERMINFO_DIRS");
+}
diff --git a/library/test/src/term/win.rs b/library/test/src/term/win.rs
new file mode 100644 (file)
index 0000000..c24cf95
--- /dev/null
@@ -0,0 +1,220 @@
+//! Windows console handling
+
+// FIXME (#13400): this is only a tiny fraction of the Windows console api
+
+use std::io;
+use std::io::prelude::*;
+
+use crate::color;
+use crate::Attr;
+use crate::Terminal;
+
+/// A Terminal implementation that uses the Win32 Console API.
+pub struct WinConsole<T> {
+    buf: T,
+    def_foreground: color::Color,
+    def_background: color::Color,
+    foreground: color::Color,
+    background: color::Color,
+}
+
+type SHORT = i16;
+type WORD = u16;
+type DWORD = u32;
+type BOOL = i32;
+type HANDLE = *mut u8;
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct SMALL_RECT {
+    Left: SHORT,
+    Top: SHORT,
+    Right: SHORT,
+    Bottom: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct COORD {
+    X: SHORT,
+    Y: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct CONSOLE_SCREEN_BUFFER_INFO {
+    dwSize: COORD,
+    dwCursorPosition: COORD,
+    wAttributes: WORD,
+    srWindow: SMALL_RECT,
+    dwMaximumWindowSize: COORD,
+}
+
+#[allow(non_snake_case)]
+#[link(name = "kernel32")]
+extern "system" {
+    fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
+    fn GetStdHandle(which: DWORD) -> HANDLE;
+    fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
+}
+
+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 | (u32::from(bits) & 0x8) // copy the hi-intensity bit
+}
+
+impl<T: Write + Send + 'static> WinConsole<T> {
+    fn apply(&mut self) {
+        let _unused = self.buf.flush();
+        let mut accum: WORD = 0;
+        accum |= color_to_bits(self.foreground);
+        accum |= color_to_bits(self.background) << 4;
+
+        unsafe {
+            // Magic -11 means stdout, from
+            // https://docs.microsoft.com/en-us/windows/console/getstdhandle
+            //
+            // You may be wondering, "but what about stderr?", and the answer
+            // to that is that setting terminal attributes on the stdout
+            // handle also sets them for stderr, since they go to the same
+            // terminal! Admittedly, this is fragile, since stderr could be
+            // redirected to a different console. This is good enough for
+            // rustc though. See #13400.
+            let out = GetStdHandle(-11i32 as DWORD);
+            SetConsoleTextAttribute(out, accum);
+        }
+    }
+
+    /// Returns `None` whenever the terminal cannot be created for some reason.
+    pub fn new(out: T) -> io::Result<WinConsole<T>> {
+        use std::mem::MaybeUninit;
+
+        let fg;
+        let bg;
+        unsafe {
+            let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
+            if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), buffer_info.as_mut_ptr())
+                != 0
+            {
+                let buffer_info = buffer_info.assume_init();
+                fg = bits_to_color(buffer_info.wAttributes);
+                bg = bits_to_color(buffer_info.wAttributes >> 4);
+            } else {
+                fg = color::WHITE;
+                bg = color::BLACK;
+            }
+        }
+        Ok(WinConsole {
+            buf: out,
+            def_foreground: fg,
+            def_background: bg,
+            foreground: fg,
+            background: bg,
+        })
+    }
+}
+
+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 + 'static> Terminal for WinConsole<T> {
+    type Output = T;
+
+    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.foreground = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.background = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
+        match attr {
+            Attr::ForegroundColor(f) => {
+                self.foreground = f;
+                self.apply();
+                Ok(true)
+            }
+            Attr::BackgroundColor(b) => {
+                self.background = b;
+                self.apply();
+                Ok(true)
+            }
+            _ => Ok(false),
+        }
+    }
+
+    fn supports_attr(&self, attr: Attr) -> bool {
+        // it claims support for underscore and reverse video, but I can't get
+        // it to do anything -cmr
+        match attr {
+            Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
+            _ => false,
+        }
+    }
+
+    fn reset(&mut self) -> io::Result<bool> {
+        self.foreground = self.def_foreground;
+        self.background = self.def_background;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn get_ref(&self) -> &T {
+        &self.buf
+    }
+
+    fn get_mut(&mut self) -> &mut T {
+        &mut self.buf
+    }
+
+    fn into_inner(self) -> T
+    where
+        Self: Sized,
+    {
+        self.buf
+    }
+}