Complete/SubCommand/Config.ps1

# Copyright (C) 2024 kzrnm
# Based on git-completion.bash (https://github.com/git/git/blob/HEAD/contrib/completion/git-completion.bash).
# Distributed under the GNU General Public License, version 2.0.
using namespace System.Management.Automation;

function Complete-GitSubCommand-config {
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([CompletionResult[]])]
    param(
        [CommandLineContext]
        [Parameter(Position = 0, Mandatory)]$Context
    )

    $gitVersion = gitVersion
    if ($gitVersion -lt [version]::new(2, 46)) {
        Complete-GitSubCommand-config-Git2_45 $Context
        return
    }

    [string] $Prev = $Context.PreviousWord()
    [string] $Current = $Context.CurrentWord()

    $subcommands = gitResolveBuiltins $Context.Command
    [string] $subcommand = $Context.SubcommandWithoutGlobalOption()
    if ($subcommand -notin $subcommands) {
        if ($Context.HasDoubledash()) { return }
        if ($Current -eq '-') {
            $script:__helpCompletion
            return
        }
        else {
            $subcommands | gitcomp -Current $Current -DescriptionBuilder { 
                switch ($_) {
                    "list" { 'List all variables set in config file' }
                    "get" { 'Emits the value of the specified key' }
                    "set" { 'Set value for one or more config options' }
                    "unset" { 'Unset value for one or more config options' }
                    "rename-section" { 'Rename the given section to a new name' }
                    "remove-section" { 'Remove the given section from the configuration file' }
                    "edit" { 'Opens an editor to modify the specified config file' }
                }
            }
            return
        }
    }
    
    $shortOpts = Get-GitShortOptions $Context.Command -Subcommand $subcommand -Current $Current
    if ($shortOpts) { return $shortOpts }

    if ($Current.StartsWith('--')) {
        gitCompleteResolveBuiltins $Context.Command $subcommand -Current $Current
        return
    }

    if ($Prev.StartsWith('--')) {
        if ((gitResolveBuiltins $Context.Command $subcommand) -eq "$Prev=") {
            return @()
        }
    }

    switch ($subcommand) {
        'get' { completeGitConfigGetSetVariables $Context }
        'unset' { completeGitConfigGetSetVariables $Context }
        'set' {
            if ($Prev -like '*.*') {
                completeConfigVariableValue -VarName $Prev -Current $Current
            }
            else {
                completeConfigVariableName -Current $Current
            }
        }
    }

    return
}
function Complete-GitSubCommand-config-Git2_45 {
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([CompletionResult[]])]
    param(
        [CommandLineContext]
        [Parameter(Position = 0, Mandatory)]$Context
    )
    $Prev = $Context.PreviousWord()
    $Current = $Context.CurrentWord()
    if (!$Context.HasDoubledash()) {
        $shortOpts = Get-GitShortOptions $Context.Command -Subcommand $subcommand -Current $Current
        if ($shortOpts) { return $shortOpts }
        if ($Prev -in ('--get', '--get-all', '--unset', '--unset-all')) {
            completeGitConfigGetSetVariables $Context
            return
        }
        if ($Current.StartsWith('--')) {
            gitCompleteResolveBuiltins $Context.Command -Current $Current
            return
        }
    }
    if ($Prev -like '*.*') {
        completeConfigVariableValue -Current $Current -VarName $Prev
    }
    else {
        completeConfigVariableName -Current $Current
    }
}

function completeGitConfigGetSetVariables {
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([string[]])]
    param(
        [Parameter(Position = 0, Mandatory)][CommandLineContext]$Context
    )

    gitConfigGetSetVariables $Context | completeList -Current $Context.CurrentWord() -ResultType ParameterValue -DescriptionBuilder {
        Get-GitConfigVariableDescription $_
    }
}

# __git_config_get_set_variables
function gitConfigGetSetVariables {
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([string[]])]
    param(
        [Parameter(Position = 0, Mandatory)][CommandLineContext]$Context
    )
    $file = @()
    $prev = ''
    for ($i = $Context.DoubledashIndex - 1; $i -gt $Context.CommandIndex; $i--) {
        $word = $Context.Words[$i]
        if (($word -in @('--system', '--global', '--local')) -or ($word -like "--file=*")) {
            $file = @($word)
            break
        }
        elseif ($word -cmatch '^-([^-]*f|-file)$') {
            $file = @('--file', $prev)
        }
        $prev = $word
    }
    __git config @file --name-only --list
}


