use std::path::Path;
use anyhow::Result;
use vcs_runner::Cmd;
use super::{last_lines, panes, run, Open};
pub struct Zmx;
impl Zmx {
fn cmd(&self) -> Cmd {
Cmd::new("zmx").env_remove("ZMX_SESSION").timeout(std::time::Duration::from_secs(10))
}
}
impl super::Multiplexer for Zmx {
fn exists(&self, name: &str) -> Result<bool> {
Ok(self.list()?.iter().any(|session| session == name))
}
fn list(&self) -> Result<Vec<String>> {
let out = run(self.cmd().args(["list", "--short"]))?;
Ok(super::first_fields(&out.stdout_lossy()))
}
fn ensure(&self, open: &Open<'_>) -> Result<()> {
if self.exists(open.name)? {
return Ok(());
}
let leaves = panes::leaves(open.layout)?;
let leaf = leaves.get(panes::focused_index(&leaves)).cloned().unwrap_or_else(panes::Leaf::shell);
let args = run_args(open.name, &leaf);
let mut cmd = self.cmd();
cmd = cmd.args(args).envs(open.extra_env.iter().map(|(k, v)| (k.clone(), v.clone())));
run(cmd)?;
Ok(())
}
fn send_once(&self, name: &str, text: &str) -> Result<()> {
let args = send_args(name, text);
run(self.cmd().args(args))?;
Ok(())
}
fn history(&self, name: &str, lines: usize) -> Result<String> {
let out = run(self.cmd().args(["history", name]))?;
Ok(last_lines(&out.stdout_lossy(), lines))
}
fn kill(&self, name: &str) -> Result<()> {
run(self.cmd().args(["kill", name, "--force"]))?;
Ok(())
}
fn attach(&self, name: &str, dir: &Path) -> Result<()> {
std::process::Command::new("zmx").args(["attach", name]).env_remove("ZMX_SESSION").current_dir(dir).status()?;
Ok(())
}
}
pub(crate) fn run_args(name: &str, leaf: &panes::Leaf) -> Vec<String> {
let mut args = vec!["run".to_string(), name.to_string(), "-d".to_string()];
match &leaf.program {
Some(program) => {
args.push(program.clone());
args.extend(leaf.args.iter().cloned());
}
None => args.push("true".to_string()),
}
args
}
pub(crate) fn send_args(name: &str, text: &str) -> Vec<String> {
let mut payload = text.to_string();
payload.push('\r');
vec!["send".to_string(), name.to_string(), payload]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_shell_session_runs_true_detached() {
assert_eq!(run_args("demo", &panes::Leaf::shell()), vec!["run", "demo", "-d", "true"]);
}
#[test]
fn a_focused_command_is_the_detached_run() {
let leaf = panes::Leaf {
program: Some("claude".to_string()),
args: vec!["--resume".to_string(), "abc".to_string()],
focus: true,
};
assert_eq!(run_args("demo", &leaf), vec!["run", "demo", "-d", "claude", "--resume", "abc"]);
}
#[test]
fn send_carries_a_carriage_return_and_no_extra_argument() {
let args = send_args("demo", "printf ready");
assert_eq!(args.len(), 3);
assert_eq!(args[0], "send");
assert_eq!(args[1], "demo");
assert_eq!(args[2], "printf ready\r");
}
}