diff options
Diffstat (limited to 'src/shells/powershell.rs')
-rw-r--r-- | src/shells/powershell.rs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/src/shells/powershell.rs b/src/shells/powershell.rs new file mode 100644 index 0000000..6b09b2e --- /dev/null +++ b/src/shells/powershell.rs @@ -0,0 +1,142 @@ +use std::io::Write; + +use clap::builder::StyledStr; +use clap::*; + +use crate::generator::{utils, Generator}; +use crate::INTERNAL_ERROR_MSG; + +/// Generate powershell completion file +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct PowerShell; + +impl Generator for PowerShell { + fn file_name(&self, name: &str) -> String { + format!("_{name}.ps1") + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + let subcommands_cases = generate_inner(cmd, ""); + + let result = format!( + r#" +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{ + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + '{bin_name}' + for ($i = 1; $i -lt $commandElements.Count; $i++) {{ + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-') -or + $element.Value -eq $wordToComplete) {{ + break + }} + $element.Value + }}) -join ';' + + $completions = @(switch ($command) {{{subcommands_cases} + }}) + + $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} | + Sort-Object -Property ListItemText +}} +"# + ); + + w!(buf, result.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { + string.replace('\'', "''") +} + +fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String { + match help { + Some(help) => escape_string(&help.to_string()), + _ => data.to_string(), + } +} + +fn generate_inner(p: &Command, previous_command_name: &str) -> String { + debug!("generate_inner"); + + let command_name = if previous_command_name.is_empty() { + p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string() + } else { + format!("{};{}", previous_command_name, &p.get_name()) + }; + + let mut completions = String::new(); + let preamble = String::from("\n [CompletionResult]::new("); + + for option in p.get_opts() { + generate_aliases(&mut completions, &preamble, option); + } + + for flag in utils::flags(p) { + generate_aliases(&mut completions, &preamble, &flag); + } + + for subcommand in p.get_subcommands() { + let data = &subcommand.get_name(); + let tooltip = get_tooltip(subcommand.get_about(), data); + + completions.push_str(&preamble); + completions.push_str( + format!("'{data}', '{data}', [CompletionResultType]::ParameterValue, '{tooltip}')") + .as_str(), + ); + } + + let mut subcommands_cases = format!( + r" + '{}' {{{} + break + }}", + &command_name, completions + ); + + for subcommand in p.get_subcommands() { + let subcommand_subcommands_cases = generate_inner(subcommand, &command_name); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} + +fn generate_aliases(completions: &mut String, preamble: &String, arg: &Arg) { + use std::fmt::Write as _; + + if let Some(aliases) = arg.get_short_and_visible_aliases() { + let tooltip = get_tooltip(arg.get_help(), aliases[0]); + for alias in aliases { + let _ = write!( + completions, + "{preamble}'-{alias}', '{alias}{}', [CompletionResultType]::ParameterName, '{tooltip}')", + // make PowerShell realize there is a difference between `-s` and `-S` + if alias.is_uppercase() { " " } else { "" }, + ); + } + } + if let Some(aliases) = arg.get_long_and_visible_aliases() { + let tooltip = get_tooltip(arg.get_help(), aliases[0]); + for alias in aliases { + let _ = write!( + completions, + "{preamble}'--{alias}', '{alias}', [CompletionResultType]::ParameterName, '{tooltip}')" + ); + } + } +} |