tmux_interface/commands/tmux.rs
1use crate::commands::constants::*;
2use crate::{Error, TmuxCommand, TmuxCommands, TmuxOutput};
3use std::borrow::Cow;
4use std::process::{Child, Command, ExitStatus, Stdio};
5
6/// enum for setting stdin, stdout, stderr, used in [`Tmux`] struct,
7/// wrapper for [`Stdio`][`std::process::Stdio`]
8// NOTE: std::process::Stdio has no Clone, Copy, there is no option to hold them
9// in some wrapper struct, instead they will be initialized in-place before
10// returning std::process::Command back from builder / converter
11#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
12pub enum StdIO {
13 /// inherit from invoker process
14 #[default]
15 Inherit,
16 /// analogous to `/dev/null`
17 Null,
18 /// pipe, used later as child process handle will be returned
19 Piped,
20}
21
22impl From<StdIO> for Stdio {
23 fn from(item: StdIO) -> Self {
24 match item {
25 StdIO::Inherit => Stdio::inherit(),
26 StdIO::Piped => Stdio::piped(),
27 StdIO::Null => Stdio::null(),
28 }
29 }
30}
31
32// XXX: set_cmds_separator
33// NOTE: [-N] missing in man
34/// [man tmux](http://man7.org/linux/man-pages/man1/tmux.1.html#DESCRIPTION)
35///
36/// # Manual
37///
38/// tmux ^3.2:
39/// ```text
40/// tmux [-2CDluvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [-T features] [command [flags]]
41/// ```
42///
43/// tmux ^2.1:
44/// ```text
45/// tmux [-2CluvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
46/// ```
47///
48/// tmux ^1.9:
49/// ```text
50/// tmux [-2lCquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
51/// ```
52///
53/// tmux ^1.8:
54/// ```text
55/// tmux [-28lCquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]
56/// ```
57///
58/// tmux ^1.4:
59/// ```text
60/// tmux [-28lquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
61/// ```
62///
63/// tmux ^1.1:
64/// ```text
65/// tmux [-28lquv] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
66/// ```
67///
68/// tmux ^1.0:
69/// ```text
70/// tmux [-28dlqUuv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
71/// ```
72///
73/// tmux ^0.9:
74/// ```text
75/// tmux [-28dqUuv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
76/// ```
77///
78/// tmux ^0.8:
79/// ```text
80/// tmux [-28dqUuVv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
81/// ```
82// XXX: using environment vars
83#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
84pub struct Tmux<'a> {
85 /// `[-2]` - Force tmux to assume the terminal supports 256 colours
86 #[cfg(feature = "tmux_0_8")]
87 pub colours256: bool,
88
89 /// `[-8]` - indicates that tmux supports 88 colours
90 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
91 pub colours88: bool,
92
93 /// `[-d]` - indicates that tmux supports defaults colours
94 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
95 pub default_colours: bool,
96
97 /// `[-q]` - prevent the server sending various information messages
98 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
99 pub prevent_msg: bool,
100
101 /// `[-C]` - Start in control mode
102 #[cfg(feature = "tmux_1_8")]
103 pub control_mode: bool,
104
105 /// `[-CC]` - Disable echo
106 #[cfg(feature = "tmux_1_8")]
107 pub disable_echo: bool,
108
109 /// `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off. With -D, command may not be specified.
110 #[cfg(feature = "tmux_3_2")]
111 pub no_daemon: bool,
112
113 /// `[-l]` - Behave as a login shell
114 #[cfg(feature = "tmux_1_0")]
115 pub login_shell: bool,
116
117 /// `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
118 #[cfg(feature = "tmux_3_2")]
119 pub no_start: bool,
120
121 /// `[-U]` - Unlock the server
122 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
123 pub unlock: bool,
124
125 /// `[-u]` - Write UTF-8 output to the terminal
126 #[cfg(feature = "tmux_0_8")]
127 pub force_utf8: bool,
128
129 /// `[-v]` - Request verbose logging
130 #[cfg(feature = "tmux_0_8")]
131 pub verbose_logging: bool,
132
133 /// `[-V]` - Report the tmux version
134 #[cfg(feature = "tmux_0_8")]
135 pub version: bool,
136
137 /// `[-c shell-command]` - Execute shell-command using the default shell
138 #[cfg(feature = "tmux_1_1")]
139 pub shell_command: Option<Cow<'a, str>>,
140
141 /// `[-f file]` - Specify an alternative configuration file
142 #[cfg(feature = "tmux_0_8")]
143 pub file: Option<Cow<'a, str>>,
144
145 /// `[-L socket-name]` - Allow a different socket name to be specified
146 #[cfg(feature = "tmux_0_8")]
147 pub socket_name: Option<Cow<'a, str>>,
148
149 /// `[-S socket-path]` - Specify a full alternative path to the server socket
150 #[cfg(feature = "tmux_0_8")]
151 pub socket_path: Option<Cow<'a, str>>,
152
153 /// `[-T features]` - Set terminal features for the client
154 #[cfg(feature = "tmux_3_2")]
155 pub features: Option<Cow<'a, str>>,
156
157 /// `[command]`
158 pub command: Option<TmuxCommands<'a>>,
159
160 /// (1)
161 pub stdin: Option<StdIO>,
162
163 /// (2)
164 pub stdout: Option<StdIO>,
165
166 /// (3)
167 pub stderr: Option<StdIO>,
168}
169
170impl<'a> Tmux<'a> {
171 pub fn new() -> Self {
172 Default::default()
173 }
174
175 /// `[-2]` - Force tmux to assume the terminal supports 256 colours
176 #[cfg(feature = "tmux_0_8")]
177 pub fn colours256(mut self) -> Self {
178 self.colours256 = true;
179 self
180 }
181
182 /// `[-8]` - indicates that tmux supports 88 colours
183 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
184 pub fn colours88(mut self) -> Self {
185 self.colours88 = true;
186 self
187 }
188
189 /// `[-d]` - indicates that tmux supports defaults colours
190 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
191 pub fn default_colours(mut self) -> Self {
192 self.default_colours = true;
193 self
194 }
195
196 /// `[-q]` - prevent the server sending various information messages
197 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
198 pub fn prevent_msg(mut self) -> Self {
199 self.prevent_msg = true;
200 self
201 }
202
203 /// `[-C]` - Start in control mode
204 #[cfg(feature = "tmux_1_8")]
205 pub fn control_mode(mut self) -> Self {
206 self.control_mode = true;
207 self
208 }
209
210 /// `[-CC]` - Disable echo
211 #[cfg(feature = "tmux_1_8")]
212 pub fn disable_echo(mut self) -> Self {
213 self.disable_echo = true;
214 self
215 }
216
217 /// `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off. With -D, command may not be specified.
218 #[cfg(feature = "tmux_3_2")]
219 pub fn no_daemon(mut self) -> Self {
220 self.no_daemon = true;
221 self
222 }
223
224 /// `[-l]` - Behave as a login shell
225 #[cfg(feature = "tmux_1_0")]
226 pub fn login_shell(mut self) -> Self {
227 self.login_shell = true;
228 self
229 }
230
231 /// `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
232 #[cfg(feature = "tmux_3_2")]
233 pub fn no_start(mut self) -> Self {
234 self.no_start = true;
235 self
236 }
237
238 /// `[-U]` - Unlock the server
239 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
240 pub fn unlock(mut self) -> Self {
241 self.unlock = true;
242 self
243 }
244
245 /// `[-u]` - Write UTF-8 output to the terminal
246 #[cfg(feature = "tmux_0_8")]
247 pub fn force_utf8(mut self) -> Self {
248 self.force_utf8 = true;
249 self
250 }
251
252 /// `[-v]` - Request verbose logging
253 #[cfg(feature = "tmux_0_8")]
254 pub fn verbose_logging(mut self) -> Self {
255 self.verbose_logging = true;
256 self
257 }
258
259 /// `[-V]` - Report the tmux version
260 #[cfg(feature = "tmux_0_8")]
261 pub fn version(mut self) -> Self {
262 self.version = true;
263 self
264 }
265
266 /// `[-c shell-command]` - Execute shell-command using the default shell
267 #[cfg(feature = "tmux_1_1")]
268 pub fn shell_command<S: Into<Cow<'a, str>>>(mut self, shell_command: S) -> Self {
269 self.shell_command = Some(shell_command.into());
270 self
271 }
272
273 /// `[-f file]` - Specify an alternative configuration file
274 #[cfg(feature = "tmux_0_8")]
275 pub fn file<S: Into<Cow<'a, str>>>(mut self, file: S) -> Self {
276 self.file = Some(file.into());
277 self
278 }
279
280 /// `[-L socket-name]` - Allow a different socket name to be specified
281 #[cfg(feature = "tmux_0_8")]
282 pub fn socket_name<S: Into<Cow<'a, str>>>(mut self, socket_name: S) -> Self {
283 self.socket_name = Some(socket_name.into());
284 self
285 }
286
287 /// `[-S socket-path]` - Specify a full alternative path to the server socket
288 #[cfg(feature = "tmux_0_8")]
289 pub fn socket_path<S: Into<Cow<'a, str>>>(mut self, socket_path: S) -> Self {
290 self.socket_path = Some(socket_path.into());
291 self
292 }
293
294 /// `[-T features]` - Set terminal features for the client
295 #[cfg(feature = "tmux_3_2")]
296 pub fn features<S: Into<Cow<'a, str>>>(mut self, features: S) -> Self {
297 self.features = Some(features.into());
298 self
299 }
300
301 /// `[command]`
302 pub fn command<T: Into<TmuxCommand<'a>>>(mut self, command: T) -> Self {
303 self.command
304 .get_or_insert(TmuxCommands::new())
305 .push(command.into());
306 self
307 }
308
309 pub fn build(self) -> TmuxCommand<'a> {
310 let mut cmd = TmuxCommand::new();
311
312 cmd.name(TMUX);
313
314 // `[-2]` - Force tmux to assume the terminal supports 256 colours
315 #[cfg(feature = "tmux_0_8")]
316 if self.colours256 {
317 cmd.push_flag(_2_KEY);
318 }
319
320 // `[-8]` - indicates that tmux supports 88 colours
321 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
322 if self.colours88 {
323 cmd.push_flag(_8_KEY);
324 }
325
326 // `[-d]` - indicates that tmux supports defaults colours
327 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
328 if self.default_colours {
329 cmd.push_flag(D_LOWERCASE_KEY);
330 }
331
332 // `[-q]` - prevent the server sending various information messages
333 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
334 if self.prevent_msg {
335 cmd.push_flag(Q_LOWERCASE_KEY);
336 }
337
338 // `[-C]` - Start in control mode
339 #[cfg(feature = "tmux_1_8")]
340 if self.control_mode {
341 cmd.push_flag(C_UPPERCASE_KEY);
342 }
343
344 // `[-CC]` - Disable echo
345 #[cfg(feature = "tmux_1_8")]
346 if self.disable_echo {
347 cmd.push_flag(CC_UPPERCASE_KEY);
348 }
349
350 // `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off. With -D, command may not be specified.
351 #[cfg(feature = "tmux_3_2")]
352 if self.no_daemon {
353 cmd.push_flag(D_UPPERCASE_KEY);
354 }
355
356 // `[-l]` - Behave as a login shell
357 #[cfg(feature = "tmux_1_0")]
358 if self.login_shell {
359 cmd.push_flag(L_LOWERCASE_KEY);
360 }
361
362 // `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
363 #[cfg(feature = "tmux_3_2")]
364 if self.no_start {
365 cmd.push_flag(N_UPPERCASE_KEY);
366 }
367
368 // `[-U]` - Unlock the server
369 #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
370 if self.unlock {
371 cmd.push_flag(U_UPPERCASE_KEY);
372 }
373
374 // `[-u]` - Write UTF-8 output to the terminal
375 #[cfg(feature = "tmux_0_8")]
376 if self.force_utf8 {
377 cmd.push_flag(U_LOWERCASE_KEY);
378 }
379
380 // `[-v]` - Request verbose logging
381 #[cfg(feature = "tmux_0_8")]
382 if self.verbose_logging {
383 cmd.push_flag(V_LOWERCASE_KEY);
384 }
385
386 // `[-V]` - Report the tmux version
387 #[cfg(feature = "tmux_0_8")]
388 if self.version {
389 cmd.push_flag(V_UPPERCASE_KEY);
390 }
391
392 // `[-c shell-command]` - Execute shell-command using the default shell
393 #[cfg(feature = "tmux_1_1")]
394 if let Some(shell_command) = self.shell_command {
395 cmd.push_option(C_LOWERCASE_KEY, shell_command);
396 }
397
398 // `[-f file]` - Specify an alternative configuration file
399 #[cfg(feature = "tmux_0_8")]
400 if let Some(file) = self.file {
401 cmd.push_option(F_LOWERCASE_KEY, file);
402 }
403
404 // `[-L socket-name]` - Allow a different socket name to be specified
405 #[cfg(feature = "tmux_0_8")]
406 if let Some(socket_name) = self.socket_name {
407 cmd.push_option(L_UPPERCASE_KEY, socket_name);
408 }
409
410 // `[-S socket-path]` - Specify a full alternative path to the server socket
411 #[cfg(feature = "tmux_0_8")]
412 if let Some(socket_path) = self.socket_path {
413 cmd.push_option(S_UPPERCASE_KEY, socket_path);
414 }
415
416 // `[-T features]` - Set terminal features for the client
417 #[cfg(feature = "tmux_3_2")]
418 if let Some(features) = self.features {
419 cmd.push_option(T_UPPERCASE_KEY, features);
420 }
421
422 // `[command]`
423 if let Some(command) = self.command {
424 cmd.push_cmds(command);
425 }
426
427 cmd
428 }
429
430 //pub fn to_command(&self) -> Command {
431 //Command::from(self)
432 //}
433
434 // XXX: ?
435 pub fn into_command(self) -> Command {
436 Command::from(self)
437 }
438
439 // XXX: command or subcommand better name?
440 pub fn with_command<T: Into<TmuxCommand<'a>>>(command: T) -> Self {
441 Tmux::new().command(command.into())
442 }
443
444 pub fn with_commands(commands: TmuxCommands<'a>) -> Self {
445 Tmux::new().commands(commands)
446 }
447
448 // XXX: ?
449 pub fn add_command<T: Into<TmuxCommand<'a>>>(mut self, command: T) -> Self {
450 self.command
451 .get_or_insert(TmuxCommands::new())
452 .push(command.into());
453 self
454 }
455
456 pub fn commands(mut self, commands: TmuxCommands<'a>) -> Self {
457 self.command = Some(commands);
458 self
459 }
460}
461
462impl<'a> Tmux<'a> {
463 /// execute tmux process, wait for output, return output
464 ///
465 /// by default:
466 /// * stdin is inherited (differs from [`std::process::Command`], where it
467 /// is not inherited by default) prevents from immediately closing tmux
468 /// after attempt to read from stdin
469 /// * stdout is inherited
470 /// * stderr is inherited
471 pub fn output(self) -> Result<TmuxOutput, Error> {
472 let mut command = Command::from(self);
473 let output = command.output()?;
474 Ok(TmuxOutput(output))
475 }
476
477 /// spawn tmux process, return process handle as child
478 /// by default:
479 /// * stdin is inherited
480 /// * stdout is inherited
481 /// * stderr is inherited
482 pub fn spawn(self) -> Result<Child, Error> {
483 let mut command = Command::from(self);
484 let child = command.spawn()?;
485 Ok(child)
486 }
487
488 /// spawn tmux process, return status result of the command execution
489 /// by default:
490 /// * stdin is inherited
491 /// * stdout is inherited
492 /// * stderr is inherited
493 pub fn status(self) -> Result<ExitStatus, Error> {
494 let mut command = Command::from(self);
495 let status = command.status()?;
496 Ok(status)
497 }
498
499 /// set stdin
500 pub fn stdin(mut self, stdin: Option<StdIO>) -> Self {
501 self.stdin = stdin;
502 self
503 }
504
505 /// set stdout
506 pub fn stdout(mut self, stdout: Option<StdIO>) -> Self {
507 self.stdout = stdout;
508 self
509 }
510
511 /// set stderr
512 pub fn stderr(mut self, stderr: Option<StdIO>) -> Self {
513 self.stderr = stderr;
514 self
515 }
516}
517
518//use crate::{HasSession, KillSession, NewSession};
519
520//impl<'a> From<NewSession<'a>> for Tmux<'a> {
521//fn from(item: NewSession<'a>) -> Self {
522//item.build().into_tmux()
523//}
524//}
525
526//impl<'a> From<HasSession<'a>> for Tmux<'a> {
527//fn from(item: HasSession<'a>) -> Self {
528//item.build().into_tmux()
529//}
530//}
531
532//impl<'a> From<KillSession<'a>> for Tmux<'a> {
533//fn from(item: KillSession<'a>) -> Self {
534//item.build().into_tmux()
535//}
536//}
537
538// convert from Tmux into Command (take Tmux -> build TmuxCommand -> into Command)
539impl<'a> From<Tmux<'a>> for Command {
540 fn from(item: Tmux<'a>) -> Self {
541 let mut command: Command = item.clone().build().into();
542
543 if let Some(stdio) = item.stdin {
544 command.stdin::<Stdio>(stdio.into());
545 } else {
546 // NOTE: inherit stdin to prevent tmux fail with error `terminal failed: not a terminal`
547 command.stdin(Stdio::inherit());
548 }
549
550 if let Some(stdio) = item.stdout {
551 command.stdout::<Stdio>(stdio.into());
552 }
553
554 if let Some(stdio) = item.stderr {
555 command.stderr::<Stdio>(stdio.into());
556 }
557
558 command
559 }
560}
561
562//impl<'a> From<&Tmux<'a>> for Command {
563//fn from(tmux: &Tmux) -> Self {
564////let mut command = Command::new(TMUX);
565
566//let cmd = match &tmux.0.name {
567//Some(cmd) => cmd,
568//None => &Cow::Borrowed(TMUX),
569//};
570//let mut command = Command::new(cmd.as_ref());
571
572////// XXX: ugly?
573////if let Some(v) = &tmux.bin_args {
574////for a in v {
575////command.arg(a.as_ref());
576////}
577////}
578
579//// XXX: ugly?
580//if let Some(v) = &tmux.0.args {
581//for a in v {
582//command.arg(a.as_ref());
583//}
584//}
585
586//command
587//}
588//}