use crate::core::tmux::TmuxTarget;
use crate::daemon::tmux::TmuxDriver;
use super::manager::{ManagedError, ManagedTmuxDriver};
pub struct RealTmuxDriver {
driver: TmuxDriver,
}
impl RealTmuxDriver {
pub fn discover() -> Result<Self, ManagedError> {
let driver =
TmuxDriver::discover().map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))?;
Ok(Self { driver })
}
}
impl ManagedTmuxDriver for RealTmuxDriver {
fn ensure_server_up(&self) -> Result<(), ManagedError> {
self.driver
.ensure_server_up()
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn create_session(&self, name: &str, workdir: &str) -> Result<(), ManagedError> {
self.driver
.create_session(name, Some(workdir))
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn kill_session(&self, name: &str) -> Result<(), ManagedError> {
self.driver
.kill_session(name)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn rename_session(&self, old: &str, new: &str) -> Result<(), ManagedError> {
let cmd = crate::core::tmux::TmuxCommand::RenameSession {
old: old.to_string(),
new: new.to_string(),
};
let output = crate::core::tmux::run_tmux(&cmd)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
Err(ManagedError::TmuxUnavailable(format!(
"tmux rename-session {old} -> {new} failed: {stderr}"
)))
}
}
fn attached_session_names(&self) -> Vec<String> {
self.driver
.list_sessions()
.map(|sessions| {
sessions
.into_iter()
.filter(|s| s.attached)
.map(|s| s.name)
.collect()
})
.unwrap_or_default()
}
fn send_line(&self, name: &str, text: &str) -> Result<(), ManagedError> {
self.driver
.send_line(&TmuxTarget::session(name), text)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn send_keys_literal(&self, name: &str, text: &str) -> Result<(), ManagedError> {
self.driver
.send_keys_literal(&TmuxTarget::session(name), text)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn send_line_to_pane(&self, name: &str, pane_id: &str, text: &str) -> Result<(), ManagedError> {
self.driver
.send_line(&TmuxTarget::pane(name, pane_id), text)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn pane_exists(&self, name: &str, pane_id: &str) -> bool {
self.driver.pane_exists(name, pane_id)
}
fn pane_exists_checked(&self, name: &str, pane_id: &str) -> Option<bool> {
self.driver.pane_exists_checked(name, pane_id)
}
fn send_keys_literal_to_pane(
&self,
name: &str,
pane_id: &str,
text: &str,
) -> Result<(), ManagedError> {
self.driver
.send_keys_literal(&TmuxTarget::pane(name, pane_id), text)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn send_interrupt(&self, name: &str) -> Result<(), ManagedError> {
self.driver
.send_interrupt(&TmuxTarget::session(name))
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn send_interrupt_to_pane(&self, name: &str, pane_id: &str) -> Result<(), ManagedError> {
self.driver
.send_interrupt(&TmuxTarget::pane(name, pane_id))
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn capture(&self, name: &str, lines: usize) -> Result<String, ManagedError> {
let lines = u32::try_from(lines).unwrap_or(u32::MAX);
self.driver
.capture(&TmuxTarget::session(name), Some(lines))
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn capture_pane(
&self,
name: &str,
pane_id: &str,
lines: usize,
) -> Result<String, ManagedError> {
let lines = u32::try_from(lines).unwrap_or(u32::MAX);
self.driver
.capture(&TmuxTarget::pane(name, pane_id), Some(lines))
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
self.driver
.list_sessions()
.map(|sessions| sessions.into_iter().map(|s| s.name).collect())
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
fn get_pane_cwd(&self, name: &str) -> Option<std::path::PathBuf> {
self.driver.pane_current_path(name)
}
fn get_pane_id(&self, name: &str) -> Option<String> {
self.driver.pane_id(name)
}
fn runtime_ready(&self, name: &str) -> bool {
crate::core::process::find_claude_pid_in_tmux(name, 1, std::time::Duration::ZERO).is_some()
}
fn set_environment(&self, name: &str, key: &str, value: &str) -> Result<(), ManagedError> {
self.driver
.set_environment(name, key, value)
.map_err(|e| ManagedError::TmuxUnavailable(e.to_string()))
}
}
pub struct NoopTmuxDriver;
impl ManagedTmuxDriver for NoopTmuxDriver {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Err(ManagedError::TmuxUnavailable("tmux not installed".into()))
}
fn kill_session(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_line(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Err(ManagedError::TmuxUnavailable("tmux not installed".into()))
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(String::new())
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
Ok(Vec::new())
}
fn set_environment(&self, _name: &str, _key: &str, _value: &str) -> Result<(), ManagedError> {
Err(ManagedError::TmuxUnavailable("tmux not installed".into()))
}
}
#[doc(hidden)]
pub struct FakeNoopTmuxDriver;
impl ManagedTmuxDriver for FakeNoopTmuxDriver {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Ok(())
}
fn kill_session(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_line(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_keys_literal(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_interrupt(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(String::new())
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn real_tmux_constructs() {
let _ = RealTmuxDriver::discover();
}
#[test]
fn fake_noop_driver_accepts_create_session() {
let driver = FakeNoopTmuxDriver;
assert!(
driver.create_session("tmpm-test-abc123", "/tmp").is_ok(),
"FakeNoopTmuxDriver::create_session must succeed"
);
}
#[test]
fn noop_driver_set_environment_fails_loudly() {
let driver = NoopTmuxDriver;
assert!(driver.set_environment("s", "K", "v").is_err());
}
#[test]
fn fake_noop_driver_list_sessions_is_empty() {
let driver = FakeNoopTmuxDriver;
let sessions = driver.list_sessions().expect("list_sessions ok");
assert!(
sessions.is_empty(),
"FakeNoopTmuxDriver must see no live sessions"
);
}
}