1use anyhow::{Context, Result};
2use colored::*;
3use std::fs;
4use std::path::{Path, PathBuf};
5pub struct ShellIntegration;
6impl ShellIntegration {
7 pub fn install() -> Result<()> {
8 println!("Installing cargo-mate...");
9 let shell = Self::detect_shell()?;
10 let rc_file = Self::get_rc_file(&shell)?;
11 println!("[SEARCH] Detected shell: {}", shell.cyan());
12 println!("[FILE] RC file: {}", rc_file.display());
13 Self::backup_rc_file(&rc_file)?;
14 Self::ensure_shipwreck_bin_exists()?;
15 let integration_code = Self::generate_integration_code(&shell);
16 Self::add_to_rc_file(&rc_file, &integration_code)?;
17 Self::create_completion_script(&shell)?;
18 println!("[RELOAD] {}", "To activate immediately, run one of these:".yellow());
19 println!(" {} {}", "source".green(), format!("{}", rc_file.display()) .cyan());
20 println!(" {} {}", "cm".green(), "activate".cyan());
21 println!(" {}", "Or restart your terminal".dimmed());
22 println!();
23 println!("[DOCS] {}", "Available commands after activation:".yellow());
24 println!(" Run cm commands in cargo and run cargo commands in cm");
25 println!(" {} - Direct cargo-mate access", "cm".cyan());
26 println!();
27 Ok(())
28 }
29 pub fn uninstall() -> Result<()> {
30 println!("[TRASH] Removing cargo-mate shell integration...");
31 let shell = Self::detect_shell()?;
32 let rc_file = Self::get_rc_file(&shell)?;
33 if !rc_file.exists() {
34 println!("[WARN] RC file not found: {}", rc_file.display());
35 return Ok(());
36 }
37 let content = fs::read_to_string(&rc_file)?;
38 let cleaned = Self::remove_integration_code(&content);
39 fs::write(&rc_file, cleaned)?;
40 println!("[RELOAD] Please restart your terminal");
41 Ok(())
42 }
43 fn ensure_shipwreck_bin_exists() -> Result<()> {
44 let shipwreck_bin = dirs::home_dir()
45 .context("Could not find home directory")?
46 .join(".shipwreck")
47 .join("bin");
48 if !shipwreck_bin.exists() {
49 fs::create_dir_all(&shipwreck_bin)?;
50 println!("[DIR] Created directory: {}", shipwreck_bin.display());
51 }
52 let cm_binary = shipwreck_bin.join("cm");
53 if cm_binary.exists() {
54 println!("[OK] Found cm binary in {}", shipwreck_bin.display());
55 #[cfg(unix)]
56 {
57 use std::os::unix::fs::PermissionsExt;
58 if let Ok(metadata) = std::fs::metadata(&cm_binary) {
59 let perms = metadata.permissions();
60 if perms.mode() & 0o111 == 0 {
61 println!("[WARN] cm binary is not executable, fixing...");
62 let current_mode = perms.mode();
63 let mut new_perms = perms;
64 new_perms.set_mode(current_mode | 0o755);
65 if let Err(e) = std::fs::set_permissions(&cm_binary, new_perms) {
66 println!("[ERROR] Failed to make cm executable: {}", e);
67 }
68 }
69 }
70 }
71 } else {
72 let mut found_binary = false;
73 for entry in std::fs::read_dir(&shipwreck_bin)
74 .unwrap_or_else(|_| std::fs::read_dir(".").unwrap())
75 {
76 if let Ok(entry) = entry {
77 let file_name = entry.file_name();
78 let name_str = file_name.to_string_lossy();
79 if name_str.starts_with("cargo-mate")
80 && name_str.ends_with(".protected")
81 {
82 println!("[WARN] Found incorrectly named binary: {}", name_str);
83 println!(
84 "[FIX] This should be renamed to 'cm' for proper cargo integration"
85 );
86 found_binary = true;
87 break;
88 }
89 }
90 }
91 if !found_binary {
92 println!("[INFO] No cm binary found in {}", shipwreck_bin.display());
93 println!(
94 "[INFO] The protected binary will be installed when you first run 'cm'"
95 );
96 }
97 }
98 Ok(())
99 }
100 pub fn detect_shell() -> Result<String> {
101 let os_type = std::env::consts::OS;
102 let is_windows = os_type == "windows";
103 if is_windows {
104 if std::env::var("PSModulePath").is_ok() {
105 return Ok("powershell".to_string());
106 } else {
107 return Ok("cmd".to_string());
108 }
109 } else {
110 if let Ok(shell_path) = std::env::var("SHELL") {
111 let shell_name = std::path::Path::new(&shell_path)
112 .file_name()
113 .and_then(|n| n.to_str())
114 .unwrap_or("");
115 match shell_name {
116 "zsh" => return Ok("zsh".to_string()),
117 "bash" => return Ok("bash".to_string()),
118 "fish" => return Ok("fish".to_string()),
119 "ash" | "dash" => return Ok("ash".to_string()),
120 "sh" => {
121 if os_type == "macos" {
122 return Ok("bash".to_string());
123 } else {
124 return Ok("ash".to_string());
125 }
126 }
127 _ => {
128 if shell_path.contains("zsh") {
129 return Ok("zsh".to_string());
130 } else if shell_path.contains("bash") {
131 return Ok("bash".to_string());
132 } else if shell_path.contains("fish") {
133 return Ok("fish".to_string());
134 }
135 }
136 }
137 }
138 if os_type == "macos" {
139 if std::process::Command::new("zsh").arg("--version").output().is_ok() {
140 return Ok("zsh".to_string());
141 }
142 }
143 }
144 match os_type {
145 "macos" => Ok("zsh".to_string()),
146 "linux" => Ok("bash".to_string()),
147 _ => Ok("bash".to_string()),
148 }
149 }
150 pub fn get_rc_file(shell: &str) -> Result<PathBuf> {
151 let home = dirs::home_dir().context("Could not find home directory")?;
152 let os_type = std::env::consts::OS;
153 let is_windows = os_type == "windows";
154 let is_macos = os_type == "macos";
155 let rc_file = match shell {
156 "powershell" => {
157 if is_windows {
158 let profile_paths = vec![
159 home.join("Documents").join("PowerShell")
160 .join("Microsoft.PowerShell_profile.ps1"), home.join("Documents")
161 .join("WindowsPowerShell")
162 .join("Microsoft.PowerShell_profile.ps1"),
163 ];
164 for path in &profile_paths {
165 if path.exists() {
166 return Ok(path.clone());
167 }
168 }
169 profile_paths[0].clone()
170 } else {
171 home.join(".profile")
172 }
173 }
174 "cmd" => {
175 if is_windows {
176 let autoexec = PathBuf::from("C:\\autoexec.bat");
177 if autoexec.exists() {
178 autoexec
179 } else {
180 home.join("cargo-mate-profile.cmd")
181 }
182 } else {
183 home.join(".profile")
184 }
185 }
186 "zsh" => {
187 let zshrc = home.join(".zshrc");
188 if zshrc.exists() || is_macos { zshrc } else { home.join(".profile") }
189 }
190 "bash" => {
191 let bashrc = home.join(".bashrc");
192 let bash_profile = home.join(".bash_profile");
193 if bashrc.exists() {
194 bashrc
195 } else if bash_profile.exists() {
196 bash_profile
197 } else {
198 bashrc
199 }
200 }
201 "fish" => {
202 let config_dir = if let Ok(xdg_config) = std::env::var(
203 "XDG_CONFIG_HOME",
204 ) {
205 PathBuf::from(xdg_config)
206 } else {
207 home.join(".config")
208 };
209 let fish_config = config_dir.join("fish").join("config.fish");
210 if let Some(parent) = fish_config.parent() {
211 let _ = fs::create_dir_all(parent);
212 }
213 fish_config
214 }
215 "ash" => {
216 let profile = home.join(".profile");
217 if profile.exists() { profile } else { profile }
218 }
219 _ => {
220 let profile = home.join(".profile");
221 if profile.exists() { profile } else { profile }
222 }
223 };
224 Ok(rc_file)
225 }
226 fn backup_rc_file(rc_file: &Path) -> Result<()> {
227 if rc_file.exists() {
228 let backup = rc_file.with_extension("bak.cargo-mate");
229 fs::copy(rc_file, &backup)?;
230 println!("[BACKUP] Backed up to: {}", backup.display());
231 }
232 Ok(())
233 }
234 fn generate_integration_code(shell: &str) -> String {
235 let home = dirs::home_dir()
236 .map(|p| p.to_string_lossy().to_string())
237 .unwrap_or_else(|| "$HOME".to_string());
238 let shipwreck_bin = format!("{}/.shipwreck/bin", home);
239 let os_type = std::env::consts::OS;
240 let is_windows = os_type == "windows";
241 match shell {
242 "powershell" => {
243 if is_windows {
244 r#"
245# === Cargo Mate (cm) Integration for PowerShell ===
246# This section was automatically added by cargo-mate
247
248# Add Cargo Mate to PATH
249$cmBinPath = "$env:USERPROFILE\.shipwreck\bin"
250if ($env:PATH -notlike "*$cmBinPath*") {
251 $env:PATH = "$cmBinPath;$env:PATH"
252}
253
254# Function to intercept cargo commands
255function cargo {
256 if (Get-Command cm -ErrorAction SilentlyContinue) {
257 # Store original cargo path to avoid infinite loops
258 $env:CARGO_BIN_PATH = (Get-Command cargo -ErrorAction SilentlyContinue).Source
259 # Call cargo-mate instead
260 & "$env:USERPROFILE\.shipwreck\bin\cm.exe" exec @args
261 } else {
262 # Fallback to original cargo if cm not found
263 if ($env:CARGO_BIN_PATH) {
264 & $env:CARGO_BIN_PATH @args
265 } else {
266 Write-Host "cargo command not found" -ForegroundColor Red
267 }
268 }
269}
270
271# Alias for quick access
272Set-Alias cg cm
273
274# Function to load project config
275function cm_load_config {
276 if (Test-Path .cg) {
277 $env:CM_PROJECT_CONFIG = ".cg"
278 # Uncomment below to show config loading
279 # Write-Host "[ANCHOR] Loaded project config: .cg" -ForegroundColor Green
280 }
281}
282
283# Load config when entering directory (PowerShell 6+)
284if ($PSVersionTable.PSVersion.Major -ge 6) {
285 Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -Action {
286 cm_load_config
287 } | Out-Null
288}
289
290# Nautical theme
291$env:CM_THEME = "nautical"
292
293# === End Cargo Mate Integration ===
294"#
295 .to_string()
296 } else {
297 r#"
298# PowerShell integration not supported on this platform
299# Please use bash/zsh/fish integration instead
300"#
301 .to_string()
302 }
303 }
304 "cmd" => {
305 if is_windows {
306 r#"
307@echo off
308REM === Cargo Mate (cm) Integration for CMD ===
309REM This section was automatically added by cargo-mate
310
311REM Add Cargo Mate to PATH
312set CM_BIN_PATH=%USERPROFILE%\.shipwreck\bin
313if "%PATH%"=="%PATH:%CM_BIN_PATH%=%" (
314 set PATH=%CM_BIN_PATH%;%PATH%
315)
316
317REM Function to intercept cargo commands (via alias)
318REM Note: CMD has limited function support, using alias instead
319doskey cargo=cm exec $*
320
321REM Alias for quick access
322doskey cg=cm
323
324REM Nautical theme
325set CM_THEME=nautical
326
327REM === End Cargo Mate Integration ===
328"#
329 .to_string()
330 } else {
331 r#"
332# CMD integration not supported on this platform
333# Please use bash/zsh/fish integration instead
334"#
335 .to_string()
336 }
337 }
338 "zsh" | "bash" => {
339 r#"
340# === Cargo Mate (cm) Integration ===
341# This section was automatically added by cargo-mate
342
343# Add ~/.shipwreck/bin to PATH for cargo-mate commands
344if [[ ":$PATH:" != *":$HOME/.shipwreck/bin:"* ]]; then
345 export PATH="$HOME/.shipwreck/bin:$PATH"
346fi
347
348# Function to check if cm command exists (more robust than command -v)
349cm_exists() {
350 # Try multiple ways to find cm
351 if command -v cm &> /dev/null; then
352 return 0
353 elif [ -x "$HOME/.shipwreck/bin/cm" ]; then
354 return 0
355 elif [ -f "$HOME/.shipwreck/bin/cm" ]; then
356 return 0
357 fi
358 return 1
359}
360
361# Function to intercept cargo commands
362cargo() {
363 if cm_exists; then
364 # Set the path to the real cargo binary to avoid infinite loops
365 export CARGO_BIN_PATH="$(command -v cargo 2>/dev/null || which cargo 2>/dev/null || echo 'cargo')"
366 # Use the protected binary from .shipwreck/bin
367 "$HOME/.shipwreck/bin/cm" exec "$@"
368 else
369 command cargo "$@"
370 fi
371}
372
373# Alias for quick access
374alias cg='cm'
375
376# Auto-complete for cm
377if [ -f ~/.shipwreck/completions/cm.bash ]; then
378 source ~/.shipwreck/completions/cm.bash
379fi
380
381# Project-specific config loader
382cm_load_config() {
383 if [ -f .cg ]; then
384 export CM_PROJECT_CONFIG=".cg"
385 # Silent config loading (remove # to show once per session)
386 # if [ -z "$CM_CONFIG_LOADED" ]; then
387 # echo "⚓ Loaded project config: .cg"
388 # export CM_CONFIG_LOADED="1"
389 # fi
390 fi
391}
392
393# Auto-load config when entering directory
394if [[ "$SHELL" == *"zsh"* ]]; then
395 autoload -U add-zsh-hook
396 add-zsh-hook chpwd cm_load_config
397elif [[ "$SHELL" == *"bash"* ]]; then
398 PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND;} cm_load_config"
399fi
400
401# Nautical prompt enhancement (optional)
402export CM_THEME="nautical"
403
404# === End Cargo Mate Integration ===
405"#
406 .to_string()
407 }
408 "fish" => {
409 r#"
410# === Cargo Mate (cm) Integration ===
411# This section was automatically added by cargo-mate
412
413# Add ~/.shipwreck/bin to PATH for cargo-mate commands
414if not contains $HOME/.shipwreck/bin $fish_user_paths
415 set -U fish_user_paths $HOME/.shipwreck/bin $fish_user_paths
416end
417
418# Function to intercept cargo commands
419function cargo
420 if command -v cm > /dev/null
421 # Set the path to the real cargo binary to avoid infinite loops
422 set -x CARGO_BIN_PATH (command -v cargo)
423 cm exec $argv
424 else
425 command cargo $argv
426 end
427end
428
429# Aliases
430alias cg='cm'
431
432# Auto-complete
433if test -f ~/.shipwreck/completions/cm.fish
434 source ~/.shipwreck/completions/cm.fish
435end
436
437# Project config loader
438function cm_load_config --on-variable PWD
439 if test -f .cg
440 set -x CM_PROJECT_CONFIG ".cg"
441 echo "[ANCHOR] Loaded project config: .cg"
442 end
443end
444
445# === End Cargo Mate Integration ===
446"#
447 .to_string()
448 }
449 _ => String::new(),
450 }
451 }
452 fn add_to_rc_file(rc_file: &Path, integration_code: &str) -> Result<()> {
453 if let Some(parent) = rc_file.parent() {
454 fs::create_dir_all(parent)
455 .with_context(|| {
456 format!("Failed to create directory: {}", parent.display())
457 })?;
458 }
459 let mut content = if rc_file.exists() {
460 fs::read_to_string(rc_file)
461 .with_context(|| {
462 format!("Failed to read RC file: {}", rc_file.display())
463 })?
464 } else {
465 String::new()
466 };
467 let has_old_integration = content.contains("=== Cargo Mate");
468 let has_new_integration = content.contains("Cargo Mate Integration");
469 if has_old_integration || has_new_integration {
470 println!("[WARN] Integration already exists, updating...");
471 content = Self::remove_integration_code(&content);
472 }
473 if !content.is_empty() && !content.ends_with('\n') {
474 content.push('\n');
475 }
476 if !content.ends_with("\n\n") {
477 content.push('\n');
478 }
479 content.push_str(integration_code);
480 if !content.ends_with('\n') {
481 content.push('\n');
482 }
483 Self::backup_rc_file(rc_file)?;
484 fs::write(rc_file, &content)
485 .with_context(|| format!("Failed to write RC file: {}", rc_file.display()))?;
486 #[cfg(unix)]
487 {
488 use std::os::unix::fs::PermissionsExt;
489 if let Ok(metadata) = fs::metadata(rc_file) {
490 let mut perms = metadata.permissions();
491 let current_mode = perms.mode();
492 perms.set_mode(current_mode | 0o600);
493 let _ = fs::set_permissions(rc_file, perms);
494 }
495 }
496 Ok(())
497 }
498 fn remove_integration_code(content: &str) -> String {
499 let lines: Vec<&str> = content.lines().collect();
500 let mut result = Vec::new();
501 let mut in_section = false;
502 for line in lines {
503 if line.contains("=== Cargo Mate") && line.contains("Integration ===") {
504 in_section = !in_section;
505 continue;
506 }
507 if !in_section {
508 result.push(line);
509 }
510 }
511 result.join("\n")
512 }
513 fn create_completion_script(shell: &str) -> Result<()> {
514 let completions_dir = dirs::home_dir()
515 .context("Could not find home directory")?
516 .join(".shipwreck")
517 .join("completions");
518 fs::create_dir_all(&completions_dir)?;
519 let os_type = std::env::consts::OS;
520 let is_windows = os_type == "windows";
521 match shell {
522 "powershell" => {
523 if is_windows {
524 let script = r#"
525# PowerShell completion for cm (cargo-mate)
526
527using namespace System.Management.Automation
528using namespace System.Management.Automation.Language
529
530function CmCompletion {
531 param($wordToComplete, $commandAst, $cursorPosition)
532
533 $commands = @(
534 "build", "test", "run", "check", "clean", "doc", "fmt", "clippy",
535 "init", "help", "journey", "anchor", "log", "tide", "map",
536 "mutiny", "config", "version", "view", "optimize", "test",
537 "checklist", "history", "install", "activate", "register", "idea", "wtf", "user"
538 )
539
540 $completions = $commands | Where-Object { $_ -like "$wordToComplete*" }
541
542 foreach ($completion in $completions) {
543 [CompletionResult]::new($completion, $completion, 'ParameterValue', $completion)
544 }
545}
546
547Register-ArgumentCompleter -CommandName cm -ScriptBlock ${function:CmCompletion}
548Register-ArgumentCompleter -CommandName cg -ScriptBlock ${function:CmCompletion}
549"#;
550 let completion_file = completions_dir.join("cm.ps1");
551 fs::write(&completion_file, script)?;
552 }
553 }
554 "cmd" => {
555 if is_windows {
556 let script = r#"
557REM CMD completion helper for cm (cargo-mate)
558REM This file provides command information for manual completion
559
560REM Available commands:
561REM build test run check clean doc fmt clippy
562REM init help journey anchor log tide map
563REM mutiny config version view optimize test
564REM checklist history install activate register idea wtf user
565"#;
566 let completion_file = completions_dir.join("cm-help.cmd");
567 fs::write(&completion_file, script)?;
568 }
569 }
570 "bash" | "zsh" => {
571 let script = r#"#!/bin/bash
572# Bash completion for cm (cargo-mate)
573
574_cm_completions() {
575 local cur prev opts
576 cur="${COMP_WORDS[COMP_CWORD]}"
577 prev="${COMP_WORDS[COMP_CWORD-1]}"
578
579 # Main commands
580 opts="build test run check clean doc fmt clippy init help \
581 journey anchor log tide map mutiny config checklist history"
582
583 # Sub-commands
584 case "${prev}" in
585 journey)
586 opts="record play list export import"
587 ;;
588 anchor)
589 opts="save restore list show diff"
590 ;;
591 log)
592 opts="add search timeline export analyze"
593 ;;
594 tide)
595 opts="show analyze export"
596 ;;
597 map)
598 opts="deps show analyze export"
599 ;;
600 mutiny)
601 opts="activate deactivate allow-warnings skip-tests force yolo status"
602 ;;
603 config)
604 opts="set get list init add-shortcut add-hook"
605 ;;
606 *)
607 # Include cargo commands too
608 if command -v cargo &> /dev/null; then
609 cargo_opts=$(cargo --list 2>/dev/null | awk '{print $1}')
610 opts="$opts $cargo_opts"
611 fi
612 ;;
613 esac
614
615 COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
616}
617
618complete -F _cm_completions cm
619complete -F _cm_completions cg
620"#;
621 let completion_file = completions_dir.join("cm.bash");
622 fs::write(&completion_file, script)?;
623 #[cfg(unix)]
624 {
625 use std::os::unix::fs::PermissionsExt;
626 let mut perms = fs::metadata(&completion_file)?.permissions();
627 perms.set_mode(0o755);
628 fs::set_permissions(&completion_file, perms)?;
629 }
630 }
631 "fish" => {
632 let script = r#"# Fish completion for cm (cargo-mate)
633
634complete -c cm -f
635
636# Main commands
637complete -c cm -n "__fish_use_subcommand" -a "build" -d "Compile the current package"
638complete -c cm -n "__fish_use_subcommand" -a "test" -d "Run tests"
639complete -c cm -n "__fish_use_subcommand" -a "run" -d "Run a binary"
640complete -c cm -n "__fish_use_subcommand" -a "check" -d "Check code without building"
641complete -c cm -n "__fish_use_subcommand" -a "journey" -d "Journey recording and playback"
642complete -c cm -n "__fish_use_subcommand" -a "anchor" -d "Save and restore project states"
643complete -c cm -n "__fish_use_subcommand" -a "log" -d "Captain's log"
644complete -c cm -n "__fish_use_subcommand" -a "tide" -d "Performance tracking"
645complete -c cm -n "__fish_use_subcommand" -a "map" -d "Dependency visualization"
646complete -c cm -n "__fish_use_subcommand" -a "mutiny" -d "Override cargo restrictions"
647complete -c cm -n "__fish_use_subcommand" -a "config" -d "Configuration management"
648
649# Journey subcommands
650complete -c cm -n "__fish_seen_subcommand_from journey" -a "record play list export import"
651
652# Anchor subcommands
653complete -c cm -n "__fish_seen_subcommand_from anchor" -a "save restore list show diff"
654
655# Copy cg alias
656complete -c cg -w cm
657"#;
658 let completion_file = completions_dir.join("cm.fish");
659 fs::write(&completion_file, script)?;
660 }
661 _ => {}
662 }
663 Ok(())
664 }
665 pub fn show_status() {
666 println!("{}", "=== Shell Integration Status ===".blue().bold());
667 let shell = Self::detect_shell().unwrap_or_else(|_| "unknown".to_string());
668 let os_type = std::env::consts::OS;
669 let is_windows = os_type == "windows";
670 println!("[SHELL] Current shell: {}", shell.cyan());
671 println!("[OS] Operating system: {}", os_type.cyan());
672 if let Ok(rc_file) = Self::get_rc_file(&shell) {
673 if rc_file.exists() {
674 let content = fs::read_to_string(&rc_file).unwrap_or_default();
675 if content.contains("=== Cargo Mate")
676 || content.contains("Cargo Mate Integration")
677 {
678 println!("[OK] Integration: {}", "Installed".green());
679 println!(
680 " Config file: {}", rc_file.display().to_string().dimmed()
681 );
682 } else {
683 println!("[X] Integration: {}", "Not installed".red());
684 }
685 } else {
686 println!("[X] Integration: {}", "Not installed".red());
687 println!(
688 " Config file location: {}", rc_file.display().to_string().dimmed()
689 );
690 }
691 }
692 if let Ok(path) = std::env::var("PATH") {
693 let separator = if is_windows { ';' } else { ':' };
694 let cm_in_path = path
695 .split(separator)
696 .any(|p| {
697 let cm_path = Path::new(p)
698 .join(if is_windows { "cm.exe" } else { "cm" });
699 cm_path.exists()
700 });
701 if cm_in_path {
702 println!("[OK] Binary in PATH: {}", "Yes".green());
703 } else {
704 println!("[WARN] Binary in PATH: {}", "No".yellow());
705 }
706 }
707 let completions_dir = dirs::home_dir()
708 .map(|h| h.join(".shipwreck").join("completions"))
709 .unwrap_or_default();
710 if completions_dir.exists() {
711 println!("[OK] Completions: {}", "Installed".green());
712 let extensions = if is_windows {
713 vec!["ps1", "cmd"]
714 } else {
715 vec!["bash", "fish"]
716 };
717 for ext in extensions {
718 let completion_file = completions_dir.join(format!("cm.{}", ext));
719 if completion_file.exists() {
720 println!(
721 " {} {} completion", "•".dimmed(), ext.to_uppercase()
722 .dimmed()
723 );
724 }
725 }
726 } else {
727 println!("[X] Completions: {}", "Not installed".red());
728 }
729 if is_windows {
730 println!();
731 println!("[WINDOWS] Windows-specific notes:");
732 println!(" • Use PowerShell for best experience");
733 println!(" • CMD support is limited");
734 println!(" • Admin privileges may be required for installation");
735 }
736 }
737 pub fn add_shell_integration(rc_file: &PathBuf, shell: &str) -> Result<()> {
738 use std::fs::OpenOptions;
739 use std::io::Write;
740 if rc_file.exists() {
741 let backup = rc_file.with_extension("bak.cargo-mate");
742 std::fs::copy(rc_file, &backup)?;
743 println!("📋 Backed up {} to {}", rc_file.display(), backup.display());
744 }
745 let integration_code = match shell {
746 "fish" => {
747 r#"
748# === Cargo Mate (cm) Integration ===
749function cargo
750 cm exec $argv
751end
752
753# Note: cm binary should be in PATH
754alias cg='cm'
755# === End Cargo Mate Integration ===
756"#
757 }
758 _ => {
759 r#"
760# === Cargo Mate (cm) Integration ===
761cargo() {
762 cm exec "$@"
763}
764# Note: cm binary should be in PATH
765alias cg='cm'
766# === End Cargo Mate Integration ===
767"#
768 }
769 };
770 let mut file = OpenOptions::new().create(true).append(true).open(rc_file)?;
771 writeln!(file, "{}", integration_code)?;
772 println!("✅ Shell integration added to {}", rc_file.display());
773 Ok(())
774 }
775}