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