]> git.lizzy.rs Git - rust.git/blob - library/test/src/term/terminfo/mod.rs
Rollup merge of #87346 - rylev:rename-force-warn, r=nikomatsakis
[rust.git] / library / test / src / term / terminfo / mod.rs
1 //! Terminfo database interface.
2
3 use std::collections::HashMap;
4 use std::env;
5 use std::error;
6 use std::fmt;
7 use std::fs::File;
8 use std::io::{self, prelude::*, BufReader};
9 use std::path::Path;
10
11 use super::color;
12 use super::Terminal;
13
14 use parm::{expand, Param, Variables};
15 use parser::compiled::{msys_terminfo, parse};
16 use searcher::get_dbpath_for_term;
17
18 /// A parsed terminfo database entry.
19 #[derive(Debug)]
20 pub(crate) struct TermInfo {
21     /// Names for the terminal
22     pub(crate) names: Vec<String>,
23     /// Map of capability name to boolean value
24     pub(crate) bools: HashMap<String, bool>,
25     /// Map of capability name to numeric value
26     pub(crate) numbers: HashMap<String, u32>,
27     /// Map of capability name to raw (unexpanded) string
28     pub(crate) strings: HashMap<String, Vec<u8>>,
29 }
30
31 /// A terminfo creation error.
32 #[derive(Debug)]
33 pub(crate) enum Error {
34     /// TermUnset Indicates that the environment doesn't include enough information to find
35     /// the terminfo entry.
36     TermUnset,
37     /// MalformedTerminfo indicates that parsing the terminfo entry failed.
38     MalformedTerminfo(String),
39     /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
40     IoError(io::Error),
41 }
42
43 impl error::Error for Error {
44     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
45         use Error::*;
46         match self {
47             IoError(e) => Some(e),
48             _ => None,
49         }
50     }
51 }
52
53 impl fmt::Display for Error {
54     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55         use Error::*;
56         match *self {
57             TermUnset => Ok(()),
58             MalformedTerminfo(ref e) => e.fmt(f),
59             IoError(ref e) => e.fmt(f),
60         }
61     }
62 }
63
64 impl TermInfo {
65     /// Creates a TermInfo based on current environment.
66     pub(crate) fn from_env() -> Result<TermInfo, Error> {
67         let term = match env::var("TERM") {
68             Ok(name) => TermInfo::from_name(&name),
69             Err(..) => return Err(Error::TermUnset),
70         };
71
72         if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
73             // msys terminal
74             Ok(msys_terminfo())
75         } else {
76             term
77         }
78     }
79
80     /// Creates a TermInfo for the named terminal.
81     pub(crate) fn from_name(name: &str) -> Result<TermInfo, Error> {
82         get_dbpath_for_term(name)
83             .ok_or_else(|| {
84                 Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
85             })
86             .and_then(|p| TermInfo::from_path(&(*p)))
87     }
88
89     /// Parse the given TermInfo.
90     pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
91         Self::_from_path(path.as_ref())
92     }
93     // Keep the metadata small
94     fn _from_path(path: &Path) -> Result<TermInfo, Error> {
95         let file = File::open(path).map_err(Error::IoError)?;
96         let mut reader = BufReader::new(file);
97         parse(&mut reader, false).map_err(Error::MalformedTerminfo)
98     }
99 }
100
101 pub(crate) mod searcher;
102
103 /// TermInfo format parsing.
104 pub(crate) mod parser {
105     //! ncurses-compatible compiled terminfo format parsing (term(5))
106     pub(crate) mod compiled;
107 }
108 pub(crate) mod parm;
109
110 /// A Terminal that knows how many colors it supports, with a reference to its
111 /// parsed Terminfo database record.
112 pub(crate) struct TerminfoTerminal<T> {
113     num_colors: u32,
114     out: T,
115     ti: TermInfo,
116 }
117
118 impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
119     fn fg(&mut self, color: color::Color) -> io::Result<bool> {
120         let color = self.dim_if_necessary(color);
121         if self.num_colors > color {
122             return self.apply_cap("setaf", &[Param::Number(color as i32)]);
123         }
124         Ok(false)
125     }
126
127     fn reset(&mut self) -> io::Result<bool> {
128         // are there any terminals that have color/attrs and not sgr0?
129         // Try falling back to sgr, then op
130         let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
131             Some(op) => match expand(&op, &[], &mut Variables::new()) {
132                 Ok(cmd) => cmd,
133                 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
134             },
135             None => return Ok(false),
136         };
137         self.out.write_all(&cmd).and(Ok(true))
138     }
139 }
140
141 impl<T: Write + Send> TerminfoTerminal<T> {
142     /// Creates a new TerminfoTerminal with the given TermInfo and Write.
143     pub(crate) fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
144         let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
145         {
146             terminfo.numbers.get("colors").map_or(0, |&n| n)
147         } else {
148             0
149         };
150
151         TerminfoTerminal { out, ti: terminfo, num_colors: nc }
152     }
153
154     /// Creates a new TerminfoTerminal for the current environment with the given Write.
155     ///
156     /// Returns `None` when the terminfo cannot be found or parsed.
157     pub(crate) fn new(out: T) -> Option<TerminfoTerminal<T>> {
158         TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
159     }
160
161     fn dim_if_necessary(&self, color: color::Color) -> color::Color {
162         if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color }
163     }
164
165     fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
166         match self.ti.strings.get(cmd) {
167             Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
168                 Ok(s) => self.out.write_all(&s).and(Ok(true)),
169                 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
170             },
171             None => Ok(false),
172         }
173     }
174 }
175
176 impl<T: Write> Write for TerminfoTerminal<T> {
177     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
178         self.out.write(buf)
179     }
180
181     fn flush(&mut self) -> io::Result<()> {
182         self.out.flush()
183     }
184 }