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