Restructuring and error handling
This commit is contained in:
parent
1e30802d32
commit
c89a8f7027
|
@ -0,0 +1,54 @@
|
||||||
|
use std::io::{Write, Result as IoResult};
|
||||||
|
|
||||||
|
/// Controlling terminals.
|
||||||
|
pub trait TermControl {
|
||||||
|
/// Print the CSI (control sequence introducer) followed by a byte string.
|
||||||
|
fn csi(&mut self, b: &[u8]) -> IoResult<usize>;
|
||||||
|
/// Print OSC (operating system command) followed by a byte string.
|
||||||
|
fn osc(&mut self, b: &[u8]) -> IoResult<usize>;
|
||||||
|
/// Print OSC (device control string) followed by a byte string.
|
||||||
|
fn dsc(&mut self, b: &[u8]) -> IoResult<usize>;
|
||||||
|
/// Clear the terminal.
|
||||||
|
fn clear(&mut self) -> IoResult<usize> {
|
||||||
|
self.csi(b"2J")
|
||||||
|
}
|
||||||
|
/// Show the cursor.
|
||||||
|
fn show(&mut self) -> IoResult<usize> {
|
||||||
|
self.csi(b"?25h")
|
||||||
|
}
|
||||||
|
/// Hide the cursor.
|
||||||
|
fn hide(&mut self) -> IoResult<usize> {
|
||||||
|
self.csi(b"?25l")
|
||||||
|
}
|
||||||
|
/// Reset the style of the cursor.
|
||||||
|
fn reset_style(&mut self) -> IoResult<usize> {
|
||||||
|
self.csi(b"m")
|
||||||
|
}
|
||||||
|
/// Go to a given position.
|
||||||
|
fn goto(&mut self, x: u16, y: u16) -> IoResult<usize> {
|
||||||
|
self.csi(&[
|
||||||
|
(x / 10000 % 10) as u8, (x / 1000 % 10) as u8, (x / 100 % 10) as u8, (x / 10 % 10) as u8, (x % 10) as u8,
|
||||||
|
b';',
|
||||||
|
(y / 10000 % 10) as u8, (y / 1000 % 10) as u8, (y / 100 % 10) as u8, (y / 10 % 10) as u8, (y % 10) as u8,
|
||||||
|
b'H',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> TermControl for W {
|
||||||
|
fn csi(&mut self, b: &[u8]) -> IoResult<usize> {
|
||||||
|
self.write(b"\x1b[").and_then(|x| {
|
||||||
|
self.write(b).map(|y| x + y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn osc(&mut self, b: &[u8]) -> IoResult<usize> {
|
||||||
|
self.write(b"\x1b]").and_then(|x| {
|
||||||
|
self.write(b).map(|y| x + y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn dsc(&mut self, b: &[u8]) -> IoResult<usize> {
|
||||||
|
self.write(b"\x1bP").and_then(|x| {
|
||||||
|
self.write(b).map(|y| x + y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
161
src/lib.rs
161
src/lib.rs
|
@ -1,160 +1,13 @@
|
||||||
#![feature(libc)]
|
#![feature(libc)]
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
use std::mem;
|
mod termios;
|
||||||
use self::libc::{c_int, c_uint, c_ushort, c_uchar, STDOUT_FILENO};
|
|
||||||
use self::libc::ioctl;
|
|
||||||
|
|
||||||
use std::io::{Write, Result as IoResult};
|
mod control;
|
||||||
|
pub use control::TermControl;
|
||||||
|
|
||||||
extern {
|
mod raw;
|
||||||
static tiocgwinsz: c_int;
|
pub use raw::{raw_mode, TerminalRestorer};
|
||||||
|
|
||||||
fn tcgetattr(filedes: c_int, termptr: *mut Termios) -> c_int;
|
mod size;
|
||||||
fn tcsetattr(filedes: c_int, opt: c_int, termptr: *mut Termios) -> c_int;
|
pub use size::termsize;
|
||||||
fn cfmakeraw(termptr: *mut Termios);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct TermSize {
|
|
||||||
row: c_ushort,
|
|
||||||
col: c_ushort,
|
|
||||||
_x: c_ushort,
|
|
||||||
_y: c_ushort,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the size of the terminal. If the program isn't running in a terminal, it will return
|
|
||||||
/// `None`.
|
|
||||||
pub fn termsize() -> Option<(usize, usize)> {
|
|
||||||
unsafe {
|
|
||||||
let mut size: TermSize = mem::zeroed();
|
|
||||||
|
|
||||||
if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 {
|
|
||||||
Some((size.col as usize, size.row as usize))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct Termios {
|
|
||||||
c_iflag: c_uint,
|
|
||||||
c_oflag: c_uint,
|
|
||||||
c_cflag: c_uint,
|
|
||||||
c_lflag: c_uint,
|
|
||||||
c_line: c_uchar,
|
|
||||||
c_cc: [c_uchar; 32],
|
|
||||||
c_ispeed: c_uint,
|
|
||||||
c_ospeed: c_uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_terminal_attr() -> (Termios, c_int) {
|
|
||||||
unsafe {
|
|
||||||
let mut ios = Termios {
|
|
||||||
c_iflag: 0,
|
|
||||||
c_oflag: 0,
|
|
||||||
c_cflag: 0,
|
|
||||||
c_lflag: 0,
|
|
||||||
c_line: 0,
|
|
||||||
c_cc: [0; 32],
|
|
||||||
c_ispeed: 0,
|
|
||||||
c_ospeed: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let attr = tcgetattr(0, &mut ios);
|
|
||||||
(ios, attr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_raw(ios: &mut Termios) {
|
|
||||||
unsafe {
|
|
||||||
cfmakeraw(&mut *ios);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_terminal_attr(ios: *mut Termios) -> c_int {
|
|
||||||
unsafe {
|
|
||||||
tcsetattr(0, 0, ios)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
|
|
||||||
/// dropped.
|
|
||||||
pub struct TerminalRestorer {
|
|
||||||
prev_ios: Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TerminalRestorer {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
set_terminal_attr(&mut self.prev_ios as *mut _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Switch to raw mode.
|
|
||||||
///
|
|
||||||
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by the
|
|
||||||
/// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from
|
|
||||||
/// stdin one byte of a time). The output is neither modified in any way.
|
|
||||||
///
|
|
||||||
/// Panics
|
|
||||||
/// ------
|
|
||||||
///
|
|
||||||
/// This may panic if the Termios settings can be set or loaded properly.
|
|
||||||
pub fn raw_mode() -> TerminalRestorer {
|
|
||||||
let (mut ios, err) = get_terminal_attr();
|
|
||||||
let prev_ios = ios.clone();
|
|
||||||
if err != 0 {
|
|
||||||
panic!("Failed to load termios settings properly.");
|
|
||||||
}
|
|
||||||
|
|
||||||
make_raw(&mut ios);
|
|
||||||
|
|
||||||
if set_terminal_attr(&mut ios as *mut _) != 0 {
|
|
||||||
panic!("Failed to init termios raw mode properly.");
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminalRestorer {
|
|
||||||
prev_ios: prev_ios,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Controlling terminals.
|
|
||||||
pub trait TermControl {
|
|
||||||
/// Print the CSI (control sequence introducer) followed by a byte string.
|
|
||||||
fn csi(&mut self, b: &[u8]) -> IoResult<usize>;
|
|
||||||
/// Clear the terminal.
|
|
||||||
fn clear(&mut self) -> IoResult<usize> {
|
|
||||||
self.csi(b"2J")
|
|
||||||
}
|
|
||||||
/// Show the cursor.
|
|
||||||
fn show(&mut self) -> IoResult<usize> {
|
|
||||||
self.csi(b"?25h")
|
|
||||||
}
|
|
||||||
/// Hide the cursor.
|
|
||||||
fn hide(&mut self) -> IoResult<usize> {
|
|
||||||
self.csi(b"?25l")
|
|
||||||
}
|
|
||||||
/// Reset the style of the cursor.
|
|
||||||
fn reset_style(&mut self) -> IoResult<usize> {
|
|
||||||
self.csi(b"0m")
|
|
||||||
}
|
|
||||||
/// Go to a given position.
|
|
||||||
fn goto(&mut self, x: u16, y: u16) -> IoResult<usize> {
|
|
||||||
self.csi(&[
|
|
||||||
(x / 10000 % 10) as u8, (x / 1000 % 10) as u8, (x / 100 % 10) as u8, (x / 10 % 10) as u8, (x % 10) as u8,
|
|
||||||
b';',
|
|
||||||
(y / 10000 % 10) as u8, (y / 1000 % 10) as u8, (y / 100 % 10) as u8, (y / 10 % 10) as u8, (y % 10) as u8,
|
|
||||||
b'H',
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> TermControl for W {
|
|
||||||
fn csi(&mut self, b: &[u8]) -> IoResult<usize> {
|
|
||||||
self.write(b"\x1b[").and_then(|x| {
|
|
||||||
self.write(b).map(|y| x + y)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
use termios::{cfmakeraw, Termios, TermiosError, get_terminal_attr, set_terminal_attr};
|
||||||
|
|
||||||
|
/// Switch to raw mode.
|
||||||
|
///
|
||||||
|
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by the
|
||||||
|
/// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from
|
||||||
|
/// stdin one byte of a time). The output is neither modified in any way.
|
||||||
|
pub fn raw_mode() -> Result<TerminalRestorer, TermiosError> {
|
||||||
|
let (mut ios, err) = get_terminal_attr();
|
||||||
|
let prev_ios = ios.clone();
|
||||||
|
if err != 0 {
|
||||||
|
return Err(TermiosError::LoadAttrError);
|
||||||
|
}
|
||||||
|
|
||||||
|
make_raw(&mut ios);
|
||||||
|
|
||||||
|
if set_terminal_attr(&mut ios as *mut _) != 0 {
|
||||||
|
Err(TermiosError::SetAttrError)
|
||||||
|
} else {
|
||||||
|
Ok(TerminalRestorer {
|
||||||
|
prev_ios: prev_ios,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_raw(ios: &mut Termios) {
|
||||||
|
unsafe {
|
||||||
|
cfmakeraw(&mut *ios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
|
||||||
|
/// dropped.
|
||||||
|
pub struct TerminalRestorer {
|
||||||
|
prev_ios: Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TerminalRestorer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
set_terminal_attr(&mut self.prev_ios as *mut _);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
use libc::ioctl;
|
||||||
|
use libc::{c_ushort, STDOUT_FILENO};
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use termios::{TermiosError, tiocgwinsz};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct TermSize {
|
||||||
|
row: c_ushort,
|
||||||
|
col: c_ushort,
|
||||||
|
_x: c_ushort,
|
||||||
|
_y: c_ushort,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of the terminal. If the program isn't running in a terminal, it will return
|
||||||
|
/// `None`.
|
||||||
|
pub fn termsize() -> Result<(usize, usize), TermiosError> {
|
||||||
|
unsafe {
|
||||||
|
let mut size: TermSize = mem::zeroed();
|
||||||
|
|
||||||
|
if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 {
|
||||||
|
Ok((size.col as usize, size.row as usize))
|
||||||
|
} else {
|
||||||
|
Err(TermiosError::TermSizeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
use libc::{c_int, c_uint, c_uchar};
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Display, Formatter, Error as FmtError};
|
||||||
|
|
||||||
|
extern {
|
||||||
|
pub static tiocgwinsz: c_int;
|
||||||
|
|
||||||
|
pub fn tcgetattr(filedes: c_int, termptr: *mut Termios) -> c_int;
|
||||||
|
pub fn tcsetattr(filedes: c_int, opt: c_int, termptr: *mut Termios) -> c_int;
|
||||||
|
pub fn cfmakeraw(termptr: *mut Termios);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Termios {
|
||||||
|
c_iflag: c_uint,
|
||||||
|
c_oflag: c_uint,
|
||||||
|
c_cflag: c_uint,
|
||||||
|
c_lflag: c_uint,
|
||||||
|
c_line: c_uchar,
|
||||||
|
c_cc: [c_uchar; 32],
|
||||||
|
c_ispeed: c_uint,
|
||||||
|
c_ospeed: c_uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_attr() -> (Termios, c_int) {
|
||||||
|
unsafe {
|
||||||
|
let mut ios = Termios {
|
||||||
|
c_iflag: 0,
|
||||||
|
c_oflag: 0,
|
||||||
|
c_cflag: 0,
|
||||||
|
c_lflag: 0,
|
||||||
|
c_line: 0,
|
||||||
|
c_cc: [0; 32],
|
||||||
|
c_ispeed: 0,
|
||||||
|
c_ospeed: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let attr = tcgetattr(0, &mut ios);
|
||||||
|
(ios, attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_terminal_attr(ios: *mut Termios) -> c_int {
|
||||||
|
unsafe {
|
||||||
|
tcsetattr(0, 0, ios)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Termios error.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum TermiosError {
|
||||||
|
/// Failed to load attributes.
|
||||||
|
LoadAttrError,
|
||||||
|
/// Failed to set attributes.
|
||||||
|
SetAttrError,
|
||||||
|
/// Failed to get terminal size.
|
||||||
|
TermSizeError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermiosError {
|
||||||
|
fn msg(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
TermiosError::LoadAttrError => "Failed to load Termios attributes.",
|
||||||
|
TermiosError::SetAttrError => "Failed to set Termios attribute.",
|
||||||
|
TermiosError::TermSizeError => "Failed to get terminal size.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TermiosError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
self.msg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TermiosError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
f.write_str(self.msg())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue