scoop-completion.psm1

# This source code is licensed under the MIT License
# Project URL - https://github.com/Moeologist/scoop-completion
# Thanks to Posh-Git - https://github.com/dahlbyk/posh-git

$scoopdir = $env:SCOOP, "$env:USERPROFILE\scoop" | Select-Object -first 1

if (!(Test-Path -Path "$scoopdir")) { Write-Warning 'no scoop installed' }

if ( -not (Get-Variable ScoopCompletionUseLocalData -Scope Global -ErrorAction SilentlyContinue)) {
    $global:ScoopCompletionUseLocalData = $true
}

$script:ScoopCommands = @('alias', 'bucket', 'cache', 'checkup', 'cleanup', 'config', 'create', 'depends', 'export', 'help', 'hold', 'home',
    'info', 'install', 'list', 'prefix', 'reset', 'search', 'status', 'unhold', 'uninstall', 'update', 'virustotal', 'which')

$script:ScoopSubcommands = @{
    alias  = 'add list rm'
    bucket = 'add list known rm'
    cache  = 'rm show'
    config  = 'rm'
}

$script:ScoopShortParams = @{
    install    = 'g i k s a'
    uninstall  = 'g p'
    cleanup    = 'g'
    virustotal = 'a s n'
    update     = 'f g i k s q'
}

$script:ScoopLongParams = @{
    install    = 'global independent no-cache skip arch'
    uninstall  = 'global purge'
    cleanup    = 'global'
    virustotal = 'arch scan no-depends'
    update     = 'force global independent no-cache skip quiet'
}

$script:ScoopParamValues = @{
    install    = @{
        a    = '32bit 64bit'
        arch = '32bit 64bit'
    }
    virustotal = @{
        a    = '32bit 64bit'
        arch = '32bit 64bit'
    }
}

$script:ScoopCommandsWithLongParams = $ScoopLongParams.Keys -join '|'
$script:ScoopCommandsWithShortParams = $ScoopShortParams.Keys -join '|'
$script:ScoopCommandsWithParamValues = $ScoopParamValues.Keys -join '|'

# 6> redirect Write-Host's output, (〒︿〒)
function script:ScoopAlias($fliter) {
    @(& scoop alias list 6>$null |
            Out-String -Stream |
            ForEach-Object { if ( $_ -match '^([\w]+)\s+.*$' ) { "$($Matches[1])" } } |
            Where-Object { $_ -like "$filter*" }
    )
}

function script:ScoopAliasCommand($alias) {
    @(& scoop alias list 6>$null |
            Out-String -Stream |
            ForEach-Object { if ( $_ -match "^$alias\s+(.*)$" ) { "$($Matches[1])" } }
    )
}

function script:ScoopExpandAlias($cmd, $others) {
    $alias = ScoopAliasCommand $cmd
    if ($alias) {
        return "scoop $alias $others"
    }
    else {
        return "scoop $cmd $others"
    }
}

function script:ScoopExpandCmdParams($commands, $command, $filter) {
    $commands.$command -split ' ' | Where-Object { $_ -like "$filter*" }
}

function script:ScoopExpandCmd($filter, $includeAliases) {
    $cmdList = @()
    $cmdList += $ScoopCommands -like "$filter*"
    if ($includeAliases) {
        $cmdList += ScoopAlias
    }
    $cmdList | Sort-Object
}

function script:ScoopLocalPackages($filter) {
    if ($global:ScoopCompletionUseLocalData) {
        @(& Get-ChildItem -Path $scoopdir\apps -Name -Directory |
                Where-Object { $_ -ne "scoop" } |
                Where-Object { $_ -like "$filter*" }
        )
    }
    else {
        @(& scoop list 6>&1 |
                ForEach-Object { if ( $_ -match '^\s*([\w][\-\.\w]*) $' ) { "$($Matches[1])" } } |
                Where-Object { $_ -like "$filter*" }
        )
    }
}

$script:ScoopRemoteCache = @()
function script:ScoopRemotePackages($filter) {
    if ($global:ScoopCompletionUseLocalData) {
        @(& Get-ChildItem -Path $scoopdir\apps\scoop\current\bucket\, $scoopdir\buckets\ -Name -Recurse -Filter *.json |
                ForEach-Object { if ( $_ -match '^.*?([\w][\-\.\w]*)\.json$' ) { "$($Matches[1])" } } |
                Where-Object { $_ -like "$filter*" }
        )
    }
    else {
        if ($script:ScoopRemoteCache.Count -eq 0) {
            $script:ScoopRemoteCache = @(& scoop search 6>&1 |
                    ForEach-Object { if ( $_ -match '^\s*([\w][\-\.\w]*) \(.+\)$' ) { "$($Matches[1])" } } |
                    Sort-Object -Unique
            )
        }
        @($script:ScoopRemoteCache |
                Where-Object { $_ -like "$filter*" }
        )
    }
}

function script:ScoopLocalCaches($filter) {
    @(& scoop cache show $filter |
            Out-String -Stream |
            ForEach-Object { if ( $_ -match '^\s*[\.1-9]+ [KMGB]+ ([\w][\-\.\w]*) .*$' ) { "$($Matches[1])" } } |
            Sort-Object -Unique |
            Where-Object { $_ -like "$filter*" }
    )
}

function script:ScoopLocalBuckets($filter) {
    @(& scoop bucket list | Where-Object { $_ -like "$filter*" })
}

