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
8const FISH_PICKER_FUNCTION: &str = r#"function try-rs-picker
9 set -l picker_args --inline-picker
10
11 if set -q TRY_RS_PICKER_HEIGHT
12 if string match -qr '^[0-9]+$' -- "$TRY_RS_PICKER_HEIGHT"
13 set picker_args $picker_args --inline-height $TRY_RS_PICKER_HEIGHT
14 end
15 end
16
17 if status --is-interactive
18 printf "\n"
19 end
20
21 set command (command try-rs $picker_args | string collect)
22 set command_status $status
23
24 if test $command_status -eq 0; and test -n "$command"
25 eval $command
26 end
27
28 if status --is-interactive
29 printf "\033[A"
30 commandline -f repaint
31 end
32end
33"#;
34
35pub fn get_shell_content(shell: &Shell) -> String {
38 let completions = get_completions_script(shell);
39 match shell {
40 Shell::Fish => {
41 format!(
42 r#"function try-rs
43 # Pass flags/options directly to stdout without capturing
44 for arg in $argv
45 if string match -q -- '-*' $arg
46 command try-rs $argv
47 return
48 end
49 end
50
51 # Captures the output of the binary (stdout) which is the "cd" command
52 # The TUI is rendered on stderr, so it doesn't interfere.
53 set command (command try-rs $argv | string collect)
54 set command_status $status
55
56 if test $command_status -eq 0; and test -n "$command"
57 eval $command
58 end
59end
60
61{picker_function}
62
63{completions}"#,
64 picker_function = FISH_PICKER_FUNCTION,
65 )
66 }
67 Shell::Zsh => {
68 format!(
69 r#"try-rs() {{
70 # Pass flags/options directly to stdout without capturing
71 for arg in "$@"; do
72 case "$arg" in
73 -*) command try-rs "$@"; return ;;
74 esac
75 done
76
77 # Captures the output of the binary (stdout) which is the "cd" command
78 # The TUI is rendered on stderr, so it doesn't interfere.
79 local output
80 output=$(command try-rs "$@")
81
82 if [ -n "$output" ]; then
83 eval "$output"
84 fi
85}}
86
87{completions}"#
88 )
89 }
90 Shell::Bash => {
91 format!(
92 r#"try-rs() {{
93 # Pass flags/options directly to stdout without capturing
94 for arg in "$@"; do
95 case "$arg" in
96 -*) command try-rs "$@"; return ;;
97 esac
98 done
99
100 # Captures the output of the binary (stdout) which is the "cd" command
101 # The TUI is rendered on stderr, so it doesn't interfere.
102 local output
103 output=$(command try-rs "$@")
104
105 if [ -n "$output" ]; then
106 eval "$output"
107 fi
108}}
109
110{completions}"#
111 )
112 }
113 Shell::PowerShell => {
114 format!(
115 r#"# try-rs integration for PowerShell
116function try-rs {{
117 # Pass flags/options directly to stdout without capturing
118 foreach ($a in $args) {{
119 if ($a -like '-*') {{
120 & try-rs.exe @args
121 return
122 }}
123 }}
124
125 # Captures the output of the binary (stdout) which is the "cd" or editor command
126 # The TUI is rendered on stderr, so it doesn't interfere.
127 $command = (try-rs.exe @args)
128
129 if ($command) {{
130 Invoke-Expression $command
131 }}
132}}
133
134{completions}"#
135 )
136 }
137 Shell::NuShell => {
138 format!(
139 r#"def --env --wrapped try-rs [
140 name_or_url?: string@__try_rs_complete
141 ...args
142] {{
143 let all_args = (if $name_or_url == null {{ [] }} else {{ [$name_or_url] }} | append $args)
144
145 # Pass flags/options directly to stdout without capturing
146 if ($all_args | any {{ |arg| $arg | str starts-with '-' }}) {{
147 ^try-rs ...$all_args
148 return
149 }}
150
151 # Capture output. Stderr (TUI) goes directly to terminal.
152 let output = (^try-rs ...$all_args | str trim)
153
154 if ($output | is-not-empty) {{
155 if ($output | str starts-with "cd ") {{
156 # Grabs the path out of stdout returned by the binary and removes the single quotes
157 let path = ($output | str replace --regex '^cd ' '' | str replace --all "'" "" | str replace --all '"' "")
158 if ($path | path exists) {{
159 cd $path
160 }}
161 }} else {{
162 # If it's not a cd command, it's likely an editor command
163 nu -c $output
164 }}
165 }}
166}}
167
168{completions}"#,
169 completions = get_completions_script(shell),
170 )
171 }
172 }
173}
174
175pub fn get_completions_script(shell: &Shell) -> String {
178 match shell {
179 Shell::Fish => {
180 r#"# try-rs tab completion for directory names
181function __try_rs_get_tries_path
182 # Check TRY_PATH environment variable first
183 if set -q TRY_PATH
184 # Check if contains comma
185 if echo "$TRY_PATH" | command grep -q ","
186 for path in (string split "," $TRY_PATH)
187 printf '%s\n' (string trim $path)
188 end
189 else
190 printf '%s\n' $TRY_PATH
191 end
192 return
193 end
194
195 # Try to read from config file
196 set -l config_paths "$HOME/.config/try-rs/config.toml" "$HOME/.try-rs/config.toml"
197 for config_path in $config_paths
198 if test -f $config_path
199 # Try tries_path (supports single or multiple paths with comma)
200 set -l tries_path (command grep -E '^\s*tries_path\s*=' $config_path 2>/dev/null | command sed 's/.*=\s*"\?\([^"]*\)"\?.*/\1/' | command sed "s|~|$HOME|" | string trim)
201 if test -n "$tries_path"
202 # Check if it contains comma (multiple paths)
203 if echo "$tries_path" | command grep -q ","
204 for path in (string split "," $tries_path)
205 printf '%s\n' (string trim $path)
206 end
207 else
208 printf '%s\n' $tries_path
209 end
210 return
211 end
212 end
213 end
214
215 # Default path
216 printf '%s\n' "$HOME/work/tries"
217end
218
219function __try_rs_complete_directories
220 for tries_path in (__try_rs_get_tries_path)
221 if test -d $tries_path
222 command ls -1 $tries_path 2>/dev/null | while read -l dir
223 if test -d "$tries_path/$dir"
224 echo $dir
225 end
226 end
227 end
228 end
229end
230
231complete -f -c try-rs -n '__fish_use_subcommand' -a '(__try_rs_complete_directories)' -d 'Try directory'
232"#.to_string()
233 }
234 Shell::Zsh => {
235 r#"# try-rs tab completion for directory names
236_try_rs_get_tries_path() {
237 # Check TRY_PATH environment variable first
238 if [[ -n "${TRY_PATH}" ]]; then
239 if [[ "${TRY_PATH}" == *","* ]]; then
240 echo "${TRY_PATH}" | tr ',' '\n'
241 else
242 echo "${TRY_PATH}"
243 fi
244 return
245 fi
246
247 # Try to read from config file
248 local config_paths=("$HOME/.config/try-rs/config.toml" "$HOME/.try-rs/config.toml")
249 for config_path in "${config_paths[@]}"; do
250 if [[ -f "$config_path" ]]; then
251 # Try tries_path (supports single or multiple paths with comma)
252 local tries_path=$(grep -E '^[[:space:]]*tries_path[[:space:]]*=' "$config_path" 2>/dev/null | sed 's/.*=[[:space:]]*"\?\([^"]*\)"\?.*/\1/' | sed "s|~|$HOME|" | tr -d '[:space:]')
253 if [[ -n "$tries_path" ]]; then
254 if [[ "$tries_path" == *","* ]]; then
255 echo "$tries_path" | tr ',' '\n'
256 else
257 echo "$tries_path"
258 fi
259 return
260 fi
261 fi
262 done
263
264 # Default path
265 echo "$HOME/work/tries"
266}
267
268_try_rs_complete() {
269 local cur="${COMP_WORDS[COMP_CWORD]}"
270 local tries_paths=$(_try_rs_get_tries_path)
271 local -a dirs=()
272
273 # Split by comma if multiple paths
274 IFS=',' read -ra PATH_ARRAY <<< "$tries_paths"
275
276 for tries_path in "${PATH_ARRAY[@]}"; do
277 # Trim whitespace
278 tries_path=$(echo "$tries_path" | xargs)
279
280 if [[ -d "$tries_path" ]]; then
281 # Get list of directories
282 while IFS= read -r dir; do
283 dirs+=("$dir")
284 done < <(ls -1 "$tries_path" 2>/dev/null | while read -r dir; do
285 if [[ -d "$tries_path/$dir" ]]; then
286 echo "$dir"
287 fi
288 done)
289 fi
290 done
291
292 COMPREPLY=($(compgen -W "${dirs[*]}" -- "$cur"))
293}
294
295complete -o default -F _try_rs_complete try-rs
296"#.to_string()
297 }
298 Shell::Bash => {
299 r#"# try-rs tab completion for directory names
300_try_rs_get_tries_path() {
301 # Check TRY_PATH environment variable first
302 if [[ -n "${TRY_PATH}" ]]; then
303 if [[ "${TRY_PATH}" == *","* ]]; then
304 echo "${TRY_PATH}" | tr ',' '\n'
305 else
306 echo "${TRY_PATH}"
307 fi
308 return
309 fi
310
311 # Try to read from config file
312 local config_paths=("$HOME/.config/try-rs/config.toml" "$HOME/.try-rs/config.toml")
313 for config_path in "${config_paths[@]}"; do
314 if [[ -f "$config_path" ]]; then
315 # Try tries_path (supports single or multiple paths with comma)
316 local tries_path=$(grep -E '^[[:space:]]*tries_path[[:space:]]*=' "$config_path" 2>/dev/null | sed 's/.*=[[:space:]]*"\?\([^"]*\)"\?.*/\1/' | sed "s|~|$HOME|" | tr -d '[:space:]')
317 if [[ -n "$tries_path" ]]; then
318 if [[ "$tries_path" == *","* ]]; then
319 echo "$tries_path" | tr ',' '\n'
320 else
321 echo "$tries_path"
322 fi
323 return
324 fi
325 fi
326 done
327
328 # Default path
329 echo "$HOME/work/tries"
330}
331
332_try_rs_complete() {
333 local cur="${COMP_WORDS[COMP_CWORD]}"
334 local tries_paths=$(_try_rs_get_tries_path)
335 local dirs=""
336
337 # Split by comma if multiple paths
338 IFS=',' read -ra PATH_ARRAY <<< "$tries_paths"
339
340 for tries_path in "${PATH_ARRAY[@]}"; do
341 # Trim whitespace
342 tries_path=$(echo "$tries_path" | xargs)
343
344 if [[ -d "$tries_path" ]]; then
345 # Get list of directories
346 while IFS= read -r dir; do
347 if [[ -d "$tries_path/$dir" ]]; then
348 dirs="$dirs $dir"
349 fi
350 done < <(ls -1 "$tries_path" 2>/dev/null)
351 fi
352 done
353
354 COMPREPLY=($(compgen -W "$dirs" -- "$cur"))
355}
356
357complete -o default -F _try_rs_complete try-rs
358"#.to_string()
359 }
360 Shell::PowerShell => {
361 r#"# try-rs tab completion for directory names
362Register-ArgumentCompleter -CommandName try-rs -ScriptBlock {
363 param($wordToComplete, $commandAst, $cursorPosition)
364
365 # Get tries path from environment variable or default
366 $triesPaths = $env:TRY_PATH
367 if (-not $triesPaths) {
368 # Try to read from config file
369 $configPaths = @(
370 "$env:USERPROFILE/.config/try-rs/config.toml",
371 "$env:USERPROFILE/.try-rs/config.toml"
372 )
373 foreach ($configPath in $configPaths) {
374 if (Test-Path $configPath) {
375 $content = Get-Content $configPath -Raw
376 # Try tries_path (supports single or multiple paths with comma)
377 if ($content -match 'tries_path\s*=\s*["'']?([^"'']+)["'']?') {
378 $triesPaths = $matches[1].Replace('~', $env:USERPROFILE).Trim()
379 break
380 }
381 }
382 }
383 }
384
385 # Default path
386 if (-not $triesPaths) {
387 $triesPaths = "$env:USERPROFILE/work/tries"
388 }
389
390 # Split by comma if multiple paths
391 $pathArray = $triesPaths -split ','
392
393 # Get directories from all paths
394 foreach ($triesPath in $pathArray) {
395 $triesPath = $triesPath.Trim()
396 if (Test-Path $triesPath) {
397 Get-ChildItem -Path $triesPath -Directory |
398 Where-Object { $_.Name -like "$wordToComplete*" } |
399 ForEach-Object {
400 [System.Management.Automation.CompletionResult]::new(
401 $_.Name,
402 $_.Name,
403 'ParameterValue',
404 $_.Name
405 )
406 }
407 }
408 }
409}
410"#.to_string()
411 }
412 Shell::NuShell => {
413 r#"# try-rs tab completion for directory names
414# Add this to your Nushell config or env file
415
416export def __try_rs_get_tries_paths [] {
417 # Check TRY_PATH environment variable first
418 if ($env.TRY_PATH? | is-not-empty) {
419 return ($env.TRY_PATH | split row "," | each { |s| $s | str trim })
420 }
421
422 # Try to read from config file
423 let config_paths = [
424 ($env.HOME | path join ".config" "try-rs" "config.toml"),
425 ($env.HOME | path join ".try-rs" "config.toml")
426 ]
427
428 for config_path in $config_paths {
429 if ($config_path | path exists) {
430 let content = (open $config_path | str trim)
431 # Try tries_path (supports single or multiple paths with comma)
432 if ($content =~ 'tries_path\\s*=\\s*"?([^"]+)"?') {
433 let path = ($content | parse -r 'tries_path\\s*=\\s*"?([^"]+)"?' | get capture0.0? | default "")
434 if ($path | is-not-empty) {
435 # Check if contains comma (multiple paths)
436 if ($path | str contains ",") {
437 return ($path | split row "," | each { |s| ($s | str trim | str replace "~" $env.HOME) })
438 else
439 return ([($path | str replace "~" $env.HOME)])
440 }
441 }
442 }
443 }
444 }
445
446 # Default path
447 [($env.HOME | path join "work" "tries")]
448}
449
450export def __try_rs_complete [context: string] {
451 let tries_paths = (__try_rs_get_tries_paths)
452
453 mut all_dirs = []
454 for tries_path in $tries_paths {
455 if ($tries_path | path exists) {
456 let dirs = (ls $tries_path | where type == "dir" | get name | path basename)
457 $all_dirs = ($all_dirs | append $dirs)
458 }
459 }
460 $all_dirs
461}
462"#.to_string()
463 }
464 }
465}
466
467pub fn get_completion_script_only(shell: &Shell) -> String {
469 let completions = get_completions_script(shell);
470 match shell {
471 Shell::NuShell => {
472 r#"# try-rs tab completion for directory names
474# Add this to your Nushell config
475
476def __try_rs_get_tries_path [] {
477 if ($env.TRY_PATH? | is-not-empty) {
478 return $env.TRY_PATH
479 }
480
481 let config_paths = [
482 ($env.HOME | path join ".config" "try-rs" "config.toml"),
483 ($env.HOME | path join ".try-rs" "config.toml")
484 ]
485
486 for config_path in $config_paths {
487 if ($config_path | path exists) {
488 let content = (open $config_path | str trim)
489 if ($content =~ 'tries_path\\s*=\\s*"?([^"]+)"?') {
490 let path = ($content | parse -r 'tries_path\\s*=\\s*"?([^"]+)"?' | get capture0.0? | default "")
491 if ($path | is-not-empty) {
492 return ($path | str replace "~" $env.HOME)
493 }
494 }
495 }
496 }
497
498 ($env.HOME | path join "work" "tries")
499}
500
501def __try_rs_complete [context: string] {
502 let tries_path = (__try_rs_get_tries_path)
503
504 if ($tries_path | path exists) {
505 ls $tries_path | where type == "dir" | get name | path basename
506 } else {
507 []
508 }
509}
510
511# Register completion
512export extern try-rs [
513 name_or_url?: string@__try_rs_complete
514 destination?: string
515 --setup: string
516 --setup-stdout: string
517 --completions: string
518 --shallow-clone(-s)
519 --worktree(-w): string
520]
521"#.to_string()
522 }
523 _ => completions,
524 }
525}
526
527pub fn get_shell_integration_path(shell: &Shell) -> PathBuf {
528 let config_dir = match shell {
529 Shell::Fish => get_base_config_dir(),
530 _ => get_config_dir(),
531 };
532
533 match shell {
534 Shell::Fish => get_fish_functions_dir().join("try-rs.fish"),
535 Shell::Zsh => config_dir.join("try-rs.zsh"),
536 Shell::Bash => config_dir.join("try-rs.bash"),
537 Shell::PowerShell => config_dir.join("try-rs.ps1"),
538 Shell::NuShell => config_dir.join("try-rs.nu"),
539 }
540}
541
542fn get_fish_functions_dir() -> PathBuf {
543 if let Ok(output) = std::process::Command::new("fish")
544 .args(["-c", "echo $__fish_config_dir"])
545 .output()
546 {
547 if output.status.success() {
548 let output_str = String::from_utf8_lossy(&output.stdout);
549 let path = PathBuf::from(output_str.trim()).join("functions");
550 if path.exists() || path.parent().map(|p| p.exists()).unwrap_or(false) {
551 return path;
552 }
553 }
554 }
555 get_base_config_dir().join("fish").join("functions")
556}
557
558fn write_fish_picker_function() -> Result<PathBuf> {
559 let file_path = get_fish_functions_dir().join("try-rs-picker.fish");
560 if let Some(parent) = file_path.parent()
561 && !parent.exists()
562 {
563 fs::create_dir_all(parent)?;
564 }
565 fs::write(&file_path, FISH_PICKER_FUNCTION)?;
566 eprintln!(
567 "Fish picker function file created at: {}",
568 file_path.display()
569 );
570 Ok(file_path)
571}
572
573pub fn is_shell_integration_configured(shell: &Shell) -> bool {
574 get_shell_integration_path(shell).exists()
575}
576
577fn append_source_to_rc(rc_path: &std::path::Path, source_cmd: &str) -> Result<()> {
579 if rc_path.exists() {
580 let content = fs::read_to_string(rc_path)?;
581 if !content.contains(source_cmd) && !content.contains("# try-rs integration") {
583 let mut file = fs::OpenOptions::new().append(true).open(rc_path)?;
584 writeln!(file, "\n# try-rs integration")?;
585 writeln!(file, "{}", source_cmd)?;
586 eprintln!("Added configuration to {}", rc_path.display());
587 } else {
588 eprintln!("Configuration already present in {}", rc_path.display());
589 }
590 } else {
591 eprintln!(
592 "You need to add the following line to {}:",
593 rc_path.display()
594 );
595 eprintln!("{}", source_cmd);
596 }
597 Ok(())
598}
599
600fn write_shell_integration(shell: &Shell) -> Result<std::path::PathBuf> {
602 let file_path = get_shell_integration_path(shell);
603 if let Some(parent) = file_path.parent()
604 && !parent.exists()
605 {
606 fs::create_dir_all(parent)?;
607 }
608 fs::write(&file_path, get_shell_content(shell))?;
609 eprintln!(
610 "{:?} function file created at: {}",
611 shell,
612 file_path.display()
613 );
614 Ok(file_path)
615}
616
617pub fn setup_shell(shell: &Shell) -> Result<()> {
619 let file_path = write_shell_integration(shell)?;
620 let home_dir = dirs::home_dir().expect("Could not find home directory");
621
622 match shell {
623 Shell::Fish => {
624 let _picker_path = write_fish_picker_function()?;
625 let fish_config_path = home_dir.join(".config").join("fish").join("config.fish");
626 eprintln!(
627 "You may need to restart your shell or run 'source {}' to apply changes.",
628 file_path.display()
629 );
630 eprintln!(
631 "Optional: append the following to {} to bind Ctrl+T:",
632 fish_config_path.display()
633 );
634 eprintln!("bind \\ct try-rs-picker");
635 eprintln!("bind -M insert \\ct try-rs-picker");
636 }
637 Shell::Zsh => {
638 let source_cmd = format!("source '{}'", file_path.display());
639 append_source_to_rc(&home_dir.join(".zshrc"), &source_cmd)?;
640 }
641 Shell::Bash => {
642 let source_cmd = format!("source '{}'", file_path.display());
643 append_source_to_rc(&home_dir.join(".bashrc"), &source_cmd)?;
644 }
645 Shell::PowerShell => {
646 let profile_path_ps7 = home_dir
647 .join("Documents")
648 .join("PowerShell")
649 .join("Microsoft.PowerShell_profile.ps1");
650 let profile_path_ps5 = home_dir
651 .join("Documents")
652 .join("WindowsPowerShell")
653 .join("Microsoft.PowerShell_profile.ps1");
654 let profile_path = if profile_path_ps7.exists() {
655 profile_path_ps7
656 } else if profile_path_ps5.exists() {
657 profile_path_ps5
658 } else {
659 profile_path_ps7
660 };
661
662 if let Some(parent) = profile_path.parent()
663 && !parent.exists()
664 {
665 fs::create_dir_all(parent)?;
666 }
667
668 let source_cmd = format!(". '{}'", file_path.display());
669 if profile_path.exists() {
670 append_source_to_rc(&profile_path, &source_cmd)?;
671 } else {
672 let mut file = fs::File::create(&profile_path)?;
673 writeln!(file, "# try-rs integration")?;
674 writeln!(file, "{}", source_cmd)?;
675 eprintln!(
676 "PowerShell profile created and configured at: {}",
677 profile_path.display()
678 );
679 }
680
681 eprintln!(
682 "You may need to restart your shell or run '. {}' to apply changes.",
683 profile_path.display()
684 );
685 eprintln!(
686 "If you get an error about running scripts, you may need to run: Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned"
687 );
688 }
689 Shell::NuShell => {
690 let nu_config_path = dirs::config_dir()
691 .expect("Could not find config directory")
692 .join("nushell")
693 .join("config.nu");
694 let source_cmd = format!("source '{}'", file_path.display());
695 if nu_config_path.exists() {
696 append_source_to_rc(&nu_config_path, &source_cmd)?;
697 } else {
698 eprintln!("Could not find config.nu at {}", nu_config_path.display());
699 eprintln!("Please add the following line manually:");
700 eprintln!("{}", source_cmd);
701 }
702 }
703 }
704
705 Ok(())
706}
707
708pub fn generate_completions(shell: &Shell) -> Result<()> {
710 let script = get_completion_script_only(shell);
711 print!("{}", script);
712 Ok(())
713}
714
715pub fn get_installed_shells() -> Vec<Shell> {
716 let mut shells = Vec::new();
717 for shell in [
718 Shell::Fish,
719 Shell::Zsh,
720 Shell::Bash,
721 Shell::PowerShell,
722 Shell::NuShell,
723 ] {
724 if is_shell_installed(&shell) {
725 shells.push(shell);
726 }
727 }
728 shells
729}
730
731fn is_shell_installed(shell: &Shell) -> bool {
732 let shell_name = match shell {
733 Shell::Fish => "fish",
734 Shell::Zsh => "zsh",
735 Shell::Bash => "bash",
736 Shell::PowerShell => "pwsh",
737 Shell::NuShell => "nu",
738 };
739
740 let output = std::process::Command::new("whereis")
741 .arg(shell_name)
742 .output();
743
744 match output {
745 Ok(out) => {
746 let result = String::from_utf8_lossy(&out.stdout);
747 let trimmed = result.trim();
748 !trimmed.is_empty()
749 && !trimmed.ends_with(':')
750 && trimmed.starts_with(&format!("{}: ", shell_name))
751 }
752 Err(_) => false,
753 }
754}
755
756pub fn clear_shell_setup() -> Result<()> {
757 let installed_shells = get_installed_shells();
758
759 if installed_shells.is_empty() {
760 eprintln!("No supported shells found on this system.");
761 return Ok(());
762 }
763
764 eprintln!("Detected shells: {:?}\n", installed_shells);
765
766 eprintln!("Files to be removed:");
767
768 for shell in &installed_shells {
769 let paths = get_shell_config_paths(shell);
770
771 for path in &paths {
772 eprintln!(" - {}", path.display());
773 }
774
775 match shell {
776 Shell::Fish => {
777 let fish_functions = get_fish_functions_dir();
778 eprintln!(
779 " - {}",
780 fish_functions.join("try-rs-picker.fish").display()
781 );
782 }
783 _ => {}
784 }
785 }
786
787 eprintln!("\nRemoving files...");
788
789 for shell in &installed_shells {
790 clear_shell_config(shell)?;
791 }
792
793 eprintln!("\nDone! Shell integration removed.");
794 Ok(())
795}
796
797fn clear_shell_config(shell: &Shell) -> Result<()> {
798 let integration_file = get_shell_integration_path(shell);
799 if integration_file.exists() {
800 fs::remove_file(&integration_file)?;
801 eprintln!("Removed integration file: {}", integration_file.display());
802 }
803
804 if let Shell::Fish = shell {
805 let picker_path = get_fish_functions_dir().join("try-rs-picker.fish");
806 if picker_path.exists() {
807 fs::remove_file(&picker_path)?;
808 eprintln!("Removed picker file: {}", picker_path.display());
809 }
810 }
811
812 let home_dir = dirs::home_dir().expect("Could not find home directory");
814 let rc_files = match shell {
815 Shell::Zsh => vec![home_dir.join(".zshrc")],
816 Shell::Bash => vec![home_dir.join(".bashrc")],
817 Shell::NuShell => vec![dirs::config_dir()
818 .expect("Could not find config directory")
819 .join("nushell")
820 .join("config.nu")],
821 Shell::PowerShell => {
822 let profile_path_ps7 = home_dir
823 .join("Documents")
824 .join("PowerShell")
825 .join("Microsoft.PowerShell_profile.ps1");
826 let profile_path_ps5 = home_dir
827 .join("Documents")
828 .join("WindowsPowerShell")
829 .join("Microsoft.PowerShell_profile.ps1");
830 vec![profile_path_ps7, profile_path_ps5]
831 },
832 _ => vec![],
833 };
834
835 for rc_path in rc_files {
836 if rc_path.exists() {
837 remove_source_from_rc(&rc_path)?;
838 }
839 }
840
841 Ok(())
842}
843
844fn remove_source_from_rc(rc_path: &std::path::Path) -> Result<()> {
845 let content = fs::read_to_string(rc_path)?;
846 if content.contains("try-rs") {
847 let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
848 let initial_count = lines.len();
849
850 lines.retain(|line| {
852 !line.contains("# try-rs integration") &&
853 !(line.contains("source") && line.contains("try-rs")) &&
854 !(line.contains(".") && line.contains("try-rs") && rc_path.extension().map_or(false, |ext| ext == "ps1"))
855 });
856
857 if lines.len() < initial_count {
858 let mut new_content = lines.join("\n");
859 if !new_content.is_empty() && !new_content.ends_with('\n') {
860 new_content.push('\n');
861 }
862 fs::write(rc_path, new_content)?;
863 eprintln!("Cleaned up integration lines from {}", rc_path.display());
864 }
865 }
866 Ok(())
867}
868
869fn get_shell_config_paths(shell: &Shell) -> Vec<PathBuf> {
870 let mut paths = Vec::new();
871 let config_dir = get_base_config_dir();
872
873 match shell {
874 Shell::Fish => {
875 let fish_functions = get_fish_functions_dir();
876 paths.push(fish_functions.join("try-rs.fish"));
877 }
878 Shell::Zsh => {
879 paths.push(config_dir.join("try-rs.zsh"));
880 }
881 Shell::Bash => {
882 paths.push(config_dir.join("try-rs.bash"));
883 }
884 Shell::PowerShell => {
885 paths.push(config_dir.join("try-rs.ps1"));
886 }
887 Shell::NuShell => {
888 paths.push(config_dir.join("try-rs.nu"));
889 }
890 }
891
892 paths
893}
894