2019-09-06 13:24:29 -04:00
|
|
|
//! # Helpers
|
|
|
|
//!
|
|
|
|
//! Various functions calling C wrappers to get/set terminal functionality
|
2019-09-06 16:40:31 -04:00
|
|
|
use nix::libc::ioctl;
|
|
|
|
use nix::libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
|
2019-08-22 16:44:47 -04:00
|
|
|
use nix::sys::termios;
|
|
|
|
use nix::sys::termios::{
|
2019-08-23 13:33:18 -04:00
|
|
|
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
|
2019-08-22 16:44:47 -04:00
|
|
|
};
|
2019-09-19 12:19:20 -04:00
|
|
|
use std::io;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::io::BufReader;
|
2019-08-22 16:44:47 -04:00
|
|
|
use std::os::unix::io::RawFd;
|
2019-09-06 13:24:29 -04:00
|
|
|
use std::sync::Arc;
|
2019-08-22 14:25:18 -04:00
|
|
|
|
2019-08-23 16:46:04 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TermSize {
|
|
|
|
/// number of rows
|
|
|
|
pub rows: u16,
|
|
|
|
/// number of columns
|
|
|
|
pub cols: u16,
|
|
|
|
}
|
|
|
|
|
2019-08-23 14:57:26 -04:00
|
|
|
/// Get a `Termios` struct, for getting/setting terminal flags
|
2019-08-22 16:44:47 -04:00
|
|
|
pub fn get_termios(fd: RawFd) -> Termios {
|
|
|
|
termios::tcgetattr(fd).unwrap()
|
|
|
|
}
|
|
|
|
|
2019-08-23 14:57:26 -04:00
|
|
|
/// Put terminal into raw mode so there is full control of terminal output
|
|
|
|
pub fn enable_raw_mode() {
|
2019-09-13 16:34:31 -04:00
|
|
|
let mut raw = get_termios(STDOUT_FILENO);
|
2019-08-22 16:44:47 -04:00
|
|
|
|
|
|
|
raw.input_flags.remove(
|
2019-08-23 13:33:18 -04:00
|
|
|
InputFlags::BRKINT
|
|
|
|
| InputFlags::ICRNL
|
|
|
|
| InputFlags::INPCK
|
|
|
|
| InputFlags::ISTRIP
|
2019-08-27 17:38:05 -04:00
|
|
|
| InputFlags::IXON,
|
2019-08-22 16:44:47 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
raw.output_flags.remove(OutputFlags::OPOST);
|
|
|
|
|
2019-08-27 17:38:05 -04:00
|
|
|
// 8 bit characters
|
|
|
|
raw.control_flags |= ControlFlags::CS8;
|
2019-08-22 16:44:47 -04:00
|
|
|
|
2019-08-27 17:38:05 -04:00
|
|
|
raw.local_flags
|
|
|
|
.remove(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
|
2019-08-22 16:44:47 -04:00
|
|
|
|
|
|
|
raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 0;
|
|
|
|
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1;
|
|
|
|
|
2019-08-23 14:57:26 -04:00
|
|
|
// Raw mode or bust!
|
|
|
|
termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &raw).unwrap();
|
2019-08-22 16:44:47 -04:00
|
|
|
}
|
|
|
|
|
2019-08-23 14:57:26 -04:00
|
|
|
/// Restore terminal to "cooked"/canonical mode
|
2019-09-06 13:24:29 -04:00
|
|
|
pub fn disable_raw_mode() {
|
|
|
|
let mutex = Arc::clone(&super::ORIGINAL_TERMIOS);
|
|
|
|
let termios = mutex.lock().unwrap();
|
|
|
|
|
2019-09-10 16:47:15 -04:00
|
|
|
// First attempt to reset terminal settings via a terminal code
|
|
|
|
print!("\x1bc");
|
|
|
|
|
|
|
|
// Restore previous terminal settings
|
2019-09-06 13:24:29 -04:00
|
|
|
termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &termios).unwrap();
|
2019-08-23 13:33:18 -04:00
|
|
|
}
|
2019-08-23 16:46:04 -04:00
|
|
|
|
2019-08-27 17:38:05 -04:00
|
|
|
/// Attempt to get the size of the terminal (in rows and columns) from an `ioctl` call
|
2019-09-13 16:34:31 -04:00
|
|
|
#[inline]
|
2019-08-23 16:46:04 -04:00
|
|
|
pub fn get_term_size() -> Option<TermSize> {
|
2019-09-13 16:34:31 -04:00
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2019-08-23 16:46:04 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2019-09-19 12:19:20 -04:00
|
|
|
|
|
|
|
/// 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<O, E>(res: Result<O, E>) -> O
|
|
|
|
where
|
|
|
|
E: std::fmt::Debug,
|
|
|
|
{
|
|
|
|
match res {
|
|
|
|
Ok(value) => value,
|
|
|
|
Err(e) => {
|
|
|
|
print!("\x1bc");
|
|
|
|
disable_raw_mode();
|
|
|
|
panic!("{:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|