]> git.lizzy.rs Git - rust.git/blob - library/term/src/terminfo/mod.rs
fec59aaa0c2794e67b9551b16d07320b7a82742c
[rust.git] / library / term / src / 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::color;
12 use crate::Attr;
13 use crate::Terminal;
14
15 use parm::{expand, Param, Variables};
16 use parser::compiled::{msys_terminfo, parse};
17 use searcher::get_dbpath_for_term;
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, u32>,
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 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 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 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 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 mod searcher;
103
104 /// TermInfo format parsing.
105 pub mod parser {
106     //! ncurses-compatible compiled terminfo format parsing (term(5))
107     pub mod compiled;
108 }
109 pub mod parm;
110
111 fn cap_for_attr(attr: Attr) -> &'static str {
112     match attr {
113         Attr::Bold => "bold",
114         Attr::Dim => "dim",
115         Attr::Italic(true) => "sitm",
116         Attr::Italic(false) => "ritm",
117         Attr::Underline(true) => "smul",
118         Attr::Underline(false) => "rmul",
119         Attr::Blink => "blink",
120         Attr::Standout(true) => "smso",
121         Attr::Standout(false) => "rmso",
122         Attr::Reverse => "rev",
123         Attr::Secure => "invis",
124         Attr::ForegroundColor(_) => "setaf",
125         Attr::BackgroundColor(_) => "setab",
126     }
127 }
128
129 /// A Terminal that knows how many colors it supports, with a reference to its
130 /// parsed Terminfo database record.
131 pub struct TerminfoTerminal<T> {
132     num_colors: u32,
133     out: T,
134     ti: TermInfo,
135 }
136
137 impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
138     type Output = T;
139     fn fg(&mut self, color: color::Color) -> io::Result<bool> {
140         let color = self.dim_if_necessary(color);
141         if self.num_colors > color {
142             return self.apply_cap("setaf", &[Param::Number(color as i32)]);
143         }
144         Ok(false)
145     }
146
147     fn bg(&mut self, color: color::Color) -> io::Result<bool> {
148         let color = self.dim_if_necessary(color);
149         if self.num_colors > color {
150             return self.apply_cap("setab", &[Param::Number(color as i32)]);
151         }
152         Ok(false)
153     }
154
155     fn attr(&mut self, attr: Attr) -> io::Result<bool> {
156         match attr {
157             Attr::ForegroundColor(c) => self.fg(c),
158             Attr::BackgroundColor(c) => self.bg(c),
159             _ => self.apply_cap(cap_for_attr(attr), &[]),
160         }
161     }
162
163     fn supports_attr(&self, attr: Attr) -> bool {
164         match attr {
165             Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
166             _ => {
167                 let cap = cap_for_attr(attr);
168                 self.ti.strings.get(cap).is_some()
169             }
170         }
171     }
172
173     fn reset(&mut self) -> io::Result<bool> {
174         // are there any terminals that have color/attrs and not sgr0?
175         // Try falling back to sgr, then op
176         let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
177             Some(op) => match expand(&op, &[], &mut Variables::new()) {
178                 Ok(cmd) => cmd,
179                 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
180             },
181             None => return Ok(false),
182         };
183         self.out.write_all(&cmd).and(Ok(true))
184     }
185
186     fn get_ref(&self) -> &T {
187         &self.out
188     }
189
190     fn get_mut(&mut self) -> &mut T {
191         &mut self.out
192     }
193
194     fn into_inner(self) -> T
195     where
196         Self: Sized,
197     {
198         self.out
199     }
200 }
201
202 impl<T: Write + Send> TerminfoTerminal<T> {
203     /// Creates a new TerminfoTerminal with the given TermInfo and Write.
204     pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
205         let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
206         {
207             terminfo.numbers.get("colors").map_or(0, |&n| n)
208         } else {
209             0
210         };
211
212         TerminfoTerminal { out, ti: terminfo, num_colors: nc }
213     }
214
215     /// Creates a new TerminfoTerminal for the current environment with the given Write.
216     ///
217     /// Returns `None` when the terminfo cannot be found or parsed.
218     pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
219         TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
220     }
221
222     fn dim_if_necessary(&self, color: color::Color) -> color::Color {
223         if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color }
224     }
225
226     fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
227         match self.ti.strings.get(cmd) {
228             Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
229                 Ok(s) => self.out.write_all(&s).and(Ok(true)),
230                 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
231             },
232             None => Ok(false),
233         }
234     }
235 }
236
237 impl<T: Write> Write for TerminfoTerminal<T> {
238     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
239         self.out.write(buf)
240     }
241
242     fn flush(&mut self) -> io::Result<()> {
243         self.out.flush()
244     }
245 }