//! # Helpers //! //! Various functions calling C wrappers to get/set terminal functionality use nix::libc::ioctl; use nix::libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use nix::sys::termios; use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; use std::io; use std::io::prelude::*; use std::io::BufReader; use std::os::unix::io::RawFd; use std::sync::Arc; #[derive(Debug)] pub struct TermSize { /// number of rows pub rows: u16, /// number of columns pub cols: u16, } /// 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(STDOUT_FILENO); raw.input_flags.remove( InputFlags::BRKINT | InputFlags::ICRNL | InputFlags::INPCK | InputFlags::ISTRIP | InputFlags::IXON, ); raw.output_flags.remove(OutputFlags::OPOST); // 8 bit characters raw.control_flags |= ControlFlags::CS8; raw.local_flags .remove(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG); 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() { let mutex = Arc::clone(&super::ORIGINAL_TERMIOS); let termios = mutex.lock().unwrap(); // First attempt to reset terminal settings via a terminal code print!("\x1bc"); // Restore previous terminal settings termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &termios).unwrap(); } /// Attempt to get the size of the terminal (in rows and columns) from an `ioctl` call #[inline] pub fn get_term_size() -> Option { #[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, } 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 } } /// Get the terminal size, the hard way. pub fn get_cursor_position() -> TermSize { // Ask the shell where the cursor is write!(io::stdout(), "\x1b[999C\x1b[999B").unwrap(); write!(io::stdout(), "\x1b[6n").unwrap(); // Explicitly flush, so that next input should be the terminal response io::stdout().flush().unwrap(); let mut buffer = vec![]; let stdin = io::stdin(); let mut br = BufReader::new(stdin.lock()); br.read_until('R' as u8, &mut buffer).unwrap(); let input = String::from_utf8(buffer).unwrap(); // Parse the escape sequence into a location // The escape sequence looks like so: Esc[y;xR let mut row_str = String::new(); let mut col_str = String::new(); let mut index = 0; for ch in input.chars() { if ch == '\x1b' || ch == '[' { continue; } match ch { ';' => { index += 1; } 'R' => break, _ => { if index == 0 { row_str.push(ch) } else { col_str.push(ch) } } } } let rows = clean_unwrap(row_str.parse()); let cols = clean_unwrap(col_str.parse()); return TermSize { cols, rows }; } /// Do the equivalent of a Result::unwrap, but cleanup terminal output /// first, so it doesn't destroy console output afterwards. pub fn clean_unwrap(res: Result) -> O where E: std::fmt::Debug, { match res { Ok(value) => value, Err(e) => { print!("\x1bc"); disable_raw_mode(); panic!("{:?}", e); } } }