Skip to main content

rz_cli/
backend.rs

1//! Unified Backend trait abstracting over cmux and zellij transports.
2
3use eyre::Result;
4
5// ---------------------------------------------------------------------------
6// PaneInfo — normalised view of a surface/pane across backends
7// ---------------------------------------------------------------------------
8
9/// Backend-agnostic pane/surface descriptor.
10#[derive(Debug, Clone)]
11pub struct PaneInfo {
12    pub id: String,
13    pub title: String,
14    pub is_plugin: bool,
15    pub is_focused: bool,
16    pub running: bool,
17}
18
19// ---------------------------------------------------------------------------
20// Backend trait
21// ---------------------------------------------------------------------------
22
23pub trait Backend {
24    /// This agent's own pane/surface ID.
25    fn own_id(&self) -> Result<String>;
26
27    /// Send text to `target` pane (paste + Enter).
28    fn send(&self, target: &str, text: &str) -> Result<()>;
29
30    /// Spawn a command in a new pane. Returns the new pane ID.
31    fn spawn(&self, cmd: &str, args: &[&str], name: Option<&str>) -> Result<String>;
32
33    /// Close a pane by ID.
34    fn close(&self, target: &str) -> Result<()>;
35
36    /// List all panes with metadata.
37    fn list_panes(&self) -> Result<Vec<PaneInfo>>;
38
39    /// List IDs of terminal (non-plugin) panes only.
40    fn list_pane_ids(&self) -> Result<Vec<String>>;
41
42    /// Read full scrollback from a pane.
43    fn read_scrollback(&self, target: &str) -> Result<String>;
44
45    /// Normalise a user-supplied pane identifier into canonical form.
46    fn normalize_id(&self, input: &str) -> String;
47
48    /// Block until `target` has output, then settle.
49    fn wait_for_ready(&self, target: &str, max_secs: u64, settle_secs: u64);
50
51    /// Name of the session / workspace.
52    fn session_name(&self) -> Result<String>;
53
54    /// Backend identifier: `"cmux"` or `"zellij"`.
55    fn backend_name(&self) -> &str;
56}
57
58// ---------------------------------------------------------------------------
59// CmuxBackend
60// ---------------------------------------------------------------------------
61
62pub struct CmuxBackend;
63
64impl Backend for CmuxBackend {
65    fn own_id(&self) -> Result<String> {
66        crate::cmux::own_surface_id()
67    }
68
69    fn send(&self, target: &str, text: &str) -> Result<()> {
70        crate::cmux::send(target, text)
71    }
72
73    fn spawn(&self, cmd: &str, args: &[&str], name: Option<&str>) -> Result<String> {
74        crate::cmux::spawn(cmd, args, name)
75    }
76
77    fn close(&self, target: &str) -> Result<()> {
78        crate::cmux::close(target)
79    }
80
81    fn list_panes(&self) -> Result<Vec<PaneInfo>> {
82        let surfaces = crate::cmux::list_surfaces()?;
83        Ok(surfaces
84            .into_iter()
85            .map(|s| PaneInfo {
86                id: s.id,
87                title: s.title,
88                is_plugin: s.surface_type != "terminal",
89                is_focused: s.is_focused,
90                running: true, // cmux surfaces are always running while listed
91            })
92            .collect())
93    }
94
95    fn list_pane_ids(&self) -> Result<Vec<String>> {
96        crate::cmux::list_surface_ids()
97    }
98
99    fn read_scrollback(&self, target: &str) -> Result<String> {
100        crate::cmux::read_text(target)
101    }
102
103    fn normalize_id(&self, input: &str) -> String {
104        // cmux IDs are UUIDs — pass through as-is
105        input.to_string()
106    }
107
108    fn wait_for_ready(&self, target: &str, max_secs: u64, settle_secs: u64) {
109        crate::cmux::wait_for_stable_output(target, max_secs, settle_secs);
110    }
111
112    fn session_name(&self) -> Result<String> {
113        std::env::var("CMUX_WORKSPACE_ID")
114            .map_err(|_| eyre::eyre!("CMUX_WORKSPACE_ID not set"))
115    }
116
117    fn backend_name(&self) -> &str {
118        "cmux"
119    }
120}
121
122// ---------------------------------------------------------------------------
123// ZellijBackend
124// ---------------------------------------------------------------------------
125
126pub struct ZellijBackend;
127
128impl Backend for ZellijBackend {
129    fn own_id(&self) -> Result<String> {
130        crate::zellij::own_pane_id()
131    }
132
133    fn send(&self, target: &str, text: &str) -> Result<()> {
134        crate::zellij::send(target, text)
135    }
136
137    fn spawn(&self, cmd: &str, args: &[&str], name: Option<&str>) -> Result<String> {
138        crate::zellij::spawn(cmd, args, name)
139    }
140
141    fn close(&self, target: &str) -> Result<()> {
142        crate::zellij::close(target)
143    }
144
145    fn list_panes(&self) -> Result<Vec<PaneInfo>> {
146        let panes = crate::zellij::list_panes()?;
147        Ok(panes
148            .into_iter()
149            .map(|p| PaneInfo {
150                id: p.pane_id(),
151                title: p.title,
152                is_plugin: p.is_plugin,
153                is_focused: p.is_focused,
154                running: !p.exited,
155            })
156            .collect())
157    }
158
159    fn list_pane_ids(&self) -> Result<Vec<String>> {
160        crate::zellij::list_pane_ids()
161    }
162
163    fn read_scrollback(&self, target: &str) -> Result<String> {
164        crate::zellij::dump(target)
165    }
166
167    fn normalize_id(&self, input: &str) -> String {
168        crate::zellij::normalize_pane_id(input)
169    }
170
171    fn wait_for_ready(&self, _target: &str, _max_secs: u64, settle_secs: u64) {
172        // Zellij `run` blocks until the pane is ready; just settle.
173        std::thread::sleep(std::time::Duration::from_secs(settle_secs));
174    }
175
176    fn session_name(&self) -> Result<String> {
177        std::env::var("ZELLIJ_SESSION_NAME")
178            .map_err(|_| eyre::eyre!("ZELLIJ_SESSION_NAME not set"))
179    }
180
181    fn backend_name(&self) -> &str {
182        "zellij"
183    }
184}
185
186// ---------------------------------------------------------------------------
187// Auto-detect
188// ---------------------------------------------------------------------------
189
190/// Detect the active backend from environment variables.
191///
192/// Returns `Some(CmuxBackend)` if `CMUX_SURFACE_ID` is set,
193/// `Some(ZellijBackend)` if `ZELLIJ` is set, or `None` otherwise.
194pub fn detect() -> Option<Box<dyn Backend>> {
195    if std::env::var("CMUX_SURFACE_ID").is_ok() {
196        return Some(Box::new(CmuxBackend));
197    }
198    if std::env::var("ZELLIJ").is_ok() {
199        return Some(Box::new(ZellijBackend));
200    }
201    None
202}