Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checking terminal capabilities on each operation #45

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 22 additions & 77 deletions src/core/driver.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Temporary fix before certain constants are used.
#![allow(dead_code)]

use std::io::{Error, ErrorKind};

use term::Error;
use term::terminfo::TermInfo;
use term::terminfo::parm;
use term::terminfo::parm::{Param, Variables};
Expand Down Expand Up @@ -48,31 +47,11 @@ const REVERSE: &'static str = "rev";
const SETFG: &'static str = "setaf";
const SETBG: &'static str = "setab";

// Array of terminal capabilities. Used as an iterator to test for functionality.
//
// At the moment all functionality is required, however in the future we should implement optional
// functionality checks so the absence of underlining or reverse video doesn't cause initialization
// to fail.
//
// TODO: Optional functionality testing.
const CAPABILITIES: &'static [&'static str] = &[ENTER_CA,
EXIT_CA,
SHOW_CURSOR,
HIDE_CURSOR,
SET_CURSOR,
CLEAR,
RESET,
UNDERLINE,
BOLD,
REVERSE,
SETFG,
SETBG];

// Driver capabilities are an enum instead of string constants (there are string constants private
// to the module however, those are only used for naming convenience and disambiguation)
// to take advantage of compile-time type-checking instead of hoping invalid strings aren't passed.
// This allows us to guarantee that driver accesses will succeed. In addition, using an enum means
// Driver doesn't need hard-coded methods for each capability we want to use.
// In addition, using an enum means Driver doesn't need hard-coded methods for each capability we
// want to use.
pub enum DevFn {
EnterCa,
ExitCa,
Expand Down Expand Up @@ -113,67 +92,33 @@ pub struct Driver {
tinfo: TermInfo,
}

// Validates and returns a reference to the terminfo database.
//
// If this function returns Ok(..), the contained terminfo database is guaranteed to have the
// functionality found in CAPABILITIES.
//
// If this function returns Err(..), the terminfo database did not contain all the required
// functionality; the error returned will provide more specific detail.
fn get_tinfo() -> Result<TermInfo, Error> {
let tinfo = TermInfo::from_env().unwrap_or({
TermInfo {
names: Default::default(),
bools: Default::default(),
numbers: Default::default(),
strings: Default::default(),
}
});

for capname in CAPABILITIES {
if !tinfo.strings.contains_key(*capname) {
return Err(Error::new(ErrorKind::NotFound,
format!("terminal missing capability: '{}'", capname)));
}
}
Ok(tinfo)
}

impl Driver {
// Creates a new `Driver`
//
// If successful, the terminfo database is guaranteed to contain all capabilities we support.
pub fn new() -> Result<Driver, Error> {
let tinfo = try!(get_tinfo());
let tinfo = try!(TermInfo::from_env());
Ok(Driver { tinfo: tinfo })
}

// Returns the device specific escape sequence for the given `DevFn`.
//
// get() will not return an error, and (in theory) should never panic. The `DevFn` enum
// restricts possible inputs to a subset that will not fail when passed to `parm::expand()`.
// This can be verified by examining the source of the `parm::expand()` function in the `term`
// crate.
//
// Furthermore, the pre-flight checks on initialization of `Driver` ensure that every
// capability is present, thus the call to `Hashmap::get()` should never fail.
pub fn get(&self, dfn: DevFn) -> Vec<u8> {
// Returns the device specific escape sequence for the given `DevFn`, or None if the terminal
// lacks the capability to perform the specified function.
pub fn get(&self, dfn: DevFn) -> Option<Vec<u8>> {
let capname = dfn.as_str();
let cap = self.tinfo.strings.get(capname).unwrap();
self.tinfo.strings.get(capname).map(|cap| {

match dfn {
DevFn::SetFg(attr) |
DevFn::SetBg(attr) => {
let params = &[Param::Number(attr as i32)];
let mut vars = Variables::new();
parm::expand(cap, params, &mut vars).unwrap()
}
DevFn::SetCursor(x, y) => {
let params = &[Param::Number(y as i32), Param::Number(x as i32)];
let mut vars = Variables::new();
parm::expand(cap, params, &mut vars).unwrap()
match dfn {
DevFn::SetFg(attr) |
DevFn::SetBg(attr) => {
let params = &[Param::Number(attr as i32)];
let mut vars = Variables::new();
parm::expand(cap, params, &mut vars).unwrap()
}
DevFn::SetCursor(x, y) => {
let params = &[Param::Number(y as i32), Param::Number(x as i32)];
let mut vars = Variables::new();
parm::expand(cap, params, &mut vars).unwrap()
}
_ => cap.clone(),
}
_ => cap.clone(),
}
})
}
}
39 changes: 23 additions & 16 deletions src/core/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ impl Terminal {
};

// Switch to alternate screen buffer. Writes the control code to the output buffer.
try!(terminal.outbuffer.write_all(&terminal.driver.get(DevFn::EnterCa)));
try!(terminal.outbuffer_write_devfn(DevFn::EnterCa));

// Hide cursor. Writes the control code to the output buffer.
try!(terminal.outbuffer.write_all(&terminal.driver.get(DevFn::HideCursor)));
try!(terminal.outbuffer_write_devfn(DevFn::HideCursor));

// Resize the buffers to the size of the underlying terminals. Using the given cell as a
// blank.
Expand All @@ -173,6 +173,13 @@ impl Terminal {
Ok(terminal)
}

fn outbuffer_write_devfn(&mut self, devfn: DevFn) -> Result<(), Error> {
match self.driver.get(devfn) {
None => Ok(()),
Some(seq) => self.outbuffer.write_all(&seq)
}
}

/// Swaps buffers to display the current backbuffer.
///
/// # Examples
Expand Down Expand Up @@ -414,7 +421,7 @@ impl Terminal {
/// ```
pub fn set_cursor(&mut self, x: usize, y: usize) -> Result<(), Error> {
if self.cursor.pos().is_none() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::ShowCursor)));
try!(self.outbuffer_write_devfn(DevFn::ShowCursor));
}
self.cursor.set_pos(Some((x, y)));
try!(self.send_cursor());
Expand All @@ -434,7 +441,7 @@ impl Terminal {
/// ```
pub fn hide_cursor(&mut self) -> Result<(), Error> {
if self.cursor.pos().is_some() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::HideCursor)));
try!(self.outbuffer_write_devfn(DevFn::HideCursor));
}
Ok(())
}
Expand Down Expand Up @@ -478,7 +485,7 @@ impl Terminal {

fn send_cursor(&mut self) -> Result<(), Error> {
if let Some((cx, cy)) = self.cursor.pos() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetCursor(cx, cy))));
try!(self.outbuffer_write_devfn(DevFn::SetCursor(cx, cy)));
}
Ok(())
}
Expand All @@ -493,7 +500,7 @@ impl Terminal {
}

