Search code examples
powershellpowershell-cmdletchocolatey

Add Custom Argument Completer for Cmdlet?


How do I add dynamic argument tab completion to a PowerShell Cmdlet?

When I type this and hit tab, I'd like for it to do tab completion.

PM> Paket-Add -NuGet FSharp.Co

These are the values I'd like to use in this example:

PM> Paket-FindPackages -SearchText FSharp.Co
FSharp.Core
FSharp.Core.3
FSharp.Configuration
FSharp.Core.Fluent-3.1
FSharp.Core.Fluent-4.0
FSharp.Compiler.Tools
FSharp.Compatibility.Scala
FSharp.Compatibility.OCaml
FSharp.Compiler.CodeDom
FSharp.Compiler.Service
FSharp.Control.Reactive
FSharp.Compatibility.Haskell
FSharp.Compatibility.OCaml.Numerics
FSharp.Compatibility.OCaml.Format
FSharp.Compatibility.OCaml.System
FSharp.Collections.ParallelSeq
FSharp.Compatibility.StandardML
FSharp.Compatibility.OCaml.LexYacc
FSharp.Control.AsyncSeq

I found this answer that gave a couple of helpful links and said I should run Get-Content function:TabExpansion2:

enter image description here

It looks like CommandCompletion.CompleteInput needs to implemented. I thought I read somewhere that there is a Hashtable of commands to functions. If so, where is it and how do I install custom ones? I'm using Chocolatey to distribute Paket.PowerShell. Here is the Cmdlet code.

UPDATE 2015-06-20:

I ended up getting it to work with the code here: https://github.com/fsprojects/Paket/blob/76de1c44853ce09029ba157855525f435d951b85/src/Paket.PowerShell/ArgumentTabCompletion.ps1

# https://github.com/mariuszwojcik/RabbitMQTools/blob/master/TabExpansions.ps1
function createCompletionResult([string]$text, [string]$value, [string]$tooltip) {
    if ([string]::IsNullOrEmpty($value)) { return }
    if ([string]::IsNullOrEmpty($text)) { $text = $value }
    if ([string]::IsNullOrEmpty($tooltip)) { $tooltip = $value }
    $completionText = @{$true="'$value'"; $false=$value  }[$value -match "\W"]
    $completionText = $completionText -replace '\[', '``[' -replace '\]', '``]'
    New-Object System.Management.Automation.CompletionResult $completionText, $text, 'ParameterValue', $tooltip | write
}

$findPackages = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
    Paket-FindPackages -SearchText $wordToComplete -Max 100 | % {
        createCompletionResult $_ $_ $_ | write
    }
}

$findPackageVersions = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
    if (-not $fakeBoundParameter.NuGet){ return }
    Paket-FindPackageVersions -Name $fakeBoundParameter.NuGet -Max 100 | % {
        createCompletionResult $_ $_ $_ | write
    }
}

# create and add $global:options to the list of completers
# http://www.powertheshell.com/dynamicargumentcompletion/
if (-not $global:options) { $global:options = @{CustomArgumentCompleters = @{};NativeArgumentCompleters = @{}}}

$global:options['CustomArgumentCompleters']['Paket-Add:NuGet'] = $findPackages
$global:options['CustomArgumentCompleters']['Paket-Add:Version'] = $findPackageVersions

$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}'

The completer param names are important. Renaming them will make it not work.


Solution

  • You may want to look at the TabExpansion++ module, which was designed to make extending tab completion easier.

    I just played with it for few minutes, and I think you want something like this based on the example:

    Import-Module TabExpansion++
    
    function PaketAddNugetCompletion
    {
        [ArgumentCompleter(Parameter = 'Nuget', Command = 'Paket-Add')]
        param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
    
        Paket-FindPackages -SearchText $wordToComplete |
            ForEach-Object {
                # not quite sure what property to use off the result, but this might work.
                New-CompletionResult -CompletionText $_ 
            }   
    }