Skip to main content

atm_tmux/
lib.rs

1//! ATM Tmux — Thin async wrapper over the tmux CLI.
2//!
3//! Provides the [`TmuxClient`] trait for tmux pane management, with a real
4//! implementation ([`RealTmuxClient`]) that shells out to `tmux` via
5//! `tokio::process::Command`, and a mock ([`MockTmuxClient`]) for testing.
6//!
7//! All code follows the panic-free policy: no `.unwrap()`, `.expect()`,
8//! `panic!()`, `unreachable!()`, `todo!()`, or direct indexing `[i]`.
9
10pub mod client;
11pub mod error;
12pub mod layout;
13pub mod mock;
14
15pub use client::RealTmuxClient;
16pub use error::TmuxError;
17pub use mock::MockTmuxClient;
18
19use async_trait::async_trait;
20use serde::{Deserialize, Serialize};
21
22/// Direction for placing a new pane relative to the target pane.
23///
24/// Maps directly to tmux `split-window` flags:
25/// - `Left`  → `-h -b` (horizontal split, new pane before/left)
26/// - `Right` → `-h`    (horizontal split, new pane after/right)
27/// - `Above` → `-v -b` (vertical split, new pane before/above)
28/// - `Below` → `-v`    (vertical split, new pane after/below)
29///
30/// The `-b` (before) flag requires tmux 3.1+.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum PaneDirection {
34    Left,
35    Right,
36    Above,
37    Below,
38}
39
40/// Information about a single tmux pane.
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct PaneInfo {
43    /// Pane ID (e.g., "%5").
44    pub pane_id: String,
45    /// Session name this pane belongs to.
46    pub session_name: String,
47    /// Window index within the session.
48    pub window_index: u32,
49    /// PID of the shell process running in the pane.
50    pub pane_pid: u32,
51    /// Pane width in columns.
52    pub width: u16,
53    /// Pane height in rows.
54    pub height: u16,
55    /// Whether this pane is the currently active pane.
56    pub is_active: bool,
57}
58
59/// Async interface for tmux pane management.
60///
61/// The real implementation shells out to the `tmux` CLI. The mock records
62/// calls for test assertions.
63#[async_trait]
64pub trait TmuxClient: Send + Sync {
65    /// Splits a pane, returning the new pane ID (e.g., "%7").
66    ///
67    /// # Arguments
68    /// * `target` — Target pane to split (e.g., "%5").
69    /// * `size` — Size specification (e.g., "30%", "20").
70    /// * `direction` — Where to place the new pane relative to the target.
71    /// * `command` — Optional command to run in the new pane.
72    async fn split_window(
73        &self,
74        target: &str,
75        size: &str,
76        direction: PaneDirection,
77        command: Option<&str>,
78    ) -> Result<String, TmuxError>;
79
80    /// Returns the current working directory of a pane, or `None` if unavailable.
81    ///
82    /// Queries tmux's `#{pane_current_path}` format variable.
83    async fn get_pane_cwd(&self, pane: &str) -> Result<Option<String>, TmuxError>;
84
85    /// Creates a new window in the given session, returning the new pane ID.
86    async fn new_window(&self, session: &str, command: Option<&str>) -> Result<String, TmuxError>;
87
88    /// Kills (closes) a pane.
89    async fn kill_pane(&self, pane: &str) -> Result<(), TmuxError>;
90
91    /// Resizes a pane.
92    ///
93    /// At least one of `width` or `height` must be `Some`.
94    async fn resize_pane(
95        &self,
96        pane: &str,
97        width: Option<u16>,
98        height: Option<u16>,
99    ) -> Result<(), TmuxError>;
100
101    /// Sends keystrokes to a pane.
102    async fn send_keys(&self, pane: &str, keys: &str) -> Result<(), TmuxError>;
103
104    /// Lists all panes across all sessions.
105    async fn list_panes(&self) -> Result<Vec<PaneInfo>, TmuxError>;
106
107    /// Displays a popup overlay.
108    async fn display_popup(
109        &self,
110        width: &str,
111        height: &str,
112        command: &str,
113    ) -> Result<(), TmuxError>;
114
115    /// Selects (focuses) a pane.
116    async fn select_pane(&self, pane: &str) -> Result<(), TmuxError>;
117
118    /// Captures the visible content of a pane.
119    ///
120    /// Returns the text currently displayed in the pane, one string per line.
121    /// Trailing blank lines are trimmed.
122    async fn capture_pane(&self, pane: &str) -> Result<Vec<String>, TmuxError>;
123
124    /// Creates a new detached tmux session, returning the initial pane ID.
125    async fn new_session(&self, name: &str) -> Result<String, TmuxError>;
126}