]> git.proxmox.com Git - rustc.git/blame - src/libterm/terminfo/mod.rs
Imported Upstream version 1.9.0+dfsg1
[rustc.git] / src / libterm / terminfo / mod.rs
CommitLineData
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
13use std::collections::HashMap;
85aaf69f 14use std::env;
92a42be0
SL
15use std::error;
16use std::fmt;
17use std::fs::File;
c34b1796
AL
18use std::io::prelude::*;
19use std::io;
92a42be0
SL
20use std::io::BufReader;
21use std::path::Path;
1a4d82fc 22
92a42be0 23use Attr;
1a4d82fc
JJ
24use color;
25use Terminal;
92a42be0 26use self::searcher::get_dbpath_for_term;
1a4d82fc 27use self::parser::compiled::{parse, msys_terminfo};
92a42be0 28use self::parm::{expand, Variables, Param};
1a4d82fc
JJ
29
30
31/// A parsed terminfo database entry.
85aaf69f 32#[derive(Debug)]
1a4d82fc
JJ
33pub 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)]
46pub 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
56impl 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
70impl 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
81impl 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
118pub mod searcher;
119
120/// TermInfo format parsing.
121pub mod parser {
122 //! ncurses-compatible compiled terminfo format parsing (term(5))
123 pub mod compiled;
124}
125pub mod parm;
126
127
92a42be0 128fn 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.
148pub struct TerminfoTerminal<T> {
149 num_colors: u16,
150 out: T,
92a42be0 151 ti: TermInfo,
1a4d82fc
JJ
152}
153
9cc50fc6 154impl<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 223impl<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
269impl<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}