1use crate::error::{GitWarpError, Result};
2use std::path::Path;
3use std::process::Command;
4
5#[derive(Debug, Clone)]
6pub enum TerminalMode {
7 Tab,
8 Window,
9 InPlace,
10 Echo,
11}
12
13impl TerminalMode {
14 pub fn from_str(s: &str) -> Option<Self> {
15 match s.to_lowercase().as_str() {
16 "tab" => Some(Self::Tab),
17 "window" => Some(Self::Window),
18 "inplace" => Some(Self::InPlace),
19 "echo" => Some(Self::Echo),
20 _ => None,
21 }
22 }
23}
24
25pub trait Terminal {
26 fn open_tab(&self, path: &Path, session_id: Option<&str>) -> Result<()>;
27 fn open_window(&self, path: &Path, session_id: Option<&str>) -> Result<()>;
28 fn switch_to_directory(&self, path: &Path) -> Result<()>;
29 fn echo_commands(&self, path: &Path) -> Result<()>;
30 fn is_supported(&self) -> bool;
31}
32
33#[cfg(target_os = "macos")]
34pub struct ITerm2;
35
36#[cfg(target_os = "macos")]
37impl Terminal for ITerm2 {
38 fn open_tab(&self, path: &Path, _session_id: Option<&str>) -> Result<()> {
39 let script = format!(r#"
40tell application "iTerm"
41 tell current window
42 create tab with default profile
43 tell current tab
44 tell current session
45 write text "cd '{}'"
46 end tell
47 end tell
48 end tell
49end tell
50"#, path.display());
51
52 self.run_applescript(&script)
53 }
54
55 fn open_window(&self, path: &Path, _session_id: Option<&str>) -> Result<()> {
56 let script = format!(r#"
57tell application "iTerm"
58 create window with default profile
59 tell current window
60 tell current tab
61 tell current session
62 write text "cd '{}'"
63 end tell
64 end tell
65 end tell
66end tell
67"#, path.display());
68
69 self.run_applescript(&script)
70 }
71
72 fn switch_to_directory(&self, path: &Path) -> Result<()> {
73 println!("cd '{}'", path.display());
74 Ok(())
75 }
76
77 fn echo_commands(&self, path: &Path) -> Result<()> {
78 println!("# Navigate to worktree:");
79 println!("cd '{}'", path.display());
80 Ok(())
81 }
82
83 fn is_supported(&self) -> bool {
84 Command::new("osascript")
86 .args(&["-e", "tell application \"iTerm\" to get version"])
87 .output()
88 .map(|output| output.status.success())
89 .unwrap_or(false)
90 }
91}
92
93#[cfg(target_os = "macos")]
94impl ITerm2 {
95 fn run_applescript(&self, script: &str) -> Result<()> {
96 let output = Command::new("osascript")
97 .args(&["-e", script])
98 .output()
99 .map_err(|e| anyhow::anyhow!("Failed to execute AppleScript: {}", e))?;
100
101 if !output.status.success() {
102 let error = String::from_utf8_lossy(&output.stderr);
103 return Err(anyhow::anyhow!("AppleScript failed: {}", error).into());
104 }
105
106 Ok(())
107 }
108}
109
110#[cfg(target_os = "macos")]
111pub struct AppleTerminal;
112
113#[cfg(target_os = "macos")]
114impl Terminal for AppleTerminal {
115 fn open_tab(&self, path: &Path, _session_id: Option<&str>) -> Result<()> {
116 let script = format!(r#"
117tell application "Terminal"
118 tell window 1
119 do script "cd '{}'" in (make new tab)
120 end tell
121end tell
122"#, path.display());
123
124 self.run_applescript(&script)
125 }
126
127 fn open_window(&self, path: &Path, _session_id: Option<&str>) -> Result<()> {
128 let script = format!(r#"
129tell application "Terminal"
130 do script "cd '{}'"
131end tell
132"#, path.display());
133
134 self.run_applescript(&script)
135 }
136
137 fn switch_to_directory(&self, path: &Path) -> Result<()> {
138 println!("cd '{}'", path.display());
139 Ok(())
140 }
141
142 fn echo_commands(&self, path: &Path) -> Result<()> {
143 println!("# Navigate to worktree:");
144 println!("cd '{}'", path.display());
145 Ok(())
146 }
147
148 fn is_supported(&self) -> bool {
149 true }
151}
152
153#[cfg(target_os = "macos")]
154impl AppleTerminal {
155 fn run_applescript(&self, script: &str) -> Result<()> {
156 let output = Command::new("osascript")
157 .args(&["-e", script])
158 .output()
159 .map_err(|e| anyhow::anyhow!("Failed to execute AppleScript: {}", e))?;
160
161 if !output.status.success() {
162 let error = String::from_utf8_lossy(&output.stderr);
163 return Err(anyhow::anyhow!("AppleScript failed: {}", error).into());
164 }
165
166 Ok(())
167 }
168}
169
170pub struct TerminalManager;
171
172impl TerminalManager {
173 pub fn get_default_terminal() -> Result<Box<dyn Terminal>> {
174 #[cfg(target_os = "macos")]
175 {
176 let iterm2 = ITerm2;
177 if iterm2.is_supported() {
178 Ok(Box::new(iterm2))
179 } else {
180 Ok(Box::new(AppleTerminal))
181 }
182 }
183
184 #[cfg(not(target_os = "macos"))]
185 {
186 Err(GitWarpError::TerminalNotSupported.into())
187 }
188 }
189
190 pub fn switch_to_worktree<P: AsRef<Path>>(
191 &self,
192 path: P,
193 mode: TerminalMode,
194 session_id: Option<&str>,
195 ) -> Result<()> {
196 let terminal = Self::get_default_terminal()?;
197 let path = path.as_ref();
198
199 match mode {
200 TerminalMode::Tab => terminal.open_tab(path, session_id),
201 TerminalMode::Window => terminal.open_window(path, session_id),
202 TerminalMode::InPlace => terminal.switch_to_directory(path),
203 TerminalMode::Echo => terminal.echo_commands(path),
204 }
205 }
206}