Files
shed/src/prompt/readline/term.rs

102 lines
2.5 KiB
Rust

use std::os::fd::{BorrowedFd, RawFd};
use nix::{libc::STDIN_FILENO, sys::termios, unistd::{isatty, read, write}};
use super::keys::{KeyCode, KeyEvent, ModKeys};
#[derive(Debug)]
pub struct Terminal {
stdin: RawFd,
stdout: RawFd,
}
impl Terminal {
pub fn new() -> Self {
assert!(isatty(STDIN_FILENO).unwrap());
Self {
stdin: STDIN_FILENO,
stdout: 1,
}
}
fn raw_mode() -> termios::Termios {
let orig = termios::tcgetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}).expect("Failed to get terminal attributes");
let mut raw = orig.clone();
termios::cfmakeraw(&mut raw);
termios::tcsetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}, termios::SetArg::TCSANOW, &raw)
.expect("Failed to set terminal to raw mode");
orig
}
pub fn restore_termios(termios: termios::Termios) {
termios::tcsetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}, termios::SetArg::TCSANOW, &termios)
.expect("Failed to restore terminal settings");
}
pub fn with_raw_mode<F: FnOnce() -> R, R>(func: F) -> R {
let saved = Self::raw_mode();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(func));
Self::restore_termios(saved);
match result {
Ok(r) => r,
Err(e) => std::panic::resume_unwind(e),
}
}
pub fn read_byte(&self, buf: &mut [u8]) -> usize {
Self::with_raw_mode(|| {
read(self.stdin, buf).expect("Failed to read from stdin")
})
}
pub fn write_bytes(&self, buf: &[u8]) {
Self::with_raw_mode(|| {
write(unsafe{BorrowedFd::borrow_raw(self.stdout)}, buf).expect("Failed to write to stdout");
});
}
pub fn write(&self, s: &str) {
self.write_bytes(s.as_bytes());
}
pub fn writeln(&self, s: &str) {
self.write(s);
self.write_bytes(b"\r\n");
}
pub fn clear(&self) {
self.write_bytes(b"\x1b[2J\x1b[H");
}
pub fn read_key(&self) -> KeyEvent {
let mut buf = [0;8];
let n = self.read_byte(&mut buf);
if buf[0] == 0x1b {
if n >= 3 && buf[1] == b'[' {
return match buf[2] {
b'A' => KeyEvent(KeyCode::Up, ModKeys::empty()),
b'B' => KeyEvent(KeyCode::Down, ModKeys::empty()),
b'C' => KeyEvent(KeyCode::Right, ModKeys::empty()),
b'D' => KeyEvent(KeyCode::Left, ModKeys::empty()),
_ => KeyEvent(KeyCode::Esc, ModKeys::empty()),
};
}
return KeyEvent(KeyCode::Esc, ModKeys::empty());
}
if let Ok(s) = core::str::from_utf8(&buf[..n]) {
if let Some(ch) = s.chars().next() {
return KeyEvent::new(ch, ModKeys::NONE);
}
}
KeyEvent(KeyCode::Null, ModKeys::empty())
}
}
impl Default for Terminal {
fn default() -> Self {
Self::new()
}
}