#![allow(
clippy::print_stdout,
clippy::print_stderr,
reason = "this module is the binary's terminal output"
)]
use std::fmt::Display;
use std::io::{IsTerminal, Write};
use std::process::ExitCode;
use crate::human::Paint;
pub(crate) fn is_terminal() -> bool {
std::io::stdout().is_terminal()
}
pub(crate) fn paint() -> Paint {
let no_color = std::env::var_os("NO_COLOR").is_some_and(|value| !value.is_empty());
Paint {
colour: is_terminal() && !no_color,
}
}
pub(crate) fn text(text: &str) -> bool {
let mut stdout = std::io::stdout().lock();
stdout
.write_all(text.as_bytes())
.and_then(|()| stdout.flush())
.is_ok()
}
pub(crate) fn line(line: impl Display) -> bool {
text(&format!("{line}\n"))
}
pub(crate) fn redraw(frame: &str) -> bool {
text(&format!("\x1b[H{}\x1b[J", frame.replace('\n', "\x1b[K\n")))
}
pub(crate) fn bytes(bytes: &[u8]) -> bool {
let mut stdout = std::io::stdout().lock();
stdout
.write_all(bytes)
.and_then(|()| stdout.flush())
.is_ok()
}
pub(crate) fn terminal_size() -> Option<(u16, u16)> {
let size = rustix::termios::tcgetwinsize(std::io::stdout()).ok()?;
(size.ws_col > 0 && size.ws_row > 0).then_some((size.ws_col, size.ws_row))
}
pub(crate) fn stdin_chunks() -> tokio::sync::mpsc::Receiver<Vec<u8>> {
let (sender, receiver) = tokio::sync::mpsc::channel(16);
std::thread::spawn(move || {
let mut stdin = std::io::stdin().lock();
let mut buffer = [0; 4096];
loop {
match std::io::Read::read(&mut stdin, &mut buffer) {
Ok(0) | Err(_) => break,
Ok(read) => {
if sender.blocking_send(buffer[..read].to_vec()).is_err() {
break;
}
}
}
}
});
receiver
}
pub(crate) struct RawMode(Option<rustix::termios::Termios>);
pub(crate) fn raw_mode() -> RawMode {
let stdin = std::io::stdin();
let Ok(before) = rustix::termios::tcgetattr(&stdin) else {
return RawMode(None);
};
let mut raw = before.clone();
raw.make_raw();
match rustix::termios::tcsetattr(&stdin, rustix::termios::OptionalActions::Now, &raw) {
Ok(()) => RawMode(Some(before)),
Err(_) => RawMode(None),
}
}
impl Drop for RawMode {
fn drop(&mut self) {
if let Some(before) = &self.0 {
let _ = rustix::termios::tcsetattr(
std::io::stdin(),
rustix::termios::OptionalActions::Now,
before,
);
}
}
}
pub(crate) fn fail(error: &dyn Display) -> ExitCode {
eprintln!("error: {error}");
ExitCode::FAILURE
}
#[cfg(feature = "tui")]
pub(crate) fn full_screen() -> std::io::Result<ratatui::DefaultTerminal> {
ratatui::try_init()
}
#[cfg(feature = "tui")]
pub(crate) fn restore() -> std::io::Result<()> {
ratatui::try_restore()
}
#[cfg(feature = "tui")]
pub(crate) fn resume() -> std::io::Result<ratatui::DefaultTerminal> {
use ratatui::crossterm::terminal::{EnterAlternateScreen, enable_raw_mode};
enable_raw_mode()?;
ratatui::crossterm::execute!(std::io::stdout(), EnterAlternateScreen)?;
ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(std::io::stdout()))
}