1use std::str::FromStr;
6
7use async_std::process::Command;
8
9use nom::{
10 character::complete::{char, digit1},
11 combinator::{all_consuming, map_res, recognize},
12 sequence::tuple,
13 IResult,
14};
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 error::{check_empty_process_output, map_add_intent, Error},
19 layout::{self, window_layout},
20 pane::Pane,
21 pane_id::{parse::pane_id, PaneId},
22 parse::{boolean, quoted_nonempty_string},
23 session::Session,
24 window_id::{parse::window_id, WindowId},
25 Result,
26};
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct Window {
31 pub id: WindowId,
33 pub index: u16,
35 pub is_active: bool,
37 pub layout: String,
39 pub name: String,
41 pub sessions: Vec<String>,
43}
44
45impl FromStr for Window {
46 type Err = Error;
47
48 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
78 let desc = "Window";
79 let intent = "#{window_id}:#{window_index}:#{?window_active,true,false}:#{window_layout}:'#{window_name}':'#{window_linked_sessions_list}'";
80
81 let (_, window) =
82 all_consuming(parse::window)(input).map_err(|e| map_add_intent(desc, intent, e))?;
83
84 Ok(window)
85 }
86}
87
88impl Window {
89 pub fn pane_ids(&self) -> Vec<PaneId> {
91 let layout = layout::parse_window_layout(&self.layout).unwrap();
92 layout.pane_ids().iter().map(PaneId::from).collect()
93 }
94}
95
96pub(crate) mod parse {
97 use super::*;
98
99 pub(crate) fn window(input: &str) -> IResult<&str, Window> {
100 let (input, (id, _, index, _, is_active, _, layout, _, name, _, session_names)) =
101 tuple((
102 window_id,
103 char(':'),
104 map_res(digit1, str::parse),
105 char(':'),
106 boolean,
107 char(':'),
108 recognize(window_layout),
109 char(':'),
110 quoted_nonempty_string,
111 char(':'),
112 quoted_nonempty_string,
113 ))(input)?;
114
115 Ok((
116 input,
117 Window {
118 id,
119 index,
120 is_active,
121 layout: layout.to_string(),
122 name: name.to_string(),
123 sessions: vec![session_names.to_string()],
124 },
125 ))
126 }
127}
128
129pub async fn available_windows() -> Result<Vec<Window>> {
135 let args = vec![
136 "list-windows",
137 "-a",
138 "-F",
139 "#{window_id}\
140 :#{window_index}\
141 :#{?window_active,true,false}\
142 :#{window_layout}\
143 :'#{window_name}'\
144 :'#{window_linked_sessions_list}'",
145 ];
146
147 let output = Command::new("tmux").args(&args).output().await?;
148 let buffer = String::from_utf8(output.stdout)?;
149
150 let result: Result<Vec<Window>> = buffer
154 .trim_end() .split('\n')
156 .map(Window::from_str)
157 .collect();
158
159 result
160}
161
162pub async fn new_window(
171 session: &Session,
172 window: &Window,
173 pane: &Pane,
174 pane_command: Option<&str>,
175) -> Result<(WindowId, PaneId)> {
176 let exact_session_name = format!("={}", session.name);
177
178 let mut args = vec![
179 "new-window",
180 "-d",
181 "-c",
182 pane.dirpath.to_str().unwrap(),
183 "-n",
184 &window.name,
185 "-t",
186 &exact_session_name,
187 "-P",
188 "-F",
189 "#{window_id}:#{pane_id}",
190 ];
191 if let Some(pane_command) = pane_command {
192 args.push(pane_command);
193 }
194
195 let output = Command::new("tmux").args(&args).output().await?;
196 let buffer = String::from_utf8(output.stdout)?;
197 let buffer = buffer.trim_end();
198
199 let desc = "new-window";
200 let intent = "#{window_id}:#{pane_id}";
201
202 let (_, (new_window_id, _, new_pane_id)) =
203 all_consuming(tuple((window_id, char(':'), pane_id)))(buffer)
204 .map_err(|e| map_add_intent(desc, intent, e))?;
205
206 Ok((new_window_id, new_pane_id))
207}
208
209pub async fn set_layout(layout: &str, window_id: &WindowId) -> Result<()> {
211 let args = vec!["select-layout", "-t", window_id.as_str(), layout];
212
213 let output = Command::new("tmux").args(&args).output().await?;
214 check_empty_process_output(&output, "select-layout")
215}
216
217pub async fn select_window(window_id: &WindowId) -> Result<()> {
219 let args = vec!["select-window", "-t", window_id.as_str()];
220
221 let output = Command::new("tmux").args(&args).output().await?;
222 check_empty_process_output(&output, "select-window")
223}
224
225#[cfg(test)]
226mod tests {
227 use super::Window;
228 use super::WindowId;
229 use crate::Result;
230 use std::str::FromStr;
231
232 #[test]
233 fn parse_list_sessions() {
234 let output = vec![
235 "@1:0:true:035d,334x85,0,0{167x85,0,0,1,166x85,168,0[166x48,168,0,2,166x36,168,49,3]}:'ignite':'pytorch'",
236 "@2:1:false:4438,334x85,0,0[334x41,0,0{167x41,0,0,4,166x41,168,0,5},334x43,0,42{167x43,0,42,6,166x43,168,42,7}]:'dates-attn':'pytorch'",
237 "@3:2:false:9e8b,334x85,0,0{167x85,0,0,8,166x85,168,0,9}:'th-bits':'pytorch'",
238 "@4:3:false:64ef,334x85,0,0,10:'docker-pytorch':'pytorch'",
239 "@5:0:true:64f0,334x85,0,0,11:'ben':'rust'",
240 "@6:1:false:64f1,334x85,0,0,12:'pyo3':'rust'",
241 "@7:2:false:64f2,334x85,0,0,13:'mdns-repeater':'rust'",
242 "@8:0:true:64f3,334x85,0,0,14:'combine':'swift'",
243 "@9:0:false:64f4,334x85,0,0,15:'copyrat':'tmux-hacking'",
244 "@10:1:false:ae3a,334x85,0,0[334x48,0,0,17,334x36,0,49{175x36,0,49,18,158x36,176,49,19}]:'mytui-app':'tmux-hacking'",
245 "@11:2:true:e2e2,334x85,0,0{175x85,0,0,20,158x85,176,0[158x42,176,0,21,158x42,176,43,27]}:'tmux-backup':'tmux-hacking'",
246 ];
247 let sessions: Result<Vec<Window>> =
248 output.iter().map(|&line| Window::from_str(line)).collect();
249 let windows = sessions.expect("Could not parse tmux sessions");
250
251 let expected = vec![
252 Window {
253 id: WindowId::from_str("@1").unwrap(),
254 index: 0,
255 is_active: true,
256 layout: String::from(
257 "035d,334x85,0,0{167x85,0,0,1,166x85,168,0[166x48,168,0,2,166x36,168,49,3]}",
258 ),
259 name: String::from("ignite"),
260 sessions: vec![String::from("pytorch")],
261 },
262 Window {
263 id: WindowId::from_str("@2").unwrap(),
264 index: 1,
265 is_active: false,
266 layout: String::from(
267 "4438,334x85,0,0[334x41,0,0{167x41,0,0,4,166x41,168,0,5},334x43,0,42{167x43,0,42,6,166x43,168,42,7}]",
268 ),
269 name: String::from("dates-attn"),
270 sessions: vec![String::from("pytorch")],
271 },
272 Window {
273 id: WindowId::from_str("@3").unwrap(),
274 index: 2,
275 is_active: false,
276 layout: String::from(
277 "9e8b,334x85,0,0{167x85,0,0,8,166x85,168,0,9}",
278 ),
279 name: String::from("th-bits"),
280 sessions: vec![String::from("pytorch")],
281 },
282 Window {
283 id: WindowId::from_str("@4").unwrap(),
284 index: 3,
285 is_active: false,
286 layout: String::from(
287 "64ef,334x85,0,0,10",
288 ),
289 name: String::from("docker-pytorch"),
290 sessions: vec![String::from("pytorch")],
291 },
292 Window {
293 id: WindowId::from_str("@5").unwrap(),
294 index: 0,
295 is_active: true,
296 layout: String::from(
297 "64f0,334x85,0,0,11",
298 ),
299 name: String::from("ben"),
300 sessions: vec![String::from("rust")],
301 },
302 Window {
303 id: WindowId::from_str("@6").unwrap(),
304 index: 1,
305 is_active: false,
306 layout: String::from(
307 "64f1,334x85,0,0,12",
308 ),
309 name: String::from("pyo3"),
310 sessions: vec![String::from("rust")],
311 },
312 Window {
313 id: WindowId::from_str("@7").unwrap(),
314 index: 2,
315 is_active: false,
316 layout: String::from(
317 "64f2,334x85,0,0,13",
318 ),
319 name: String::from("mdns-repeater"),
320 sessions: vec![String::from("rust")],
321 },
322 Window {
323 id: WindowId::from_str("@8").unwrap(),
324 index: 0,
325 is_active: true,
326 layout: String::from(
327 "64f3,334x85,0,0,14",
328 ),
329 name: String::from("combine"),
330 sessions: vec![String::from("swift")],
331 },
332 Window {
333 id: WindowId::from_str("@9").unwrap(),
334 index: 0,
335 is_active: false,
336 layout: String::from(
337 "64f4,334x85,0,0,15",
338 ),
339 name: String::from("copyrat"),
340 sessions: vec![String::from("tmux-hacking")],
341 },
342 Window {
343 id: WindowId::from_str("@10").unwrap(),
344 index: 1,
345 is_active: false,
346 layout: String::from(
347 "ae3a,334x85,0,0[334x48,0,0,17,334x36,0,49{175x36,0,49,18,158x36,176,49,19}]",
348 ),
349 name: String::from("mytui-app"),
350 sessions: vec![String::from("tmux-hacking")],
351 },
352 Window {
353 id: WindowId::from_str("@11").unwrap(),
354 index: 2,
355 is_active: true,
356 layout: String::from(
357 "e2e2,334x85,0,0{175x85,0,0,20,158x85,176,0[158x42,176,0,21,158x42,176,43,27]}",
358 ),
359 name: String::from("tmux-backup"),
360 sessions: vec![String::from("tmux-hacking")],
361 },
362 ];
363
364 assert_eq!(windows, expected);
365 }
366}