Skip to main content

cc_switch/cli/
completion.rs

1use crate::config::ConfigStorage;
2use anyhow::Result;
3use clap::CommandFactory;
4use colored::*;
5use std::fs;
6use std::io::stdout;
7
8/// Generate shell aliases for eval
9///
10/// # Arguments
11/// * `shell` - Shell type (fish, zsh, bash)
12///
13/// # Errors
14/// Returns error if shell is not supported
15pub fn generate_aliases(shell: &str) -> Result<()> {
16    match shell {
17        "fish" => {
18            println!("alias cs='cc-switch'");
19            println!("alias ccd='claude --dangerously-skip-permissions'");
20            println!("alias cx='cc-switch codex'");
21        }
22        "zsh" => {
23            println!("alias cs='cc-switch'");
24            println!("alias ccd='claude --dangerously-skip-permissions'");
25            println!("alias cx='cc-switch codex'");
26        }
27        "bash" => {
28            println!("alias cs='cc-switch'");
29            println!("alias ccd='claude --dangerously-skip-permissions'");
30            println!("alias cx='cc-switch codex'");
31        }
32        _ => {
33            anyhow::bail!(
34                "Unsupported shell: {}. Supported shells: fish, zsh, bash",
35                shell
36            );
37        }
38    }
39
40    Ok(())
41}
42
43/// Generate shell completion script
44///
45/// # Arguments
46/// * `shell` - Shell type (fish, zsh, bash, elvish, powershell, nushell)
47///
48/// # Errors
49/// Returns error if shell is not supported or generation fails
50pub fn generate_completion(shell: &str) -> Result<()> {
51    use crate::cli::Cli;
52
53    let mut app = Cli::command();
54
55    match shell {
56        "fish" => {
57            generate_fish_completion(&mut app);
58        }
59        "zsh" => {
60            clap_complete::generate(
61                clap_complete::shells::Zsh,
62                &mut app,
63                "cc-switch",
64                &mut stdout(),
65            );
66
67            // Add aliases for zsh
68            println!("\n# Useful aliases for cc-switch");
69            println!("# Add these aliases to your ~/.zshrc:");
70            println!("alias cs='cc-switch'");
71            println!("alias ccd='claude --dangerously-skip-permissions'");
72            println!("# Or run this command to add aliases temporarily:");
73            println!("alias cs='cc-switch'; alias ccd='claude --dangerously-skip-permissions'");
74
75            println!("\n# Zsh completion generated successfully");
76            println!("# Add this to your ~/.zsh/completions/_cc-switch");
77            println!("# Or add this line to your ~/.zshrc:");
78            println!("# fpath=(~/.zsh/completions $fpath)");
79            println!("# Then restart your shell or run 'source ~/.zshrc'");
80            println!(
81                "{}",
82                "# Aliases 'cs' and 'ccd' have been added for convenience".green()
83            );
84        }
85        "bash" => {
86            clap_complete::generate(
87                clap_complete::shells::Bash,
88                &mut app,
89                "cc-switch",
90                &mut stdout(),
91            );
92
93            // Add aliases for bash
94            println!("\n# Useful aliases for cc-switch");
95            println!("# Add these aliases to your ~/.bashrc or ~/.bash_profile:");
96            println!("alias cs='cc-switch'");
97            println!("alias ccd='claude --dangerously-skip-permissions'");
98            println!("# Or run this command to add aliases temporarily:");
99            println!("alias cs='cc-switch'; alias ccd='claude --dangerously-skip-permissions'");
100
101            println!("\n# Bash completion generated successfully");
102            println!("# Add this to your ~/.bash_completion or /etc/bash_completion.d/");
103            println!("# Then restart your shell or run 'source ~/.bashrc'");
104            println!(
105                "{}",
106                "# Aliases 'cs' and 'ccd' have been added for convenience".green()
107            );
108        }
109        "elvish" => {
110            clap_complete::generate(
111                clap_complete::shells::Elvish,
112                &mut app,
113                "cc-switch",
114                &mut stdout(),
115            );
116
117            // Add aliases for elvish
118            println!("\n# Useful aliases for cc-switch");
119            println!("fn cs {{|@args| cc-switch $@args }}");
120            println!("fn ccd {{|@args| claude --dangerously-skip-permissions $@args }}");
121
122            println!("\n# Elvish completion generated successfully");
123            println!(
124                "{}",
125                "# Aliases 'cs' and 'ccd' have been added for convenience".green()
126            );
127        }
128        "powershell" => {
129            clap_complete::generate(
130                clap_complete::shells::PowerShell,
131                &mut app,
132                "cc-switch",
133                &mut stdout(),
134            );
135
136            // Add aliases for PowerShell
137            println!("\n# Useful aliases for cc-switch");
138            println!("Set-Alias -Name cs -Value cc-switch");
139            println!("function ccd {{ claude --dangerously-skip-permissions @args }}");
140
141            println!("\n# PowerShell completion generated successfully");
142            println!(
143                "{}",
144                "# Aliases 'cs' and 'ccd' have been added for convenience".green()
145            );
146        }
147        _ => {
148            anyhow::bail!(
149                "Unsupported shell: {}. Supported shells: fish, zsh, bash, elvish, powershell",
150                shell
151            );
152        }
153    }
154
155    Ok(())
156}
157
158/// List available configuration aliases for shell completion
159///
160/// Outputs all stored configuration aliases, one per line
161/// Also includes 'cc' and 'official' as special aliases for resetting to default Claude
162/// For contexts where user types 'cc-switch use c' or similar, 'current' is prioritized first
163///
164/// # Errors
165/// Returns error if loading configurations fails
166pub fn list_aliases_for_completion() -> Result<()> {
167    let storage = ConfigStorage::load()?;
168
169    // Always include 'cc' and 'official' for reset functionality
170    println!("cc");
171    println!("official");
172
173    // Prioritize 'current' first if it exists - this ensures when user types 'cc-switch use c'
174    // or 'cs use c', the 'current' configuration appears first in completion
175    if storage.configurations.contains_key("current") {
176        println!("current");
177    }
178
179    // Output all other stored aliases in alphabetical order
180    let mut aliases: Vec<String> = storage.configurations.keys().cloned().collect();
181    aliases.sort();
182
183    for alias_name in aliases {
184        if alias_name != "current" {
185            println!("{alias_name}");
186        }
187    }
188
189    Ok(())
190}
191
192/// List available Codex configuration aliases for shell completion
193///
194/// Outputs all stored Codex configuration aliases, one per line
195///
196/// # Errors
197/// Returns error if loading configurations fails
198pub fn list_codex_aliases_for_completion() -> Result<()> {
199    let storage = ConfigStorage::load()?;
200
201    // Output all stored Codex aliases in alphabetical order
202    if let Some(ref configs) = storage.codex_configurations {
203        let mut aliases: Vec<String> = configs.keys().cloned().collect();
204        aliases.sort();
205
206        for alias_name in aliases {
207            println!("{alias_name}");
208        }
209    }
210
211    Ok(())
212}
213
214/// Generate custom fish completion with dynamic alias completion
215///
216/// # Arguments
217/// * `app` - The CLI application struct
218fn generate_fish_completion(app: &mut clap::Command) {
219    // Generate basic completion
220    clap_complete::generate(
221        clap_complete::shells::Fish,
222        app,
223        "cc-switch",
224        &mut std::io::stdout(),
225    );
226
227    // Add custom completion for use subcommand with dynamic aliases
228    println!("\n# Custom completion for use subcommand with dynamic aliases");
229    println!(
230        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand use' -f -a '(cc-switch --list-aliases)' -d 'Configuration alias name'"
231    );
232    // Also support 'switch' as alias for 'use'
233    println!("# Custom completion for switch subcommand (alias for use)");
234    println!(
235        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand switch' -f -a '(cc-switch --list-aliases)' -d 'Configuration alias name'"
236    );
237    // Custom completion for remove subcommand with dynamic aliases
238    println!("# Custom completion for remove subcommand with dynamic aliases");
239    println!(
240        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand remove' -f -a '(cc-switch --list-aliases)' -d 'Configuration alias name'"
241    );
242
243    // Add custom completion for codex subcommand
244    println!("\n# Custom completion for codex subcommand with dynamic aliases");
245    println!(
246        "complete -c cc-switch -n '__fish_seen_subcommand_from codex' -n '__fish_seen_subcommand_from use' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
247    );
248    println!(
249        "complete -c cc-switch -n '__fish_seen_subcommand_from codex' -n '__fish_seen_subcommand_from remove' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
250    );
251
252    // Add useful aliases that can be eval'd
253    println!("\n# To use these aliases immediately, run:");
254    println!("# eval \"(cc-switch alias fish)\"");
255    println!("\n# Or add them permanently to your ~/.config/fish/config.fish:");
256    println!("# echo \"alias cs='cc-switch'\" >> ~/.config/fish/config.fish");
257    println!(
258        "# echo \"alias ccd='claude --dangerously-skip-permissions'\" >> ~/.config/fish/config.fish"
259    );
260    println!("\n# IMPORTANT: For cs alias completion to work, you must also:");
261    println!(
262        "# 1. Add the completion script: cc-switch completion fish > ~/.config/fish/completions/cc-switch.fish"
263    );
264    println!("# 2. OR run: eval \"(cc-switch completion fish)\" | source");
265
266    // Add completion for the 'cs' alias
267    println!("\n# Completion for the 'cs' alias");
268    println!("complete -c cs -w cc-switch");
269
270    // Add completion for cs alias subcommands (but NOT configuration aliases at top level)
271    println!("\n# Completion for 'cs' alias subcommands");
272    println!(
273        "complete -c cs -n '__fish_use_subcommand' -f -a 'add remove list set-default-dir completion alias use switch current codex' -d 'Subcommand'"
274    );
275
276    // Add completion for cs list subcommand flags
277    println!("\n# Completion for 'cs list' subcommand");
278    println!(
279        "complete -c cs -n '__fish_seen_subcommand_from list' -l plain -s p -d 'Plain text output'"
280    );
281    println!(
282        "complete -c cs -n '__fish_seen_subcommand_from list' -l name -s n -d 'Show only name and URL'"
283    );
284
285    // Add completion for the 'cx' alias (cc-switch codex)
286    println!("\n# Completion for the 'cx' alias (cc-switch codex)");
287    println!("complete -c cx -f");
288    println!(
289        "complete -c cx -n '__fish_use_subcommand' -f -a 'add use remove list' -d 'Codex subcommand'"
290    );
291    println!(
292        "complete -c cx -n '__fish_seen_subcommand_from use' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
293    );
294    println!(
295        "complete -c cx -n '__fish_seen_subcommand_from remove' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
296    );
297    println!(
298        "complete -c cx -n '__fish_seen_subcommand_from list' -f -l plain -s p -d 'Plain text output'"
299    );
300    println!(
301        "complete -c cx -n '__fish_seen_subcommand_from list' -f -l name -s n -d 'Show only name and auth mode'"
302    );
303    println!(
304        "complete -c cx -n '__fish_seen_subcommand_from add' -f -l interactive -s i -d 'Interactive mode'"
305    );
306    println!(
307        "complete -c cx -n '__fish_seen_subcommand_from add' -f -l from-file -d 'Import from JSON file' -r"
308    );
309
310    println!("\n# Fish completion generated successfully");
311    println!("# Add this to your ~/.config/fish/completions/cc-switch.fish");
312    println!("# Then restart your shell or run 'source ~/.config/fish/config.fish'");
313    println!(
314        "{}",
315        "# Aliases 'cs' and 'ccd' have been added for convenience".green()
316    );
317
318    // Generate separate completion file for cx function
319    // Fish doesn't auto-load completions for functions, so we create a dedicated file
320    generate_cx_completion_file();
321}
322
323/// Generate separate completion file for cx fish function
324///
325/// Fish doesn't automatically load completion files for functions, only for commands.
326/// This creates ~/.config/fish/completions/cx.fish
327fn generate_cx_completion_file() {
328    // Fish uses ~/.config/fish/completions on all platforms (including macOS)
329    let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("~"));
330    let completions_dir = home.join(".config").join("fish").join("completions");
331
332    if !completions_dir.exists()
333        && let Err(e) = fs::create_dir_all(&completions_dir)
334    {
335        eprintln!("Warning: Could not create completions directory: {e}");
336        return;
337    }
338
339    let cx_content = r#"# Completion for 'cx' alias (cc-switch codex)
340# cx is a fish function; disable file completion by default
341complete -c cx -f
342complete -c cx -n '__fish_use_subcommand' -f -a 'add use remove list' -d 'Codex subcommand'
343complete -c cx -n '__fish_seen_subcommand_from use' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'
344complete -c cx -n '__fish_seen_subcommand_from remove' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'
345complete -c cx -n '__fish_seen_subcommand_from list' -f -l plain -s p -d 'Plain text output'
346complete -c cx -n '__fish_seen_subcommand_from list' -f -l name -s n -d 'Show only name and auth mode'
347complete -c cx -n '__fish_seen_subcommand_from add' -f -l interactive -s i -d 'Interactive mode'
348complete -c cx -n '__fish_seen_subcommand_from add' -f -l from-file -d 'Import from JSON file' -r
349"#;
350
351    let cx_path = completions_dir.join("cx.fish");
352
353    if let Err(e) = fs::write(&cx_path, cx_content) {
354        eprintln!("Warning: Could not write cx.fish: {e}");
355    }
356
357    eprintln!("\nCreated completion file: {}", cx_path.display());
358}