fn send_clear(&mut self) -> Result<(), Error> {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Clear)));
try!(self.outbuffer_write_devfn(DevFn::Clear));
try!(self.send_cursor());
try!(self.flush());
self.cursor.invalidate_last_pos();
Expand All @@ -503,14 +510,14 @@ impl Terminal {
fn send_style(&mut self, cell: Cell) -> Result<(), Error> {
if cell.fg() != self.laststyle.fg() || cell.bg() != self.laststyle.bg() ||
cell.attrs() != self.laststyle.attrs() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Reset)));
try!(self.outbuffer_write_devfn(DevFn::Reset));

match cell.attrs() {
Attr::Bold => try!(self.outbuffer.write_all(&self.driver.get(DevFn::Bold))),
Attr::Bold => try!(self.outbuffer_write_devfn(DevFn::Bold)),
Attr::Underline => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Underline)))
try!(self.outbuffer_write_devfn(DevFn::Underline))
}
Attr::Reverse => try!(self.outbuffer.write_all(&self.driver.get(DevFn::Reverse))),
Attr::Reverse => try!(self.outbuffer_write_devfn(DevFn::Reverse)),
_ => {}
}

Expand All @@ -524,13 +531,13 @@ impl Terminal {
match fgcol {
Color::Default => {}
fgc @ _ => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetFg(fgc.as_byte()))));
try!(self.outbuffer_write_devfn(DevFn::SetFg(fgc.as_byte())));
}
}
match bgcol {
Color::Default => {}
bgc @ _ => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetBg(bgc.as_byte()))));
try!(self.outbuffer_write_devfn(DevFn::SetBg(bgc.as_byte())));
}
}
Ok(())
Expand Down Expand Up @@ -688,10 +695,10 @@ impl IndexMut<Pos> for Terminal {

impl Drop for Terminal {
fn drop(&mut self) {
self.outbuffer.write_all(&self.driver.get(DevFn::ShowCursor)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::Reset)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::Clear)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::ExitCa)).unwrap();
self.outbuffer_write_devfn(DevFn::ShowCursor).unwrap();
self.outbuffer_write_devfn(DevFn::Reset).unwrap();
self.outbuffer_write_devfn(DevFn::Clear).unwrap();
self.outbuffer_write_devfn(DevFn::ExitCa).unwrap();
self.flush().unwrap();
self.termctl.reset().unwrap();
SIGWINCH_STATUS.store(false, Ordering::SeqCst);
Expand Down