use std::process::Command;
use crate::error::{BosunError, Result};
use crate::tmux::client::sync_tmux;
pub fn attach_with_ctrl_q_detach(socket: Option<&str>, name: &str) -> Result<()> {
ensure_ctrl_q_bound(socket);
run_attach(socket, name)
}
pub fn ensure_ctrl_q_bound(socket: Option<&str>) {
let out = sync_tmux(socket, ["bind-key", "-T", "root", "C-q", "detach-client"]).output();
match out {
Ok(o) if o.status.success() => {}
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("bind-key C-q: {}", stderr.trim());
}
Err(e) => tracing::warn!("bind-key C-q: {}", e),
}
}
pub fn clear_ctrl_q_bound(socket: Option<&str>) {
let out = sync_tmux(socket, ["unbind-key", "-T", "root", "C-q"]).output();
if let Ok(o) = out {
if !o.status.success() {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("unbind-key C-q: {}", stderr.trim());
}
}
}
fn run_attach(socket: Option<&str>, name: &str) -> Result<()> {
let status = sync_tmux(socket, ["attach-session", "-t", name])
.status()
.map_err(BosunError::Io)?;
if !status.success() {
return Err(BosunError::Tmux(format!(
"attach-session -t {} failed: {}",
name, status
)));
}
Ok(())
}
pub fn emergency_unbind(socket: Option<&str>) {
let runs: &[&[&str]] = &[
&["unbind-key", "-T", "root", "C-q"],
&["unbind-key", "-n", "S-Left"],
&["unbind-key", "-n", "S-Right"],
&["unbind-key", "-T", "prefix", "o"],
];
for args in runs {
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 2);
if let Some(s) = socket {
argv.push("-L");
argv.push(s);
}
argv.extend_from_slice(args);
let _ = Command::new("tmux").args(&argv).output();
}
}
pub fn ensure_session_cycle_bound(socket: Option<&str>) {
let tmux = match socket {
Some(s) => format!("tmux -L {}", shell_quote(s)),
None => "tmux".to_string(),
};
let fmt = "##{?session_last_attached,##{session_last_attached},0} ##{session_name}";
let exclude = crate::tmux::control_client::MONITOR_SESSION;
let left_cmd = format!(
"T=$({tmux} list-sessions -F '{fmt}' \
| sort -rnk1 \
| awk -v cur=\"$({tmux} display-message -p '##S')\" \
'$2 != cur && $2 != \"{exclude}\" {{print $2; exit}}'); \
[ -n \"$T\" ] && {tmux} switch-client -t \"$T\""
);
let right_cmd = format!(
"cur=$({tmux} display-message -p '##S'); \
L=$({tmux} list-sessions -F '{fmt}' \
| sort -rnk1 \
| awk -v cur=\"$cur\" \
'$2 != cur && $2 != \"{exclude}\" {{print $2}}'); \
T=$(printf '%s\\n' \"$L\" | sed -n '2p'); \
[ -z \"$T\" ] && T=$(printf '%s\\n' \"$L\" | sed -n '1p'); \
[ -n \"$T\" ] && {tmux} switch-client -t \"$T\""
);
for (key, body) in [("S-Left", &left_cmd), ("S-Right", &right_cmd)] {
let out = sync_tmux(socket, ["bind-key", "-n", key, "run-shell", body]).output();
match out {
Ok(o) if o.status.success() => {}
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("bind-key {}: {}", key, stderr.trim());
}
Err(e) => tracing::warn!("bind-key {}: {}", key, e),
}
}
}
pub fn clear_session_cycle_bound(socket: Option<&str>) {
for key in ["S-Left", "S-Right"] {
let out = sync_tmux(socket, ["unbind-key", "-n", key]).output();
if let Ok(o) = out {
if !o.status.success() {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("unbind-key {}: {}", key, stderr.trim());
}
}
}
}
pub fn ensure_quick_jump_bound(socket: Option<&str>) {
let tmux = match socket {
Some(s) => format!("tmux -L {}", shell_quote(s)),
None => "tmux".to_string(),
};
let exclude = crate::tmux::control_client::MONITOR_SESSION;
let cmd = format!("{tmux} choose-tree -Zs -f '##{{!=:##{{session_name}},{exclude}}}'");
let out = sync_tmux(
socket,
[
"bind-key",
"-T",
"prefix",
"o",
"display-popup",
"-E",
"-h",
"70%",
"-w",
"60%",
"-T",
" bosun · quick switch ",
&cmd,
],
)
.output();
match out {
Ok(o) if o.status.success() => {}
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("bind-key prefix o: {}", stderr.trim());
}
Err(e) => tracing::warn!("bind-key prefix o: {}", e),
}
}
pub fn clear_quick_jump_bound(socket: Option<&str>) {
let out = sync_tmux(socket, ["unbind-key", "-T", "prefix", "o"]).output();
if let Ok(o) = out {
if !o.status.success() {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!("unbind-key prefix o: {}", stderr.trim());
}
}
}
fn shell_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for c in s.chars() {
if c == '\'' {
out.push_str("'\\''");
} else {
out.push(c);
}
}
out.push('\'');
out
}