use std::io::IsTerminal as _;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LineCaps {
Own,
None,
}
impl LineCaps {
pub fn can_own(self) -> bool {
matches!(self, Self::Own)
}
}
static PROTOCOL_MODE: AtomicBool = AtomicBool::new(false);
pub fn enter_protocol_mode() {
PROTOCOL_MODE.store(true, Ordering::SeqCst);
}
pub fn protocol_mode() -> bool {
PROTOCOL_MODE.load(Ordering::SeqCst)
}
pub(crate) fn probe(
stdin_tty: bool,
stdout_tty: bool,
term: Option<&str>,
protocol: bool,
) -> LineCaps {
if protocol {
return LineCaps::None;
}
if !stdin_tty || !stdout_tty {
return LineCaps::None;
}
match term {
None | Some("") | Some("dumb") => LineCaps::None,
Some(_) => LineCaps::Own,
}
}
impl LineCaps {
pub fn detect() -> Self {
detect()
}
}
pub fn detect() -> LineCaps {
static PROBE: OnceLock<LineCaps> = OnceLock::new();
if protocol_mode() {
return LineCaps::None;
}
*PROBE.get_or_init(|| {
probe(
std::io::stdin().is_terminal(),
std::io::stdout().is_terminal(),
std::env::var("TERM").ok().as_deref(),
false,
)
})
}
#[cfg(test)]
mod tests {
use super::{probe, LineCaps};
#[test]
fn only_two_real_ttys_with_a_usable_term_may_own_the_line() {
let cases: &[(bool, bool, Option<&str>, bool, LineCaps)] = &[
(true, true, Some("xterm-256color"), false, LineCaps::Own),
(true, true, Some("screen"), false, LineCaps::Own),
(true, true, Some("xterm-256color"), true, LineCaps::None),
(false, true, Some("xterm"), false, LineCaps::None),
(true, false, Some("xterm"), false, LineCaps::None),
(false, false, Some("xterm"), false, LineCaps::None),
(true, true, Some("dumb"), false, LineCaps::None),
(true, true, Some(""), false, LineCaps::None),
(true, true, None, false, LineCaps::None),
];
for &(sin, sout, term, proto, want) in cases {
assert_eq!(
probe(sin, sout, term, proto),
want,
"stdin_tty={sin} stdout_tty={sout} TERM={term:?} protocol={proto}"
);
}
}
#[test]
fn forced_color_into_a_pipe_can_never_own_the_line() {
assert_eq!(
probe(true, false, Some("xterm-256color"), false),
LineCaps::None
);
}
#[test]
fn no_color_policy_does_not_revoke_line_ownership() {
assert_eq!(
probe(true, true, Some("xterm-256color"), false),
LineCaps::Own,
"NO_COLOR must dim the line, not delete it"
);
}
}