test/term/terminfo/
mod.rs
1use std::collections::HashMap;
4use std::fs::File;
5use std::io::prelude::*;
6use std::path::Path;
7use std::{env, error, fmt, io};
8
9use parm::{Param, Variables, expand};
10use parser::compiled::{msys_terminfo, parse};
11use searcher::get_dbpath_for_term;
12
13use super::{Terminal, color};
14
15#[allow(unused)]
17#[derive(Debug)]
18pub(crate) struct TermInfo {
19 pub(crate) names: Vec<String>,
21 pub(crate) bools: HashMap<String, bool>,
23 pub(crate) numbers: HashMap<String, u32>,
25 pub(crate) strings: HashMap<String, Vec<u8>>,
27}
28
29#[derive(Debug)]
31pub(crate) enum Error {
32 TermUnset,
35 MalformedTerminfo(String),
37 IoError(io::Error),
39}
40
41impl error::Error for Error {
42 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
43 use Error::*;
44 match self {
45 IoError(e) => Some(e),
46 _ => None,
47 }
48 }
49}
50
51impl fmt::Display for Error {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 use Error::*;
54 match *self {
55 TermUnset => Ok(()),
56 MalformedTerminfo(ref e) => e.fmt(f),
57 IoError(ref e) => e.fmt(f),
58 }
59 }
60}
61
62impl TermInfo {
63 pub(crate) fn from_env() -> Result<TermInfo, Error> {
65 let term = match env::var("TERM") {
66 Ok(name) => TermInfo::from_name(&name),
67 Err(..) => return Err(Error::TermUnset),
68 };
69
70 if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
71 Ok(msys_terminfo())
73 } else {
74 term
75 }
76 }
77
78 pub(crate) fn from_name(name: &str) -> Result<TermInfo, Error> {
80 if cfg!(miri) {
81 return Ok(TermInfo {
84 names: Default::default(),
85 bools: Default::default(),
86 numbers: Default::default(),
87 strings: Default::default(),
88 });
89 }
90
91 get_dbpath_for_term(name)
92 .ok_or_else(|| {
93 Error::IoError(io::const_error!(io::ErrorKind::NotFound, "terminfo file not found"))
94 })
95 .and_then(|p| TermInfo::from_path(&(*p)))
96 }
97
98 pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
100 Self::_from_path(path.as_ref())
101 }
102 fn _from_path(path: &Path) -> Result<TermInfo, Error> {
104 let mut reader = File::open_buffered(path).map_err(Error::IoError)?;
105 parse(&mut reader, false).map_err(Error::MalformedTerminfo)
106 }
107}
108
109pub(crate) mod searcher;
110
111pub(crate) mod parser {
113 pub(crate) mod compiled;
115}
116pub(crate) mod parm;
117
118pub(crate) struct TerminfoTerminal<T> {
121 num_colors: u32,
122 out: T,
123 ti: TermInfo,
124}
125
126impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
127 fn fg(&mut self, color: color::Color) -> io::Result<bool> {
128 let color = self.dim_if_necessary(color);
129 if cfg!(miri) && color < 8 {
130 return write!(self.out, "\x1B[3{color}m").and(Ok(true));
134 }
135 if self.num_colors > color {
136 return self.apply_cap("setaf", &[Param::Number(color as i32)]);
137 }
138 Ok(false)
139 }
140
141 fn reset(&mut self) -> io::Result<bool> {
142 if cfg!(miri) {
143 return write!(self.out, "\x1B[0m").and(Ok(true));
144 }
145 let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
148 Some(op) => match expand(op, &[], &mut Variables::new()) {
149 Ok(cmd) => cmd,
150 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
151 },
152 None => return Ok(false),
153 };
154 self.out.write_all(&cmd).and(Ok(true))
155 }
156}
157
158impl<T: Write + Send> TerminfoTerminal<T> {
159 pub(crate) fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
161 let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
162 {
163 terminfo.numbers.get("colors").map_or(0, |&n| n)
164 } else {
165 0
166 };
167
168 TerminfoTerminal { out, ti: terminfo, num_colors: nc }
169 }
170
171 pub(crate) fn new(out: T) -> Option<TerminfoTerminal<T>> {
175 TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
176 }
177
178 fn dim_if_necessary(&self, color: color::Color) -> color::Color {
179 if color >= self.num_colors && (8..16).contains(&color) { color - 8 } else { color }
180 }
181
182 fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
183 match self.ti.strings.get(cmd) {
184 Some(cmd) => match expand(cmd, params, &mut Variables::new()) {
185 Ok(s) => self.out.write_all(&s).and(Ok(true)),
186 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
187 },
188 None => Ok(false),
189 }
190 }
191}
192
193impl<T: Write> Write for TerminfoTerminal<T> {
194 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
195 self.out.write(buf)
196 }
197
198 fn flush(&mut self) -> io::Result<()> {
199 self.out.flush()
200 }
201}