use std::fs::{File, OpenOptions};
use std::io::{Stderr, Write};
use crossterm::tty::IsTty;
pub struct UiStream {
inner: Inner,
}
enum Inner {
Tty(File),
Stderr(Stderr),
}
impl UiStream {
pub fn open() -> Self {
#[cfg(unix)]
const CONSOLE: &str = "/dev/tty";
#[cfg(windows)]
const CONSOLE: &str = "CONOUT$";
let inner = match OpenOptions::new().read(true).write(true).open(CONSOLE) {
Ok(file) => Inner::Tty(file),
Err(_) => Inner::Stderr(std::io::stderr()),
};
UiStream { inner }
}
pub fn is_tty(&self) -> bool {
match &self.inner {
Inner::Tty(file) => file.is_tty(),
Inner::Stderr(err) => err.is_tty(),
}
}
pub fn is_dev_tty(&self) -> bool {
matches!(self.inner, Inner::Tty(_))
}
#[allow(dead_code)]
pub fn size(&self) -> (u16, u16) {
crossterm::terminal::size().unwrap_or((80, 24))
}
}
impl Write for UiStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match &mut self.inner {
Inner::Tty(file) => file.write(buf),
Inner::Stderr(err) => err.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match &mut self.inner {
Inner::Tty(file) => file.flush(),
Inner::Stderr(err) => err.flush(),
}
}
}
pub struct RawModeGuard;
impl RawModeGuard {
pub fn enable() -> std::io::Result<Self> {
crossterm::terminal::enable_raw_mode()?;
Ok(RawModeGuard)
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
let _ = crossterm::terminal::disable_raw_mode();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_never_panics_and_size_is_positive() {
let stream = UiStream::open();
let (w, h) = stream.size();
assert!(w > 0 && h > 0);
}
#[test]
fn a_regular_file_is_not_a_tty() {
let file = tempfile::tempfile().expect("tempfile");
assert!(!file.is_tty());
}
}