function script:ScoopRemoteBuckets($filter) {
    @(& scoop bucket known | Where-Object { $_ -like "$filter*" })
}

function script:ScoopExpandLongParams($cmd, $filter) {
    $ScoopLongParams[$cmd] -split ' ' |
        Where-Object { $_ -like "$filter*" } |
        Sort-Object |
        ForEach-Object { -join ("--", $_) }
}

function script:ScoopExpandShortParams($cmd, $filter) {
    $ScoopShortParams[$cmd] -split ' ' |
        Where-Object { $_ -like "$filter*" } |
        Sort-Object |
        ForEach-Object { -join ("-", $_) }
}

function script:ScoopExpandParamValues($cmd, $param, $filter) {
    $ScoopParamValues[$cmd][$param] -split ' ' |
        Where-Object { $_ -like "$filter*" } |
        Sort-Object
}

function ScoopTabExpansion($lastBlock) {
    if ($lastBlock -match "^$(Get-AliasPattern scoop)\s+(?<cmd>\S+)\s+(?<others>.*)$") {
        $lastBlock = $(ScoopExpandAlias $matches['cmd'] $matches['others'])
    }

    switch -regex ($lastBlock -replace "^$(Get-AliasPattern scoop)\s+", "") {
        # Handles Scoop <cmd> --<param> <value>
        "^(?<cmd>$ScoopCommandsWithParamValues).* --(?<param>.+) (?<value>\w*)$" {
            if ($ScoopParamValues[$matches['cmd']][$matches['param']]) {
                return ScoopExpandParamValues $matches['cmd'] $matches['param'] $matches['value']
            }
        }

        # Handles Scoop <cmd> -<shortparam> <value>
        "^(?<cmd>$ScoopCommandsWithParamValues).* -(?<param>.+) (?<value>\w*)$" {
            if ($ScoopParamValues[$matches['cmd']][$matches['param']]) {
                return ScoopExpandParamValues $matches['cmd'] $matches['param'] $matches['value']
            }
        }

        # Handles uninstall package names
        "^(uninstall|cleanup|virustotal|update|prefix|reset|hold|unhold)\s+(?:.+\s+)?(?<package>[\w][\-\.\w]*)?$" {
            return ScoopLocalPackages $matches['package']
        }

        # Handles install package names
        "^(install|info|home|depends)\s+(?:.+\s+)?(?<package>[\w][\-\.\w]*)?$" {
            return ScoopRemotePackages $matches['package']
        }

        # Handles cache (rm/show) cache names
        "^cache (rm|show)\s+(?:.+\s+)?(?<cache>[\w][\-\.\w]*)?$" {
            return ScoopLocalCaches $matches['cache']
        }

        # Handles bucket rm bucket names
        "^bucket rm\s+(?:.+\s+)?(?<bucket>[\w][\-\.\w]*)?$" {
            return ScoopLocalBuckets $matches['bucket']
        }

        # Handles bucket add bucket names
        "^bucket add\s+(?:.+\s+)?(?<bucket>[\w][\-\.\w]*)?$" {
            return ScoopRemoteBuckets $matches['bucket']
        }

        # Handles bucket rm bucket names
        "^alias rm\s+(?:.+\s+)?(?<alias>[\w][\-\.\w]*)?$" {
            return ScoopAlias $matches['alias']
        }

        # Handles Scoop help <cmd>
        "^help (?<cmd>\S*)$" {
            return ScoopExpandCmd $matches['cmd'] $false
        }

        # Handles Scoop <cmd> <subcmd>
        "^(?<cmd>$($ScoopSubcommands.Keys -join '|'))\s+(?<op>\S*)$" {
            return ScoopExpandCmdParams $ScoopSubcommands $matches['cmd'] $matches['op']
        }

        # Handles Scoop <cmd>
        "^(?<cmd>\S*)$" {
            return ScoopExpandCmd $matches['cmd'] $true
        }

        # Handles Scoop <cmd> --<param>
        "^(?<cmd>$ScoopCommandsWithLongParams).* --(?<param>\S*)$" {
            return ScoopExpandLongParams $matches['cmd'] $matches['param']
        }

        # Handles Scoop <cmd> -<shortparam>
        "^(?<cmd>$ScoopCommandsWithShortParams).* -(?<shortparam>\S*)$" {
            return ScoopExpandShortParams $matches['cmd'] $matches['shortparam']
        }
    }
}

function Get-AliasPattern($exe) {
    $aliases = @($exe, "$exe\.ps1", "$exe\.cmd") + @(Get-Alias | Where-Object { $_.Definition -eq $exe } | Select-Object -Exp Name)
    "($($aliases -join '|'))"
}

if (Test-Path Function:\TabExpansion) {
    Rename-Item Function:\TabExpansion TabExpansionBackup_Scoop
}

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

    switch -regex ($lastBlock) {
        # Execute Scoop tab completion for all Scoop-related commands
        "^$(Get-AliasPattern scoop) (.*)" {
            ScoopTabExpansion $lastBlock
        }

        # Fall back on existing tab expansion
        default {
            if (Test-Path Function:\TabExpansionBackup_Scoop) {
                TabExpansionBackup_Scoop $line $lastWord
            }
        }
    }
}

Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete

$exportModuleMemberParams = @{
    Function = @(
        'TabExpansion'
    )
}

Export-ModuleMember @exportModuleMemberParams