1use std::{path::PathBuf, str::FromStr};
7
8use async_std::process::Command;
9use nom::{
10 character::complete::{char, not_line_ending},
11 combinator::all_consuming,
12 sequence::tuple,
13 IResult,
14};
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 error::{map_add_intent, Error},
19 pane::Pane,
20 pane_id::{parse::pane_id, PaneId},
21 parse::quoted_nonempty_string,
22 session_id::{parse::session_id, SessionId},
23 window::Window,
24 window_id::{parse::window_id, WindowId},
25 Result,
26};
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct Session {
31 pub id: SessionId,
33 pub name: String,
35 pub dirpath: PathBuf,
37}
38
39impl FromStr for Session {
40 type Err = Error;
41
42 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
65 let desc = "Session";
66 let intent = "#{session_id}:'#{session_name}':#{session_path}";
67
68 let (_, sess) =
69 all_consuming(parse::session)(input).map_err(|e| map_add_intent(desc, intent, e))?;
70
71 Ok(sess)
72 }
73}
74
75pub(crate) mod parse {
76 use super::*;
77
78 pub(crate) fn session(input: &str) -> IResult<&str, Session> {
79 let (input, (id, _, name, _, dirpath)) = tuple((
80 session_id,
81 char(':'),
82 quoted_nonempty_string,
83 char(':'),
84 not_line_ending,
85 ))(input)?;
86
87 Ok((
88 input,
89 Session {
90 id,
91 name: name.to_string(),
92 dirpath: dirpath.into(),
93 },
94 ))
95 }
96}
97
98pub async fn available_sessions() -> Result<Vec<Session>> {
104 let args = vec![
105 "list-sessions",
106 "-F",
107 "#{session_id}:'#{session_name}':#{session_path}",
108 ];
109
110 let output = Command::new("tmux").args(&args).output().await?;
111 let buffer = String::from_utf8(output.stdout)?;
112
113 let result: Result<Vec<Session>> = buffer
116 .trim_end() .split('\n')
118 .map(Session::from_str)
119 .collect();
120
121 result
122}
123
124pub async fn new_session(
132 session: &Session,
133 window: &Window,
134 pane: &Pane,
135 pane_command: Option<&str>,
136) -> Result<(SessionId, WindowId, PaneId)> {
137 let mut args = vec![
138 "new-session",
139 "-d",
140 "-c",
141 pane.dirpath.to_str().unwrap(),
142 "-s",
143 &session.name,
144 "-n",
145 &window.name,
146 "-P",
147 "-F",
148 "#{session_id}:#{window_id}:#{pane_id}",
149 ];
150 if let Some(pane_command) = pane_command {
151 args.push(pane_command);
152 }
153
154 let output = Command::new("tmux").args(&args).output().await?;
155 let buffer = String::from_utf8(output.stdout)?;
156 let buffer = buffer.trim_end();
157
158 let desc = "new-session";
159 let intent = "#{session_id}:#{window_id}:#{pane_id}";
160 let (_, (new_session_id, _, new_window_id, _, new_pane_id)) = all_consuming(tuple((
161 session_id,
162 char(':'),
163 window_id,
164 char(':'),
165 pane_id,
166 )))(buffer)
167 .map_err(|e| map_add_intent(desc, intent, e))?;
168
169 Ok((new_session_id, new_window_id, new_pane_id))
170}
171
172#[cfg(test)]
173mod tests {
174 use super::Session;
175 use super::SessionId;
176 use crate::Result;
177 use std::path::PathBuf;
178 use std::str::FromStr;
179
180 #[test]
181 fn parse_list_sessions() {
182 let output = vec![
183 "$1:'pytorch':/Users/graelo/ml/pytorch",
184 "$2:'rust':/Users/graelo/rust",
185 "$3:'server: $':/Users/graelo/swift",
186 "$4:'tmux-hacking':/Users/graelo/tmux",
187 ];
188 let sessions: Result<Vec<Session>> =
189 output.iter().map(|&line| Session::from_str(line)).collect();
190 let sessions = sessions.expect("Could not parse tmux sessions");
191
192 let expected = vec![
193 Session {
194 id: SessionId::from_str("$1").unwrap(),
195 name: String::from("pytorch"),
196 dirpath: PathBuf::from("/Users/graelo/ml/pytorch"),
197 },
198 Session {
199 id: SessionId::from_str("$2").unwrap(),
200 name: String::from("rust"),
201 dirpath: PathBuf::from("/Users/graelo/rust"),
202 },
203 Session {
204 id: SessionId::from_str("$3").unwrap(),
205 name: String::from("server: $"),
206 dirpath: PathBuf::from("/Users/graelo/swift"),
207 },
208 Session {
209 id: SessionId::from_str("$4").unwrap(),
210 name: String::from("tmux-hacking"),
211 dirpath: PathBuf::from("/Users/graelo/tmux"),
212 },
213 ];
214
215 assert_eq!(sessions, expected);
216 }
217}