posh-HumpCompletion.ps1

function DebugMessage($message) {
    $threadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId
    $appDomainId = [AppDomain]::CurrentDomain.Id
    [System.Diagnostics.Debug]::WriteLine("PoshHump: $threadId : $appDomainId :$message")

    [System.Diagnostics.Debug]::WriteLine("PoshHump: $message")
}

function GetCommandWithVerbAndHumpSuffix($commandName) {
    $separatorIndex = $commandName.IndexOf('-')
    if ($separatorIndex -ge 0){
        $verb = $commandName.SubString(0, $separatorIndex)
        $suffix = $commandName.SubString($separatorIndex+1)
        return [PSCustomObject] @{
            "Verb" = $verb
            "SuffixHumpForm" = $suffix -creplace "[a-z]","" # case sensitive replace
            "Command" = $commandName 
        }   
    }    
}
function GetCommandsWithVerbAndHumpSuffix() {
    # TODO - add caching
    $commandsGroupedByVerb = Get-Command `
        | ForEach-Object { GetCommandWithVerbAndHumpSuffix $_.Name} `
        | Group-Object Verb
    $commands = @{}
    $commandsGroupedByVerb | ForEach-Object { $commands[$_.Name] = $_.Group | group-object SuffixHumpForm }
    return $commands
}
function GetWildcardSuffixForm($suffix){
    # create a wildcard form of a suffix. E.g. for "AzRGr" return "Az*R*Gr*"
    if ($suffix -eq $null -or $suffix.Length -eq 0){
        return "*"
    }
    $result = $suffix[0]
    for($i=1 ; $i -lt $suffix.Length ; $i++){
        if ([char]::IsUpper($suffix[$i])) {
            $result += "*"
        }
        $result += $suffix[$i]
    }
    $result += "*"
    return $result
}
$Powershell = $null
$Runspace = $null
function EnsureHumpCompletionCommandCache(){
    if ($global:HumpCompletionCommandCache -eq $null) {
        if ($script:runspace -eq $null) {
            DebugMessage -message "loading command cache"
            $global:HumpCompletionCommandCache = GetCommandsWithVerbAndHumpSuffix
        } else {
            DebugMessage -message "loading command cache - wait on async load"
            $foo = $script:Runspace.AsyncWaitHandle.WaitOne()
            $global:HumpCompletionCommandCache = $script:powershell.EndInvoke($script:iar).result
            $script:Powershell.Dispose()
            $script:Runspace.Close()
            $script:Runspace = $null            
            DebugMessage -message "loading command cache - async load commplete $($global:HumpCompletionCommandCache.Count)"
        }
    }
}
function LoadHumpCompletionCommandCacheAsync(){
    DebugMessage -message "LoadHumpCompletionCommandCacheAsync"
    if ($script:Runspace -eq $null) {
        DebugMessage -message "LoadHumpCompletionCommandCacheAsync - starting..."
        $script:Runspace = [RunspaceFactory]::CreateRunspace()
        $script:Runspace.Open()
        # Set variable to prevent installation of the TabExpansion function in the created runspace
        # Otherwise we end up recursively spinning up runspaces to load the commands!
        $script:Runspace.SessionStateProxy.SetVariable('poshhumpSkipTabCompletionInstall',$true)

        $script:Powershell = [PowerShell]::Create()
        $script:Powershell.Runspace = $script:Runspace

        $scriptBlock = {
            $result = GetCommandsWithVerbAndHumpSuffix
            @{ "result" = $result} # work around group enumeration as it loses the grouping!
        }
        $script:Powershell.AddScript($scriptBlock) | out-null
        
        $script:iar = $script:PowerShell.BeginInvoke()
    }
}
function PoshHumpTabExpansion($line) {

    if($line -match "^(?<verb>\S+)-(?<suffix>[A-Z]*)$") {
        EnsureHumpCompletionCommandCache
        
        $command = $matches[0]
        $commandInfo = GetCommandWithVerbAndHumpSuffix $command
        $verb = $matches['verb']
        $suffix= $matches['suffix']
        $suffixWildcardForm = GetWildcardSuffixForm $suffix 
        $wildcardForm = "$verb-$suffixWildcardForm"
        $commands = $global:HumpCompletionCommandCache
        if ($commands[$verb] -ne $null) {
            return $commands[$verb] `
                | Where-Object { 
                    # $_.Name is suffix hump form
                    # Match on hump form of completion word
                    $_.Name.StartsWith($commandInfo.SuffixHumpForm)
                } `
                | Select-Object -ExpandProperty Group `
                | Select-Object -ExpandProperty Command `
                | Where-Object { $_ -like $wildcardForm } `
                | Sort-Object
        }
    }
}

function Clear-HumpCompletionCommandCache() {
    [Cmdletbinding()]
    param()

    DebugMessage -message "PoshHumpTabExpansion:clearing command cache"
    $global:HumpCompletionCommandCache = $null
}
function Stop-HumpCompletion(){
    [Cmdletbinding()]
    param()

    $global:HumpCompletionEnabled = $false
}
function Start-HumpCompletion(){
    [Cmdletbinding()]
    param()
    
    $global:HumpCompletionEnabled = $true
}

# install the handler!
DebugMessage -message "Installing: Test PoshHumpTabExpansionBackup function"
if ($poshhumpSkipTabCompletionInstall){
    DebugMessage -message "Skipping tab expansion installation"
} else {
    if (-not (Test-Path Function:\PoshHumpTabExpansionBackup)) {

        if (Test-Path Function:\TabExpansion) {
            DebugMessage -message "Installing: Backup TabExpansion function"
            Rename-Item Function:\TabExpansion PoshHumpTabExpansionBackup
        }

        function TabExpansion($line="", $lastWord="") {
            $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()

            if ($global:HumpCompletionEnabled) {
                DebugMessage -message "PoshHump:input: $lastBlock"
                $result = PoshHumpTabExpansion $lastBlock
            }

            if ($result -ne $null) {
                $result
            } else {
                # Fall back on existing tab expansion
                if (Test-Path Function:\PoshHumpTabExpansionBackup) { 
                    PoshHumpTabExpansionBackup $line $lastWord 
                }
            }
        }
        LoadHumpCompletionCommandCacheAsync
    }
}
$global:HumpCompletionEnabled = $true