diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/test/src/term/terminfo/mod.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/test/src/term/terminfo/mod.rs')
-rw-r--r-- | library/test/src/term/terminfo/mod.rs | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/library/test/src/term/terminfo/mod.rs b/library/test/src/term/terminfo/mod.rs new file mode 100644 index 000000000..694473f52 --- /dev/null +++ b/library/test/src/term/terminfo/mod.rs @@ -0,0 +1,185 @@ +//! Terminfo database interface. + +use std::collections::HashMap; +use std::env; +use std::error; +use std::fmt; +use std::fs::File; +use std::io::{self, prelude::*, BufReader}; +use std::path::Path; + +use super::color; +use super::Terminal; + +use parm::{expand, Param, Variables}; +use parser::compiled::{msys_terminfo, parse}; +use searcher::get_dbpath_for_term; + +/// A parsed terminfo database entry. +#[allow(unused)] +#[derive(Debug)] +pub(crate) struct TermInfo { + /// Names for the terminal + pub(crate) names: Vec<String>, + /// Map of capability name to boolean value + pub(crate) bools: HashMap<String, bool>, + /// Map of capability name to numeric value + pub(crate) numbers: HashMap<String, u32>, + /// Map of capability name to raw (unexpanded) string + pub(crate) strings: HashMap<String, Vec<u8>>, +} + +/// A terminfo creation error. +#[derive(Debug)] +pub(crate) enum Error { + /// TermUnset Indicates that the environment doesn't include enough information to find + /// the terminfo entry. + TermUnset, + /// MalformedTerminfo indicates that parsing the terminfo entry failed. + MalformedTerminfo(String), + /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry. + IoError(io::Error), +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use Error::*; + match self { + IoError(e) => Some(e), + _ => None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Error::*; + match *self { + TermUnset => Ok(()), + MalformedTerminfo(ref e) => e.fmt(f), + IoError(ref e) => e.fmt(f), + } + } +} + +impl TermInfo { + /// Creates a TermInfo based on current environment. + pub(crate) fn from_env() -> Result<TermInfo, Error> { + let term = match env::var("TERM") { + Ok(name) => TermInfo::from_name(&name), + Err(..) => return Err(Error::TermUnset), + }; + + if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) { + // msys terminal + Ok(msys_terminfo()) + } else { + term + } + } + + /// Creates a TermInfo for the named terminal. + pub(crate) fn from_name(name: &str) -> Result<TermInfo, Error> { + get_dbpath_for_term(name) + .ok_or_else(|| { + Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found")) + }) + .and_then(|p| TermInfo::from_path(&(*p))) + } + + /// Parse the given TermInfo. + pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> { + Self::_from_path(path.as_ref()) + } + // Keep the metadata small + fn _from_path(path: &Path) -> Result<TermInfo, Error> { + let file = File::open(path).map_err(Error::IoError)?; + let mut reader = BufReader::new(file); + parse(&mut reader, false).map_err(Error::MalformedTerminfo) + } +} + +pub(crate) mod searcher; + +/// TermInfo format parsing. +pub(crate) mod parser { + //! ncurses-compatible compiled terminfo format parsing (term(5)) + pub(crate) mod compiled; +} +pub(crate) mod parm; + +/// A Terminal that knows how many colors it supports, with a reference to its +/// parsed Terminfo database record. +pub(crate) struct TerminfoTerminal<T> { + num_colors: u32, + out: T, + ti: TermInfo, +} + +impl<T: Write + Send> Terminal for TerminfoTerminal<T> { + fn fg(&mut self, color: color::Color) -> io::Result<bool> { + let color = self.dim_if_necessary(color); + if self.num_colors > color { + return self.apply_cap("setaf", &[Param::Number(color as i32)]); + } + Ok(false) + } + + fn reset(&mut self) -> io::Result<bool> { + // are there any terminals that have color/attrs and not sgr0? + // Try falling back to sgr, then op + let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) { + Some(op) => match expand(&op, &[], &mut Variables::new()) { + Ok(cmd) => cmd, + Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)), + }, + None => return Ok(false), + }; + self.out.write_all(&cmd).and(Ok(true)) + } +} + +impl<T: Write + Send> TerminfoTerminal<T> { + /// Creates a new TerminfoTerminal with the given TermInfo and Write. + pub(crate) fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> { + let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab") + { + terminfo.numbers.get("colors").map_or(0, |&n| n) + } else { + 0 + }; + + TerminfoTerminal { out, ti: terminfo, num_colors: nc } + } + + /// Creates a new TerminfoTerminal for the current environment with the given Write. + /// + /// Returns `None` when the terminfo cannot be found or parsed. + pub(crate) fn new(out: T) -> Option<TerminfoTerminal<T>> { + TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok() + } + + fn dim_if_necessary(&self, color: color::Color) -> color::Color { + if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color } + } + + fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> { + match self.ti.strings.get(cmd) { + Some(cmd) => match expand(&cmd, params, &mut Variables::new()) { + Ok(s) => self.out.write_all(&s).and(Ok(true)), + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + }, + None => Ok(false), + } + } +} + +impl<T: Write> Write for TerminfoTerminal<T> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.out.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.out.flush() + } +} |