use std::io::IsTerminal;
use rmux_proto::ClientTerminalContext;
pub(crate) const ATTACH_TERMINAL_REQUIRED_MESSAGE: &str = "open terminal failed: not a terminal";
pub(crate) fn require_attach_terminal() -> Result<(), &'static str> {
require_attach_terminal_from(std::io::stdin().is_terminal())
}
fn require_attach_terminal_from(stdin_is_terminal: bool) -> Result<(), &'static str> {
if stdin_is_terminal {
Ok(())
} else {
Err(ATTACH_TERMINAL_REQUIRED_MESSAGE)
}
}
pub(crate) fn client_terminal_context_from_parts(
terminal_features: Vec<String>,
utf8: bool,
) -> ClientTerminalContext {
let mut context = ClientTerminalContext {
terminal_features,
utf8,
};
apply_detected_client_terminal_features(&mut context);
context
}
pub(crate) fn apply_detected_client_terminal_features(context: &mut ClientTerminalContext) {
#[cfg(windows)]
apply_windows_terminal_features(
context,
std::env::var_os("WT_SESSION").is_some_and(|value| !value.is_empty()),
);
#[cfg(not(windows))]
let _ = context;
}
#[cfg(windows)]
fn apply_windows_terminal_features(context: &mut ClientTerminalContext, is_windows_terminal: bool) {
push_unique_terminal_feature(&mut context.terminal_features, "sync");
push_unique_terminal_feature(&mut context.terminal_features, "bpaste");
push_unique_terminal_feature(&mut context.terminal_features, "mouse");
push_unique_terminal_feature(&mut context.terminal_features, "clipboard");
if is_windows_terminal {
context.utf8 = true;
}
}
#[cfg(windows)]
fn push_unique_terminal_feature(features: &mut Vec<String>, feature: &str) {
if !features
.iter()
.any(|value| value.eq_ignore_ascii_case(feature))
{
features.push(feature.to_owned());
}
}
#[cfg(test)]
mod tests {
use super::{
client_terminal_context_from_parts, require_attach_terminal_from,
ATTACH_TERMINAL_REQUIRED_MESSAGE,
};
#[test]
fn attach_terminal_preflight_rejects_redirected_stdin() {
assert_eq!(
require_attach_terminal_from(false),
Err(ATTACH_TERMINAL_REQUIRED_MESSAGE)
);
assert_eq!(require_attach_terminal_from(true), Ok(()));
}
#[test]
fn detected_client_terminal_context_preserves_explicit_features() {
let context = client_terminal_context_from_parts(vec!["RGB".to_owned()], true);
assert!(context.utf8);
assert!(context
.terminal_features
.iter()
.any(|feature| feature == "RGB"));
}
#[cfg(windows)]
#[test]
fn windows_terminal_advertises_vt_features_and_utf8() {
let mut context = rmux_proto::ClientTerminalContext::default();
super::apply_windows_terminal_features(&mut context, true);
assert!(context.utf8);
assert_eq!(
context.terminal_features,
vec!["sync", "bpaste", "mouse", "clipboard"]
);
}
#[cfg(windows)]
#[test]
fn non_windows_terminal_vt_outer_still_advertises_mouse_and_bpaste() {
let mut context = rmux_proto::ClientTerminalContext::default();
super::apply_windows_terminal_features(&mut context, false);
assert!(!context.utf8);
assert_eq!(
context.terminal_features,
vec!["sync", "bpaste", "mouse", "clipboard"]
);
}
#[cfg(windows)]
#[test]
fn detected_windows_terminal_features_are_not_duplicated() {
let mut context = rmux_proto::ClientTerminalContext {
terminal_features: vec!["SYNC".to_owned(), "BPASTE".to_owned(), "MOUSE".to_owned()],
utf8: false,
};
super::apply_windows_terminal_features(&mut context, true);
assert!(context.utf8);
assert_eq!(
context.terminal_features,
vec!["SYNC", "BPASTE", "MOUSE", "clipboard"]
);
}
}