]> git.lizzy.rs Git - rust.git/blob - src/libterm/terminfo/mod.rs
Auto merge of #58341 - alexreg:cosmetic-2-doc-comments, r=steveklabnik
[rust.git] / src / libterm / 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 crate::Attr;
12 use crate::color;
13 use crate::Terminal;
14
15 use searcher::get_dbpath_for_term;
16 use parser::compiled::{parse, msys_terminfo};
17 use parm::{expand, Variables, Param};
18
19 /// A parsed terminfo database entry.
20 #[derive(Debug)]
21 pub struct TermInfo {
22     /// Names for the terminal
23     pub names: Vec<String>,
24     /// Map of capability name to boolean value
25     pub bools: HashMap<String, bool>,
26     /// Map of capability name to numeric value
27     pub numbers: HashMap<String, u16>,
28     /// Map of capability name to raw (unexpanded) string
29     pub strings: HashMap<String, Vec<u8>>,
30 }
31
32 /// A terminfo creation error.
33 #[derive(Debug)]
34 pub 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 description(&self) -> &str {
46         "failed to create TermInfo"
47     }
48
49     fn cause(&self) -> Option<&dyn error::Error> {
50         use Error::*;
51         match *self {
52             IoError(ref e) => Some(e),
53             _ => None,
54         }
55     }
56 }
57
58 impl fmt::Display for Error {
59     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60         use Error::*;
61         match *self {
62             TermUnset => Ok(()),
63             MalformedTerminfo(ref e) => e.fmt(f),
64             IoError(ref e) => e.fmt(f),
65         }
66     }
67 }
68
69 impl TermInfo {
70     /// Creates a TermInfo based on current environment.
71     pub fn from_env() -> Result<TermInfo, Error> {
72         let term = match env::var("TERM") {
73             Ok(name) => TermInfo::from_name(&name),
74             Err(..) => return Err(Error::TermUnset),
75         };
76
77         if term.is_err() && env::var("MSYSCON").ok().map_or(false, |s| "mintty.exe" == s) {
78             // msys terminal
79             Ok(msys_terminfo())
80         } else {
81             term
82         }
83     }
84
85     /// Creates a TermInfo for the named terminal.
86     pub fn from_name(name: &str) -> Result<TermInfo, Error> {
87         get_dbpath_for_term(name)
88             .ok_or_else(|| {
89                 Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
90             })
91             .and_then(|p| TermInfo::from_path(&(*p)))
92     }
93
94     /// Parse the given TermInfo.
95     pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
96         Self::_from_path(path.as_ref())
97     }
98     // Keep the metadata small
99     fn _from_path(path: &Path) -> Result<TermInfo, Error> {
100         let file = File::open(path).map_err(Error::IoError)?;
101         let mut reader = BufReader::new(file);
102         parse(&mut reader, false).map_err(Error::MalformedTerminfo)
103     }
104 }
105
106 pub mod searcher;
107
108 /// TermInfo format parsing.
109 pub mod parser {
110     //! ncurses-compatible compiled terminfo format parsing (term(5))
111     pub mod compiled;
112 }
113 pub mod parm;
114
115
116 fn cap_for_attr(attr: Attr) -> &'static str {
117     match attr {
118         Attr::Bold => "bold",
119         Attr::Dim => "dim",
120         Attr::Italic(true) => "sitm",
121         Attr::Italic(false) => "ritm",
122         Attr::Underline(true) => "smul",
123         Attr::Underline(false) => "rmul",
124         Attr::Blink => "blink",
125         Attr::Standout(true) => "smso",
126         Attr::Standout(false) => "rmso",
127         Attr::Reverse => "rev",
128         Attr::Secure => "invis",
129         Attr::ForegroundColor(_) => "setaf",
130         Attr::BackgroundColor(_) => "setab",
131     }
132 }
133
134 /// A Terminal that knows how many colors it supports, with a reference to its
135 /// parsed Terminfo database record.
136 pub struct TerminfoTerminal<T> {
137     num_colors: u16,
138     out: T,
139     ti: TermInfo,
140 }
141
142 impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
143     type Output = T;
144     fn fg(&mut self, color: color::Color) -> io::Result<bool> {
145         let color = self.dim_if_necessary(color);
146         if self.num_colors > color {
147             return self.apply_cap("setaf", &[Param::Number(color as i32)]);
148         }
149         Ok(false)
150     }
151
152     fn bg(&mut self, color: color::Color) -> io::Result<bool> {
153         let color = self.dim_if_necessary(color);
154         if self.num_colors > color {
155             return self.apply_cap("setab", &[Param::Number(color as i32)]);
156         }
157         Ok(false)
158     }
159
160     fn attr(&mut self, attr: Attr) -> io::Result<bool> {
161         match attr {
162             Attr::ForegroundColor(c) => self.fg(c),
163             Attr::BackgroundColor(c) => self.bg(c),
164             _ => self.apply_cap(cap_for_attr(attr), &[]),
165         }
166     }
167
168     fn supports_attr(&self, attr: Attr) -> bool {
169         match attr {
170             Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
171             _ => {
172                 let cap = cap_for_attr(attr);
173                 self.ti.strings.get(cap).is_some()
174             }
175         }
176     }
177
178     fn reset(&mut self) -> io::Result<bool> {
179         // are there any terminals that have color/attrs and not sgr0?
180         // Try falling back to sgr, then op
181         let cmd = match ["sgr0", "sgr", "op"]
182                             .iter()
183                             .filter_map(|cap| self.ti.strings.get(*cap))
184                             .next() {
185             Some(op) => {
186                 match expand(&op, &[], &mut Variables::new()) {
187                     Ok(cmd) => cmd,
188                     Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
189                 }
190             }
191             None => return Ok(false),
192         };
193         self.out.write_all(&cmd).and(Ok(true))
194     }
195
196     fn get_ref(&self) -> &T {
197         &self.out
198     }
199
200     fn get_mut(&mut self) -> &mut T {
201         &mut self.out
202     }
203
204     fn into_inner(self) -> T
205         where Self: Sized
206     {
207         self.out
208     }
209 }
210
211 impl<T: Write + Send> TerminfoTerminal<T> {
212     /// Creates a new TerminfoTerminal with the given TermInfo and Write.
213     pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
214         let nc = if terminfo.strings.contains_key("setaf") &&
215                     terminfo.strings.contains_key("setab") {
216             terminfo.numbers.get("colors").map_or(0, |&n| n)
217         } else {
218             0
219         };
220
221         TerminfoTerminal {
222             out,
223             ti: terminfo,
224             num_colors: nc,
225         }
226     }
227
228     /// Creates a new TerminfoTerminal for the current environment with the given Write.
229     ///
230     /// Returns `None` when the terminfo cannot be found or parsed.
231     pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
232         TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
233     }
234
235     fn dim_if_necessary(&self, color: color::Color) -> color::Color {
236         if color >= self.num_colors && color >= 8 && color < 16 {
237             color - 8
238         } else {
239             color
240         }
241     }
242
243     fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
244         match self.ti.strings.get(cmd) {
245             Some(cmd) => {
246                 match expand(&cmd, params, &mut Variables::new()) {
247                     Ok(s) => self.out.write_all(&s).and(Ok(true)),
248                     Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
249                 }
250             }
251             None => Ok(false),
252         }
253     }
254 }
255
256
257 impl<T: Write> Write for TerminfoTerminal<T> {
258     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
259         self.out.write(buf)
260     }
261
262     fn flush(&mut self) -> io::Result<()> {
263         self.out.flush()
264     }
265 }