Skip to main content

ssm_core/
terminal.rs

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        // Just ensure it runs without panicking regardless of environment
146        let _ = detect_context();
147    }
148}