use std::io::{Result, Write};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum Multiplexer {
#[default]
None,
Screen,
Tmux,
}
impl Multiplexer {
#[must_use]
pub fn detect() -> Self {
if std::env::var_os("TMUX").is_some() {
Self::Tmux
} else if std::env::var_os("STY").is_some() {
Self::Screen
} else {
Self::None
}
}
pub fn write_passthrough<W: Write>(self, writer: &mut W, payload: &[u8]) -> Result<()> {
match self {
Self::None => writer.write_all(payload),
Self::Tmux => write_tmux_passthrough(writer, payload),
Self::Screen => write_screen_passthrough(writer, payload),
}
}
}
fn write_tmux_passthrough<W: Write>(writer: &mut W, payload: &[u8]) -> Result<()> {
writer.write_all(b"\x1bPtmux;")?;
let mut start = 0;
for (i, &byte) in payload.iter().enumerate() {
if byte == 0x1b {
writer.write_all(&payload[start..i])?;
writer.write_all(b"\x1b\x1b")?;
start = i + 1;
}
}
writer.write_all(&payload[start..])?;
writer.write_all(b"\x1b\\")?;
Ok(())
}
fn write_screen_passthrough<W: Write>(writer: &mut W, payload: &[u8]) -> Result<()> {
let mut remaining = payload;
loop {
writer.write_all(b"\x1bP")?;
if let Some(pos) = find_st(remaining) {
writer.write_all(&remaining[..pos])?;
writer.write_all(b"\x1b\\")?;
remaining = &remaining[pos + 2..];
if remaining.is_empty() {
return Ok(());
}
} else {
writer.write_all(remaining)?;
writer.write_all(b"\x1b\\")?;
return Ok(());
}
}
}
fn find_st(bytes: &[u8]) -> Option<usize> {
bytes.windows(2).position(|w| w == b"\x1b\\")
}
#[cfg(test)]
mod tests {
use super::*;
use temp_env::with_vars;
#[test]
fn detect_none() {
with_vars(vec![("TMUX", None::<&str>), ("STY", None)], || {
assert_eq!(Multiplexer::detect(), Multiplexer::None);
});
}
#[test]
fn detect_tmux() {
with_vars(
vec![
("TMUX", Some("/tmp/tmux-1000/default,1234,0")),
("STY", None),
],
|| {
assert_eq!(Multiplexer::detect(), Multiplexer::Tmux);
},
);
}
#[test]
fn detect_screen() {
with_vars(
vec![("TMUX", None::<&str>), ("STY", Some("4321.pts-0.host"))],
|| {
assert_eq!(Multiplexer::detect(), Multiplexer::Screen);
},
);
}
#[test]
fn tmux_beats_screen_when_both_set() {
with_vars(vec![("TMUX", Some("x")), ("STY", Some("y"))], || {
assert_eq!(Multiplexer::detect(), Multiplexer::Tmux);
});
}
#[test]
fn no_multiplexer_writes_payload_verbatim() {
let mut out = Vec::new();
Multiplexer::None
.write_passthrough(&mut out, b"\x1b[31mhi\x1b[0m")
.unwrap();
assert_eq!(out, b"\x1b[31mhi\x1b[0m");
}
#[test]
fn tmux_doubles_escapes_and_wraps() {
let mut out = Vec::new();
Multiplexer::Tmux
.write_passthrough(&mut out, b"\x1b]1337;hi\x1b\\")
.unwrap();
assert_eq!(
out,
b"\x1bPtmux;\x1b\x1b]1337;hi\x1b\x1b\\\x1b\\".as_slice()
);
}
#[test]
fn screen_wraps_and_splits_on_st() {
let mut out = Vec::new();
Multiplexer::Screen
.write_passthrough(&mut out, b"first\x1b\\second")
.unwrap();
assert_eq!(out, b"\x1bPfirst\x1b\\\x1bPsecond\x1b\\".as_slice());
}
}