Common/CommandLineContext.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.IO;

class CommandLineContext {
    [string[]] $Words
    [DirectoryInfo] $gitDir
    [string] $Command
    [int] $CommandIndex
    [string[]] $gitCArgs

    [int] $CurrentIndex = -1
    [int] $SubcommandLikeIndex = -1
    [int] $DoubledashIndex = -1

    CommandLineContext (
        [string[]] $Words,
        [int] $CurrentIndex
    ) {
        $this.CurrentIndex = $CurrentIndex

        $this.gitDir = $null
        $this.gitCArgs = @()
        $this.Command = ''
        $this.InitWords($Words, 1)
    }

    hidden InitWords([string[]]$wds, [int]$start) {
        $this.Words = $wds
        :globalflag for ($i = $start; $i -lt $wds.Length; $i++) {
            if ($i -eq $this.CurrentIndex) { continue }
            $s = $wds[$i]
            switch -Wildcard -CaseSensitive ($s) {
                '--' {
                    $this.DoubledashIndex = $i
                    break globalflag
                }
                '--git-dir=*' {
                    $this.gitDir = [DirectoryInfo]::new($s.Substring('--git-dir='.Length))
                    continue
                }
                '--git-dir' {
                    ++$i
                    if (($i -lt $this.Words.Count) -and ($i -ne $this.CurrentIndex)) {
                        $this.gitDir = [DirectoryInfo]::new($this.Words[$i])
                    }
                    continue
                }
                '--bare' {
                    $this.gitDir = [DirectoryInfo]::new('.')
                    continue
                }
                '--help' {
                    if ($i -lt $this.CurrentIndex) {
                        $this.Command = 'help'
                        $this.CommandIndex = $i
                    }
                    break globalflag
                }
                { $_ -cin @('-c', '--work-tree', '--namespace') } {
                    ++$i
                    continue
                }
                '-C' {
                    $this.gitCArgs += @('-C', $this.Words[++$i])
                    continue
                }
                '-*' {
                    continue
                }
                default {
                    if ($i -lt $this.CurrentIndex) {
                        $this.Command = $s
                        $this.CommandIndex = $i
                    }
                    break globalflag
                }
            }
        }

        if ($this.DoubledashIndex -ge 0) { return }
        if ($this.CommandIndex -lt 0) { return }

        :subcommand for ($i++; $i -lt $this.CurrentIndex; $i++) {
            $s = $wds[$i]
            switch -Wildcard -CaseSensitive ($s) {
                '--' {
                    $this.DoubledashIndex = $i
                    break subcommand
                }
                '-*' { continue }
                default {
                    $this.SubcommandLikeIndex = $i
                    break subcommand
                }
            }
        }

        if ($this.DoubledashIndex -ge 0) { return }
        for ($i++; $i -lt $wds.Length; $i++) {
            if ($i -eq $this.CurrentIndex) { continue }
            if ($wds[$i] -eq '--') {
                $this.DoubledashIndex = $i
                break
            }
        }

        if ($this.DoubledashIndex -ge 0) { return }
        $this.DoubledashIndex = $wds.Length
    }

    [string] Subcommand() {
        if ($this.SubcommandLikeIndex -lt 0) { 
            return $null
        }
        return $this.Words[$this.SubcommandLikeIndex]
    }

    [string] SubcommandWithoutGlobalOption() {
        if ($this.SubcommandLikeIndex -ne $this.CommandIndex + 1) { 
            return $null
        }
        return $this.Words[$this.SubcommandLikeIndex]
    }

    [string] CurrentWord() { return $this.Words[$this.CurrentIndex] }
    [string] PreviousWord() { return $this.Words[$this.CurrentIndex - 1] }

    # __git_has_doubledash
    [bool] HasDoubledash() {
        return $this.DoubledashIndex -lt $this.CurrentIndex
    }

    [bool] ReplaceCommand([string[]]$NewCommand) {
        if ($this.CommandIndex -le 0) { return $false }
        $additionalSize = $NewCommand.Length - 1

        $wds = New-Object string[] ($this.Words.Length + $additionalSize)
        [array]::Copy(
            $this.Words,
            0,
            $wds,
            0,
            $this.CommandIndex) | Out-Null
        [array]::Copy($NewCommand,
            0,
            $wds,
            $this.CommandIndex,
            $NewCommand.Length) | Out-Null
        [array]::Copy(
            $this.Words,
            $this.CommandIndex + 1,
            $wds,
            $this.CommandIndex + $NewCommand.Length,
            $this.Words.Length - $this.CommandIndex - 1) | Out-Null

        $this.CurrentIndex += $additionalSize
        $this.SubcommandLikeIndex = -1
        $this.DoubledashIndex = -1
        $this.InitWords($wds, $this.CommandIndex)
        return $true
    }
}