]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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; | |
85aaf69f | 14 | use std::env; |
92a42be0 SL |
15 | use std::error; |
16 | use std::fmt; | |
17 | use std::fs::File; | |
c34b1796 AL |
18 | use std::io::prelude::*; |
19 | use std::io; | |
92a42be0 SL |
20 | use std::io::BufReader; |
21 | use std::path::Path; | |
1a4d82fc | 22 | |
92a42be0 | 23 | use Attr; |
1a4d82fc JJ |
24 | use color; |
25 | use Terminal; | |
92a42be0 | 26 | use self::searcher::get_dbpath_for_term; |
1a4d82fc | 27 | use self::parser::compiled::{parse, msys_terminfo}; |
92a42be0 | 28 | use self::parm::{expand, Variables, Param}; |
1a4d82fc JJ |
29 | |
30 | ||
31 | /// A parsed terminfo database entry. | |
85aaf69f | 32 | #[derive(Debug)] |
1a4d82fc JJ |
33 | pub struct TermInfo { |
34 | /// Names for the terminal | |
92a42be0 | 35 | pub names: Vec<String>, |
1a4d82fc JJ |
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 | |
92a42be0 SL |
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> { | |
54a0048b | 112 | let file = File::open(path).map_err(|e| Error::IoError(e))?; |
92a42be0 SL |
113 | let mut reader = BufReader::new(file); |
114 | parse(&mut reader, false).map_err(|e| Error::MalformedTerminfo(e)) | |
115 | } | |
1a4d82fc JJ |
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 | ||
92a42be0 | 128 | fn cap_for_attr(attr: Attr) -> &'static str { |
1a4d82fc | 129 | match attr { |
92a42be0 SL |
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", | |
1a4d82fc JJ |
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, | |
92a42be0 | 151 | ti: TermInfo, |
1a4d82fc JJ |
152 | } |
153 | ||
9cc50fc6 | 154 | impl<T: Write + Send> Terminal for TerminfoTerminal<T> { |
92a42be0 | 155 | type Output = T; |
c34b1796 | 156 | fn fg(&mut self, color: color::Color) -> io::Result<bool> { |
1a4d82fc JJ |
157 | let color = self.dim_if_necessary(color); |
158 | if self.num_colors > color { | |
92a42be0 | 159 | return self.apply_cap("setaf", &[Param::Number(color as i32)]); |
1a4d82fc JJ |
160 | } |
161 | Ok(false) | |
162 | } | |
163 | ||
c34b1796 | 164 | fn bg(&mut self, color: color::Color) -> io::Result<bool> { |
1a4d82fc JJ |
165 | let color = self.dim_if_necessary(color); |
166 | if self.num_colors > color { | |
92a42be0 | 167 | return self.apply_cap("setab", &[Param::Number(color as i32)]); |
1a4d82fc JJ |
168 | } |
169 | Ok(false) | |
170 | } | |
171 | ||
92a42be0 | 172 | fn attr(&mut self, attr: Attr) -> io::Result<bool> { |
1a4d82fc | 173 | match attr { |
92a42be0 SL |
174 | Attr::ForegroundColor(c) => self.fg(c), |
175 | Attr::BackgroundColor(c) => self.bg(c), | |
176 | _ => self.apply_cap(cap_for_attr(attr), &[]), | |
1a4d82fc JJ |
177 | } |
178 | } | |
179 | ||
92a42be0 | 180 | fn supports_attr(&self, attr: Attr) -> bool { |
1a4d82fc | 181 | match attr { |
92a42be0 | 182 | Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0, |
1a4d82fc JJ |
183 | _ => { |
184 | let cap = cap_for_attr(attr); | |
185 | self.ti.strings.get(cap).is_some() | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
92a42be0 SL |
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 | } | |
1a4d82fc | 202 | } |
92a42be0 SL |
203 | None => return Ok(false), |
204 | }; | |
205 | self.out.write_all(&cmd).and(Ok(true)) | |
1a4d82fc JJ |
206 | } |
207 | ||
7453a54e | 208 | fn get_ref(&self) -> &T { |
92a42be0 SL |
209 | &self.out |
210 | } | |
1a4d82fc | 211 | |
7453a54e | 212 | fn get_mut(&mut self) -> &mut T { |
92a42be0 SL |
213 | &mut self.out |
214 | } | |
1a4d82fc | 215 | |
92a42be0 SL |
216 | fn into_inner(self) -> T |
217 | where Self: Sized | |
218 | { | |
219 | self.out | |
220 | } | |
1a4d82fc JJ |
221 | } |
222 | ||
9cc50fc6 | 223 | impl<T: Write + Send> TerminfoTerminal<T> { |
92a42be0 SL |
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 | |
1a4d82fc JJ |
231 | }; |
232 | ||
92a42be0 SL |
233 | TerminfoTerminal { |
234 | out: out, | |
235 | ti: terminfo, | |
236 | num_colors: nc, | |
1a4d82fc | 237 | } |
92a42be0 | 238 | } |
1a4d82fc | 239 | |
92a42be0 SL |
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() | |
1a4d82fc JJ |
245 | } |
246 | ||
247 | fn dim_if_necessary(&self, color: color::Color) -> color::Color { | |
248 | if color >= self.num_colors && color >= 8 && color < 16 { | |
92a42be0 SL |
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 | } | |
1a4d82fc JJ |
265 | } |
266 | } | |
267 | ||
268 | ||
c34b1796 AL |
269 | impl<T: Write> Write for TerminfoTerminal<T> { |
270 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
271 | self.out.write(buf) | |
1a4d82fc JJ |
272 | } |
273 | ||
c34b1796 | 274 | fn flush(&mut self) -> io::Result<()> { |
1a4d82fc JJ |
275 | self.out.flush() |
276 | } | |
277 | } |