Skip to main content

gwm/
multiplexer.rs

1//! Terminal-multiplexer integration. Builds the argv vectors for
2//! `tmux new-window/split-window` and `zellij action new-tab/new-pane` so
3//! `gwm tmux <pattern>` / `gwm zellij <pattern>` can open a worktree in
4//! one keystroke from inside an already-running multiplexer session.
5//!
6//! The command builders are pure functions returning `Vec<String>` so the
7//! integration tests can pin the exact incantation without spawning tmux
8//! or zellij on every test runner. The actual `std::process::Command`
9//! spawn lives in `cli.rs`, matching the lazygit-launch pattern in
10//! `tui/mod.rs::run_lazygit`.
11
12use std::path::Path;
13
14/// Multiplexer the user opted into via `gwm tmux …` / `gwm zellij …`.
15/// Carried through the CLI dispatch so the not-running error and the
16/// argv builder share one source of truth for the binary name.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Multiplexer {
19  Tmux,
20  Zellij,
21}
22
23impl Multiplexer {
24  /// Binary name as it appears on `$PATH`. Used both for the spawn and
25  /// for the `<bin> session not running` error string.
26  pub fn binary(self) -> &'static str {
27    match self {
28      Multiplexer::Tmux => "tmux",
29      Multiplexer::Zellij => "zellij",
30    }
31  }
32}
33
34/// How to open the worktree inside the multiplexer.
35/// `Window` = new tmux window / zellij tab (the default — full screen real estate).
36/// `Split`  = split the current pane (the `-p` flag — keeps both views visible).
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum SpawnMode {
39  Window,
40  Split,
41}
42
43/// Build `tmux new-window -n <name> -c <path>` (Window) or
44/// `tmux split-window -c <path>` (Split). `<name>` is the worktree's
45/// short name so it shows up legibly in tmux's status bar; tmux panes
46/// don't carry a name attribute, so Split intentionally omits `-n`.
47pub fn build_tmux_command(name: &str, path: &Path, mode: SpawnMode) -> Vec<String> {
48  let path_str = path.display().to_string();
49  match mode {
50    SpawnMode::Window => vec![
51      "tmux".into(),
52      "new-window".into(),
53      "-n".into(),
54      name.into(),
55      "-c".into(),
56      path_str,
57    ],
58    SpawnMode::Split => vec!["tmux".into(), "split-window".into(), "-c".into(), path_str],
59  }
60}
61
62/// Build `zellij action new-tab --name <name> --cwd <path>` (Window) or
63/// `zellij action new-pane --cwd <path>` (Split). `--cwd` on `new-tab`
64/// requires zellij ≥ 0.40 — older versions surface their own error,
65/// which is preferable to silently ignoring the cwd.
66pub fn build_zellij_command(name: &str, path: &Path, mode: SpawnMode) -> Vec<String> {
67  let path_str = path.display().to_string();
68  match mode {
69    SpawnMode::Window => vec![
70      "zellij".into(),
71      "action".into(),
72      "new-tab".into(),
73      "--name".into(),
74      name.into(),
75      "--cwd".into(),
76      path_str,
77    ],
78    SpawnMode::Split => vec![
79      "zellij".into(),
80      "action".into(),
81      "new-pane".into(),
82      "--cwd".into(),
83      path_str,
84    ],
85  }
86}
87
88/// `true` when `$TMUX` is set to a non-empty value — tmux exports the
89/// socket path to every process spawned inside a session, so its
90/// presence is the canonical "am I inside tmux?" probe.
91///
92/// Takes the env value as a parameter (rather than reading it directly)
93/// so the unit tests can exercise both branches without mutating the
94/// process environment. The CLI dispatcher calls
95/// `detect_tmux(std::env::var("TMUX").ok())`.
96pub fn detect_tmux(env: Option<String>) -> bool {
97  match env {
98    Some(s) => !s.is_empty(),
99    None => false,
100  }
101}
102
103/// `true` when `$ZELLIJ` is set to a non-empty value. Zellij exports the
104/// variable to every command spawned inside a session, similar to tmux.
105pub fn detect_zellij(env: Option<String>) -> bool {
106  match env {
107    Some(s) => !s.is_empty(),
108    None => false,
109  }
110}