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