vsts-cli-tabcompletion.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] # needed to override tab completion
Param()

$global:VstsCompletion = @{}

$script:flagRegex = "^ (-[^, =]+),? ?(--[^= ]+)?"

function script:Get-Build
{
    vsts build list | ConvertFrom-VstsCli
}

function script:Get-AutoCompleteResult
{
    param([Parameter(ValueFromPipeline = $true)] $value)
    
    Process
    {
        New-Object System.Management.Automation.CompletionResult $value
    }
}

filter script:MatchingCommand($commandName)
{
    if ($_.StartsWith($commandName))
    {
        $_
    }
}

$completion_Vsts = {
    param($commandName, $commandAst, $cursorPosition)

    if ($global:VstsCompletion.Count -eq 0)
    {
        $global:VstsCompletion["commands"] = @{}
        $global:VstsCompletion["options"] = @()
        
        vsts --help | ForEach-Object {
            Write-Output $_
            if ($_ -match "^\s{4,5}(\w+)\s+(.+)") # 4 spaces in help before commands
            {
                $global:VstsCompletion["commands"][$Matches[1]] = @{}
                
                $currentCommand = $global:VstsCompletion["commands"][$Matches[1]]
                $currentCommand["options"] = @()
            }
            elseif ($_ -match $flagRegex)
            {
                $global:VstsCompletion["options"] += $Matches[1]
                if ($Matches[2] -ne $null)
                {
                    $global:VstsCompletion["options"] += $Matches[2]
                }
            }
        }
    }

    $command = $null
    $commandParameters = @{}
    $state = "Unknown"
    $wordToComplete = $commandAst.CommandElements | Where-Object { $_.ToString() -eq $commandName } | Foreach-Object { $commandAst.CommandElements.IndexOf($_) }

    for ($i = 1; $i -lt $commandAst.CommandElements.Count; $i++)
    {
        $p = $commandAst.CommandElements[$i].ToString()

        if ($p.StartsWith("--"))
        {
            if ($commandAst.CommandElements.Count -eq 2 -or $commandAst.CommandElements.Count -eq 3)
            {
                @('--help') | Get-AutoCompleteResult
            }
            elseif($commandAst.CommandElements.Count -eq 4)
            {
                $script:optionCompletion = @()
                    vsts $commandAst.CommandElements[1].ToString() $commandAst.CommandElements[2].ToString() --help | ForEach-Object {
                        if ($_ -match "^\s{4,6}--(\w+)\s+(.+)") # 4 spaces and -- in help before option
                        {
                            $script:optionCompletion += "--$($Matches[1])"
                        }
                    }
                $script:optionCompletion | Get-AutoCompleteResult
            }

            if ($state -eq "Unknown" -or $state -eq "Options")
            {
                $commandParameters[$i] = "Option"
                $state = "Options"
            }
            else
            {
                $commandParameters[$i] = "CommandOption"
                $state = "CommandOptions"
            }
        } 
        else 
        {
            if ($state -ne "CommandOptions")
            {
                $commandParameters[$i] = "Command"
                $command = $p
                $state = "CommandOptions"
            } 
            else 
            {
                $commandParameters[$i] = "CommandOther"
            }
        }
    }
    
    if ($wordToComplete -eq $null)
    {
        $commandToComplete = "Command"
        if ($commandParameters.Count -gt 0)
        {
            if ($commandParameters[$commandParameters.Count] -eq "Command")
            {
                $commandToComplete = "CommandOther"
            }
        } 
    }
    else
    {
        $commandToComplete = $commandParameters[$wordToComplete]
    }

    switch ($commandToComplete)
    {
        "Command" { $global:VstsCompletion["commands"].Keys | MatchingCommand -Command $commandName | Sort-Object | Get-AutoCompleteResult }
        "Option" { $global:VstsCompletion["options"] | MatchingCommand -Command $commandName | Sort-Object | Get-AutoCompleteResult }
        "CommandOption"
        { 
            $options = $global:VstsCompletion["commands"][$command]["options"]
            if ($options.Count -eq 0)
            {
                vsts $command --help | ForEach-Object {
                    if ($_ -match $flagRegex)
                    {
                        $options += $Matches[1]
                        if ($Matches[2] -ne $null)
                        {
                            $options += $Matches[2]
                        }
                    }
                }
            }

            $global:VstsCompletion["commands"][$command]["options"] = $options
            $options | MatchingCommand -Command $commandName | Sort-Object | Get-AutoCompleteResult
        }
        "CommandOther"
        {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] 
            $filter = $null
            switch ($command)
            {
                "build" { @('list', 'queue', 'show')  | Get-AutoCompleteResult }
                "code" { @('pr', 'repo')             | Get-AutoCompleteResult }
                "project" { @('create', 'list', 'show') | Get-AutoCompleteResult }
                "work" { @('item')                   | Get-AutoCompleteResult }
            }
            
        }
        default { $global:VstsCompletion["commands"].Keys | MatchingCommand -Command $commandName }
    }
}


if (Get-Command Register-ArgumentCompleter -ea Ignore)
{
    Register-ArgumentCompleter -CommandName 'vsts' -ScriptBlock $completion_Vsts -Native
    Register-ArgumentCompleter -CommandName 'iv' -ScriptBlock $completion_Vsts -Native
    Register-ArgumentCompleter -CommandName 'Invoke-VstsCli' -ScriptBlock $completion_Vsts -Native
}
else
{
    # in version 3 and 4 of PS, one needs to install TabExpansionPlusPlus for backcompat. No check for the psversion needed since the manifest does that already.
    throw "Required module TabExpansionPlusPlus is not installed. Please install it using 'Install-Module TabExpansionPlusPlus'"    
}