1 //! Terminfo database interface.
3 use std::collections::HashMap;
8 use std::io::{self, prelude::*, BufReader};
15 use searcher::get_dbpath_for_term;
16 use parser::compiled::{parse, msys_terminfo};
17 use parm::{expand, Variables, Param};
19 /// A parsed terminfo database entry.
22 /// Names for the terminal
23 pub names: Vec<String>,
24 /// Map of capability name to boolean value
25 pub bools: HashMap<String, bool>,
26 /// Map of capability name to numeric value
27 pub numbers: HashMap<String, u16>,
28 /// Map of capability name to raw (unexpanded) string
29 pub strings: HashMap<String, Vec<u8>>,
32 /// A terminfo creation error.
35 /// TermUnset Indicates that the environment doesn't include enough information to find
36 /// the terminfo entry.
38 /// MalformedTerminfo indicates that parsing the terminfo entry failed.
39 MalformedTerminfo(String),
40 /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
44 impl error::Error for Error {
45 fn description(&self) -> &str {
46 "failed to create TermInfo"
49 fn cause(&self) -> Option<&dyn error::Error> {
52 IoError(ref e) => Some(e),
58 impl fmt::Display for Error {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 MalformedTerminfo(ref e) => e.fmt(f),
64 IoError(ref e) => e.fmt(f),
70 /// Creates a TermInfo based on current environment.
71 pub fn from_env() -> Result<TermInfo, Error> {
72 let term = match env::var("TERM") {
73 Ok(name) => TermInfo::from_name(&name),
74 Err(..) => return Err(Error::TermUnset),
77 if term.is_err() && env::var("MSYSCON").ok().map_or(false, |s| "mintty.exe" == s) {
85 /// Creates a TermInfo for the named terminal.
86 pub fn from_name(name: &str) -> Result<TermInfo, Error> {
87 get_dbpath_for_term(name)
89 Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
91 .and_then(|p| TermInfo::from_path(&(*p)))
94 /// Parse the given TermInfo.
95 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
96 Self::_from_path(path.as_ref())
98 // Keep the metadata small
99 fn _from_path(path: &Path) -> Result<TermInfo, Error> {
100 let file = File::open(path).map_err(Error::IoError)?;
101 let mut reader = BufReader::new(file);
102 parse(&mut reader, false).map_err(Error::MalformedTerminfo)
108 /// TermInfo format parsing.
110 //! ncurses-compatible compiled terminfo format parsing (term(5))
116 fn cap_for_attr(attr: Attr) -> &'static str {
118 Attr::Bold => "bold",
120 Attr::Italic(true) => "sitm",
121 Attr::Italic(false) => "ritm",
122 Attr::Underline(true) => "smul",
123 Attr::Underline(false) => "rmul",
124 Attr::Blink => "blink",
125 Attr::Standout(true) => "smso",
126 Attr::Standout(false) => "rmso",
127 Attr::Reverse => "rev",
128 Attr::Secure => "invis",
129 Attr::ForegroundColor(_) => "setaf",
130 Attr::BackgroundColor(_) => "setab",
134 /// A Terminal that knows how many colors it supports, with a reference to its
135 /// parsed Terminfo database record.
136 pub struct TerminfoTerminal<T> {
142 impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
144 fn fg(&mut self, color: color::Color) -> io::Result<bool> {
145 let color = self.dim_if_necessary(color);
146 if self.num_colors > color {
147 return self.apply_cap("setaf", &[Param::Number(color as i32)]);
152 fn bg(&mut self, color: color::Color) -> io::Result<bool> {
153 let color = self.dim_if_necessary(color);
154 if self.num_colors > color {
155 return self.apply_cap("setab", &[Param::Number(color as i32)]);
160 fn attr(&mut self, attr: Attr) -> io::Result<bool> {
162 Attr::ForegroundColor(c) => self.fg(c),
163 Attr::BackgroundColor(c) => self.bg(c),
164 _ => self.apply_cap(cap_for_attr(attr), &[]),
168 fn supports_attr(&self, attr: Attr) -> bool {
170 Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
172 let cap = cap_for_attr(attr);
173 self.ti.strings.get(cap).is_some()
178 fn reset(&mut self) -> io::Result<bool> {
179 // are there any terminals that have color/attrs and not sgr0?
180 // Try falling back to sgr, then op
181 let cmd = match ["sgr0", "sgr", "op"]
183 .filter_map(|cap| self.ti.strings.get(*cap))
186 match expand(&op, &[], &mut Variables::new()) {
188 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
191 None => return Ok(false),
193 self.out.write_all(&cmd).and(Ok(true))
196 fn get_ref(&self) -> &T {
200 fn get_mut(&mut self) -> &mut T {
204 fn into_inner(self) -> T
211 impl<T: Write + Send> TerminfoTerminal<T> {
212 /// Creates a new TerminfoTerminal with the given TermInfo and Write.
213 pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
214 let nc = if terminfo.strings.contains_key("setaf") &&
215 terminfo.strings.contains_key("setab") {
216 terminfo.numbers.get("colors").map_or(0, |&n| n)
228 /// Creates a new TerminfoTerminal for the current environment with the given Write.
230 /// Returns `None` when the terminfo cannot be found or parsed.
231 pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
232 TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
235 fn dim_if_necessary(&self, color: color::Color) -> color::Color {
236 if color >= self.num_colors && color >= 8 && color < 16 {
243 fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
244 match self.ti.strings.get(cmd) {
246 match expand(&cmd, params, &mut Variables::new()) {
247 Ok(s) => self.out.write_all(&s).and(Ok(true)),
248 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
257 impl<T: Write> Write for TerminfoTerminal<T> {
258 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
262 fn flush(&mut self) -> io::Result<()> {