1use std::collections::HashMap;
14use std::env;
15use std::fs::File;
16use std::io::prelude::*;
17use std::io;
18use std::io::BufReader;
19use std::path::Path;
20
21use Attr;
22use color;
23use Terminal;
24use Result;
25use self::searcher::get_dbpath_for_term;
26use self::parser::compiled::parse;
27use self::parm::{expand, Param, Variables};
28use self::Error::*;
29
30fn is_ansi(name: &str) -> bool {
32 static ANSI_TERM_PREFIX: &'static [&'static str] = &[
34 "Eterm", "ansi", "eterm", "iterm", "konsole", "linux", "mrxvt", "msyscon", "rxvt",
35 "screen", "tmux", "xterm",
36 ];
37 match ANSI_TERM_PREFIX.binary_search(&name) {
38 Ok(_) => true,
39 Err(0) => false,
40 Err(idx) => name.starts_with(ANSI_TERM_PREFIX[idx - 1]),
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct TermInfo {
47 pub names: Vec<String>,
49 pub bools: HashMap<&'static str, bool>,
51 pub numbers: HashMap<&'static str, u32>,
53 pub strings: HashMap<&'static str, Vec<u8>>,
55}
56
57impl TermInfo {
58 pub fn from_env() -> Result<TermInfo> {
60 let term_var = env::var("TERM").ok();
61 let term_name = term_var.as_ref().map(|s| &**s).or_else(|| {
62 env::var("MSYSCON").ok().and_then(|s| {
63 if s == "mintty.exe" {
64 Some("msyscon")
65 } else {
66 None
67 }
68 })
69 });
70 if let Some(term_name) = term_name {
71 return TermInfo::from_name(term_name);
72 } else {
73 return Err(::Error::TermUnset);
74 }
75 }
76
77 pub fn from_name(name: &str) -> Result<TermInfo> {
79 if let Some(path) = get_dbpath_for_term(name) {
80 match TermInfo::from_path(&path) {
81 Ok(term) => return Ok(term),
82 Err(::Error::Io(_)) => {}
84 Err(e) => return Err(e),
86 }
87 }
88 if is_ansi(name) {
90 let mut strings = HashMap::new();
91 strings.insert("sgr0", b"\x1B[0m".to_vec());
92 strings.insert("bold", b"\x1B[1m".to_vec());
93 strings.insert("setaf", b"\x1B[3%p1%dm".to_vec());
94 strings.insert("setab", b"\x1B[4%p1%dm".to_vec());
95
96 let mut numbers = HashMap::new();
97 numbers.insert("colors", 8);
98
99 Ok(TermInfo {
100 names: vec![name.to_owned()],
101 bools: HashMap::new(),
102 numbers: numbers,
103 strings: strings,
104 })
105 } else {
106 Err(::Error::TerminfoEntryNotFound)
107 }
108 }
109
110 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo> {
112 Self::_from_path(path.as_ref())
113 }
114 fn _from_path(path: &Path) -> Result<TermInfo> {
121 let file = File::open(path).map_err(::Error::Io)?;
122 let mut reader = BufReader::new(file);
123 parse(&mut reader, false)
124 }
125
126 pub fn apply_cap(&self, cmd: &str, params: &[Param], out: &mut io::Write) -> Result<()> {
128 match self.strings.get(cmd) {
129 Some(cmd) => match expand(cmd, params, &mut Variables::new()) {
130 Ok(s) => {
131 out.write_all(&s)?;
132 Ok(())
133 }
134 Err(e) => Err(e.into()),
135 },
136 None => Err(::Error::NotSupported),
137 }
138 }
139
140 pub fn reset(&self, out: &mut io::Write) -> Result<()> {
142 let cmd = match [
145 ("sgr0", &[] as &[Param]),
146 ("sgr", &[Param::Number(0)]),
147 ("op", &[]),
148 ].iter()
149 .filter_map(|&(cap, params)| self.strings.get(cap).map(|c| (c, params)))
150 .next()
151 {
152 Some((op, params)) => match expand(op, params, &mut Variables::new()) {
153 Ok(cmd) => cmd,
154 Err(e) => return Err(e.into()),
155 },
156 None => return Err(::Error::NotSupported),
157 };
158 out.write_all(&cmd)?;
159 Ok(())
160 }
161}
162
163#[derive(Debug, Eq, PartialEq)]
164pub enum Error {
166 BadMagic(u16),
170 NotUtf8(::std::str::Utf8Error),
176 ShortNames,
178 TooManyBools,
180 TooManyNumbers,
182 TooManyStrings,
184 InvalidLength,
186 NamesMissingNull,
188 StringsMissingNull,
190}
191
192impl ::std::fmt::Display for Error {
193 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
194 use std::error::Error;
195 match *self {
196 NotUtf8(e) => write!(f, "{}", e),
197 BadMagic(v) => write!(f, "bad magic number {:x} in terminfo header", v),
198 _ => f.write_str(self.description()),
199 }
200 }
201}
202
203impl ::std::convert::From<::std::string::FromUtf8Error> for Error {
204 fn from(v: ::std::string::FromUtf8Error) -> Self {
205 NotUtf8(v.utf8_error())
206 }
207}
208
209impl ::std::error::Error for Error {
210 fn description(&self) -> &str {
211 match *self {
212 BadMagic(..) => "incorrect magic number at start of file",
213 ShortNames => "no names exposed, need at least one",
214 TooManyBools => "more boolean properties than libterm knows about",
215 TooManyNumbers => "more number properties than libterm knows about",
216 TooManyStrings => "more string properties than libterm knows about",
217 InvalidLength => "invalid length field value, must be >= -1",
218 NotUtf8(ref e) => e.description(),
219 NamesMissingNull => "names table missing NUL terminator",
220 StringsMissingNull => "string table missing NUL terminator",
221 }
222 }
223
224 fn cause(&self) -> Option<&::std::error::Error> {
225 match *self {
226 NotUtf8(ref e) => Some(e),
227 _ => None,
228 }
229 }
230}
231
232pub mod searcher;
233
234pub mod parser {
236 pub mod compiled;
238 mod names;
239}
240pub mod parm;
241
242fn cap_for_attr(attr: Attr) -> &'static str {
243 match attr {
244 Attr::Bold => "bold",
245 Attr::Dim => "dim",
246 Attr::Italic(true) => "sitm",
247 Attr::Italic(false) => "ritm",
248 Attr::Underline(true) => "smul",
249 Attr::Underline(false) => "rmul",
250 Attr::Blink => "blink",
251 Attr::Standout(true) => "smso",
252 Attr::Standout(false) => "rmso",
253 Attr::Reverse => "rev",
254 Attr::Secure => "invis",
255 Attr::ForegroundColor(_) => "setaf",
256 Attr::BackgroundColor(_) => "setab",
257 }
258}
259
260#[derive(Clone, Debug)]
263pub struct TerminfoTerminal<T> {
264 num_colors: u32,
265 out: T,
266 ti: TermInfo,
267}
268
269impl<T: Write> Terminal for TerminfoTerminal<T> {
270 type Output = T;
271 fn fg(&mut self, color: color::Color) -> Result<()> {
272 let color = self.dim_if_necessary(color);
273 if self.num_colors > color {
274 return self.ti
275 .apply_cap("setaf", &[Param::Number(color as i32)], &mut self.out);
276 }
277 Err(::Error::ColorOutOfRange)
278 }
279
280 fn bg(&mut self, color: color::Color) -> Result<()> {
281 let color = self.dim_if_necessary(color);
282 if self.num_colors > color {
283 return self.ti
284 .apply_cap("setab", &[Param::Number(color as i32)], &mut self.out);
285 }
286 Err(::Error::ColorOutOfRange)
287 }
288
289 fn attr(&mut self, attr: Attr) -> Result<()> {
290 match attr {
291 Attr::ForegroundColor(c) => self.fg(c),
292 Attr::BackgroundColor(c) => self.bg(c),
293 _ => self.ti.apply_cap(cap_for_attr(attr), &[], &mut self.out),
294 }
295 }
296
297 fn supports_attr(&self, attr: Attr) -> bool {
298 match attr {
299 Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
300 _ => {
301 let cap = cap_for_attr(attr);
302 self.ti.strings.get(cap).is_some()
303 }
304 }
305 }
306
307 fn reset(&mut self) -> Result<()> {
308 self.ti.reset(&mut self.out)
309 }
310
311 fn supports_reset(&self) -> bool {
312 ["sgr0", "sgr", "op"]
313 .iter()
314 .any(|&cap| self.ti.strings.get(cap).is_some())
315 }
316
317 fn supports_color(&self) -> bool {
318 self.num_colors > 0 && self.supports_reset()
319 }
320
321 fn cursor_up(&mut self) -> Result<()> {
322 self.ti.apply_cap("cuu1", &[], &mut self.out)
323 }
324
325 fn delete_line(&mut self) -> Result<()> {
326 self.ti.apply_cap("el", &[], &mut self.out)
327 }
328
329 fn carriage_return(&mut self) -> Result<()> {
330 self.ti.apply_cap("cr", &[], &mut self.out)
331 }
332
333 fn get_ref(&self) -> &T {
334 &self.out
335 }
336
337 fn get_mut(&mut self) -> &mut T {
338 &mut self.out
339 }
340
341 fn into_inner(self) -> T
342 where
343 Self: Sized,
344 {
345 self.out
346 }
347}
348
349impl<T: Write> TerminfoTerminal<T> {
350 pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
352 let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
353 {
354 terminfo.numbers.get("colors").map_or(0, |&n| n)
355 } else {
356 0
357 };
358
359 TerminfoTerminal {
360 out: out,
361 ti: terminfo,
362 num_colors: nc as u32,
363 }
364 }
365
366 pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
370 TermInfo::from_env()
371 .map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti))
372 .ok()
373 }
374
375 fn dim_if_necessary(&self, color: color::Color) -> color::Color {
376 if color >= self.num_colors && color >= 8 && color < 16 {
377 color - 8
378 } else {
379 color
380 }
381 }
382}
383
384impl<T: Write> Write for TerminfoTerminal<T> {
385 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
386 self.out.write(buf)
387 }
388
389 fn flush(&mut self) -> io::Result<()> {
390 self.out.flush()
391 }
392}