PSReadLineVIExtension.psm1

# {{{ Handler
Set-PSReadLineKeyHandler -Chord "c,i" -ViMode Command `
    -ScriptBlock { VIChangeInnerBlock }
Set-PSReadLineKeyHandler -Chord "c,a" -ViMode Command `
    -ScriptBlock { VIChangeOuterBlock }
Set-PSReadLineKeyHandler -Chord "d,i" -ViMode Command `
    -ScriptBlock { VIDeleteInnerBlock }
Set-PSReadLineKeyHandler -Chord "d,a" -ViMode Command `
    -ScriptBlock { VIDeleteOuterBlock }
Set-PSReadLineKeyHandler -Chord "c,s" -ViMode Command `
    -ScriptBlock { VIChangeSurround }
Set-PSReadLineKeyHandler -Chord "d,s" -ViMode Command `
    -ScriptBlock { VIDeleteSurround }
Set-PSReadLineKeyHandler -Chord "Ctrl+a" -ViMode Command `
    -ScriptBlock { VIIncrement $args[0] $args[1] }
Set-PSReadLineKeyHandler -Chord "Ctrl+x" -ViMode Command `
    -ScriptBlock { VIDecrement $args[0] $args[1] }
Set-PSReadLineKeyHandler -Chord "+,y" -ViMode Command `
    -ScriptBlock { VIGlobalYank }
Set-PSReadLineKeyHandler -Chord "+,p" -ViMode Command `
    -ScriptBlock { VIGlobalPaste }
Set-PSReadLineKeyHandler -Chord "+,P" -ViMode Command `
    -ScriptBlock { VIGlobalPasteBefore}
Set-PSReadLineKeyHandler -Chord "g,e" -viMode Command `
    -ScriptBlock { ViBackwardEndOfWord }
Set-PSReadLineKeyHandler -Chord "g,E" -viMode Command `
    -ScriptBlock { VIBackwardEndOfGlob }
Set-PsReadLineKeyHandler -Chord "g,M" -viMode Command `
    -ScriptBlock { VIMiddleOfLine }
Set-PsReadLineKeyHandler -Chord "g,f" -viMode Command `
    -ScriptBlock {VIOpenFileUnderCursor }
Set-PsReadLineKeyHandler -Chord "g,m" -viMode Command `
    -ScriptBlock { VIMiddleOfScreen }
Set-PsReadlineKeyHandler -Chord ':,w' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::ValidateAndAcceptLine()
    }
Set-PsReadlineKeyHandler -Chord ':,x' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::ValidateAndAcceptLine()
    }
Set-PsReadlineKeyHandler -Chord ':,q' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::CancelLine()
    }
