1 //! Terminfo database interface.
3 use std::collections::HashMap;
8 use std::io::{self, prelude::*, BufReader};
14 use parm::{expand, Param, Variables};
15 use parser::compiled::{msys_terminfo, parse};
16 use searcher::get_dbpath_for_term;
18 /// A parsed terminfo database entry.
20 pub(crate) struct TermInfo {
21 /// Names for the terminal
22 pub(crate) names: Vec<String>,
23 /// Map of capability name to boolean value
24 pub(crate) bools: HashMap<String, bool>,
25 /// Map of capability name to numeric value
26 pub(crate) numbers: HashMap<String, u32>,
27 /// Map of capability name to raw (unexpanded) string
28 pub(crate) strings: HashMap<String, Vec<u8>>,
31 /// A terminfo creation error.
33 pub(crate) enum Error {
34 /// TermUnset Indicates that the environment doesn't include enough information to find
35 /// the terminfo entry.
37 /// MalformedTerminfo indicates that parsing the terminfo entry failed.
38 MalformedTerminfo(String),
39 /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
43 impl error::Error for Error {
44 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
47 IoError(e) => Some(e),
53 impl fmt::Display for Error {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 MalformedTerminfo(ref e) => e.fmt(f),
59 IoError(ref e) => e.fmt(f),
65 /// Creates a TermInfo based on current environment.
66 pub(crate) fn from_env() -> Result<TermInfo, Error> {
67 let term = match env::var("TERM") {
68 Ok(name) => TermInfo::from_name(&name),
69 Err(..) => return Err(Error::TermUnset),
72 if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
80 /// Creates a TermInfo for the named terminal.
81 pub(crate) fn from_name(name: &str) -> Result<TermInfo, Error> {
82 get_dbpath_for_term(name)
84 Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
86 .and_then(|p| TermInfo::from_path(&(*p)))
89 /// Parse the given TermInfo.
90 pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
91 Self::_from_path(path.as_ref())
93 // Keep the metadata small
94 fn _from_path(path: &Path) -> Result<TermInfo, Error> {
95 let file = File::open(path).map_err(Error::IoError)?;
96 let mut reader = BufReader::new(file);
97 parse(&mut reader, false).map_err(Error::MalformedTerminfo)
101 pub(crate) mod searcher;
103 /// TermInfo format parsing.
104 pub(crate) mod parser {
105 //! ncurses-compatible compiled terminfo format parsing (term(5))
106 pub(crate) mod compiled;
110 /// A Terminal that knows how many colors it supports, with a reference to its
111 /// parsed Terminfo database record.
112 pub(crate) struct TerminfoTerminal<T> {
118 impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
119 fn fg(&mut self, color: color::Color) -> io::Result<bool> {
120 let color = self.dim_if_necessary(color);
121 if self.num_colors > color {
122 return self.apply_cap("setaf", &[Param::Number(color as i32)]);
127 fn reset(&mut self) -> io::Result<bool> {
128 // are there any terminals that have color/attrs and not sgr0?
129 // Try falling back to sgr, then op
130 let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
131 Some(op) => match expand(&op, &[], &mut Variables::new()) {
133 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
135 None => return Ok(false),
137 self.out.write_all(&cmd).and(Ok(true))
141 impl<T: Write + Send> TerminfoTerminal<T> {
142 /// Creates a new TerminfoTerminal with the given TermInfo and Write.
143 pub(crate) fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
144 let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
146 terminfo.numbers.get("colors").map_or(0, |&n| n)
151 TerminfoTerminal { out, ti: terminfo, num_colors: nc }
154 /// Creates a new TerminfoTerminal for the current environment with the given Write.
156 /// Returns `None` when the terminfo cannot be found or parsed.
157 pub(crate) fn new(out: T) -> Option<TerminfoTerminal<T>> {
158 TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
161 fn dim_if_necessary(&self, color: color::Color) -> color::Color {
162 if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color }
165 fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
166 match self.ti.strings.get(cmd) {
167 Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
168 Ok(s) => self.out.write_all(&s).and(Ok(true)),
169 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
176 impl<T: Write> Write for TerminfoTerminal<T> {
177 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
181 fn flush(&mut self) -> io::Result<()> {