use std::{
fs::File,
io::{self, IsTerminal},
};
use anyhow::anyhow;
#[cfg(unix)]
use rustix::{
io::dup,
stdio::{dup2_stdout, stdout},
};
#[cfg(unix)]
use std::os::fd::OwnedFd;
pub struct StdoutRedirect {
#[cfg(unix)]
saved_stdout: Option<OwnedFd>,
}
impl StdoutRedirect {
pub fn try_new_for_tui(write_to_stdout: bool) -> anyhow::Result<Self> {
if !write_to_stdout || io::stdout().is_terminal() {
return Ok(Self {
#[cfg(unix)]
saved_stdout: None,
});
}
#[cfg(unix)]
{
let tty = File::options()
.read(true)
.write(true)
.open("/dev/tty")
.map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?;
let saved_fd = dup(stdout()).map_err(|e| anyhow!("Failed to duplicate stdout: {e}"))?;
dup2_stdout(&tty).map_err(|e| anyhow!("Failed to redirect stdout to /dev/tty: {e}"))?;
Ok(Self {
saved_stdout: Some(saved_fd),
})
}
#[cfg(not(unix))]
{
Err(anyhow!(
"`--write-to-stdout` with piped stdout is not supported on this platform"
))
}
}
pub fn restore(&mut self) -> anyhow::Result<()> {
#[cfg(unix)]
if let Some(saved_stdout) = self.saved_stdout.take() {
dup2_stdout(&saved_stdout).map_err(|e| anyhow!("Failed to restore stdout: {e}"))?;
}
Ok(())
}
}
impl Drop for StdoutRedirect {
fn drop(&mut self) {
let _ = self.restore();
}
}