use libc::ioctl; /// Helper functions, especially to reproduce C std/posix functions use libc::{c_ushort, STDOUT_FILENO, TIOCGWINSZ}; use nix::errno::*; use nix::sys::termios; use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; use std::io; use std::io::prelude::*; use std::os::unix::io::RawFd; use std::process::exit; #[derive(Debug)] pub struct TermSize { /// number of rows pub rows: u16, /// number of columns pub cols: u16, } #[repr(C)] #[derive(Debug)] struct UnixTermSize { /// number of rows pub rows: c_ushort, /// number of columns pub cols: c_ushort, x: c_ushort, y: c_ushort, } // Redefine the posix constants for rust land /// The value of the raw file descriptor for STDIN pub const STDIN_FILENO: i32 = 0; // pub const STDOUT_FILENO: i32 = 1; // pub const STDERR_FILENO: i32 = 2; /// Convert Ctrl+letter chords to their /// ASCII table equivalents #[inline] pub fn ctrl_key(c: char) -> char { let key = c as u8; if !c.is_ascii() { panic!("CTRL_KEY only accepts ASCII characters"); } // Intentionally "overflow" u8 to wrap around to the // beginning of the ASCII table. Ctrl+a is 1, Ctrl+b is 2, etc. (key & 0x1f) as char } pub fn die(code: &Errno, msg: &str) -> ! { clear_and_reset(); eprintln!("{:?} ({})", code, msg); exit(1) } /// Get a `Termios` struct, for getting/setting terminal flags pub fn get_termios(fd: RawFd) -> Termios { termios::tcgetattr(fd).unwrap() } /// Put terminal into raw mode so there is full control of terminal output pub fn enable_raw_mode() { let mut raw = get_termios(STDIN_FILENO); raw.input_flags.remove( InputFlags::BRKINT | InputFlags::IGNCR | InputFlags::IGNBRK | InputFlags::ICRNL | InputFlags::INLCR | InputFlags::INPCK | InputFlags::ISTRIP | InputFlags::IXON | InputFlags::PARMRK, ); raw.output_flags.remove(OutputFlags::OPOST); raw.local_flags.remove( LocalFlags::ECHO | LocalFlags::ECHONL | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG, ); raw.control_flags .remove(ControlFlags::CSIZE | ControlFlags::PARENB); // 8 bit characters raw.control_flags |= termios::ControlFlags::CS8; raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 0; raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1; // Raw mode or bust! termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &raw).unwrap(); } /// Restore terminal to "cooked"/canonical mode pub fn disable_raw_mode(original: &Termios) { termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, original).unwrap(); } pub fn clear_and_reset() { let stdout = io::stdout(); let mut handle = stdout.lock(); // Clear screen let mut buffer = String::from("\x1b[2J").into_bytes(); handle.write_all(&mut buffer).unwrap(); // Reposition cursor let mut buffer = String::from("\x1b[H").into_bytes(); handle.write_all(&mut buffer).unwrap(); } pub fn get_term_size() -> Option { let raw = UnixTermSize { rows: 0, cols: 0, x: 0, y: 0, }; let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &raw) }; if r == 0 { Some(TermSize { rows: raw.rows, cols: raw.cols, }) } else { None } }