1use super::{tmux_cmd, tmux_silent};
5use anyhow::{bail, Result};
6use std::path::{Path, PathBuf};
7use std::process::Stdio;
8
9pub fn is_available() -> bool {
11 tmux_cmd(&["-V"])
12 .stdout(Stdio::null())
13 .stderr(Stdio::null())
14 .status()
15 .map(|s| s.success())
16 .unwrap_or(false)
17}
18
19pub fn is_inside_tmux() -> bool {
21 std::env::var("TMUX").is_ok()
22}
23
24pub fn list_sessions_with_paths() -> Vec<(String, PathBuf)> {
26 let Ok(output) = tmux_cmd(&["list-sessions", "-F", "#{session_name}:#{session_path}"]).output()
27 else {
28 return vec![];
29 };
30
31 String::from_utf8_lossy(&output.stdout)
32 .lines()
33 .filter_map(|line| {
34 let mut parts = line.splitn(2, ':');
35 let name = parts.next()?.trim().to_string();
36 let path = parts.next()?.trim().to_string();
37 if name.is_empty() || path.is_empty() {
38 return None;
39 }
40 Some((name, PathBuf::from(path)))
41 })
42 .collect()
43}
44
45pub fn session_exists(name: &str) -> bool {
47 tmux_silent(&["has-session", "-t", name])
48 .status()
49 .map(|s| s.success())
50 .unwrap_or(false)
51}
52
53pub fn create_session(name: &str, start_dir: &Path) -> Result<()> {
55 let status = tmux_silent(&[
56 "new-session",
57 "-d",
58 "-s",
59 name,
60 "-c",
61 &start_dir.to_string_lossy(),
62 ])
63 .status()?;
64 if !status.success() {
65 bail!("tmux new-session failed for {}", name);
66 }
67 Ok(())
68}
69
70pub fn kill_session(name: &str) -> Result<()> {
72 tmux_silent(&["kill-session", "-t", name]).status()?;
73 Ok(())
74}
75
76pub fn rename_session(old_name: &str, new_name: &str) -> Result<()> {
78 let status = tmux_silent(&["rename-session", "-t", old_name, new_name]).status()?;
79 if !status.success() {
80 bail!("tmux rename-session failed");
81 }
82 Ok(())
83}
84
85pub fn attach_session_cmd(name: &str) -> AttachCommand {
86 if is_inside_tmux() {
87 AttachCommand::SwitchClient(name.to_string())
88 } else {
89 AttachCommand::Attach(name.to_string())
90 }
91}
92
93pub enum AttachCommand {
94 SwitchClient(String),
95 Attach(String),
96}
97
98pub fn user_has_tmux_config() -> bool {
100 let xdg = std::env::var("XDG_CONFIG_HOME")
101 .map(PathBuf::from)
102 .unwrap_or_else(|_| dirs::home_dir().unwrap_or_default().join(".config"));
103 dirs::home_dir()
104 .map(|h| h.join(".tmux.conf").exists())
105 .unwrap_or(false)
106 || xdg.join("tmux/tmux.conf").exists()
107}
108
109pub fn apply_server_defaults() {
111 let _ = tmux_silent(&["set-option", "-g", "extended-keys", "on"]).status();
112}
113
114pub fn apply_session_defaults(session: &str, mobile_detach_key: Option<&str>) {
116 let _ = tmux_silent(&["set-option", "-t", session, "mouse", "on"]).status();
117 let _ = tmux_silent(&["set-option", "-t", session, "focus-events", "on"]).status();
118 if !user_has_tmux_config() {
119 let _ = tmux_silent(&["set-option", "-t", session, "prefix", "C-a"]).status();
120 let _ = tmux_silent(&["bind-key", "-T", "prefix", "a", "send-prefix"]).status();
121 }
122 if let Some(key) = mobile_detach_key {
123 let _ = tmux_silent(&["bind-key", "-n", key, "detach-client"]).status();
124 }
125}
126
127pub fn switch_client(name: &str) -> Result<()> {
129 let status = tmux_silent(&["switch-client", "-t", name]).status()?;
130 if !status.success() {
131 bail!("tmux switch-client failed for {}", name);
132 }
133 Ok(())
134}
135
136pub fn attach_foreground(name: &str) -> Result<()> {
138 tmux_cmd(&["attach-session", "-t", name]).status()?;
139 Ok(())
140}
141
142pub fn server_pid() -> Option<u32> {
144 let output = tmux_cmd(&["display-message", "-p", "#{pid}"])
145 .output()
146 .ok()?;
147 String::from_utf8_lossy(&output.stdout).trim().parse().ok()
148}
149
150pub const OPT_MUTED: &str = "@wsx-muted";
152
153pub fn set_session_opt(session: &str, key: &str, value: &str) {
155 let _ = tmux_silent(&["set-option", "-t", session, key, value]).status();
156}
157
158pub fn send_keys(session: &str, keys: &str) -> Result<()> {
160 tmux_silent(&["send-keys", "-t", session, keys, "Enter"]).status()?;
161 Ok(())
162}
163
164pub fn send_keys_raw(session: &str, keys: &str) -> Result<()> {
166 tmux_silent(&["send-keys", "-t", session, keys]).status()?;
167 Ok(())
168}
169
170pub fn send_ctrl_c(session: &str) -> Result<()> {
172 tmux_silent(&["send-keys", "-t", session, "C-c"]).status()?;
173 Ok(())
174}
175
176pub fn unique_session_name(base: &str) -> String {
178 if !session_exists(base) {
179 return base.to_string();
180 }
181 let mut n = 2;
182 loop {
183 let candidate = format!("{}_{}", base, n);
184 if !session_exists(&candidate) {
185 return candidate;
186 }
187 n += 1;
188 }
189}