diff --git a/Cargo.toml b/Cargo.toml index 240e5878..0b194643 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,5 +21,8 @@ appveyor = { repository = "Stebalien/term" } winapi = "0.2" kernel32-sys = "0.2" +[target.'cfg(unix)'.dependencies] +libc = "0.2" + [features] default=[] diff --git a/examples/kitchen_sink.rs b/examples/kitchen_sink.rs new file mode 100644 index 00000000..3b87d2e9 --- /dev/null +++ b/examples/kitchen_sink.rs @@ -0,0 +1,13 @@ +// An example of the avaiable functionality in term + +extern crate term; + +fn main() { + let mut t = term::stdout().unwrap(); + + print!("Dims: "); + t.fg(term::color::GREEN).unwrap(); + print!("{:?}", t.dims().unwrap()); + t.reset().unwrap(); + println!(); +} diff --git a/src/lib.rs b/src/lib.rs index a5824d4b..4db18dfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,8 @@ //! //! and this to your crate root: //! -//! ```rust +//! ``` +//! # #[allow(unused_extern_crates)] //! extern crate term; //! ``` //! @@ -75,6 +76,8 @@ pub mod terminfo; #[cfg(windows)] mod win; +#[cfg(unix)] +mod unix; /// Alias for stdout terminals. pub type StdoutTerminal = Terminal + Send; @@ -168,6 +171,20 @@ pub enum Attr { BackgroundColor(color::Color), } +/// A struct representing the number of columns and rows of the terminal, and, if available, the +/// width and height of the terminal in pixels +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone, Default)] +pub struct Dims { + /// The number of rows in the terminal + pub rows: u16, + /// The number of columns in the terminal + pub columns: u16, + /// If available, the pixel width of the terminal + pub pixel_width: Option, + /// If available, the pixel height of the terminal + pub pixel_height: Option, +} + /// An error arising from interacting with the terminal. #[derive(Debug)] pub enum Error { @@ -390,6 +407,9 @@ pub trait Terminal: Write { /// Returns `Ok(true)` if the deletion code was printed, or `Err(e)` if there was an error. fn carriage_return(&mut self) -> Result<()>; + /// Gets the size of the terminal window, if available + fn dims(&self) -> Result; + /// Gets an immutable reference to the stream inside fn get_ref(&self) -> &Self::Output; diff --git a/src/terminfo/mod.rs b/src/terminfo/mod.rs index 71cdd644..e50cb8a2 100644 --- a/src/terminfo/mod.rs +++ b/src/terminfo/mod.rs @@ -19,6 +19,7 @@ use std::io::BufReader; use std::path::Path; use Attr; +use Dims; use color; use Terminal; use Result; @@ -27,6 +28,11 @@ use self::parser::compiled::parse; use self::parm::{expand, Variables, Param}; use self::Error::*; +#[cfg(unix)] +use std::os::unix::io::AsRawFd; +#[cfg(unix)] +use unix::win_size; + /// Returns true if the named terminal supports basic ANSI escape codes. fn is_ansi(name: &str) -> bool { @@ -283,6 +289,87 @@ pub struct TerminfoTerminal { ti: TermInfo, } +#[cfg(unix)] +impl Terminal for TerminfoTerminal { + type Output = T; + fn fg(&mut self, color: color::Color) -> Result<()> { + let color = self.dim_if_necessary(color); + if self.num_colors > color { + return self.ti.apply_cap("setaf", &[Param::Number(color as i32)], &mut self.out); + } + Err(::Error::ColorOutOfRange) + } + + fn bg(&mut self, color: color::Color) -> Result<()> { + let color = self.dim_if_necessary(color); + if self.num_colors > color { + return self.ti.apply_cap("setab", &[Param::Number(color as i32)], &mut self.out); + } + Err(::Error::ColorOutOfRange) + } + + fn attr(&mut self, attr: Attr) -> Result<()> { + match attr { + Attr::ForegroundColor(c) => self.fg(c), + Attr::BackgroundColor(c) => self.bg(c), + _ => self.ti.apply_cap(cap_for_attr(attr), &[], &mut self.out), + } + } + + fn supports_attr(&self, attr: Attr) -> bool { + match attr { + Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0, + _ => { + let cap = cap_for_attr(attr); + self.ti.strings.get(cap).is_some() + } + } + } + + fn reset(&mut self) -> Result<()> { + self.ti.reset(&mut self.out) + } + + fn supports_reset(&self) -> bool { + ["sgr0", "sgr", "op"].iter().any(|&cap| self.ti.strings.get(cap).is_some()) + } + + fn supports_color(&self) -> bool { + self.num_colors > 0 && self.supports_reset() + } + + fn cursor_up(&mut self) -> Result<()> { + self.ti.apply_cap("cuu1", &[], &mut self.out) + } + + fn delete_line(&mut self) -> Result<()> { + self.ti.apply_cap("el", &[], &mut self.out) + } + + fn carriage_return(&mut self) -> Result<()> { + self.ti.apply_cap("cr", &[], &mut self.out) + } + + fn dims(&self) -> Result { + win_size(self.out.as_raw_fd()).map(|s| s.into()) + } + + fn get_ref(&self) -> &T { + &self.out + } + + fn get_mut(&mut self) -> &mut T { + &mut self.out + } + + fn into_inner(self) -> T + where Self: Sized + { + self.out + } +} + +#[cfg(windows)] impl Terminal for TerminfoTerminal { type Output = T; fn fg(&mut self, color: color::Color) -> Result<()> { @@ -343,6 +430,10 @@ impl Terminal for TerminfoTerminal { self.ti.apply_cap("cr", &[], &mut self.out) } + fn dims(&self) -> Result { + Err(::Error::NotSupported) + } + fn get_ref(&self) -> &T { &self.out } diff --git a/src/unix.rs b/src/unix.rs new file mode 100644 index 00000000..2bf2acd0 --- /dev/null +++ b/src/unix.rs @@ -0,0 +1,32 @@ +extern crate libc; + +use std::os::unix::io::RawFd; +use std::io; +use std::mem; +use Dims; +use Result; + +/// Get the window size from a file descriptor (0 for stdout) +/// +/// Option is returned as there is no distinction between errors (all ENOSYS) +pub fn win_size(fd: RawFd) -> Result { + unsafe { + let ws: libc::winsize = mem::uninitialized(); + if libc::ioctl(fd, libc::TIOCGWINSZ, &ws) == 0 { + Ok(ws) + } else { + Err(io::Error::last_os_error().into()) + } + } +} + +impl From for Dims { + fn from(sz: libc::winsize) -> Dims { + Dims { + rows: sz.ws_row as u16, + columns: sz.ws_col as u16, + pixel_width: Some(sz.ws_xpixel as u32), + pixel_height: Some(sz.ws_ypixel as u32), + } + } +} diff --git a/src/win.rs b/src/win.rs index 3d571d50..b30b417c 100644 --- a/src/win.rs +++ b/src/win.rs @@ -24,6 +24,7 @@ use Error; use Result; use Terminal; use color; +use Dims; /// A Terminal implementation which uses the Win32 Console API. pub struct WinConsole { @@ -285,6 +286,22 @@ impl Terminal for WinConsole { } } + fn dims(&self) -> Result { + let handle = try!(conout()); + unsafe { + let mut buffer_info = ::std::mem::uninitialized(); + if kernel32::GetConsoleScreenBufferInfo(handle, &mut buffer_info) != 0 { + Ok(Dims { + rows: (buffer_info.srWindow.Bottom - buffer_info.srWindow.Top + 1) as u16, + columns: (buffer_info.srWindow.Right - buffer_info.srWindow.Left + 1) as u16, + ..Default::default() + }) + } else { + Err(io::Error::last_os_error().into()) + } + } + } + fn get_ref<'a>(&'a self) -> &'a T { &self.buf } diff --git a/tests/winsize.rs b/tests/winsize.rs new file mode 100644 index 00000000..6846a82e --- /dev/null +++ b/tests/winsize.rs @@ -0,0 +1,10 @@ +extern crate term; + +#[cfg(unix)] +#[test] +fn test_winsize() { + let t = term::stdout().unwrap(); + // This makes sure we don't try to provide dims on an incorrect platform, it also may trigger + // any memory errors. + let _dims = t.dims().unwrap(); +}