use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TmuxTarget {
pub session: String,
#[serde(default)]
pub pane: Option<String>,
}
impl TmuxTarget {
pub fn session(name: impl Into<String>) -> Self {
Self {
session: name.into(),
pane: None,
}
}
pub fn pane(name: impl Into<String>, pane: impl Into<String>) -> Self {
Self {
session: name.into(),
pane: Some(pane.into()),
}
}
pub fn as_target(&self) -> String {
match &self.pane {
Some(p) => format!("{}:{}", self.session, p),
None => self.session.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TmuxCommand {
NewSession {
name: String,
workdir: Option<String>,
},
KillSession {
name: String,
},
HasSession {
name: String,
},
ListSessions,
ListWindows {
name: String,
},
ListPanes {
name: String,
},
SendKeys {
target: TmuxTarget,
keys: String,
literal: bool,
},
CapturePane {
target: TmuxTarget,
lines: Option<u32>,
},
}
pub const SESSION_LIST_FORMAT: &str = "#{session_name}:#{session_created}:#{session_attached}";
pub const WINDOW_LIST_FORMAT: &str = "#{window_index}:#{window_name}";
pub const PANE_LIST_FORMAT: &str = "#{pane_id}:#{pane_active}";
pub fn tmux_argv(cmd: &TmuxCommand) -> Vec<String> {
match cmd {
TmuxCommand::NewSession { name, workdir } => {
let mut argv = vec![
"new-session".to_string(),
"-A".to_string(),
"-d".to_string(),
"-s".to_string(),
name.clone(),
];
if let Some(dir) = workdir {
argv.push("-c".to_string());
argv.push(dir.clone());
}
argv
}
TmuxCommand::KillSession { name } => {
vec!["kill-session".into(), "-t".into(), name.clone()]
}
TmuxCommand::HasSession { name } => {
vec!["has-session".into(), "-t".into(), name.clone()]
}
TmuxCommand::ListSessions => {
vec![
"list-sessions".into(),
"-F".into(),
SESSION_LIST_FORMAT.into(),
]
}
TmuxCommand::ListWindows { name } => {
vec![
"list-windows".into(),
"-t".into(),
name.clone(),
"-F".into(),
WINDOW_LIST_FORMAT.into(),
]
}
TmuxCommand::ListPanes { name } => {
vec![
"list-panes".into(),
"-t".into(),
name.clone(),
"-F".into(),
PANE_LIST_FORMAT.into(),
]
}
TmuxCommand::SendKeys {
target,
keys,
literal,
} => {
let mut argv = vec![
"send-keys".to_string(),
"-t".to_string(),
target.as_target(),
];
if *literal {
argv.push("-l".to_string());
}
argv.push(keys.clone());
argv
}
TmuxCommand::CapturePane { target, lines } => {
let mut argv = vec![
"capture-pane".to_string(),
"-t".to_string(),
target.as_target(),
"-p".to_string(),
];
if let Some(n) = lines {
argv.push("-S".to_string());
argv.push(format!("-{n}"));
}
argv
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn target_renders_session_and_pane() {
assert_eq!(TmuxTarget::session("s").as_target(), "s");
assert_eq!(TmuxTarget::pane("s", "%2").as_target(), "s:%2");
}
#[test]
fn new_session_argv() {
let argv = tmux_argv(&TmuxCommand::NewSession {
name: "trusty-mpm-1".into(),
workdir: Some("/tmp/proj".into()),
});
assert_eq!(
argv,
[
"new-session",
"-A",
"-d",
"-s",
"trusty-mpm-1",
"-c",
"/tmp/proj"
]
);
}
#[test]
fn new_session_argv_without_workdir() {
let argv = tmux_argv(&TmuxCommand::NewSession {
name: "s".into(),
workdir: None,
});
assert_eq!(argv, ["new-session", "-A", "-d", "-s", "s"]);
}
#[test]
fn send_keys_literal_argv() {
let argv = tmux_argv(&TmuxCommand::SendKeys {
target: TmuxTarget::session("s"),
keys: "claude --help".into(),
literal: true,
});
assert_eq!(argv, ["send-keys", "-t", "s", "-l", "claude --help"]);
}
#[test]
fn send_keys_keyname_argv() {
let argv = tmux_argv(&TmuxCommand::SendKeys {
target: TmuxTarget::pane("s", "%1"),
keys: "Enter".into(),
literal: false,
});
assert_eq!(argv, ["send-keys", "-t", "s:%1", "Enter"]);
}
#[test]
fn capture_argv() {
let argv = tmux_argv(&TmuxCommand::CapturePane {
target: TmuxTarget::session("s"),
lines: Some(50),
});
assert_eq!(argv, ["capture-pane", "-t", "s", "-p", "-S", "-50"]);
let argv = tmux_argv(&TmuxCommand::CapturePane {
target: TmuxTarget::session("s"),
lines: None,
});
assert_eq!(argv, ["capture-pane", "-t", "s", "-p"]);
}
#[test]
fn list_sessions_uses_canonical_format() {
let argv = tmux_argv(&TmuxCommand::ListSessions);
assert_eq!(argv, ["list-sessions", "-F", SESSION_LIST_FORMAT]);
}
#[test]
fn list_windows_argv() {
let argv = tmux_argv(&TmuxCommand::ListWindows {
name: "work".into(),
});
assert_eq!(
argv,
["list-windows", "-t", "work", "-F", WINDOW_LIST_FORMAT]
);
}
#[test]
fn list_panes_argv() {
let argv = tmux_argv(&TmuxCommand::ListPanes {
name: "work".into(),
});
assert_eq!(argv, ["list-panes", "-t", "work", "-F", PANE_LIST_FORMAT]);
}
}