提交 0ee230a0 编写于 作者: B Bryce Van Dyk

libterm: bring across changes from term

This brings across changes made to the term library to libterm. This
includes removing instances or unwrap, fixing format string handling, and
removing a TODO.

This fix does not bring all changes across, as term now relies on cargo
deps that cannot be brought into the rust build at this stage, but has
attempted as best to cross port changes not relying on this. This notably
limits extra functionality since implemented int he Terminal trait in
Term.

This is in partly in response to rust issue #29992.
上级 2f95de3b
......@@ -20,7 +20,7 @@
use std::{cmp, error, fmt};
use std::io::prelude::*;
use std::io;
use term::{self, WriterWrapper};
use term;
/// maximum number of lines we will print for each error; arbitrary.
const MAX_LINES: usize = 6;
......@@ -318,7 +318,7 @@ pub struct EmitterWriter {
}
enum Destination {
Terminal(Box<term::Terminal<WriterWrapper> + Send>),
Terminal(Box<term::StderrTerminal>),
Raw(Box<Write + Send>),
}
......@@ -365,7 +365,7 @@ pub fn new(dst: Box<Write + Send>,
fn print_maybe_styled(&mut self,
args: fmt::Arguments,
color: term::attr::Attr,
color: term::Attr,
print_newline_at_end: bool) -> io::Result<()> {
match self.dst {
Terminal(ref mut t) => {
......@@ -408,13 +408,13 @@ fn print_diagnostic(&mut self, topic: &str, lvl: Level,
try!(write!(&mut self.dst, "{} ", topic));
}
try!(print_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
try!(print_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}: ", lvl.to_string()));
try!(print_maybe_styled!(self, term::attr::Bold, "{}", msg));
try!(print_maybe_styled!(self, term::Attr::Bold, "{}", msg));
match code {
Some(code) => {
let style = term::attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
try!(print_maybe_styled!(self, style, " [{}]", code.clone()));
}
None => ()
......@@ -646,7 +646,7 @@ fn highlight_lines(&mut self,
s.pop();
}
try!(println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
try!(println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s));
}
}
......@@ -719,7 +719,7 @@ fn end_highlight_lines(&mut self,
}
}
s.push('^');
println_maybe_styled!(self, term::attr::ForegroundColor(lvl.color()),
println_maybe_styled!(self, term::Attr::ForegroundColor(lvl.color()),
"{}", s)
}
......
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
......@@ -11,9 +11,9 @@
//! Terminal formatting library.
//!
//! This crate provides the `Terminal` trait, which abstracts over an [ANSI
//! Terminal][ansi] to provide color printing, among other things. There are two implementations,
//! the `TerminfoTerminal`, which uses control characters from a
//! [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
//! Terminal][ansi] to provide color printing, among other things. There are two
//! implementations, the `TerminfoTerminal`, which uses control characters from
//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
//! API][win].
//!
//! # Examples
......@@ -21,19 +21,18 @@
//! ```no_run
//! # #![feature(rustc_private)]
//! extern crate term;
//!
//! use std::io::prelude::*;
//!
//! fn main() {
//! let mut t = term::stdout().unwrap();
//!
//! t.fg(term::color::GREEN).unwrap();
//! (write!(t, "hello, ")).unwrap();
//! write!(t, "hello, ").unwrap();
//!
//! t.fg(term::color::RED).unwrap();
//! (writeln!(t, "world!")).unwrap();
//! writeln!(t, "world!").unwrap();
//!
//! t.reset().unwrap();
//! assert!(t.reset().unwrap());
//! }
//! ```
//!
......@@ -58,84 +57,60 @@
#![deny(missing_docs)]
#![feature(box_syntax)]
#![feature(rustc_private)]
#![feature(staged_api)]
#![feature(str_char)]
#![feature(vec_push_all)]
#![cfg_attr(windows, feature(libc))]
// Handle rustfmt skips
#![feature(custom_attribute)]
#![allow(unused_attributes)]
#[macro_use]
extern crate log;
use std::io::prelude::*;
pub use terminfo::TerminfoTerminal;
#[cfg(windows)]
pub use win::WinConsole;
use std::io::prelude::*;
use std::io;
use std::io::{self, Stdout, Stderr};
pub mod terminfo;
#[cfg(windows)]
mod win;
/// A hack to work around the fact that `Box<Write + Send>` does not
/// currently implement `Write`.
pub struct WriterWrapper {
wrapped: Box<Write + Send>,
}
impl Write for WriterWrapper {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.wrapped.write(buf)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.wrapped.flush()
}
}
/// Alias for stdout terminals.
pub type StdoutTerminal = Terminal<Output=Stdout> + Send;
/// Alias for stderr terminals.
pub type StderrTerminal = Terminal<Output=Stderr> + Send;
#[cfg(not(windows))]
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
/// opened.
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() })
pub fn stdout() -> Option<Box<StdoutTerminal>> {
TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
}
#[cfg(windows)]
/// Return a Terminal wrapping stdout, or None if a terminal couldn't be
/// opened.
pub fn stdout() -> Option<Box<Terminal<WriterWrapper> + Send>> {
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stdout() });
match ti {
Some(t) => Some(t),
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stdout() }),
}
pub fn stdout() -> Option<Box<StdoutTerminal>> {
TerminfoTerminal::new(io::stdout())
.map(|t| Box::new(t) as Box<StdoutTerminal>)
.or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
}
#[cfg(not(windows))]
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
/// opened.
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() })
pub fn stderr() -> Option<Box<StderrTerminal>> {
TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>)
}
#[cfg(windows)]
/// Return a Terminal wrapping stderr, or None if a terminal couldn't be
/// opened.
pub fn stderr() -> Option<Box<Terminal<WriterWrapper> + Send>> {
let ti = TerminfoTerminal::new(WriterWrapper { wrapped: box std::io::stderr() });
match ti {
Some(t) => Some(t),
None => WinConsole::new(WriterWrapper { wrapped: box std::io::stderr() }),
}
pub fn stderr() -> Option<Box<StderrTerminal>> {
TerminfoTerminal::new(io::stderr())
.map(|t| Box::new(t) as Box<StderrTerminal>)
.or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box<StderrTerminal>))
}
......@@ -164,43 +139,41 @@ pub mod color {
pub const BRIGHT_WHITE: Color = 15;
}
/// Terminal attributes
pub mod attr {
pub use self::Attr::*;
/// Terminal attributes for use with term.attr().
///
/// Most attributes can only be turned on and must be turned off with term.reset().
/// The ones that can be turned off explicitly take a boolean value.
/// Color is also represented as an attribute for convenience.
#[derive(Copy, Clone)]
pub enum Attr {
/// Bold (or possibly bright) mode
Bold,
/// Dim mode, also called faint or half-bright. Often not supported
Dim,
/// Italics mode. Often not supported
Italic(bool),
/// Underline mode
Underline(bool),
/// Blink mode
Blink,
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
Standout(bool),
/// Reverse mode, inverts the foreground and background colors
Reverse,
/// Secure mode, also called invis mode. Hides the printed text
Secure,
/// Convenience attribute to set the foreground color
ForegroundColor(super::color::Color),
/// Convenience attribute to set the background color
BackgroundColor(super::color::Color),
}
/// Terminal attributes for use with term.attr().
///
/// Most attributes can only be turned on and must be turned off with term.reset().
/// The ones that can be turned off explicitly take a boolean value.
/// Color is also represented as an attribute for convenience.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Attr {
/// Bold (or possibly bright) mode
Bold,
/// Dim mode, also called faint or half-bright. Often not supported
Dim,
/// Italics mode. Often not supported
Italic(bool),
/// Underline mode
Underline(bool),
/// Blink mode
Blink,
/// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
Standout(bool),
/// Reverse mode, inverts the foreground and background colors
Reverse,
/// Secure mode, also called invis mode. Hides the printed text
Secure,
/// Convenience attribute to set the foreground color
ForegroundColor(color::Color),
/// Convenience attribute to set the background color
BackgroundColor(color::Color),
}
/// A terminal with similar capabilities to an ANSI Terminal
/// (foreground/background colors etc).
pub trait Terminal<T: Write>: Write {
pub trait Terminal: Write {
/// The terminal's output writer type.
type Output: Write;
/// Sets the foreground color to the given color.
///
/// If the color is a bright color, but the terminal only supports 8 colors,
......@@ -222,24 +195,29 @@ pub trait Terminal<T: Write>: Write {
/// Sets the given terminal attribute, if supported. Returns `Ok(true)`
/// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
/// there was an I/O error.
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool>;
fn attr(&mut self, attr: Attr) -> io::Result<bool>;
/// Returns whether the given terminal attribute is supported.
fn supports_attr(&self, attr: attr::Attr) -> bool;
fn supports_attr(&self, attr: Attr) -> bool;
/// Resets all terminal attributes and color to the default.
/// Returns `Ok()`.
fn reset(&mut self) -> io::Result<()>;
/// Resets all terminal attributes and colors to their defaults.
///
/// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
/// was an I/O error.
///
/// *Note: This does not flush.*
///
/// That means the reset command may get buffered so, if you aren't planning on doing anything
/// else that might flush stdout's buffer (e.g. writing a line of text), you should flush after
/// calling reset.
fn reset(&mut self) -> io::Result<bool>;
/// Gets an immutable reference to the stream inside
fn get_ref<'a>(&'a self) -> &'a T;
fn get_ref<'a>(&'a self) -> &'a Self::Output;
/// Gets a mutable reference to the stream inside
fn get_mut<'a>(&'a mut self) -> &'a mut T;
}
fn get_mut<'a>(&'a mut self) -> &'a mut Self::Output;
/// A terminal which can be unwrapped.
pub trait UnwrappableTerminal<T: Write>: Terminal<T> {
/// Returns the contained stream, destroying the `Terminal`
fn unwrap(self) -> T;
fn into_inner(self) -> Self::Output where Self: Sized;
}
......@@ -12,16 +12,20 @@
use std::collections::HashMap;
use std::env;
use std::error;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::io;
use std::io::BufReader;
use std::path::Path;
use attr;
use Attr;
use color;
use Terminal;
use UnwrappableTerminal;
use self::searcher::open;
use self::searcher::get_dbpath_for_term;
use self::parser::compiled::{parse, msys_terminfo};
use self::parm::{expand, Number, Variables};
use self::parm::{expand, Variables, Param};
/// A parsed terminfo database entry.
......@@ -37,6 +41,80 @@ pub struct TermInfo {
pub strings: HashMap<String, Vec<u8>>,
}
/// A terminfo creation error.
#[derive(Debug)]
pub 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 description(&self) -> &str {
"failed to create TermInfo"
}
fn cause(&self) -> Option<&error::Error> {
use self::Error::*;
match self {
&IoError(ref e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
&TermUnset => Ok(()),
&MalformedTerminfo(ref e) => e.fmt(f),
&IoError(ref e) => e.fmt(f),
}
}
}
impl TermInfo {
/// Create a TermInfo based on current environment.
pub 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").ok().map_or(false, |s| "mintty.exe" == s) {
// msys terminal
Ok(msys_terminfo())
} else {
term
}
}
/// Create a TermInfo for the named terminal.
pub 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 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 = try!(File::open(path).map_err(|e| Error::IoError(e)));
let mut reader = BufReader::new(file);
parse(&mut reader, false).map_err(|e| Error::MalformedTerminfo(e))
}
}
pub mod searcher;
/// TermInfo format parsing.
......@@ -47,21 +125,21 @@ pub mod parser {
pub mod parm;
fn cap_for_attr(attr: attr::Attr) -> &'static str {
fn cap_for_attr(attr: Attr) -> &'static str {
match attr {
attr::Bold => "bold",
attr::Dim => "dim",
attr::Italic(true) => "sitm",
attr::Italic(false) => "ritm",
attr::Underline(true) => "smul",
attr::Underline(false) => "rmul",
attr::Blink => "blink",
attr::Standout(true) => "smso",
attr::Standout(false) => "rmso",
attr::Reverse => "rev",
attr::Secure => "invis",
attr::ForegroundColor(_) => "setaf",
attr::BackgroundColor(_) => "setab",
Attr::Bold => "bold",
Attr::Dim => "dim",
Attr::Italic(true) => "sitm",
Attr::Italic(false) => "ritm",
Attr::Underline(true) => "smul",
Attr::Underline(false) => "rmul",
Attr::Blink => "blink",
Attr::Standout(true) => "smso",
Attr::Standout(false) => "rmso",
Attr::Reverse => "rev",
Attr::Secure => "invis",
Attr::ForegroundColor(_) => "setaf",
Attr::BackgroundColor(_) => "setab",
}
}
......@@ -70,23 +148,15 @@ fn cap_for_attr(attr: attr::Attr) -> &'static str {
pub struct TerminfoTerminal<T> {
num_colors: u16,
out: T,
ti: Box<TermInfo>,
ti: TermInfo,
}
impl<T: Write+Send+'static> Terminal<T> for TerminfoTerminal<T> {
impl<T: Write+Send> Terminal for TerminfoTerminal<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(self.ti
.strings
.get("setaf")
.unwrap(),
&[Number(color as isize)],
&mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
return self.apply_cap("setaf", &[Param::Number(color as i32)]);
}
Ok(false)
}
......@@ -94,42 +164,22 @@ fn fg(&mut self, color: color::Color) -> io::Result<bool> {
fn bg(&mut self, color: color::Color) -> io::Result<bool> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(self.ti
.strings
.get("setab")
.unwrap(),
&[Number(color as isize)],
&mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
return self.apply_cap("setab", &[Param::Number(color as i32)]);
}
Ok(false)
}
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
match attr {
attr::ForegroundColor(c) => self.fg(c),
attr::BackgroundColor(c) => self.bg(c),
_ => {
let cap = cap_for_attr(attr);
let parm = self.ti.strings.get(cap);
if parm.is_some() {
let s = expand(parm.unwrap(), &[], &mut Variables::new());
if s.is_ok() {
try!(self.out.write_all(&s.unwrap()));
return Ok(true);
}
}
Ok(false)
}
Attr::ForegroundColor(c) => self.fg(c),
Attr::BackgroundColor(c) => self.bg(c),
_ => self.apply_cap(cap_for_attr(attr), &[]),
}
}
fn supports_attr(&self, attr: attr::Attr) -> bool {
fn supports_attr(&self, attr: Attr) -> bool {
match attr {
attr::ForegroundColor(_) | attr::BackgroundColor(_) => self.num_colors > 0,
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
_ => {
let cap = cap_for_attr(attr);
self.ti.strings.get(cap).is_some()
......@@ -137,22 +187,22 @@ fn supports_attr(&self, attr: attr::Attr) -> bool {
}
}
fn reset(&mut self) -> io::Result<()> {
let mut cap = self.ti.strings.get("sgr0");
if cap.is_none() {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
cap = self.ti.strings.get("sgr");
if cap.is_none() {
cap = self.ti.strings.get("op");
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 ["sg0", "sgr", "op"]
.iter()
.filter_map(|cap| self.ti.strings.get(*cap))
.next() {
Some(op) => {
match expand(&op, &[], &mut Variables::new()) {
Ok(cmd) => cmd,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
}
}
}
let s = cap.map_or(Err("can't find terminfo capability `sgr0`".to_owned()),
|op| expand(op, &[], &mut Variables::new()));
if s.is_ok() {
return self.out.write_all(&s.unwrap());
}
Ok(())
None => return Ok(false),
};
self.out.write_all(&cmd).and(Ok(true))
}
fn get_ref<'a>(&'a self) -> &'a T {
......@@ -162,64 +212,36 @@ fn get_ref<'a>(&'a self) -> &'a T {
fn get_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.out
}
}
impl<T: Write+Send+'static> UnwrappableTerminal<T> for TerminfoTerminal<T> {
fn unwrap(self) -> T {
fn into_inner(self) -> T
where Self: Sized
{
self.out
}
}
impl<T: Write+Send+'static> TerminfoTerminal<T> {
/// Returns `None` whenever the terminal cannot be created for some
/// reason.
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
let term = match env::var("TERM") {
Ok(t) => t,
Err(..) => {
debug!("TERM environment variable not defined");
return None;
}
};
let mut file = match open(&term[..]) {
Ok(f) => f,
Err(err) => {
return match env::var("MSYSCON") {
Ok(ref val) if &val[..] == "mintty.exe" => {
// msys terminal
Some(box TerminfoTerminal {
out: out,
ti: msys_terminfo(),
num_colors: 8,
})
}
_ => {
debug!("error finding terminfo entry: {:?}", err);
None
}
};
}
};
let ti = parse(&mut file, false);
if ti.is_err() {
debug!("error parsing terminfo entry: {:?}", ti.err().unwrap());
return None;
}
let inf = ti.unwrap();
let nc = if inf.strings.get("setaf").is_some() && inf.strings.get("setab").is_some() {
inf.numbers.get("colors").map_or(0, |&n| n)
impl<T: Write+Send> TerminfoTerminal<T> {
/// Create a new TerminfoTerminal with the given TermInfo and Write.
pub 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
};
Some(box TerminfoTerminal {
TerminfoTerminal {
out: out,
ti: inf,
ti: terminfo,
num_colors: nc,
})
}
}
/// Create a new TerminfoTerminal for the current environment with the given Write.
///
/// Returns `None` when the terminfo cannot be found or parsed.
pub 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 {
......@@ -229,6 +251,18 @@ fn dim_if_necessary(&self, color: color::Color) -> color::Color {
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),
}
}
}
......
此差异已折叠。
......@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(non_upper_case_globals)]
#![allow(non_upper_case_globals, missing_docs)]
//! ncurses-compatible compiled terminfo format parsing (term(5))
......@@ -20,7 +20,6 @@
// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
#[rustfmt_skip]
#[allow(missing_docs)]
pub static boolfnames: &'static[&'static str] = &["auto_left_margin", "auto_right_margin",
"no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
"hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
......@@ -34,14 +33,12 @@
"return_does_clr_eol"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static boolnames: &'static[&'static str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
"gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
"nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
"xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static numfnames: &'static[&'static str] = &[ "columns", "init_tabs", "lines",
"lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
"width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
......@@ -53,14 +50,12 @@
"new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static numnames: &'static[&'static str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
"vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
"spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
"btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static stringfnames: &'static[&'static str] = &[ "back_tab", "bell", "carriage_return",
"change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
"column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
......@@ -135,7 +130,6 @@
"acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
#[rustfmt_skip]
#[allow(missing_docs)]
pub static stringnames: &'static[&'static str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
"_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
"ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
......@@ -170,114 +164,132 @@
"OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
"box1"];
/// Parse a compiled terminfo entry, using long capability names if `longnames` is true
pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String> {
macro_rules! try { ($e:expr) => (
fn read_le_u16(r: &mut io::Read) -> io::Result<u16> {
let mut b = [0; 2];
let mut amt = 0;
while amt < b.len() {
match try!(r.read(&mut b[amt..])) {
0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")),
n => amt += n,
}
}
Ok((b[0] as u16) | ((b[1] as u16) << 8))
}
fn read_byte(r: &mut io::Read) -> io::Result<u8> {
match r.bytes().next() {
Some(s) => s,
None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
}
}
/// Parse a compiled terminfo entry, using long capability names if `longnames`
/// is true
pub fn parse(file: &mut io::Read, longnames: bool) -> Result<TermInfo, String> {
macro_rules! try( ($e:expr) => (
match $e {
Ok(e) => e,
Err(e) => return Err(format!("{:?}", e))
Err(e) => return Err(format!("{}", e))
}
) }
) );
let bnames;
let snames;
let nnames;
if longnames {
bnames = boolfnames;
snames = stringfnames;
nnames = numfnames;
let (bnames, snames, nnames) = if longnames {
(boolfnames, stringfnames, numfnames)
} else {
bnames = boolnames;
snames = stringnames;
nnames = numnames;
}
(boolnames, stringnames, numnames)
};
// Check magic number
let magic = try!(read_le_u16(file));
if magic != 0x011A {
return Err(format!("invalid magic number: expected {:x}, found {:x}",
0x011A_usize,
magic as usize));
0x011A,
magic));
}
let names_bytes = try!(read_le_u16(file)) as isize;
let bools_bytes = try!(read_le_u16(file)) as isize;
let numbers_count = try!(read_le_u16(file)) as isize;
let string_offsets_count = try!(read_le_u16(file)) as isize;
let string_table_bytes = try!(read_le_u16(file)) as isize;
// According to the spec, these fields must be >= -1 where -1 means that the feature is not
// supported. Using 0 instead of -1 works because we skip sections with length 0.
macro_rules! read_nonneg {
() => {{
match try!(read_le_u16(file)) as i16 {
n if n >= 0 => n as usize,
-1 => 0,
_ => return Err("incompatible file: length fields must be >= -1".to_string()),
}
}}
}
let names_bytes = read_nonneg!();
let bools_bytes = read_nonneg!();
let numbers_count = read_nonneg!();
let string_offsets_count = read_nonneg!();
let string_table_bytes = read_nonneg!();
assert!(names_bytes > 0);
if names_bytes == 0 {
return Err("incompatible file: names field must be at least 1 byte wide".to_string());
}
if (bools_bytes as usize) > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_owned());
if bools_bytes > boolnames.len() {
return Err("incompatible file: more booleans than expected".to_string());
}
if (numbers_count as usize) > numnames.len() {
return Err("incompatible file: more numbers than expected".to_owned());
if numbers_count > numnames.len() {
return Err("incompatible file: more numbers than expected".to_string());
}
if (string_offsets_count as usize) > stringnames.len() {
return Err("incompatible file: more string offsets than expected".to_owned());
if string_offsets_count > stringnames.len() {
return Err("incompatible file: more string offsets than expected".to_string());
}
// don't read NUL
let bytes = try!(read_exact(file, names_bytes as usize - 1));
let mut bytes = Vec::new();
try!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
let names_str = match String::from_utf8(bytes) {
Ok(s) => s,
Err(_) => return Err("input not utf-8".to_owned()),
Err(_) => return Err("input not utf-8".to_string()),
};
let term_names: Vec<String> = names_str.split('|')
.map(str::to_owned)
.map(|s| s.to_string())
.collect();
try!(read_byte(file)); // consume NUL
let mut bools_map = HashMap::new();
if bools_bytes != 0 {
for i in 0..bools_bytes {
let b = try!(read_byte(file));
if b == 1 {
bools_map.insert(bnames[i as usize].to_owned(), true);
}
}
// consume NUL
if try!(read_byte(file)) != b'\0' {
return Err("incompatible file: missing null terminator for names section".to_string());
}
let bools_map: HashMap<String, bool> = try! {
(0..bools_bytes).filter_map(|i| match read_byte(file) {
Err(e) => Some(Err(e)),
Ok(1) => Some(Ok((bnames[i].to_string(), true))),
Ok(_) => None
}).collect()
};
if (bools_bytes + names_bytes) % 2 == 1 {
try!(read_byte(file)); // compensate for padding
}
let mut numbers_map = HashMap::new();
if numbers_count != 0 {
for i in 0..numbers_count {
let n = try!(read_le_u16(file));
if n != 0xFFFF {
numbers_map.insert(nnames[i as usize].to_owned(), n);
}
}
}
let mut string_map = HashMap::new();
if string_offsets_count != 0 {
let mut string_offsets = Vec::with_capacity(10);
for _ in 0..string_offsets_count {
string_offsets.push(try!(read_le_u16(file)));
}
let numbers_map: HashMap<String, u16> = try! {
(0..numbers_count).filter_map(|i| match read_le_u16(file) {
Ok(0xFFFF) => None,
Ok(n) => Some(Ok((nnames[i].to_string(), n))),
Err(e) => Some(Err(e))
}).collect()
};
let string_table = try!(read_exact(file, string_table_bytes as usize));
let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
let string_offsets: Vec<u16> = try!((0..string_offsets_count)
.map(|_| read_le_u16(file))
.collect());
if string_table.len() != string_table_bytes as usize {
return Err("error: hit EOF before end of string table".to_owned());
}
let mut string_table = Vec::new();
try!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
for (i, v) in string_offsets.iter().enumerate() {
let offset = *v;
if offset == 0xFFFF {
// non-entry
continue;
}
try!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| {
// non-entry
offset != 0xFFFF
}).map(|(i, offset)| {
let offset = offset as usize;
let name = if snames[i] == "_" {
stringfnames[i]
......@@ -288,30 +300,22 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
if offset == 0xFFFE {
// undocumented: FFFE indicates cap@, which means the capability is not present
// unsure if the handling for this is correct
string_map.insert(name.to_owned(), Vec::new());
continue;
return Ok((name.to_string(), Vec::new()));
}
// Find the offset of the NUL we want to go to
let nulpos = string_table[offset as usize..string_table_bytes as usize]
.iter()
.position(|&b| b == 0);
let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
match nulpos {
Some(len) => {
string_map.insert(name.to_string(),
string_table[offset as usize..(offset as usize + len)]
.to_vec())
}
None => {
return Err("invalid file: missing NUL in string_table".to_owned());
}
};
}
}
Some(len) => Ok((name.to_string(), string_table[offset..offset + len].to_vec())),
None => Err("invalid file: missing NUL in string_table".to_string()),
}
}).collect())
} else {
HashMap::new()
};
// And that's all there is to it
Ok(box TermInfo {
Ok(TermInfo {
names: term_names,
bools: bools_map,
numbers: numbers_map,
......@@ -319,42 +323,27 @@ pub fn parse(file: &mut Read, longnames: bool) -> Result<Box<TermInfo>, String>
})
}
fn read_le_u16<R: Read + ?Sized>(r: &mut R) -> io::Result<u16> {
let mut b = [0; 2];
assert_eq!(try!(r.read(&mut b)), 2);
Ok((b[0] as u16) | ((b[1] as u16) << 8))
}
fn read_byte<R: Read + ?Sized>(r: &mut R) -> io::Result<u8> {
let mut b = [0; 1];
assert_eq!(try!(r.read(&mut b)), 1);
Ok(b[0])
}
fn read_exact<R: Read + ?Sized>(r: &mut R, sz: usize) -> io::Result<Vec<u8>> {
let mut v = Vec::with_capacity(sz);
try!(r.take(sz as u64).read_to_end(&mut v));
assert_eq!(v.len(), sz);
Ok(v)
}
/// Create a dummy TermInfo struct for msys terminals
pub fn msys_terminfo() -> Box<TermInfo> {
pub fn msys_terminfo() -> TermInfo {
let mut strings = HashMap::new();
strings.insert("sgr0".to_owned(), b"\x1B[0m".to_vec());
strings.insert("bold".to_owned(), b"\x1B[1m".to_vec());
strings.insert("setaf".to_owned(), b"\x1B[3%p1%dm".to_vec());
strings.insert("setab".to_owned(), b"\x1B[4%p1%dm".to_vec());
box TermInfo {
names: vec!["cygwin".to_owned()], // msys is a fork of an older cygwin version
strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
let mut numbers = HashMap::new();
numbers.insert("colors".to_string(), 8u16);
TermInfo {
names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
bools: HashMap::new(),
numbers: HashMap::new(),
numbers: numbers,
strings: strings,
}
}
#[cfg(test)]
mod tests {
mod test {
use super::{boolnames, boolfnames, numnames, numfnames, stringnames, stringfnames};
......
......@@ -13,29 +13,26 @@
//! Does not support hashed database, only filesystem!
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::fs;
use std::path::PathBuf;
/// Return path to database entry for `term`
#[allow(deprecated)]
pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
if term.is_empty() {
return None;
}
let homedir = env::home_dir();
pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
let mut dirs_to_search = Vec::new();
let first_char = term.char_at(0);
let first_char = match term.chars().next() {
Some(c) => c,
None => return None,
};
// Find search directory
match env::var_os("TERMINFO") {
Some(dir) => dirs_to_search.push(PathBuf::from(dir)),
None => {
if homedir.is_some() {
if let Some(mut homedir) = env::home_dir() {
// ncurses compatibility;
dirs_to_search.push(homedir.unwrap().join(".terminfo"))
homedir.push(".terminfo");
dirs_to_search.push(homedir)
}
match env::var("TERMINFO_DIRS") {
Ok(dirs) => {
......@@ -61,35 +58,26 @@ pub fn get_dbpath_for_term(term: &str) -> Option<Box<PathBuf>> {
};
// Look for the terminal in all of the search directories
for p in &dirs_to_search {
if p.exists() {
let f = first_char.to_string();
let newp = p.join(&f).join(term);
if newp.exists() {
return Some(box newp);
for mut p in dirs_to_search {
if fs::metadata(&p).is_ok() {
p.push(&first_char.to_string());
p.push(&term);
if fs::metadata(&p).is_ok() {
return Some(p);
}
// on some installations the dir is named after the hex of the char (e.g. OS X)
let f = format!("{:x}", first_char as usize);
let newp = p.join(&f).join(term);
if newp.exists() {
return Some(box newp);
}
}
}
None
}
p.pop();
p.pop();
/// Return open file for `term`
pub fn open(term: &str) -> Result<File, String> {
match get_dbpath_for_term(term) {
Some(x) => {
match File::open(&*x) {
Ok(file) => Ok(file),
Err(e) => Err(format!("error opening file: {:?}", e)),
// on some installations the dir is named after the hex of the char
// (e.g. OS X)
p.push(&format!("{:x}", first_char as usize));
p.push(term);
if fs::metadata(&p).is_ok() {
return Some(p);
}
}
None => Err(format!("could not find terminfo entry for {:?}", term)),
}
None
}
#[test]
......@@ -109,11 +97,3 @@ fn x(t: &str) -> String {
assert!(x("screen") == "/usr/share/terminfo/s/screen");
env::remove_var("TERMINFO_DIRS");
}
#[test]
#[ignore(reason = "see test_get_dbpath_for_term")]
fn test_open() {
open("screen").unwrap();
let t = open("nonexistent terminal that hopefully does not exist");
assert!(t.is_err());
}
......@@ -17,9 +17,9 @@
use std::io;
use std::io::prelude::*;
use attr;
use Attr;
use color;
use {Terminal, UnwrappableTerminal};
use Terminal;
/// A Terminal implementation which uses the Win32 Console API.
pub struct WinConsole<T> {
......@@ -115,7 +115,7 @@ fn apply(&mut self) {
/// Returns `None` whenever the terminal cannot be created for some
/// reason.
pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
pub fn new(out: T) -> io::Result<WinConsole<T>> {
let fg;
let bg;
unsafe {
......@@ -128,7 +128,7 @@ pub fn new(out: T) -> Option<Box<Terminal<T> + Send + 'static>> {
bg = color::BLACK;
}
}
Some(box WinConsole {
Ok(WinConsole {
buf: out,
def_foreground: fg,
def_background: bg,
......@@ -148,7 +148,9 @@ fn flush(&mut self) -> io::Result<()> {
}
}
impl<T: Write+Send+'static> Terminal<T> for WinConsole<T> {
impl<T: Write+Send+'static> Terminal for WinConsole<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> io::Result<bool> {
self.foreground = color;
self.apply();
......@@ -163,14 +165,14 @@ fn bg(&mut self, color: color::Color) -> io::Result<bool> {
Ok(true)
}
fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
fn attr(&mut self, attr: Attr) -> io::Result<bool> {
match attr {
attr::ForegroundColor(f) => {
Attr::ForegroundColor(f) => {
self.foreground = f;
self.apply();
Ok(true)
}
attr::BackgroundColor(b) => {
Attr::BackgroundColor(b) => {
self.background = b;
self.apply();
Ok(true)
......@@ -179,21 +181,21 @@ fn attr(&mut self, attr: attr::Attr) -> io::Result<bool> {
}
}
fn supports_attr(&self, attr: attr::Attr) -> bool {
fn supports_attr(&self, attr: Attr) -> bool {
// it claims support for underscore and reverse video, but I can't get
// it to do anything -cmr
match attr {
attr::ForegroundColor(_) | attr::BackgroundColor(_) => true,
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
_ => false,
}
}
fn reset(&mut self) -> io::Result<()> {
fn reset(&mut self) -> io::Result<bool> {
self.foreground = self.def_foreground;
self.background = self.def_background;
self.apply();
Ok(())
Ok(true)
}
fn get_ref<'a>(&'a self) -> &'a T {
......@@ -203,10 +205,10 @@ fn get_ref<'a>(&'a self) -> &'a T {
fn get_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.buf
}
}
impl<T: Write+Send+'static> UnwrappableTerminal<T> for WinConsole<T> {
fn unwrap(self) -> T {
fn into_inner(self) -> T
where Self: Sized
{
self.buf
}
}
......@@ -428,7 +428,7 @@ pub enum TestResult {
unsafe impl Send for TestResult {}
enum OutputLocation<T> {
Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
Pretty(Box<term::StdoutTerminal>),
Raw(T),
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册