# __git_complete_config_variable_name_and_value
function completeConfigOptionVariableNameAndValue {
    [OutputType([CompletionResult[]])]
    param(
        [Parameter(Mandatory)][AllowEmptyString()][string] $Current,
        [string] $Prefix = ''
    )

    if ($Current -match '(.*)=(.*)') {
        $VarName = $Matches[1]
        return (completeConfigVariableValue -VarName $VarName -Prefix "$Prefix$VarName=" -Current $Matches[2])
    }
    else {
        return (completeConfigVariableName -Prefix $Prefix -Suffix "=" -Current $Current)
    }
}


# __git_complete_config_variable_value
function completeConfigVariableValue {
    [OutputType([CompletionResult[]])]
    param(
        [Parameter(Mandatory)][AllowEmptyString()][string] $Current,
        [Parameter(Mandatory)][AllowEmptyString()][string] $VarName,
        [string] $Prefix = ''
    )

    function remote {
        $remotes = [string[]](__git remote)
        $remotes | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
    }

    switch -Wildcard ($VarName.ToLowerInvariant()) {
        "branch.*.remote" {
            remote
            return
        }
        "branch.*.pushremote" {
            remote
            return
        }
        "branch.*.pushdefault" {
            remote
            return
        }
        "branch.*.merge" {
            gitCompleteRefs -Current $Current -Prefix $Prefix
            return
        }
        "branch.*.rebase" {
            "false", "true", "merges", "interactive" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "remote.pushdefault" {
            remote
            return
        }
        "remote.*.fetch" {
            if ($Current -eq "") {
                $result = "refs/heads"
                return [CompletionResult]::new(
                    "${Prefix}$result",
                    $result,
                    'ParameterValue',
                    $result
                )
            }
            try {
                $remote = ($VarName -replace "^remote\." -replace "\.fetch$")
                (__git ls-remote $remote 'refs/heads/*') -match "(\S+)\s+(\S+)" | Out-Null
                # $hash = $Matches[1]
                $ref = $Matches[2]
                $result = "${ref}:refs/remotes/$remote/$($ref -replace '^refs/heads/')"
                return [CompletionResult]::new(
                    "${Prefix}$result",
                    $result,
                    'ParameterValue',
                    $result
                )
            }
            catch {
                # not found
            }
            return
        }
        "remote.*.push" {
            __git for-each-ref --format='%(refname):%(refname)' refs/heads | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "pull.twohead" {
            gitListMergeStrategies | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "pull.octopus" {
            gitListMergeStrategies | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "color.pager" {
            "false", "true" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "color.*.*" {
            "normal", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "bold", "dim", "ul", "blink", "reverse" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "color.*" {
            "false", "true", "always", "never", "auto" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "diff.submodule" {
            $script:gitDiffSubmoduleFormats | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "diff.algorithm" {
            return (
                [CompletionResult]::new(
                    "${Prefix}default",
                    'default',
                    'ParameterValue',
                    'The basic greedy diff algorithm'
                ),
                [CompletionResult]::new(
                    "${Prefix}myers",
                    'myers',
                    'ParameterValue',
                    'The basic greedy diff algorithm. Currently, this is the default'
                ),
                [CompletionResult]::new(
                    "${Prefix}minimal",
                    'minimal',
                    'ParameterValue',
                    'Spend extra time to make sure the smallest possible diff is produced'
                ),
                [CompletionResult]::new(
                    "${Prefix}patience",
                    'patience',
                    'ParameterValue',
                    'Use "patience diff" algorithm when generating patches'
                ),
                [CompletionResult]::new(
                    "${Prefix}histogram",
                    'histogram',
                    'ParameterValue',
                    'This algorithm extends the patience algorithm to "support low-occurrence common elements"'
                ) | filterCompletionResult $Current
            )
        }
        "http.proxyAuthMethod" {
            return (
                [CompletionResult]::new(
                    "${Prefix}anyauth",
                    'anyauth',
                    'ParameterValue',
                    'Automatically pick a suitable authentication method'
                ),
                [CompletionResult]::new(
                    "${Prefix}basic",
                    'basic',
                    'ParameterValue',
                    'HTTP Basic authentication'
                ),
                [CompletionResult]::new(
                    "${Prefix}digest",
                    'digest',
                    'ParameterValue',
                    'HTTP Digest authentication; this prevents the password from being transmitted to the proxy in clear text'
                ),
                [CompletionResult]::new(
                    "${Prefix}negotiate",
                    'negotiate',
                    'ParameterValue',
                    ' GSS-Negotiate authentication (compare the --negotiate option of curl)'
                ),
                [CompletionResult]::new(
                    "${Prefix}ntlm",
                    'ntlm',
                    'ParameterValue',
                    'NTLM authentication (compare the --ntlm option of curl)'
                ) | filterCompletionResult $Current
            )
        }
        "help.format" {
            "man", "info", "web", "html" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "log.date" {
            $script:gitLogDateFormats | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "sendemail.aliasfiletype" {
            "mutt", "mailrc", "pine", "elm", "gnus" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "sendemail.confirm" {
            $script:gitSendEmailConfirmOptions | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "sendemail.suppresscc" {
            $script:gitSendEmailSuppressccOptions | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
        "sendemail.transferencoding" {
            "7bit", "8bit", "quoted-printable", "base64" | completeList -Current $Current -Prefix $Prefix -ResultType ParameterValue
            return
        }
    }
}

# __git_complete_config_variable_name
function completeConfigVariableName {
    [OutputType([CompletionResult[]])]
    param(
        [Parameter(Mandatory)][AllowEmptyString()][string] $Current,
        [string] $Prefix = '',
        [string] $Suffix = ''
    )

    $DescriptionBuilder = [scriptblock] {
        Get-GitConfigVariableDescription $_
    }

    if ($Current -match "(branch|guitool|difftool|man|mergetool|remote|submodule|url)\.(.*)\.([^\.]*)") {
        $section = $Matches[1]
        $second = $Matches[2]
        gitSecondLevelConfigVarsForSection $section | ForEach-Object {
            "$section.$second.$_"
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    if ($Current -match "branch\.(.*)") {
        $section = 'branch'
        $second = $Matches[1]
        gitHeads -Current $second | ForEach-Object { "$section.$_." } | completeList -DescriptionBuilder $DescriptionBuilder
        gitFirstLevelConfigVarsForSection $section | ForEach-Object {
            "$section.$_"
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    if ($Current -match "pager\.(.*)") {
        $section = 'pager'
        $second = $Matches[1]
        gitAllCommands main others alias nohelpers | ForEach-Object {
            "$section.$_"
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    if ($Current -match "remote\.(.*)") {
        $section = 'remote'
        $second = $Matches[1]
        
        __git remote | Where-Object {
            $_.StartsWith($second) 
        } | ForEach-Object {
            "$section.$_."
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder

        gitFirstLevelConfigVarsForSection $section | ForEach-Object {
            "$section.$_"
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    if ($Current -match "submodule\.(.*)") {
        $section = 'submodule'
        $second = $Matches[1]
        $gitTopPath = (__git rev-parse --show-toplevel)

        __git config -f "$gitTopPath/.gitmodules" --name-only --list 2>$null |
        ForEach-Object {
            if ($_ -match 'submodule\.(.*)\.path') {
                $sub = $Matches[1]
                "$section.$sub."
            }
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder

        gitFirstLevelConfigVarsForSection $section | ForEach-Object {
            "$section.$_"
        } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    if ($Current -match "([^\.]*)\.(.*)") {
        $section = $Matches[1]
        gitConfigVars | Where-Object { !$_.EndsWith('.') } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder -Suffix $Suffix
        return
    }
    else {
        gitConfigSections | ForEach-Object { "$_." } | completeList -Current $Current -Prefix $Prefix -DescriptionBuilder $DescriptionBuilder
    }
    return
}