git_x/commands/
completion.rs

1use crate::core::traits::Command;
2use crate::{GitXError, Result};
3use clap_complete::Shell;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7pub struct CompletionInstallCommand {
8    shell: Shell,
9}
10
11impl CompletionInstallCommand {
12    pub fn new(shell: Shell) -> Self {
13        Self { shell }
14    }
15
16    fn get_completion_directory(&self) -> Result<PathBuf> {
17        let home = std::env::var("HOME")
18            .map_err(|_| GitXError::Other("HOME environment variable not set".to_string()))?;
19
20        match self.shell {
21            Shell::Bash => {
22                // Try bash completion directories in order of preference
23                let dirs = vec![
24                    format!("{}/.local/share/bash-completion/completions", home),
25                    format!("{}/.bash_completion.d", home),
26                ];
27
28                for dir in dirs {
29                    let path = PathBuf::from(&dir);
30                    if path.parent().is_some_and(|p| p.exists()) {
31                        if !path.exists() {
32                            fs::create_dir_all(&path).map_err(|e| {
33                                GitXError::Other(format!(
34                                    "Failed to create directory {dir}: {e}"
35                                ))
36                            })?;
37                        }
38                        return Ok(path);
39                    }
40                }
41
42                // Fallback: create the standard location
43                let fallback =
44                    PathBuf::from(format!("{home}/.local/share/bash-completion/completions"));
45                fs::create_dir_all(&fallback).map_err(|e| {
46                    GitXError::Other(format!("Failed to create completion directory: {e}"))
47                })?;
48                Ok(fallback)
49            }
50            Shell::Zsh => {
51                // Try zsh completion directories
52                let dirs = vec![
53                    format!("{}/.local/share/zsh/site-functions", home),
54                    format!("{}/.zsh/completions", home),
55                ];
56
57                for dir in dirs {
58                    let path = PathBuf::from(&dir);
59                    if path.parent().is_some_and(|p| p.exists()) {
60                        if !path.exists() {
61                            fs::create_dir_all(&path).map_err(|e| {
62                                GitXError::Other(format!(
63                                    "Failed to create directory {dir}: {e}"
64                                ))
65                            })?;
66                        }
67                        return Ok(path);
68                    }
69                }
70
71                // Fallback: create the standard location
72                let fallback = PathBuf::from(format!("{home}/.local/share/zsh/site-functions"));
73                fs::create_dir_all(&fallback).map_err(|e| {
74                    GitXError::Other(format!("Failed to create completion directory: {e}"))
75                })?;
76                Ok(fallback)
77            }
78            Shell::Fish => {
79                let dir = format!("{home}/.config/fish/completions");
80                let path = PathBuf::from(&dir);
81                fs::create_dir_all(&path).map_err(|e| {
82                    GitXError::Other(format!(
83                        "Failed to create fish completions directory: {e}"
84                    ))
85                })?;
86                Ok(path)
87            }
88            Shell::PowerShell => {
89                Err(GitXError::Other(
90                    "PowerShell completion installation not supported. Use 'git x completion powershell' and add to your profile manually.".to_string(),
91                ))
92            }
93            Shell::Elvish => {
94                Err(GitXError::Other(
95                    "Elvish completion installation not supported. Use 'git x completion elvish' and add to rc.elv manually.".to_string(),
96                ))
97            }
98            _ => {
99                Err(GitXError::Other(format!(
100                    "Unsupported shell: {:?}",
101                    self.shell
102                )))
103            }
104        }
105    }
106
107    fn get_completion_filename(&self) -> &str {
108        match self.shell {
109            Shell::Bash => "git-x",
110            Shell::Zsh => "_git-x",
111            Shell::Fish => "git-x.fish",
112            _ => "git-x",
113        }
114    }
115
116    fn generate_completion_script(&self) -> Result<String> {
117        use crate::cli::Cli;
118        use clap::CommandFactory;
119        use clap_complete::generate;
120        use std::io::Cursor;
121
122        let mut cmd = Cli::command();
123        let mut buf = Cursor::new(Vec::new());
124
125        match self.shell {
126            Shell::Bash => {
127                generate(clap_complete::shells::Bash, &mut cmd, "git-x", &mut buf);
128            }
129            Shell::Zsh => {
130                generate(clap_complete::shells::Zsh, &mut cmd, "git-x", &mut buf);
131            }
132            Shell::Fish => {
133                generate(clap_complete::shells::Fish, &mut cmd, "git-x", &mut buf);
134            }
135            _ => {
136                return Err(GitXError::Other(format!(
137                    "Completion generation not supported for {:?}",
138                    self.shell
139                )));
140            }
141        }
142
143        String::from_utf8(buf.into_inner())
144            .map_err(|e| GitXError::Other(format!("Failed to generate completion script: {e}")))
145    }
146
147    fn get_shell_setup_instructions(&self, completion_path: &Path) -> String {
148        match self.shell {
149            Shell::Bash => {
150                format!(
151                    "Completion installed to: {}\n\n\
152                    To enable bash completions, add this to your ~/.bashrc or ~/.bash_profile:\n\
153                    if [[ -d ~/.local/share/bash-completion/completions ]]; then\n\
154                        for completion in ~/.local/share/bash-completion/completions/*; do\n\
155                            [[ -r \"$completion\" ]] && source \"$completion\"\n\
156                        done\n\
157                    fi\n\n\
158                    Then restart your shell or run: source ~/.bashrc",
159                    completion_path.display()
160                )
161            }
162            Shell::Zsh => {
163                format!(
164                    "Completion installed to: {}\n\n\
165                    To enable zsh completions, add this to your ~/.zshrc:\n\
166                    if [[ -d ~/.local/share/zsh/site-functions ]]; then\n\
167                        fpath=(~/.local/share/zsh/site-functions $fpath)\n\
168                        autoload -U compinit && compinit\n\
169                    fi\n\n\
170                    Then restart your shell or run: source ~/.zshrc\n\
171                    You may also need to clear the completion cache: rm -f ~/.zcompdump*",
172                    completion_path.display()
173                )
174            }
175            Shell::Fish => {
176                format!(
177                    "Completion installed to: {}\n\n\
178                    Fish completions are automatically loaded from ~/.config/fish/completions/\n\
179                    Restart your shell or run: fish -c 'complete --erase; source ~/.config/fish/config.fish'",
180                    completion_path.display()
181                )
182            }
183            _ => format!("Completion installed to: {}", completion_path.display()),
184        }
185    }
186}
187
188impl Command for CompletionInstallCommand {
189    fn execute(&self) -> Result<String> {
190        let completion_dir = self.get_completion_directory()?;
191        let filename = self.get_completion_filename();
192        let completion_path = completion_dir.join(filename);
193
194        let completion_script = self.generate_completion_script()?;
195
196        fs::write(&completion_path, completion_script).map_err(|e| {
197            GitXError::Other(format!(
198                "Failed to write completion file to {}: {}",
199                completion_path.display(),
200                e
201            ))
202        })?;
203
204        let instructions = self.get_shell_setup_instructions(&completion_path);
205
206        Ok(format!(
207            "✅ Shell completion installed successfully!\n\n{instructions}"
208        ))
209    }
210
211    fn name(&self) -> &'static str {
212        "completion-install"
213    }
214
215    fn description(&self) -> &'static str {
216        "Install shell completion to standard location"
217    }
218}