1use crate::cli::Shell;
2use crate::config::{get_base_config_dir, get_config_dir};
3use anyhow::Result;
4use std::fs;
5use std::io::Write;
6use std::path::PathBuf;
7
8pub fn get_shell_content(shell: &Shell) -> &'static str {
11 match shell {
12 Shell::Fish => {
13 r#"function try-rs
14 # Pass flags/options directly to stdout without capturing
15 for arg in $argv
16 if string match -q -- '-*' $arg
17 command try-rs $argv
18 return
19 end
20 end
21
22 # Captures the output of the binary (stdout) which is the "cd" command
23 # The TUI is rendered on stderr, so it doesn't interfere.
24 set command (command try-rs $argv | string collect)
25
26 if test -n "$command"
27 eval $command
28 end
29end
30"#
31 }
32 Shell::Zsh => {
33 r#"try-rs() {
34 # Pass flags/options directly to stdout without capturing
35 for arg in "$@"; do
36 case "$arg" in
37 -*) command try-rs "$@"; return ;;
38 esac
39 done
40
41 # Captures the output of the binary (stdout) which is the "cd" command
42 # The TUI is rendered on stderr, so it doesn't interfere.
43 local output
44 output=$(command try-rs "$@")
45
46 if [ -n "$output" ]; then
47 eval "$output"
48 fi
49}
50"#
51 }
52 Shell::Bash => {
53 r#"try-rs() {
54 # Pass flags/options directly to stdout without capturing
55 for arg in "$@"; do
56 case "$arg" in
57 -*) command try-rs "$@"; return ;;
58 esac
59 done
60
61 # Captures the output of the binary (stdout) which is the "cd" command
62 # The TUI is rendered on stderr, so it doesn't interfere.
63 local output
64 output=$(command try-rs "$@")
65
66 if [ -n "$output" ]; then
67 eval "$output"
68 fi
69}
70"#
71 }
72 Shell::PowerShell => {
73 r#"# try-rs integration for PowerShell
74function try-rs {
75 # Pass flags/options directly to stdout without capturing
76 foreach ($a in $args) {
77 if ($a -like '-*') {
78 & try-rs.exe @args
79 return
80 }
81 }
82
83 # Captures the output of the binary (stdout) which is the "cd" or editor command
84 # The TUI is rendered on stderr, so it doesn't interfere.
85 $command = (try-rs.exe @args)
86
87 if ($command) {
88 Invoke-Expression $command
89 }
90}
91"#
92 }
93 Shell::NuShell => {
94 r#"def --wrapped try-rs [...args] {
95 # Pass flags/options directly to stdout without capturing
96 for arg in $args {
97 if ($arg | str starts-with '-') {
98 ^try-rs.exe ...$args
99 return
100 }
101 }
102
103 # Capture output. Stderr (TUI) goes directly to terminal.
104 let output = (try-rs.exe ...$args)
105
106 if ($output | is-not-empty) {
107
108 # Grabs the path out of stdout returned by the binary and removes the single quotes
109 let $path = ($output | split row ' ').1 | str replace --all "'" ''
110 cd $path
111 }
112}
113"#
114 }
115 }
116}
117
118pub fn get_shell_integration_path(shell: &Shell) -> PathBuf {
119 let config_dir = match shell {
120 Shell::Fish => get_base_config_dir(),
121 _ => get_config_dir(),
122 };
123
124 match shell {
125 Shell::Fish => config_dir
126 .join("fish")
127 .join("functions")
128 .join("try-rs.fish"),
129 Shell::Zsh => config_dir.join("try-rs.zsh"),
130 Shell::Bash => config_dir.join("try-rs.bash"),
131 Shell::PowerShell => config_dir.join("try-rs.ps1"),
132 Shell::NuShell => config_dir.join("try-rs.nu"),
133 }
134}
135
136pub fn is_shell_integration_configured(shell: &Shell) -> bool {
137 get_shell_integration_path(shell).exists()
138}
139
140fn append_source_to_rc(rc_path: &std::path::Path, source_cmd: &str) -> Result<()> {
142 if rc_path.exists() {
143 let content = fs::read_to_string(rc_path)?;
144 if !content.contains(source_cmd) {
145 let mut file = fs::OpenOptions::new().append(true).open(rc_path)?;
146 writeln!(file, "\n# try-rs integration")?;
147 writeln!(file, "{}", source_cmd)?;
148 eprintln!("Added configuration to {}", rc_path.display());
149 } else {
150 eprintln!("Configuration already present in {}", rc_path.display());
151 }
152 } else {
153 eprintln!("You need to add the following line to {}:", rc_path.display());
154 eprintln!("{}", source_cmd);
155 }
156 Ok(())
157}
158
159fn write_shell_integration(shell: &Shell) -> Result<std::path::PathBuf> {
161 let file_path = get_shell_integration_path(shell);
162 if let Some(parent) = file_path.parent()
163 && !parent.exists()
164 {
165 fs::create_dir_all(parent)?;
166 }
167 fs::write(&file_path, get_shell_content(shell))?;
168 eprintln!("{:?} function file created at: {}", shell, file_path.display());
169 Ok(file_path)
170}
171
172pub fn setup_shell(shell: &Shell) -> Result<()> {
174 let file_path = write_shell_integration(shell)?;
175 let home_dir = dirs::home_dir().expect("Could not find home directory");
176
177 match shell {
178 Shell::Fish => {
179 eprintln!(
180 "You may need to restart your shell or run 'source {}' to apply changes.",
181 file_path.display()
182 );
183 }
184 Shell::Zsh => {
185 let source_cmd = format!("source '{}'", file_path.display());
186 append_source_to_rc(&home_dir.join(".zshrc"), &source_cmd)?;
187 }
188 Shell::Bash => {
189 let source_cmd = format!("source '{}'", file_path.display());
190 append_source_to_rc(&home_dir.join(".bashrc"), &source_cmd)?;
191 }
192 Shell::PowerShell => {
193 let profile_path_ps7 = home_dir
194 .join("Documents")
195 .join("PowerShell")
196 .join("Microsoft.PowerShell_profile.ps1");
197 let profile_path_ps5 = home_dir
198 .join("Documents")
199 .join("WindowsPowerShell")
200 .join("Microsoft.PowerShell_profile.ps1");
201 let profile_path = if profile_path_ps7.exists() {
202 profile_path_ps7
203 } else if profile_path_ps5.exists() {
204 profile_path_ps5
205 } else {
206 profile_path_ps7
207 };
208
209 if let Some(parent) = profile_path.parent()
210 && !parent.exists()
211 {
212 fs::create_dir_all(parent)?;
213 }
214
215 let source_cmd = format!(". '{}'", file_path.display());
216 if profile_path.exists() {
217 append_source_to_rc(&profile_path, &source_cmd)?;
218 } else {
219 let mut file = fs::File::create(&profile_path)?;
220 writeln!(file, "# try-rs integration")?;
221 writeln!(file, "{}", source_cmd)?;
222 eprintln!(
223 "PowerShell profile created and configured at: {}",
224 profile_path.display()
225 );
226 }
227
228 eprintln!(
229 "You may need to restart your shell or run '. {}' to apply changes.",
230 profile_path.display()
231 );
232 eprintln!(
233 "If you get an error about running scripts, you may need to run: Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned"
234 );
235 }
236 Shell::NuShell => {
237 let nu_config_path = dirs::config_dir()
238 .expect("Could not find config directory")
239 .join("nushell")
240 .join("config.nu");
241 let source_cmd = format!("source '{}'", file_path.display());
242 if nu_config_path.exists() {
243 append_source_to_rc(&nu_config_path, &source_cmd)?;
244 } else {
245 eprintln!("Could not find config.nu at {}", nu_config_path.display());
246 eprintln!("Please add the following line manually:");
247 eprintln!("{}", source_cmd);
248 }
249 }
250 }
251
252 Ok(())
253}