if($VIExperimental -eq $true){
    Write-Host "Using Experimental VISettings"
    Set-PSReadLineKeyHandler -Chord "g,U" -viMode Command `
    -ScriptBlock { VICapitalize }
    Set-PSReadLineKeyHandler -Chord "g,u" -viMode Command `
    -ScriptBlock { VILowerize }
    Set-PsReadLineKeyHandler -Chord 'Alt+p' -viMode Command `
    -ScriptBlock { CSHLoadPreviousFromHistory }
    Set-PsReadLineKeyHandler -Chord 'Alt+n' -viMode Command `
    -ScriptBlock { CSHLoadNextFromHistory }
    Set-PsReadLineKeyHandler -Chord 'Alt+p' -viMode Insert `
    -ScriptBlock { CSHLoadPreviousFromHistory }
    Set-PsReadLineKeyHandler -Chord 'Alt+n' -viMode Insert `
    -ScriptBlock { CSHLoadNextFromHistory }
    Set-PsReadLineKeyHandler -Chord "Ctrl+)" -viMode Command `
    -ScriptBlock { VIGetHelp }
    Set-PsReadLineKeyHandler -Chord "Ctrl+)" -viMode Insert `
    -ScriptBlock { VIGetHelp }
}
#}}}
$LocalShell = New-Object -ComObject wscript.shell
$Digits = (0..9)
$Separator = "$[({})]-._ '```":\/"
$CmdLEtSeparator =  "$[({})]._ '```":\/"
$script:HistoryLine = -1
$HistorySeparator ="`r`n"
$HistoryFile = (Get-PSReadLineOption).HistorySavePath
######################################################################
# Section Function #
######################################################################
# {{{ Utility Section
function NumericArgument {
    param(
        [int]$FirstKey
    )
    $Keys = @()
    do {
        $NextEntry = ([Console]::ReadKey($true)).KeyChar.ToString()
        if($Digits -contains $NextEntry ){
            $Keys += $NextEntry
            $StillDigit = $true
        }else{
            $StillDigit = $false
        }
    }while($StillDigit -eq $true)
    return @($NextEntry, [int](@($FirstKey) + $Keys -join '') )
}
# }}}
# {{{ Vi Help
function VIGetHelp {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
                [ref]$Cursor)
    if( $null -ne $ENV:PAGER){
        $Pager = $ENV:PAGER
    }else{
        $Pager = "more"
    }
    $Command = " $Line "
    $CmdLetCursor = $Cursor + 1
    $CommandStart = 1 + $Command.LastIndexOfAny($CmdLetSeparator, $CmdLetCursor)
    $CommandEnd = $Command.IndexOfAny($CmdLetSeparator, $CmdLetCursor)
    $Command = $Command.Substring($CommandStart, `
            $CommandEnd - $CommandStart + 1)
    $CmdType = Get-Command $Command.Trim()
    if( $null -eq $CmdType  ){
        start-process "pwsh" -argumentlist ('-noprofile','-command', 'echo'`
                , "'$command'", "|",$pager) -wait -nonewwindow
    }elseif( $CmdType.CommandType -eq "cmdlet") {
        start-process "pwsh" -argumentlist ('-noprofile','-command', 'get-help'`
                , '-full', $command, '|', $pager) -wait -nonewwindow
    }elseif($CmdType.CommandType -eq 'Application'){
        & $Command.TRim() -h 2>&1 | out-null
        if($LASTEXITCODE -eq 0 ){
            start-process "pwsh" -argumentlist ('-noprofile','-command', `
                    $command,'-h','2>&1', '|', $pager) -Wait -NoNewWindow
        }else{
            & $Command.TRim() --help 2>&1 | out-null
            if($LASTEXITCODE -eq 0){
                start-process "pwsh" -argumentlist ('-noprofile', `
                        '-command' ,$command,'--help','2>&1', '|', $pager) `
                -Wait -NoNewWindow
            } else {
                start-process "pwsh" -argumentlist ('-noprofile', `
                        '-command',  $command,'/?','2>&1', '|', $pager)`
                -Wait -NoNewWindow
            }
        }
    }
}
# }}}
# {{{ csh extension
function cshloadpreviousfromhistory {
    $line = $null
    $cursor = $null
    [microsoft.powershell.psconsolereadline]::getbufferstate([ref]$line,`
                [ref]$cursor)
    if($line.trim().length -gt 0){
        $line = [regex]::escape($line)
        $matches = get-content $historyfile -delimiter $historyseparator | `
            select-string -pattern "^$line"
        if( $matches.count -eq 0){
            return
        }
        ${script:HistoryLine} = $Matches[-1].LineNumber
        if($PSVersionTable.PSVersion.Major -gt 5 ){
            $Line = $Matches[-1].Line
        }else{
            $Line = $Matches[-1].Line.Trim()
        }
        [Microsoft.PowerShell.PSConsoleReadLine]::DeleteLine()
    }else{
        ${script:HistoryLine}--
        $Line = (Get-Content $HistoryFile `
            -Delimiter $HistorySeparator)[${script:HistoryLine}].Trim()
    }
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert($Line)
}

function CSHLoadNextFromHistory {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
                [ref]$Cursor)
    if($Line.Trim().Length -gt 0){
        $Line = [Regex]::Escape($Line)
        $Matches = Get-Content $HistoryFile -Delimiter $HistorySeparator | `
            Select-String -Pattern "^$Line"
        if( $Matches.Count -eq 0){
            return
        }
        ${script:HistoryLine} = $Matches[-1].LineNumber
        if($PSVersionTable.PSVersion.Major -gt 5 ){
            $Line = $Matches[-1].Line
        }else{
            $Line = $Matches[-1].Line.Trim()
        }
        [Microsoft.PowerShell.PSConsoleReadLine]::DeleteLine()
    }else{
        $Line = (Get-Content $HistoryFile `
            -Delimiter $HistorySeparator)[${script:HistoryLine}].Trim()
    }
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert($Line)
}

# }}}
# {{{ g function

function GetReplacement {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
            [ref]$Cursor)
    $Movement = ([Console]::ReadKey($true)).KeyChar.ToString()
    if($Digits -contains $Movement.ToString() ){
        ($Movement, $IntArgument) = NumericArgument($Movement)
    }
    if(-not($IntArgument)){
        $IntArgument = 1
    }
    $Replacement = ''
    if($Movement -ceq 'l'){
        $Replacement = $Line.Substring($Cursor, $IntArgument)
    }elseif($Movement -ceq 'h'){
        $Cursor -= $IntArgument - 1
        $Replacement = $Line.Substring($Cursor, $IntArgument)
    }elseif($Movement -ceq 'w' -and $Movement -ceq 'e'){
        $EndPos = $Line.IndexOfAny($Separator, $Cursor )
        $Replacement = $Line.SubString($Cursor, $EndPos - $Cursor )
    }elseif($Movement -ceq 'W' -and $Movement -ceq 'E'){
        $EndPos = $Line.IndexOf(' ', $Cursor )
        $Replacement = $Line.SubString($Cursor, $EndPos - $Cursor )
    }elseif($Movement -ceq 'b'){
        $StartPos = $Line.LastIndexOfAny($Separator, $Cursor )
        $Replacement = $Line.SubString($StartPos, $Cursor - $StartPos )
        $Cursor = $StartPos
    }elseif($Movement -ceq 'B'){
        $StartPos = $Line.LastIndexOf(' ', $Cursor )
        $Replacement = $Line.SubString($StartPos, $Cursor - $StartPos )
        $Cursor = $StartPos
    }elseif($Movement -ceq 'i'){
        $Quotes = New-Object system.collections.hashtable
        $Quotes["'"] = @("'","'")
        $Quotes['"'] = @('"','"')
        $Quotes["("] = @('(',')')
        $Quotes[")"] = @('(',')')
        $Quotes["b"] = @('(',')')
        $Quotes["{"] = @('{','}')
        $Quotes["}"] = @('{','}')
        $Quotes["B"] = @('{','}')
        $Quotes["["] = @('[',']')
        $Quotes["]"] = @('[',']')
        $Command = ([Console]::ReadKey($true)).KeyChar.ToString()
        if($Command -ceq 'w') {
            $StartPos = $Line.LastIndexOfAny($Separator, $Cursor )
            $EndPos = $Line.IndexOfAny($Separator, $Cursor )
            if($StartPos -gt 0 -and $EndPos -lt 0){
                $EndPos = $Line.Length
            }
            $Replacement = $Line.SubString($StartPos, $EndPos - $StartPos )
            $Cursor = $StartPos
        }elseif($Command -ceq 'W'){
            $StartPos = $Line.LastIndexOf(' ', $Cursor )
            $EndPos = $Line.IndexOf(' ', $Cursor )
            if($StartPos -gt 0 -and $EndPos -lt 0){
                $EndPos = $Line.Length
            }
            $Replacement = $Line.SubString($StartPos, $EndPos - $StartPos )
            $Cursor = $StartPos
        }elseif( $Quotes.ContainsKey($Command)){
            ($StartChar,$EndChar)=$Quotes[$Command]
            $StartPos = $Line.LastIndexOf($StartChar, $Cursor )
            $EndPos = $Line.IndexOf($EndChar, $Cursor )
            if($StartPos -gt 0 -and $EndPos -lt 0){
                $EndPos = $Line.Length
            }
            $Replacement = $Line.SubString($StartPos, $EndPos - $StartPos )
            $Cursor = $StartPos
        }
    }
    return @($Cursor, $Replacement)
}

function VICapitalize {
    ($Cursor, $Replacement ) = GetReplacement
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($Cursor,`
                $Replacement.Length, $Replacement.toUpper() )
}

function VILowerize {
    ($Cursor, $Replacement ) = GetReplacement
    $Replacement.toLower()
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($Cursor,`
                $Replacement.Length, $Replacement.ToLower() )
}

function VIMiddleOfLine {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $Cursor = $Line.Length / 2
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($Cursor)
}

function VIMiddleOfScreen {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $Cursor = $host.UI.RawUI.WindowSize.Width / 2
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($Cursor)
}

function VIOpenFileUnderCursor {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $Separator = "' `""
    $StartChar = $Line.LastIndexOfAny($Separator, $Cursor) + 1
    $EndChar = $Line.IndexOfAny($Separator, $Cursor) 
    if($EndChar -eq -1){
        $EndChar = $Line.Length
    }
    # Out-File -inputObject "$Line $Cursor $StartChar $EndChar" -path c:\temp\log.Txt
    $Path = $Line.Substring($StartChar, $EndChar - $StartChar)
    if( Test-Path $Path -PathType Leaf){
        Start-Process $ENV:EDITOR -ArgumentList $PAth -Wait -NoNewWindow
    }
}

function VIBackwardEndOfWord {
    [Microsoft.PowerShell.PSConsoleReadLine]::ViBackwardWord()
    [Microsoft.PowerShell.PSConsoleReadLine]::ViBackwardWord()
    [Microsoft.PowerShell.PSConsoleReadLine]::NextWordEnd()
}

function VIBackwardEndOfGlob {
    [Microsoft.PowerShell.PSConsoleReadLine]::ViBackwardGlob()
    [Microsoft.PowerShell.PSConsoleReadLine]::ViBackwardGlob()
    [Microsoft.PowerShell.PSConsoleReadLine]::ViEndOfGlob()
}

# }}}
# {{{ Increment/decrement

function VIDecrement( $key , $arg ){
    $Separator = "$[({})]-._ '```":"
    $Caps = $Separator + ([char]'A'..[char]'z' | `
        Foreach-Object { [char]$_ }) -join ''
    $ConditionalStatements = @('elseif','if','else')
    $BoolValues = @('true','false')
    [int]$NumericArg = 0
    [Microsoft.PowerShell.PSConsoleReadLine]::TryGetArgAsInt($arg,
          [ref]$NumericArg, 1)
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $EndChar = $Line.indexOfAny($Caps, $Cursor)
    $StartChar = $Line.LastIndexOfAny($Caps, $Cursor) + 1
    $IsNumeric = $true
    $IsStringStatement = $false
    if($EndChar -lt 0 -and $StartChar -gt 0){
        $EndChar = $Line.Length
    }elseif($EndChar - $StartChar -le 0){
        $IsNumeric = $false
        $EndChar = $Line.indexOfAny($Separator, $Cursor)
        $StartChar = $Line.LastIndexOfAny($Separator, $Cursor) + 1
        if($StartChar -gt 0 -and $EndChar -lt 0){
            $EndChar = $Line.Length
        }elseif($StartChar -le 0 -and $EndChar -lt 0){
            $StartChar = 0
            $EndChar = $Line.Length
        }
        $CurrentStatement = $Line.Substring($StartChar, $EndChar - $StartChar)
        if($ConditionalStatements -contains $CurrentStatement){
            $NextIndex = ([array]::IndexOf(
                        $ConditionalStatements, $CurrentStatement)`
                - $NumericArg) % $ConditionalStatements.Length
            $NextVal = $ConditionalStatements[$NextIndex]
            $IsStringStatement = $true
        }elseif( $BoolValues -contains $CurrentStatement){
            $NextIndex = ([array]::IndexOf(
                        $BoolValues, $CurrentStatement)`
                - $NumericArg) % $BoolValues.Length
            $NextVal = $BoolValues[$NextIndex]
            $IsStringStatement = $true
        }elseif( Test-Path Variable:VIIncrementArray){
            if( $VIIncrementArray[0] -is [array] ) {
                foreach($UserStrings in $VIIncrementArray){
                    if($UserStrings -contains $CurrentStatement ){
                        $NextIndex = ([array]::IndexOf(
                                    $UserStrings, $CurrentStatement)`
                            - $NumericArg) % $UserStrings.Length
                        $NextVal = $UserStrings[$NextIndex]
                        $IsStringStatement = $true
                    }
                }
            }else{
                if($VIIncrementArray -contains $CurrentStatement ){
                    $NextIndex = ([array]::IndexOf(
                                $VIIncrementArray, $CurrentStatement)`
                        - $NumericArg) % $VIIncrementArray.Length
                    $NextVal = $VIIncrementArray[$NextIndex]
                    $IsStringStatement = $true
                }
            }
        }
    }
    if( $IsNumeric -eq $false -and $IsStringStatement -eq $false){
        return
    }
    if($IsNumeric){
        [int]$NextVal = $Line.SubString($StartChar, $EndChar - $StartChar)
        $NextVal -= $NumericArg
    }
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($StartChar,`
                $EndChar - $StartChar, $nextVal.ToString() )
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($EndChar - 1)
}

function VIIncrement( $key , $arg ){
    $Separator = "$[({})]-._ '```":"
    $Caps = $Separator + ([char]'A'..[char]'z' | `
        Foreach-Object { [char]$_ }) -join ''
    $ConditionalStatements = @('elseif','if','else')
    $BoolValues = @('true','false')
    [int]$NumericArg = 1
    [Microsoft.PowerShell.PSConsoleReadLine]::TryGetArgAsInt($arg,
          [ref]$NumericArg, 1)
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $EndChar = $Line.indexOfAny($Caps, $Cursor)
    $StartChar = $Line.LastIndexOfAny($Caps, $Cursor) + 1
    $IsNumeric = $true
    $IsStringStatement = $false
    if($EndChar -lt 0 -and $StartChar -gt 0){
        $EndChar = $Line.Length
    }elseif($EndChar - $StartChar -le 0){
        $IsNumeric = $false
        $EndChar = $Line.indexOfAny($Separator, $Cursor)
        $StartChar = $Line.LastIndexOfAny($Separator, $Cursor) + 1
        if($StartChar -gt 0 -and $EndChar -lt 0){
            $EndChar = $Line.Length
        }elseif($StartChar -le 0 -and $EndChar -lt 0){
            $StartChar = 0
            $EndChar = $Line.Length
        }
        $CurrentStatement = $Line.Substring($StartChar, $EndChar - $StartChar)
        if($ConditionalStatements -contains $CurrentStatement){
            $NextIndex = ([array]::IndexOf(
                        $ConditionalStatements, $CurrentStatement)`
                + $NumericArg) % $ConditionalStatements.Length
            $NextVal = $ConditionalStatements[$NextIndex]
            $IsStringStatement = $true
        }elseif( $BoolValues -contains $CurrentStatement){
            $NextIndex = ([array]::IndexOf(
                        $BoolValues, $CurrentStatement)`
                - $NumericArg) % $BoolValues.Length
            $NextVal = $BoolValues[$NextIndex]
            $IsStringStatement = $true
        }elseif( Test-Path Variable:VIIncrementArray){
            if( $VIIncrementArray[0] -is [array] ) {
                foreach($UserStrings in $VIIncrementArray){
                    if($UserStrings -contains $CurrentStatement ){
                        $NextIndex = ([array]::IndexOf(
                                    $UserStrings, $CurrentStatement)`
                            + $NumericArg) % $UserStrings.Length
                        $NextVal = $UserStrings[$NextIndex]
                        $IsStringStatement = $true
                    }
                }
            }else{
                if($VIIncrementArray -contains $CurrentStatement ){
                    $NextIndex = ([array]::IndexOf(
                                $VIIncrementArray, $CurrentStatement)`
                        + $NumericArg) % $VIIncrementArray.Length
                    $NextVal = $VIIncrementArray[$NextIndex]
                    $IsStringStatement = $true
                }
            }
        }
    }
    if( $IsNumeric -eq $false -and $IsStringStatement -eq $false){
        return
    }
    if($IsNumeric){
        [int]$NextVal = $Line.SubString($StartChar, $EndChar - $StartChar)
        $NextVal += $NumericArg
    }
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($StartChar,`
                $EndChar - $StartChar, $NextVal.ToString() )
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($EndChar - 1)
}
# }}}

# {{{ InnerBlock
function VIChangeInnerBlock(){
    VIDeleteInnerBlock
    [Microsoft.PowerShell.PSConsoleReadLine]::ViInsertMode()
}

function VIDeleteInnerBlock(){
    $Caps = "$[({})]-._ '```"\/" + ([char]'A'..[char]'Z' | `
        Foreach-Object { [char]$_ }) -join ''
    $Quotes = New-Object system.collections.hashtable
    $Quotes["'"] = @("'","'")
    $Quotes['"'] = @('"','"')
    $Quotes["("] = @('(',')')
    $Quotes[")"] = @('(',')')
    $Quotes["b"] = @('(',')')
    $Quotes["{"] = @('{','}')
    $Quotes["}"] = @('{','}')
    $Quotes["B"] = @('{','}')
    $Quotes["["] = @('[',']')
    $Quotes["]"] = @('[',']')
    $Quotes[">"] = @('<','>')
    $Quotes["<"] = @('<','>')
    $Quotes["w"] = @("$[({})]-._ '```"\/", "$[({})]-._ '```"\/")
    $Quotes["W"] = @(' ', ' ')
    $Quotes['C'] = @($Caps, $Caps)
    $Quote = ([Console]::ReadKey($true)).KeyChar
    if( $Quotes.ContainsKey($quote.ToString())){
        $Line = $Null
        $Cursor = $Null
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
                [ref]$Cursor)
        $OpeningQuotes = $Quotes[$Quote.ToString()][0]
        $ClosingQuotes = $Quotes[$Quote.ToString()][1]
        if($ClosingQuotes.length -gt 1){
            $EndChar=$Line.IndexOfAny($ClosingQuotes, $Cursor)
        }else{
            $EndChar=$Line.IndexOf($ClosingQuotes, $Cursor)
        }
        if($OpeningQuotes.Length -gt 1){
            $StartChar=$Line.LastIndexOfAny($OpeningQuotes, $Cursor) + 1
        }else{
            $StartChar=$Line.LastIndexOf($OpeningQuotes, $Cursor) + 1
        }
        if(($OpeningQuotes.Length -gt 1 -or $Quote -ceq 'W' -or $Quote -ceq 'C'`
                ) -and $EndChar -lt 0){
            $EndChar = $Line.Length
        }
        if(($OpeningQuotes.Length -gt 1 -or $Quote -ceq 'W')`
                -and $StartChar -lt 0){
            $StartChar = 0
        }
        if( $Quote.ToString() -eq 'C'){
            $StartChar -= 1
        }
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition(
                    $StartChar )
        if($Quote.ToString() -ceq 'w'){
            [Microsoft.PowerShell.PSConsoleReadLine]::DeleteWord()
        }elseif( $Quote.ToString() -ceq 'W'){
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteGlob()
        }elseif($Quote.ToString() -eq '"' -or $Quote.ToString() -eq "'" ){
            $LocalShell.SendKeys($Quote)
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToBeforeChar()
        }elseif( $Quote.ToString() -eq '(' -or $Quote.ToString() -eq '[' -or `
                $Quote.ToString() -eq '{' -or $Quote.ToString() -ceq 'B' `
                -or $Quote.ToString() -ceq 'b' -or $Quote.ToString() -ceq ')' `
                -or $Quote.ToString() -ceq ']' -or $Quote.ToString() -ceq '}' `
                -or $Quote.ToString() -ceq '<' -or $Quote.ToString() -ceq '>' `
                ){
            $LocalShell.SendKeys("{$($ClosingQuotes.ToString())}")
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToBeforeChar()
            out-file -inputobject "CI : {$($ClosingQuotes.ToString())}" -path c:\temp\log.txt
        } elseif( $Quote.ToString() -eq 'C') {
            if($EndChar -eq $Line.Length){
                [Microsoft.PowerShell.PSConsoleReadLine]::DeleteToEnd()
            }elseif($Line[$EndChar] -eq ' '){
                [Microsoft.PowerShell.PSConsoleReadLine]::DeleteWord()
            }else {
                $LocalShell.SendKeys($Line[$EndChar])
                [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToBeforeChar()
            }
        }
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($StartChar)
    }
}

# }}}

# {{{ OuterBlock

function VIChangeOuterBlock(){
    VIDeleteOuterBlock
    [Microsoft.PowerShell.PSConsoleReadLine]::ViInsertMode()
}

function VIDeleteOuterBlock(){
    $Quotes = New-Object system.collections.hashtable
    $Quotes["'"] = @("'","'")
    $Quotes['"'] = @('"','"')
    $Quotes["("] = @('(',')')
    $Quotes[")"] = @('(',')')
    $Quotes["b"] = @('(',')')
    $Quotes["{"] = @('{','}')
    $Quotes["}"] = @('{','}')
    $Quotes["B"] = @('{','}')
    $Quotes["["] = @('[',']')
    $Quotes["]"] = @('[',']')
    $Quotes[">"] = @('<','>')
    $Quotes["<"] = @('<','>')
    $Quotes["w"] = @("$[({})]-._ '```"\/", "$[({})]-._ '```"\/")
    $Quotes["W"] = @(' ', ' ')
    $Quote = ([Console]::ReadKey($true)).KeyChar
    if( $Quotes.ContainsKey($quote.ToString())){
        $Line = $Null
        $Cursor = $Null
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
                [ref]$Cursor)
        $OpeningQuotes = $Quotes[$Quote.ToString()][0]
        $ClosingQuotes = $Quotes[$Quote.ToString()][1]
        if($ClosingQuotes.Length -gt 1){
            $EndChar=$Line.IndexOfAny($ClosingQuotes, $Cursor) + 1
        }else{
            $EndChar=$Line.IndexOf($ClosingQuotes, $Cursor) +1
        }
        if($OpeningQuotes.length -gt 1){
            $StartChar=$Line.LastIndexOfAny($OpeningQuotes, $Cursor)
        }else{
            $StartChar=$Line.LastIndexOf($OpeningQuotes, $Cursor)
        }
        if(($OpeningQuotes.Length -gt 1 -or $Quote -ceq 'W') `
                -and $EndChar -eq 0){
            $EndChar = $Line.Length
        }
        if(($OpeningQuotes.Length -gt 1 -or $Quote -ceq 'W')`
                -and $StartChar -lt 0){
            $StartChar = 0
        }
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition(
                    $StartChar + 1)
        if($Quote.ToString() -ceq 'w'){
            [Microsoft.PowerShell.PSConsoleReadLine]::DeleteWord()
        }elseif( $Quote.ToString() -ceq 'W'){
            if($StartChar -eq 0){
                $StartChar--
            }
            [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition(
                    $StartChar + 1 )
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteGlob()
        }elseif($Quote.ToString() -eq '"' -or $Quote.ToString() -eq "'" ){
            [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition(
                    $StartChar )
            $LocalShell.SendKeys($Quote)
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToChar()
        }elseif( $Quote.ToString() -eq '(' -or $Quote.ToString() -eq '[' -or `
                $Quote.ToString() -eq '{' -or $Quote.ToString() -eq ')' -or `
                $Quote.ToString() -eq ']' -or $Quote.ToString() -eq '}'-or `
                $Quote.ToString() -ceq '<' -or $Quote.ToString() -ceq '>' -or `
                $Quote.ToString() -ceq 'b' -or $Quote.ToString() -ceq 'B'){
            [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition(
                    $StartChar )
            $LocalShell.SendKeys("{" + $ClosingQuotes.ToString() + "}")
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToChar()
        } elseif( $Quote.ToString() -eq 'C') {
            $LocalShell.SendKeys($Line[$EndChar])
            [Microsoft.PowerShell.PSConsoleReadLine]::ViDeleteToChar()

        }
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($StartChar)
    }
}
# }}}

# {{{ Surround
function VIChangeSurround(){
    # inspired by tpope vim-surround
    # https://github.com/tpope/vim-surround
    $Quotes = @{
        "'" = @("'","'");
        '"'= @('"','"');
        "(" = @('(',')');
        "{" = @('{','}');
        "[" = @('[',']');
    }
    $Line = $Null
    $Cursor = $Null
    $Search = ([Console]::ReadKey($true)).KeyChar
    $Replace = ([Console]::ReadKey($true)).KeyChar
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
            [ref]$Cursor)
    $SearchOpeningQuotes = $Quotes[$Search.ToString()][0]
    $SearchClosingQuotes = $Quotes[$Search.ToString()][1]
    $ReplaceOpeningQuotes = $Quotes[$Replace.ToString()][0]
    $ReplaceClosingQuotes = $Quotes[$Replace.ToString()][1]
    $EndChar=$Line.indexOf($SearchClosingQuotes, $Cursor)
    $StartChar=$Line.LastIndexOf($SearchOpeningQuotes, $Cursor)
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($StartChar, `
        1,$ReplaceOpeningQuotes )
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($EndChar, `
        1,$ReplaceClosingQuotes )
}

function VIDeleteSurround(){
    # inspired by tpope vim-surround
    # https://github.com/tpope/vim-surround
    $Quotes = @{
        "'" = @("'","'");
        '"'= @('"','"');
        "(" = @('(',')');
        "b" = @('(',')');
        "{" = @('{','}');
        # "B" = @('{','}');
        "[" = @('[',']');
    }
    $Line = $Null
    $Cursor = $Null
    $Search = ([Console]::ReadKey($true)).KeyChar
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
            [ref]$Cursor)
    $SearchOpeningQuotes = $Quotes[$Search.ToString()][0]
    $SearchClosingQuotes = $Quotes[$Search.ToString()][1]
    $EndChar=$Line.indexOf($SearchClosingQuotes, $Cursor)
    $StartChar=$Line.LastIndexOf($SearchOpeningQuotes, $Cursor)
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($StartChar, `
        1,'')
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($EndChar - 1, `
        1,'' )
}
# }}}

# {{{ Global Clipboard
function VIGlobalYank (){
    $Line = $Null
    $Cursor = $Null
    [Microsoft.Powershell.PSConsoleReadline]::GetBufferState([ref] $Line,
            [ref] $Cursor)
    Set-ClipBoard $Line
}

function VIGlobalPasteBefore{
    $Line = $Null
    $Cursor = $Null
    [Microsoft.Powershell.PSConsoleReadline]::GetBufferState([ref] $Line,
            [ref] $Cursor)
    $Lines = (Get-ClipBoard).Split("`n") 
    if($Lines.Count -gt 1){
        $LastLine = $Lines[-1]
        $Lines[0..($Lines.Length-2)]| Foreach-Object {
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                    $_.Replace("`t",' ') )
            [Microsoft.Powershell.PSConsoleReadline]::AddLine()
                }
        [Microsoft.Powershell.PSConsoleReadline]::Insert( `
            $LastLine.Replace("`t",' ') )
    }else{
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                    $Lines.Replace("`t",' ') )
    }
}

function VIGlobalPaste (){
    $Line = $Null
    $Cursor = $Null
    [Microsoft.Powershell.PSConsoleReadline]::GetBufferState([ref] $Line,
            [ref] $Cursor)
    $Lines = (Get-ClipBoard).Split("`n") 
    if($Cursor -ge ($Line.Length-1) ){
        if($Lines.Count -gt 1){
            $LastLine = $Lines[-1]
            $FirstLine = $Lines[0]
            [Microsoft.Powershell.PSConsoleReadline]::Replace(0, $Line.Length ,`
                    $Line + $FirstLine) 
            "$Line$FirstLine" | out-file c:\temp\log.txt
            $Lines[1..($Lines.Length-2)]| Foreach-Object {
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                    $_.Replace("`t",' ') )
                    [Microsoft.Powershell.PSConsoleReadline]::AddLine()
            }
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                $LastLine.Replace("`t",' ') )
        }else{ 
            $Length = $Line.Length
            $Line += $Lines
            $Line | out-file c:\temp\log.txt
            [Microsoft.Powershell.PSConsoleReadline]::Replace(0, $Length , `
                    $Line) 
        }
    } else {
        [Microsoft.Powershell.PSConsoleReadline]::SetCursorPosition($Cursor + 1)
        if($Lines.Count -gt 1){
            $LastLine = $Lines[-1]
            $Lines[0..($Lines.Length-2)]| Foreach-Object {
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                    $_.Replace("`t",' ') )
                    [Microsoft.Powershell.PSConsoleReadline]::AddLine()
            }
            [Microsoft.Powershell.PSConsoleReadline]::Insert( `
                $LastLine.Replace("`t",' ') )
        }else{
            $Length = $Line.Length
            [Microsoft.Powershell.PSConsoleReadline]::Replace(0, $Length, `
                        $($Line + $Lines) )
        }
    }
}
# }}}

Export-ModuleMember -Function 'VIDecrement', 'VIIncrement', `
    'VIChangeInnerBlock', 'VIDeleteInnerBlock', 'VIChangeOuterBlock', `
    'VIDeleteOuterBlock', 'VIChangeSurround', 'VIDeleteSurround', `
    'VIGlobalYank', 'VIGlobalPaste'
################################################################################
# Author - belot.nicolas@gmail.com #
################################################################################
# CHANGELOG #
################################################################################
# DONE: Change Quotes declaration to be case sensitive (W) #
# DONE: Add Handler for Next Camel Word (Maybe cd and cD) #
# Should be ciC and diC #
# VERSION: 0.0.1 #
# DONE: Use compatible Ps5 char range operator #
# VERSION: 0.0.2 #
# DONE: Add function to access global clipboard #
# DONE: Delete must add erase text in register (VIDelete*) #
# VERSION: 0.0.3 #
# FIXED: Outter Text malfunction when word contains special char #
# FIXED: [cd]iW do nothing #
# DONE: Change Inner Cap should work with endOfWord #
# DONE: Change Inner Cap should work with endOfLine #
# VERSION: 0.0.4 #
# DONE: (In|De)Crement do not work at end of line #
# DONE: Use all exception numeric for inc or dec #
# VERSION: 1.0.0 #
# FIXED: ciC problem with end of word #
# FIXED: Global paste does not insert at correct place #
# FIXED: Remove new line after paste #
# FIXED: Increment/Decrement return error when cursor is not on number #
# DONE: Increment if/elseif/else #
# FIXED:Increment take the first cond statement found #
# DONE: Increment true/false #
# VERSION: 1.0.1 #
# DONE: Add user defined increment array #
# FIXED: Increment does not support end of line #
# VERSION: 1.0.2 #
# FIXED: Increment crash when line contains only one word #
# FIXED: ciw doesn't consider path separtor #
# VERSION: 1.0.3 #
# DONE: Implement gU and gu operator #
# FIXED: Preserve line end in global paste #
# DONE: Implement gE and ge operator #
# DONE: add [ai]b as an equivalent to [ai][()] #
# NOTE: DigitArgument() do not read previous keysend #
# DONE: Add ESC+P ESC+N CSH equivalent (not really vi function) #
# VERSION: 1.0.4 #
# FIXED: add a[ movement #
# DONE: map gM (go to midlle of line ) #
# DONE: map gf (Edit File under cursor) #
# DONE: add i< i> a< a> #
# DONE: add [ai]B as an equivalent to [ai][{}] #
# VERSION: 1.0.5 #
# FIXED: Use PsReadLine get option #
# FIXED: Get-Content do not remove delim in posh5 #
# VERSION: 1.0.6 #
# FIXME: B to not work #
# FIXED: Global Paste After does not work when at end of line #
# DONE: Ctrl+) to open help on cmdlet #
# DONE: Ctrl+) Invoke Help (/? , -h or --help on application #
# VERSION: 1.0.7 #
# HEAD: #
################################################################################
# {{{CODING FORMAT #
################################################################################
# Variable = CamelCase #
# TabSpace = 4 #
# Max Line Width = 80 #
# Bracket Style : OTBS #
################################################################################