use crate::error::SysError;
use rustix::io::retry_on_intr;
use rustix::termios::{self, LocalModes, OptionalActions, SpecialCodeIndex, Termios};
use std::io::{Error, LineWriter, Write};
use std::os::fd::BorrowedFd;
use std::slice;
pub fn is_tty(fd: &BorrowedFd) -> bool {
termios::isatty(fd)
}
pub enum TtyMode {
Canon,
CanonNoEcho,
}
pub fn set_tty_mode(tty_fd: &BorrowedFd, mode: TtyMode) -> Result<(), SysError> {
let mut term = match retry_on_intr(|| termios::tcgetattr(tty_fd)) {
Ok(term) => term,
Err(err) => return Err(SysError("tcgetattr()", err)),
};
match mode {
TtyMode::Canon => term.local_modes |= LocalModes::ICANON,
TtyMode::CanonNoEcho => {
term.local_modes |= LocalModes::ICANON;
term.local_modes &= !LocalModes::ECHO;
}
};
if let Err(err) = retry_on_intr(|| termios::tcsetattr(tty_fd, OptionalActions::Now, &term)) {
return Err(SysError("tcsetattr()", err));
}
Ok(())
}
#[allow(non_snake_case)]
pub struct TtyCodes {
pub VEOF: char,
}
pub fn get_tty_codes(tty_fd: &BorrowedFd) -> Result<TtyCodes, SysError> {
let term = match retry_on_intr(|| termios::tcgetattr(tty_fd)) {
Ok(term) => term,
Err(err) => return Err(SysError("tcgetattr()", err)),
};
let codes = TtyCodes {
VEOF: term.special_codes[SpecialCodeIndex::VEOF] as char,
};
Ok(codes)
}
pub fn copy_tty_size(dst_tty_fd: &BorrowedFd, src_tty_fd: &BorrowedFd) -> Result<(), SysError> {
let win_size = match retry_on_intr(|| termios::tcgetwinsize(src_tty_fd)) {
Ok(win_size) => win_size,
Err(err) => return Err(SysError("tcgetwinsize()", err)),
};
if let Err(err) = retry_on_intr(|| termios::tcsetwinsize(dst_tty_fd, win_size)) {
return Err(SysError("tcsetwinsize()", err));
}
Ok(())
}
pub fn save_tty_state(tty_fd: &BorrowedFd) -> Result<Termios, SysError> {
match retry_on_intr(|| termios::tcgetattr(tty_fd)) {
Ok(term) => Ok(term),
Err(err) => Err(SysError("tcgetattr()", err)),
}
}
pub fn restore_tty_state(tty_fd: &BorrowedFd, term: &Termios) -> Result<(), SysError> {
if let Err(err) = retry_on_intr(|| termios::tcsetattr(tty_fd, OptionalActions::Now, term)) {
return Err(SysError("tcsetattr()", err));
}
Ok(())
}
pub struct AnsiStripper<W: Write> {
parser: vte::Parser,
performer: AnsiPerformer<W>,
}
impl<W: Write> AnsiStripper<W> {
pub fn new(output: W) -> Self {
AnsiStripper {
parser: vte::Parser::new(),
performer: AnsiPerformer {
line_writer: LineWriter::new(output),
last_err: None,
},
}
}
}
impl<W: Write> Write for AnsiStripper<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
self.parser.advance(&mut self.performer, buf);
if let Some(err) = self.performer.last_err.take() {
return Err(err);
}
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), Error> {
self.performer.line_writer.flush()
}
}
struct AnsiPerformer<W: Write> {
line_writer: LineWriter<W>,
last_err: Option<Error>,
}
impl<W: Write> vte::Perform for AnsiPerformer<W> {
fn print(&mut self, c: char) {
self.last_err = self
.line_writer
.write_all(slice::from_ref(&(c as u8)))
.err();
}
fn execute(&mut self, b: u8) {
if b == b'\t' || b == b'\n' {
self.last_err = self.line_writer.write_all(slice::from_ref(&b)).err();
}
}
}