1use std::process::{Command, Stdio};
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum TerminalContext {
5 Tmux,
6 Zellij,
7 Iterm2,
8 Kitty,
9 WezTerm,
10 Alacritty,
11 GhosttyOrOther(String),
12 Unknown,
13}
14
15#[derive(Debug, Clone, PartialEq)]
16pub enum SpawnResult {
17 Spawned,
18 Foreground,
19}
20
21pub fn detect_context() -> TerminalContext {
22 if std::env::var("TMUX").is_ok() {
23 return TerminalContext::Tmux;
24 }
25 if std::env::var("ZELLIJ").is_ok() {
26 return TerminalContext::Zellij;
27 }
28 if std::env::var("KITTY_PID").is_ok() {
29 return TerminalContext::Kitty;
30 }
31 if std::env::var("WEZTERM_EXECUTABLE").is_ok() {
32 return TerminalContext::WezTerm;
33 }
34 if std::env::var("ALACRITTY_SOCKET").is_ok() {
35 return TerminalContext::Alacritty;
36 }
37 if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
38 match term_program.to_lowercase().as_str() {
39 "iterm.app" => return TerminalContext::Iterm2,
40 "ghostty" => return TerminalContext::GhosttyOrOther("ghostty".to_string()),
41 other if !other.is_empty() => {
42 return TerminalContext::GhosttyOrOther(other.to_string())
43 }
44 _ => {}
45 }
46 }
47 TerminalContext::Unknown
48}
49
50pub fn spawn_ssh_command(alias: &str, extra_args: &[String]) -> Vec<String> {
51 let mut args = vec!["ssh".to_string(), alias.to_string()];
52 args.extend_from_slice(extra_args);
53 args
54}
55
56pub fn spawn_in_context(context: &TerminalContext, ssh_args: &[String]) -> SpawnResult {
57 match context {
58 TerminalContext::Tmux => {
59 let ssh_cmd = ssh_args.join(" ");
60 let _ = Command::new("tmux")
61 .args(["split-window", "-h", &ssh_cmd])
62 .status();
63 SpawnResult::Spawned
64 }
65 TerminalContext::Zellij => {
66 let mut args = vec!["run".to_string(), "--".to_string()];
67 args.extend_from_slice(ssh_args);
68 let _ = Command::new("zellij").args(&args).status();
69 SpawnResult::Spawned
70 }
71 TerminalContext::Iterm2 => {
72 let ssh_cmd = ssh_args.join(" ");
73 let script = format!(
74 r#"tell application "iTerm2"
75 tell current window
76 create tab with default profile
77 tell current session
78 write text "{}"
79 end tell
80 end tell
81end tell"#,
82 ssh_cmd.replace('"', "\\\"")
83 );
84 let _ = Command::new("osascript").arg("-e").arg(&script).status();
85 SpawnResult::Spawned
86 }
87 TerminalContext::Kitty => {
88 let mut args = vec!["launch".to_string(), "--type=tab".to_string()];
89 args.extend_from_slice(ssh_args);
90 let _ = Command::new("kitten").args(&args).status();
91 SpawnResult::Spawned
92 }
93 TerminalContext::WezTerm => {
94 let mut args = vec!["cli".to_string(), "spawn".to_string(), "--".to_string()];
95 args.extend_from_slice(ssh_args);
96 let _ = Command::new("wezterm").args(&args).status();
97 SpawnResult::Spawned
98 }
99 TerminalContext::Alacritty => {
100 let mut args = vec!["msg".to_string(), "create-window".to_string(), "--".to_string()];
101 args.extend_from_slice(ssh_args);
102 let _ = Command::new("alacritty").args(&args).status();
103 SpawnResult::Spawned
104 }
105 TerminalContext::GhosttyOrOther(_) | TerminalContext::Unknown => {
106 spawn_foreground(ssh_args);
107 SpawnResult::Foreground
108 }
109 }
110}
111
112pub fn spawn_foreground(ssh_args: &[String]) {
113 if ssh_args.is_empty() {
114 return;
115 }
116 let program = &ssh_args[0];
117 let args = &ssh_args[1..];
118 let _ = Command::new(program)
119 .args(args)
120 .stdin(Stdio::inherit())
121 .stdout(Stdio::inherit())
122 .stderr(Stdio::inherit())
123 .status();
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_spawn_ssh_command_basic() {
132 let args = spawn_ssh_command("myserver", &[]);
133 assert_eq!(args, vec!["ssh", "myserver"]);
134 }
135
136 #[test]
137 fn test_spawn_ssh_command_extra_args() {
138 let extra = vec!["-v".to_string(), "-p".to_string(), "2222".to_string()];
139 let args = spawn_ssh_command("myserver", &extra);
140 assert_eq!(args, vec!["ssh", "myserver", "-v", "-p", "2222"]);
141 }
142
143 #[test]
144 fn test_detect_context_does_not_panic() {
145 let _ = detect_context();
147 }
148}