1use eyre::Result;
4
5#[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
19pub trait Backend {
24 fn own_id(&self) -> Result<String>;
26
27 fn send(&self, target: &str, text: &str) -> Result<()>;
29
30 fn spawn(&self, cmd: &str, args: &[&str], name: Option<&str>) -> Result<String>;
32
33 fn close(&self, target: &str) -> Result<()>;
35
36 fn list_panes(&self) -> Result<Vec<PaneInfo>>;
38
39 fn list_pane_ids(&self) -> Result<Vec<String>>;
41
42 fn read_scrollback(&self, target: &str) -> Result<String>;
44
45 fn normalize_id(&self, input: &str) -> String;
47
48 fn wait_for_ready(&self, target: &str, max_secs: u64, settle_secs: u64);
50
51 fn session_name(&self) -> Result<String>;
53
54 fn backend_name(&self) -> &str;
56}
57
58pub 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, })
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 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
122pub 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 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
186pub struct TmuxBackend;
191
192impl Backend for TmuxBackend {
193 fn own_id(&self) -> Result<String> { crate::tmux::own_pane_id() }
194 fn send(&self, target: &str, text: &str) -> Result<()> { crate::tmux::send(target, text) }
195 fn spawn(&self, cmd: &str, args: &[&str], name: Option<&str>) -> Result<String> { crate::tmux::spawn(cmd, args, name) }
196 fn close(&self, target: &str) -> Result<()> { crate::tmux::close(target) }
197 fn list_panes(&self) -> Result<Vec<PaneInfo>> {
198 let panes = crate::tmux::list_panes()?;
199 Ok(panes.into_iter().map(|p| PaneInfo {
200 id: p.id,
201 title: p.title,
202 is_plugin: false,
203 is_focused: p.active,
204 running: true,
205 }).collect())
206 }
207 fn list_pane_ids(&self) -> Result<Vec<String>> { crate::tmux::list_pane_ids() }
208 fn read_scrollback(&self, target: &str) -> Result<String> { crate::tmux::dump(target) }
209 fn normalize_id(&self, input: &str) -> String { crate::tmux::normalize_pane_id(input) }
210 fn wait_for_ready(&self, target: &str, max_secs: u64, settle_secs: u64) { crate::tmux::wait_for_stable_output(target, max_secs, settle_secs); }
211 fn session_name(&self) -> Result<String> { crate::tmux::session_name() }
212 fn backend_name(&self) -> &str { "tmux" }
213}
214
215pub fn detect() -> Option<Box<dyn Backend>> {
225 if std::env::var("CMUX_SURFACE_ID").is_ok() {
226 return Some(Box::new(CmuxBackend));
227 }
228 if std::env::var("ZELLIJ").is_ok() {
229 return Some(Box::new(ZellijBackend));
230 }
231 if std::env::var("TMUX").is_ok() {
232 return Some(Box::new(TmuxBackend));
233 }
234 None
235}