1use std::path::PathBuf;
7use std::str::FromStr;
8
9use async_std::process::Command;
10use nom::{
11 character::complete::{char, digit1, not_line_ending},
12 combinator::{all_consuming, map_res},
13 sequence::tuple,
14 IResult,
15};
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 error::{check_empty_process_output, map_add_intent, Error},
20 pane_id::{parse::pane_id, PaneId},
21 parse::{boolean, quoted_nonempty_string},
22 window_id::WindowId,
23 Result,
24};
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Pane {
29 pub id: PaneId,
31 pub index: u16,
33 pub is_active: bool,
35 pub title: String,
37 pub dirpath: PathBuf,
39 pub command: String,
41}
42
43impl FromStr for Pane {
44 type Err = Error;
45
46 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
68 let desc = "Pane";
69 let intent = "#{pane_id}:#{pane_index}:#{?pane_active,true,false}:'#{pane_title}':'#{pane_current_command}':#{pane_current_path}";
70
71 let (_, pane) =
72 all_consuming(parse::pane)(input).map_err(|e| map_add_intent(desc, intent, e))?;
73
74 Ok(pane)
75 }
76}
77
78impl Pane {
79 pub async fn capture(&self) -> Result<Vec<u8>> {
87 let args = vec![
88 "capture-pane",
89 "-t",
90 self.id.as_str(),
91 "-J", "-e", "-p", "-S", "-", "-E", "-", ];
99
100 let output = Command::new("tmux").args(&args).output().await?;
101
102 Ok(output.stdout)
103 }
104}
105
106pub(crate) mod parse {
107 use super::*;
108
109 pub(crate) fn pane(input: &str) -> IResult<&str, Pane> {
110 let (input, (id, _, index, _, is_active, _, title, _, command, _, dirpath)) =
111 tuple((
112 pane_id,
113 char(':'),
114 map_res(digit1, str::parse),
115 char(':'),
116 boolean,
117 char(':'),
118 quoted_nonempty_string,
119 char(':'),
120 quoted_nonempty_string,
121 char(':'),
122 not_line_ending,
123 ))(input)?;
124
125 Ok((
126 input,
127 Pane {
128 id,
129 index,
130 is_active,
131 title: title.into(),
132 dirpath: dirpath.into(),
133 command: command.into(),
134 },
135 ))
136 }
137}
138
139pub async fn available_panes() -> Result<Vec<Pane>> {
145 let args = vec![
146 "list-panes",
147 "-a",
148 "-F",
149 "#{pane_id}\
150 :#{pane_index}\
151 :#{?pane_active,true,false}\
152 :'#{pane_title}'\
153 :'#{pane_current_command}'\
154 :#{pane_current_path}",
155 ];
156
157 let output = Command::new("tmux").args(&args).output().await?;
158 let buffer = String::from_utf8(output.stdout)?;
159
160 let result: Result<Vec<Pane>> = buffer
163 .trim_end() .split('\n')
165 .map(Pane::from_str)
166 .collect();
167
168 result
169}
170
171pub async fn new_pane(
174 reference_pane: &Pane,
175 pane_command: Option<&str>,
176 window_id: &WindowId,
177) -> Result<PaneId> {
178 let mut args = vec![
179 "split-window",
180 "-h",
181 "-c",
182 reference_pane.dirpath.to_str().unwrap(),
183 "-t",
184 window_id.as_str(),
185 "-P",
186 "-F",
187 "#{pane_id}",
188 ];
189 if let Some(pane_command) = pane_command {
190 args.push(pane_command);
191 }
192
193 let output = Command::new("tmux").args(&args).output().await?;
194 let buffer = String::from_utf8(output.stdout)?;
195
196 let new_id = PaneId::from_str(buffer.trim_end())?;
197 Ok(new_id)
198}
199
200pub async fn select_pane(pane_id: &PaneId) -> Result<()> {
202 let args = vec!["select-pane", "-t", pane_id.as_str()];
203
204 let output = Command::new("tmux").args(&args).output().await?;
205 check_empty_process_output(&output, "select-pane")
206}
207
208#[cfg(test)]
209mod tests {
210 use super::Pane;
211 use super::PaneId;
212 use crate::Result;
213 use std::path::PathBuf;
214 use std::str::FromStr;
215
216 #[test]
217 fn parse_list_panes() {
218 let output = vec![
219 "%20:0:false:'rmbp':'nvim':/Users/graelo/code/rust/tmux-backup",
220 "%21:1:true:'graelo@server: ~':'tmux':/Users/graelo/code/rust/tmux-backup",
221 "%27:2:false:'rmbp':'man man':/Users/graelo/code/rust/tmux-backup",
222 ];
223 let panes: Result<Vec<Pane>> = output.iter().map(|&line| Pane::from_str(line)).collect();
224 let panes = panes.expect("Could not parse tmux panes");
225
226 let expected = vec![
227 Pane {
228 id: PaneId::from_str("%20").unwrap(),
229 index: 0,
230 is_active: false,
231 title: String::from("rmbp"),
232 dirpath: PathBuf::from_str("/Users/graelo/code/rust/tmux-backup").unwrap(),
233 command: String::from("nvim"),
234 },
235 Pane {
236 id: PaneId(String::from("%21")),
237 index: 1,
238 is_active: true,
239 title: String::from("graelo@server: ~"),
240 dirpath: PathBuf::from_str("/Users/graelo/code/rust/tmux-backup").unwrap(),
241 command: String::from("tmux"),
242 },
243 Pane {
244 id: PaneId(String::from("%27")),
245 index: 2,
246 is_active: false,
247 title: String::from("rmbp"),
248 dirpath: PathBuf::from_str("/Users/graelo/code/rust/tmux-backup").unwrap(),
249 command: String::from("man man"),
250 },
251 ];
252
253 assert_eq!(panes, expected);
254 }
255}