use std::process::{Command, ExitStatus, Output}; use crate::{Session, SessionSource}; pub use error::Error; mod error; #[derive(Debug)] pub struct Tmux { socket_name: String, } impl Tmux { const SESSION_FORMAT: &str = r##"Session(name: "#S", #{?session_last_attached,timestamp: #{session_last_attached}#, state: Updated,timestamp: #{session_created}#, state: Connected})"##; pub fn new(socket_name: String) -> Self { Self { socket_name } } pub fn show(&self, name: &String) -> Result { let output = Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("display") .arg("-p") .arg("-t") .arg(name) .arg(Self::SESSION_FORMAT) .output()? .stdout; let output_str = std::str::from_utf8(&output)?; Ok(ron::from_str(output_str)?) } pub fn list(&self) -> Result { Ok(Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("list-sessions") .arg("-F") .arg(Self::SESSION_FORMAT) .output()?) } pub fn switch(&self, host: &String) -> Result { if Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("has-session") .arg("-t") .arg(host) .status()? .success() { self.create(host)?; } if std::env::var("TMUX").is_ok() { self.attach(host) } else { self.detach_then_attach(host) } } fn create(&self, host: &String) -> Result { Ok(Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("new-session") .arg("-ds") .arg(host) .arg("ssh") .arg("-t") .arg(host) .arg("zsh -l -c 'tmux new -A'") .status()?) } fn attach(&self, host: &String) -> Result { Ok(Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("attach-session") .arg("-t") .arg(host) .status()?) } fn detach_then_attach(&self, host: &String) -> Result { Ok(Command::new("tmux") .arg("detach") .arg("-E") .arg(format!("tmux -L ssh attach -t {host}")) .status()?) } } impl SessionSource for Tmux { type Error = Error; type Iter = Vec; fn sessions(&self) -> Result { let output = Command::new("tmux") .arg("-L") .arg(&self.socket_name) .arg("list-sessions") .arg("-F") .arg(Self::SESSION_FORMAT) .output()? .stdout; let sessions = std::str::from_utf8(&output)? .lines() .filter_map(|s| match ron::from_str(s) { Ok(session) => Some(session), Err(err) => { tracing::warn!(%err, "Invalid session format"); None } }) .collect(); Ok(sessions) } }