git_x/commands/
completion.rs1use 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 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 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 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 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}