]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | //! Terminfo database interface. |
2 | ||
3 | use std::collections::HashMap; | |
85aaf69f | 4 | use std::env; |
92a42be0 SL |
5 | use std::error; |
6 | use std::fmt; | |
7 | use std::fs::File; | |
9fa01778 | 8 | use std::io::{self, prelude::*, BufReader}; |
92a42be0 | 9 | use std::path::Path; |
1a4d82fc | 10 | |
9fa01778 | 11 | use crate::color; |
dfeec247 | 12 | use crate::Attr; |
9fa01778 | 13 | use crate::Terminal; |
1a4d82fc | 14 | |
dfeec247 XL |
15 | use parm::{expand, Param, Variables}; |
16 | use parser::compiled::{msys_terminfo, parse}; | |
9fa01778 | 17 | use searcher::get_dbpath_for_term; |
1a4d82fc JJ |
18 | |
19 | /// A parsed terminfo database entry. | |
85aaf69f | 20 | #[derive(Debug)] |
1a4d82fc JJ |
21 | pub struct TermInfo { |
22 | /// Names for the terminal | |
92a42be0 | 23 | pub names: Vec<String>, |
1a4d82fc JJ |
24 | /// Map of capability name to boolean value |
25 | pub bools: HashMap<String, bool>, | |
26 | /// Map of capability name to numeric value | |
dfeec247 | 27 | pub numbers: HashMap<String, u32>, |
1a4d82fc | 28 | /// Map of capability name to raw (unexpanded) string |
92a42be0 SL |
29 | pub strings: HashMap<String, Vec<u8>>, |
30 | } | |
31 | ||
32 | /// A terminfo creation error. | |
33 | #[derive(Debug)] | |
34 | pub enum Error { | |
35 | /// TermUnset Indicates that the environment doesn't include enough information to find | |
36 | /// the terminfo entry. | |
37 | TermUnset, | |
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. | |
41 | IoError(io::Error), | |
42 | } | |
43 | ||
44 | impl error::Error for Error { | |
dfeec247 | 45 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
9fa01778 | 46 | use Error::*; |
dfeec247 XL |
47 | match self { |
48 | IoError(e) => Some(e), | |
92a42be0 SL |
49 | _ => None, |
50 | } | |
51 | } | |
52 | } | |
53 | ||
54 | impl fmt::Display for Error { | |
9fa01778 XL |
55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
56 | use Error::*; | |
b7449926 XL |
57 | match *self { |
58 | TermUnset => Ok(()), | |
59 | MalformedTerminfo(ref e) => e.fmt(f), | |
60 | IoError(ref e) => e.fmt(f), | |
92a42be0 SL |
61 | } |
62 | } | |
63 | } | |
64 | ||
65 | impl TermInfo { | |
9fa01778 | 66 | /// Creates a TermInfo based on current environment. |
92a42be0 SL |
67 | pub fn from_env() -> Result<TermInfo, Error> { |
68 | let term = match env::var("TERM") { | |
69 | Ok(name) => TermInfo::from_name(&name), | |
70 | Err(..) => return Err(Error::TermUnset), | |
71 | }; | |
72 | ||
60c5eb7d | 73 | if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) { |
92a42be0 SL |
74 | // msys terminal |
75 | Ok(msys_terminfo()) | |
76 | } else { | |
77 | term | |
78 | } | |
79 | } | |
80 | ||
9fa01778 | 81 | /// Creates a TermInfo for the named terminal. |
92a42be0 SL |
82 | pub fn from_name(name: &str) -> Result<TermInfo, Error> { |
83 | get_dbpath_for_term(name) | |
84 | .ok_or_else(|| { | |
85 | Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found")) | |
86 | }) | |
87 | .and_then(|p| TermInfo::from_path(&(*p))) | |
88 | } | |
89 | ||
90 | /// Parse the given TermInfo. | |
91 | pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> { | |
92 | Self::_from_path(path.as_ref()) | |
93 | } | |
94 | // Keep the metadata small | |
95 | fn _from_path(path: &Path) -> Result<TermInfo, Error> { | |
b7449926 | 96 | let file = File::open(path).map_err(Error::IoError)?; |
92a42be0 | 97 | let mut reader = BufReader::new(file); |
b7449926 | 98 | parse(&mut reader, false).map_err(Error::MalformedTerminfo) |
92a42be0 | 99 | } |
1a4d82fc JJ |
100 | } |
101 | ||
102 | pub mod searcher; | |
103 | ||
104 | /// TermInfo format parsing. | |
105 | pub mod parser { | |
106 | //! ncurses-compatible compiled terminfo format parsing (term(5)) | |
107 | pub mod compiled; | |
108 | } | |
109 | pub mod parm; | |
110 | ||
92a42be0 | 111 | fn cap_for_attr(attr: Attr) -> &'static str { |
1a4d82fc | 112 | match attr { |
92a42be0 SL |
113 | Attr::Bold => "bold", |
114 | Attr::Dim => "dim", | |
115 | Attr::Italic(true) => "sitm", | |
116 | Attr::Italic(false) => "ritm", | |
117 | Attr::Underline(true) => "smul", | |
118 | Attr::Underline(false) => "rmul", | |
119 | Attr::Blink => "blink", | |
120 | Attr::Standout(true) => "smso", | |
121 | Attr::Standout(false) => "rmso", | |
122 | Attr::Reverse => "rev", | |
123 | Attr::Secure => "invis", | |
124 | Attr::ForegroundColor(_) => "setaf", | |
125 | Attr::BackgroundColor(_) => "setab", | |
1a4d82fc JJ |
126 | } |
127 | } | |
128 | ||
129 | /// A Terminal that knows how many colors it supports, with a reference to its | |
130 | /// parsed Terminfo database record. | |
131 | pub struct TerminfoTerminal<T> { | |
dfeec247 | 132 | num_colors: u32, |
1a4d82fc | 133 | out: T, |
92a42be0 | 134 | ti: TermInfo, |
1a4d82fc JJ |
135 | } |
136 | ||
9cc50fc6 | 137 | impl<T: Write + Send> Terminal for TerminfoTerminal<T> { |
92a42be0 | 138 | type Output = T; |
c34b1796 | 139 | fn fg(&mut self, color: color::Color) -> io::Result<bool> { |
1a4d82fc JJ |
140 | let color = self.dim_if_necessary(color); |
141 | if self.num_colors > color { | |
92a42be0 | 142 | return self.apply_cap("setaf", &[Param::Number(color as i32)]); |
1a4d82fc JJ |
143 | } |
144 | Ok(false) | |
145 | } | |
146 | ||
c34b1796 | 147 | fn bg(&mut self, color: color::Color) -> io::Result<bool> { |
1a4d82fc JJ |
148 | let color = self.dim_if_necessary(color); |
149 | if self.num_colors > color { | |
92a42be0 | 150 | return self.apply_cap("setab", &[Param::Number(color as i32)]); |
1a4d82fc JJ |
151 | } |
152 | Ok(false) | |
153 | } | |
154 | ||
92a42be0 | 155 | fn attr(&mut self, attr: Attr) -> io::Result<bool> { |
1a4d82fc | 156 | match attr { |
92a42be0 SL |
157 | Attr::ForegroundColor(c) => self.fg(c), |
158 | Attr::BackgroundColor(c) => self.bg(c), | |
159 | _ => self.apply_cap(cap_for_attr(attr), &[]), | |
1a4d82fc JJ |
160 | } |
161 | } | |
162 | ||
92a42be0 | 163 | fn supports_attr(&self, attr: Attr) -> bool { |
1a4d82fc | 164 | match attr { |
92a42be0 | 165 | Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0, |
1a4d82fc JJ |
166 | _ => { |
167 | let cap = cap_for_attr(attr); | |
168 | self.ti.strings.get(cap).is_some() | |
169 | } | |
170 | } | |
171 | } | |
172 | ||
92a42be0 SL |
173 | fn reset(&mut self) -> io::Result<bool> { |
174 | // are there any terminals that have color/attrs and not sgr0? | |
175 | // Try falling back to sgr, then op | |
dfeec247 XL |
176 | let cmd = |
177 | match ["sgr0", "sgr", "op"].iter().filter_map(|cap| self.ti.strings.get(*cap)).next() { | |
178 | Some(op) => match expand(&op, &[], &mut Variables::new()) { | |
92a42be0 SL |
179 | Ok(cmd) => cmd, |
180 | Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)), | |
dfeec247 XL |
181 | }, |
182 | None => return Ok(false), | |
183 | }; | |
92a42be0 | 184 | self.out.write_all(&cmd).and(Ok(true)) |
1a4d82fc JJ |
185 | } |
186 | ||
7453a54e | 187 | fn get_ref(&self) -> &T { |
92a42be0 SL |
188 | &self.out |
189 | } | |
1a4d82fc | 190 | |
7453a54e | 191 | fn get_mut(&mut self) -> &mut T { |
92a42be0 SL |
192 | &mut self.out |
193 | } | |
1a4d82fc | 194 | |
92a42be0 | 195 | fn into_inner(self) -> T |
dfeec247 XL |
196 | where |
197 | Self: Sized, | |
92a42be0 SL |
198 | { |
199 | self.out | |
200 | } | |
1a4d82fc JJ |
201 | } |
202 | ||
9cc50fc6 | 203 | impl<T: Write + Send> TerminfoTerminal<T> { |
9fa01778 | 204 | /// Creates a new TerminfoTerminal with the given TermInfo and Write. |
92a42be0 | 205 | pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> { |
dfeec247 XL |
206 | let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab") |
207 | { | |
92a42be0 SL |
208 | terminfo.numbers.get("colors").map_or(0, |&n| n) |
209 | } else { | |
210 | 0 | |
1a4d82fc JJ |
211 | }; |
212 | ||
dfeec247 | 213 | TerminfoTerminal { out, ti: terminfo, num_colors: nc } |
92a42be0 | 214 | } |
1a4d82fc | 215 | |
9fa01778 | 216 | /// Creates a new TerminfoTerminal for the current environment with the given Write. |
92a42be0 SL |
217 | /// |
218 | /// Returns `None` when the terminfo cannot be found or parsed. | |
219 | pub fn new(out: T) -> Option<TerminfoTerminal<T>> { | |
220 | TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok() | |
1a4d82fc JJ |
221 | } |
222 | ||
223 | fn dim_if_necessary(&self, color: color::Color) -> color::Color { | |
dfeec247 | 224 | if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color } |
92a42be0 SL |
225 | } |
226 | ||
227 | fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> { | |
228 | match self.ti.strings.get(cmd) { | |
dfeec247 XL |
229 | Some(cmd) => match expand(&cmd, params, &mut Variables::new()) { |
230 | Ok(s) => self.out.write_all(&s).and(Ok(true)), | |
231 | Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), | |
232 | }, | |
92a42be0 SL |
233 | None => Ok(false), |
234 | } | |
1a4d82fc JJ |
235 | } |
236 | } | |
237 | ||
c34b1796 AL |
238 | impl<T: Write> Write for TerminfoTerminal<T> { |
239 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
240 | self.out.write(buf) | |
1a4d82fc JJ |
241 | } |
242 | ||
c34b1796 | 243 | fn flush(&mut self) -> io::Result<()> { |
1a4d82fc JJ |
244 | self.out.flush() |
245 | } | |
246 | } |