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 daemon statusline' -d 'Subcommand'"
274    );
275
276    // Add completion for daemon subcommand
277    println!("\n# Completion for 'daemon' subcommand");
278    println!(
279        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and not __fish_seen_subcommand_from start stop status restart' -f -a 'start stop status restart' -d 'Daemon action'"
280    );
281    println!(
282        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from start' -l foreground -d 'Run in the foreground'"
283    );
284    println!(
285        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from start' -l log-level -d 'Log level (error/warn/info/debug/trace)' -r -f -a 'error warn info debug trace'"
286    );
287    println!(
288        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from start' -s v -l verbose -d 'Increase verbosity (-v/-vv/-vvv)'"
289    );
290    println!(
291        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from restart' -l foreground -d 'Run in the foreground after restart'"
292    );
293    println!(
294        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from restart' -l log-level -d 'Log level (error/warn/info/debug/trace)' -r -f -a 'error warn info debug trace'"
295    );
296    println!(
297        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from restart' -s v -l verbose -d 'Increase verbosity (-v/-vv/-vvv)'"
298    );
299    println!(
300        "complete -c cc-switch -n '__fish_cc_switch_using_subcommand daemon; and __fish_seen_subcommand_from status' -l json -d 'Output as JSON'"
301    );
302
303    // Add completion for cs list subcommand flags
304    println!("\n# Completion for 'cs list' subcommand");
305    println!(
306        "complete -c cs -n '__fish_seen_subcommand_from list' -l plain -s p -d 'Plain text output'"
307    );
308    println!(
309        "complete -c cs -n '__fish_seen_subcommand_from list' -l name -s n -d 'Show only name and URL'"
310    );
311
312    // Add completion for 'cs daemon' subcommand
313    println!("\n# Completion for 'cs daemon' subcommand");
314    println!(
315        "complete -c cs -n '__fish_seen_subcommand_from daemon; and not __fish_seen_subcommand_from start stop status restart' -f -a 'start stop status restart' -d 'Daemon action'"
316    );
317    println!(
318        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from start' -l foreground -d 'Run in the foreground'"
319    );
320    println!(
321        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from start' -l log-level -d 'Log level' -r -f -a 'error warn info debug trace'"
322    );
323    println!(
324        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from start' -s v -l verbose -d 'Increase verbosity'"
325    );
326    println!(
327        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from restart' -l foreground -d 'Run in the foreground after restart'"
328    );
329    println!(
330        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from restart' -l log-level -d 'Log level' -r -f -a 'error warn info debug trace'"
331    );
332    println!(
333        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from restart' -s v -l verbose -d 'Increase verbosity'"
334    );
335    println!(
336        "complete -c cs -n '__fish_seen_subcommand_from daemon; and __fish_seen_subcommand_from status' -l json -d 'Output as JSON'"
337    );
338
339    // Add completion for 'cs statusline' subcommand
340    println!("\n# Completion for 'cs statusline' subcommand");
341    println!(
342        "complete -c cs -n '__fish_seen_subcommand_from statusline' -f -a 'install uninstall' -d 'Statusline action'"
343    );
344
345    // Add completion for the 'cx' alias (cc-switch codex)
346    println!("\n# Completion for the 'cx' alias (cc-switch codex)");
347    println!("complete -c cx -f");
348    println!(
349        "complete -c cx -n '__fish_use_subcommand' -f -a 'add use remove list' -d 'Codex subcommand'"
350    );
351    println!(
352        "complete -c cx -n '__fish_seen_subcommand_from use' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
353    );
354    println!(
355        "complete -c cx -n '__fish_seen_subcommand_from remove' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'"
356    );
357    println!(
358        "complete -c cx -n '__fish_seen_subcommand_from list' -f -l plain -s p -d 'Plain text output'"
359    );
360    println!(
361        "complete -c cx -n '__fish_seen_subcommand_from list' -f -l name -s n -d 'Show only name and auth mode'"
362    );
363    println!(
364        "complete -c cx -n '__fish_seen_subcommand_from add' -f -l interactive -s i -d 'Interactive mode'"
365    );
366    println!(
367        "complete -c cx -n '__fish_seen_subcommand_from add' -f -l from-file -d 'Import from auth.json (defaults to ~/.codex/auth.json if no path)' -r"
368    );
369
370    println!("\n# Fish completion generated successfully");
371    println!("# Add this to your ~/.config/fish/completions/cc-switch.fish");
372    println!("# Then restart your shell or run 'source ~/.config/fish/config.fish'");
373    println!(
374        "{}",
375        "# Aliases 'cs' and 'ccd' have been added for convenience".green()
376    );
377
378    // Generate separate completion file for cx function
379    // Fish doesn't auto-load completions for functions, so we create a dedicated file
380    generate_cx_completion_file();
381}
382
383/// Generate separate completion file for cx fish function
384///
385/// Fish doesn't automatically load completion files for functions, only for commands.
386/// This creates ~/.config/fish/completions/cx.fish
387fn generate_cx_completion_file() {
388    // Fish uses ~/.config/fish/completions on all platforms (including macOS)
389    let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("~"));
390    let completions_dir = home.join(".config").join("fish").join("completions");
391
392    if !completions_dir.exists()
393        && let Err(e) = fs::create_dir_all(&completions_dir)
394    {
395        eprintln!("Warning: Could not create completions directory: {e}");
396        return;
397    }
398
399    let cx_content = r#"# Completion for 'cx' alias (cc-switch codex)
400# cx is a fish function; disable file completion by default
401complete -c cx -f
402complete -c cx -n '__fish_use_subcommand' -f -a 'add use remove list' -d 'Codex subcommand'
403complete -c cx -n '__fish_seen_subcommand_from use' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'
404complete -c cx -n '__fish_seen_subcommand_from remove' -f -a '(cc-switch --list-codex-aliases)' -d 'Codex configuration alias name'
405complete -c cx -n '__fish_seen_subcommand_from list' -f -l plain -s p -d 'Plain text output'
406complete -c cx -n '__fish_seen_subcommand_from list' -f -l name -s n -d 'Show only name and auth mode'
407complete -c cx -n '__fish_seen_subcommand_from add' -f -l interactive -s i -d 'Interactive mode'
408complete -c cx -n '__fish_seen_subcommand_from add' -f -l from-file -d 'Import from auth.json (defaults to ~/.codex/auth.json if no path)' -r
409"#;
410
411    let cx_path = completions_dir.join("cx.fish");
412
413    if let Err(e) = fs::write(&cx_path, cx_content) {
414        eprintln!("Warning: Could not write cx.fish: {e}");
415    }
416
417    eprintln!("\nCreated completion file: {}", cx_path.display());
418}