use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::OnceLock;
use crate::tools;
struct Saved {
tmux: PathBuf,
socket: String,
pane: String,
value: Option<String>,
}
static SAVED: OnceLock<Saved> = OnceLock::new();
fn target(env: impl Fn(&str) -> Option<String>) -> Option<(String, String)> {
let set = |name: &str| env(name).filter(|v| !v.is_empty());
let in_tmux = set("TERM").is_some_and(|t| t.starts_with("tmux"))
|| set("TERM_PROGRAM").is_some_and(|p| p == "tmux");
let socket = set("TMUX")?.split(',').next()?.to_string();
let pane = set("TMUX_PANE")?;
(in_tmux && socket.starts_with('/') && pane.starts_with('%')).then_some((socket, pane))
}
fn tmux(path: &PathBuf, socket: &str) -> Command {
let mut command = Command::new(path);
command
.env_clear()
.envs(tools::child_env(&tools::Tools::default()))
.arg("-S")
.arg(socket)
.stdin(Stdio::null())
.stderr(Stdio::null());
command
}
pub fn save() {
let Some((socket, pane)) = target(|name| std::env::var(name).ok()) else {
return;
};
let Some(path) = tools::on_path("tmux") else {
return;
};
let Ok(out) = tmux(&path, &socket)
.args(["show-options", "-pqv", "-t", &pane, "allow-passthrough"])
.output()
else {
return;
};
if out.status.success() {
let value = String::from_utf8_lossy(&out.stdout).trim().to_string();
let value = (!value.is_empty()).then_some(value);
let _ = SAVED.set(Saved {
tmux: path,
socket,
pane,
value,
});
}
}
fn restore_args(pane: &str, value: Option<&str>) -> Vec<String> {
let mut args = vec!["set-option".to_string(), "-p".into()];
if value.is_none() {
args.push("-u".into());
}
args.extend(["-t".into(), pane.into(), "allow-passthrough".into()]);
args.extend(value.map(String::from));
args
}
pub fn restore() {
if let Some(saved) = SAVED.get() {
let _ = tmux(&saved.tmux, &saved.socket)
.args(restore_args(&saved.pane, saved.value.as_deref()))
.stdout(Stdio::null())
.status();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn env<'a>(vars: &'a [(&str, &str)]) -> impl Fn(&str) -> Option<String> + 'a {
move |name| {
vars.iter()
.find(|(k, _)| *k == name)
.map(|(_, v)| v.to_string())
}
}
#[test]
fn only_the_pane_tmux_names_is_touched() {
let pane = [
("TERM_PROGRAM", "tmux"),
("TMUX", "/tmp/tmux-1000/default,1234,0"),
("TMUX_PANE", "%3"),
];
assert_eq!(
target(env(&pane)),
Some(("/tmp/tmux-1000/default".into(), "%3".into()))
);
assert_eq!(target(env(&[("TERM", "tmux-256color")])), None);
assert_eq!(target(env(&[("TERM", "xterm-256color")])), None);
let odd = [
("TERM", "tmux-256color"),
("TMUX", "relative,1,0"),
("TMUX_PANE", "%3"),
];
assert_eq!(target(env(&odd)), None);
}
#[test]
fn the_old_value_is_put_back_or_unset() {
assert_eq!(
restore_args("%3", Some("off")),
["set-option", "-p", "-t", "%3", "allow-passthrough", "off"]
);
assert_eq!(
restore_args("%3", None),
["set-option", "-p", "-u", "-t", "%3", "allow-passthrough"]
);
}
}