theater_cli/commands/
completion.rs

1use clap::{CommandFactory, Parser};
2use clap_complete::{generate, Shell};
3use std::io;
4use tracing::debug;
5
6use crate::error::{CliError, CliResult};
7use crate::CommandContext;
8
9#[derive(Debug, Parser)]
10pub struct CompletionArgs {
11    /// Shell to generate completions for
12    #[arg(value_enum)]
13    pub shell: Shell,
14
15    /// Output file (defaults to stdout)
16    #[arg(short, long)]
17    pub output: Option<std::path::PathBuf>,
18}
19
20/// Generate shell completion scripts
21pub async fn execute_async(args: &CompletionArgs, ctx: &CommandContext) -> CliResult<()> {
22    debug!("Generating shell completion for: {:?}", args.shell);
23
24    let mut app = crate::Cli::command();
25    let app_name = app.get_name().to_string();
26
27    match &args.output {
28        Some(output_path) => {
29            debug!("Writing completion to file: {:?}", output_path);
30
31            let mut file = std::fs::File::create(output_path).map_err(|e| CliError::IoError {
32                operation: format!("create completion file: {}", output_path.display()),
33                source: e,
34            })?;
35
36            generate(args.shell, &mut app, &app_name, &mut file);
37
38            ctx.output.success(&format!(
39                "Shell completion for {} written to: {}",
40                args.shell,
41                output_path.display()
42            ))?;
43        }
44        None => {
45            debug!("Writing completion to stdout");
46            generate(args.shell, &mut app, &app_name, &mut io::stdout());
47        }
48    }
49
50    // Only show installation instructions when writing to a file
51    // Don't show them when output goes to stdout (for eval)
52    if args.output.is_some() && !ctx.json {
53        show_installation_instructions(args.shell, ctx)?;
54    }
55
56    Ok(())
57}
58
59/// Show installation instructions for the generated completion script
60fn show_installation_instructions(shell: Shell, ctx: &CommandContext) -> CliResult<()> {
61    let instructions = match shell {
62        Shell::Bash => {
63            r#"
64To install bash completions:
65
661. Save the completion script:
67   theater completion bash > ~/.local/share/bash-completion/completions/theater
68
692. Or add to your ~/.bashrc:
70   eval "$(theater completion bash)"
71
723. Restart your shell or run:
73   source ~/.bashrc
74"#
75        }
76        Shell::Zsh => {
77            r#"
78To install zsh completions:
79
801. Save the completion script to a directory in your $fpath:
81   theater completion zsh > ~/.local/share/zsh/site-functions/_theater
82
832. Or add to your ~/.zshrc:
84   eval "$(theater completion zsh)"
85
863. Restart your shell or run:
87   source ~/.zshrc
88"#
89        }
90        Shell::Fish => {
91            r#"
92To install fish completions:
93
941. Save the completion script:
95   theater completion fish > ~/.config/fish/completions/theater.fish
96
972. Or add to your fish config:
98   theater completion fish | source
99
1003. Restart your shell
101"#
102        }
103        Shell::PowerShell => {
104            r#"
105To install PowerShell completions:
106
1071. Add to your PowerShell profile:
108   theater completion powershell | Out-String | Invoke-Expression
109
1102. Or save to a file and dot-source it in your profile:
111   theater completion powershell > theater_completion.ps1
112   . .\theater_completion.ps1
113"#
114        }
115        Shell::Elvish => {
116            r#"
117To install Elvish completions:
118
1191. Add to your ~/.config/elvish/rc.elv:
120   eval (theater completion elvish | slurp)
121"#
122        }
123        _ => "Please refer to your shell's documentation for completion installation.",
124    };
125
126    ctx.output.info(instructions)?;
127    Ok(())
128}