PSReadLineVIExtension.psm1

# {{{ Handler
Set-PSReadLineKeyHandler -Chord "c,i" -ViMode Command `
    -ScriptBlock { VIChangeInnerBlock } `
    -Description 'Change Inner block'
Set-PSReadLineKeyHandler -Chord "c,a" -ViMode Command `
    -ScriptBlock { VIChangeOuterBlock } `
    -Description 'Change outter block'
Set-PSReadLineKeyHandler -Chord "d,i" -ViMode Command `
    -ScriptBlock { VIDeleteInnerBlock } `
    -Description 'Delete Inner Word'
Set-PSReadLineKeyHandler -Chord "d,a" -ViMode Command `
    -ScriptBlock { VIDeleteOuterBlock } `
    -Description 'Delete Outter Word'
Set-PSReadLineKeyHandler -Chord "c,s" -ViMode Command `
    -ScriptBlock { VIChangeSurround } `
    -Description 'Change Surrounding'
Set-PSReadLineKeyHandler -Chord "d,s" -ViMode Command `
    -ScriptBlock { VIDeleteSurround } `
    -Description 'Delete Surrounding'
Set-PSReadLineKeyHandler -Chord "Ctrl+a" -ViMode Command `
    -ScriptBlock { VIIncrement $args[0] $args[1] } `
    -Description 'Increment Argument'
Set-PSReadLineKeyHandler -Chord "Ctrl+x" -ViMode Command `
    -ScriptBlock { VIDecrement $args[0] $args[1] } `
    -Description 'Decrement Argument'
Set-PSReadLineKeyHandler -Chord "+,y" -ViMode Command `
    -ScriptBlock { VIGlobalYank } `
    -Description 'Yank CommandLine to system clipboard'
Set-PSReadLineKeyHandler -Chord "+,p" -ViMode Command `
    -ScriptBlock { VIGlobalPaste } `
    -Description 'Paste system clipboard at cursor'
Set-PSReadLineKeyHandler -Chord "+,P" -ViMode Command `
    -ScriptBlock { VIGlobalPasteBefore} `
    -Description 'Paste system clipboard before cursor'
Set-PSReadLineKeyHandler -Chord "g,e" -viMode Command `
    -ScriptBlock { ViBackwardEndOfWord } `
    -Description 'Move to End of previous word'
Set-PSReadLineKeyHandler -Chord "g,E" -viMode Command `
    -ScriptBlock { VIBackwardEndOfGlob } `
    -Description 'Move to End of previous glob'
Set-PsReadLineKeyHandler -Chord "g,M" -viMode Command `
    -ScriptBlock { VIMiddleOfLine } `
    -Description 'Move to Middle of Line'
Set-PsReadLineKeyHandler -Chord "g,f" -viMode Command `
    -ScriptBlock {VIOpenFileUnderCursor } `
    -Description 'Open File under cursor'
Set-PsReadLineKeyHandler -Chord "g,m" -viMode Command `
    -ScriptBlock { VIMiddleOfScreen } `
    -Description 'Move to Middle of Screen'
Set-PsReadLineKeyHandler -Chord "g,P" -viMode Command `
    -ScriptBlock {VIgPasteBefore } `
    -Description "Paste Before and put cursor after yanked text"
Set-PsReadLineKeyHandler -Chord "g,p" -viMode Command `
    -ScriptBlock {VIgPasteAfter } `
    -Description "Paste after and put cursor after yanked text"
Set-PsReadlineKeyHandler -Chord ':,w' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::ValidateAndAcceptLine()
    } `
    -Description 'Validate and AcceptLine'
Set-PsReadlineKeyHandler -Chord ':,x' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::ValidateAndAcceptLine()
    } `
    -Description 'Validate and AcceptLine'
Set-PsReadlineKeyHandler -Chord ':,q' -ViMode Command `
    -ScriptBlock {
        [Microsoft.PowerShell.PSConsoleReadLine]::CancelLine()
    } `
    -Description 'Cancel Line'
if($VIExperimental -eq $true){
    Write-Host "Using Experimental VISettings"
    Set-PSReadLineKeyHandler -Chord "g,U" -viMode Command `
    -ScriptBlock { VICapitalize } `
    -Description 'Capitalize'
    Set-PSReadLineKeyHandler -Chord "g,u" -viMode Command `
    -ScriptBlock { VILowerize } `
    -Description 'Lowerize'
    Set-PSReadLineKeyHandler -Chord "g,alt+2" -viMode Command `
    -ScriptBlock { VIChangeCase } `
    -Description 'Change Case'
    Set-PSReadLineKeyHandler -Chord "g,~" -viMode Command `
    -ScriptBlock { VIChangeCase } `
    -Description 'Change Case'
    Set-PsReadLineKeyHandler -Chord 'Alt+p' -viMode Command `
    -ScriptBlock { CSHLoadPreviousFromHistory } `
    -Description 'Load Previous entry From History '
    Set-PsReadLineKeyHandler -Chord 'Alt+n' -viMode Command `
    -ScriptBlock { CSHLoadNextFromHistory } `
    -Description 'Load Next entry From History '
    Set-PsReadLineKeyHandler -Chord 'Alt+p' -viMode Insert `
    -ScriptBlock { CSHLoadPreviousFromHistory } `
    -Description 'Load Previous entry From History '
    Set-PsReadLineKeyHandler -Chord 'Alt+n' -viMode Insert `
    -ScriptBlock { CSHLoadNextFromHistory } `
    -Description 'Load Next entry From History '
    Set-PsReadLineKeyHandler -Chord "Ctrl+)" -viMode Command `
    -ScriptBlock { VIGetHelp } `
    -Description 'Open Help for Command under cursor'
    Set-PsReadLineKeyHandler -Chord "Ctrl+)" -viMode Insert `
    -ScriptBlock { VIGetHelp } `
    -Description 'Open Help for Command under cursor'
    Set-PSReadlineKeyHandler -Chord "z,=" -ScriptBlock { VIZWordSubstitution } `
        -ViMode Command -Description "List similar CmdLet"
}
#}}}
$LocalShell = New-Object -ComObject wscript.shell
$Digits = (0..9)
$Separator = "$[({})]-._ '```":\/"
$CmdLEtSeparator =  "$[({})]._ '```":\/"
$script:HistoryLine = -1
$HistorySeparator ="`r`n"
$HistoryFile = (Get-PSReadLineOption).HistorySavePath
$SBDisplayChoice = {
    param([array]$List)
    $Msg = "`n"
    for($i=0;$i -lt $List.Count ;$i++){
        $Msg+= "$($i+1) : $($List[$i])`n"
        # [Console]::Write( "$i : $($List[$i]) `n")
    }
    $Choice = Read-Host "$Msg Enter the correction number or press enter"
    if( $Choice -gt 0 -or $Choice -le $List.Count){
        return $List[$Choice - 1]
    }else{
        return $null
    }
}
######################################################################
# 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 '') )
}

function LevenstienDistance {

# get-ld.ps1 (Levenshtein Distance)
# Levenshtein Distance is the # of edits it takes to get from 1 string to another
# This is one way of measuring the "similarity" of 2 strings
# Many useful purposes that can help in determining if 2 strings are similar possibly
# with different punctuation or misspellings/typos.
#
########################################################

# Putting this as first non comment or empty line declares the parameters
# the script accepts
###########
param([string] $first, [string] $second, [switch] $ignoreCase)

# No NULL check needed, why is that?
# PowerShell parameter handling converts Nulls into empty strings
# so we will never get a NULL string but we may get empty strings(length = 0)
#########################

$len1 = $first.length
$len2 = $second.length

# If either string has length of zero, the # of edits/distance between them
# is simply the length of the other string
#######################################
if($len1 -eq 0)
{ return $len2 }

if($len2 -eq 0)
{ return $len1 }

# make everything lowercase if ignoreCase flag is set
if($ignoreCase -eq $true)
{
  $first = $first.tolowerinvariant()
  $second = $second.tolowerinvariant()
}

# create 2d Array to store the "distances"
$dist = new-object -type 'int[,]' -arg ($len1+1),($len2+1)

# initialize the first row and first column which represent the 2
# strings we're comparing
for($i = 0; $i -le $len1; $i++) 
{  $dist[$i,0] = $i }
for($j = 0; $j -le $len2; $j++) 
{  $dist[0,$j] = $j }

$cost = 0

for($i = 1; $i -le $len1;$i++)
{
  for($j = 1; $j -le $len2;$j++)
  {
    if($second[$j-1] -ceq $first[$i-1])
    {
      $cost = 0
    }
    else   
    {
      $cost = 1
    }
    
    # The value going into the cell is the min of 3 possibilities:
    # 1. The cell immediately above plus 1
    # 2. The cell immediately to the left plus 1
    # 3. The cell diagonally above and to the left plus the 'cost'
    ##############
    # I had to add lots of parentheses to "help" the Powershell parser
    # And I separated out the tempmin variable for readability
    $tempmin = [System.Math]::Min(([int]$dist[($i-1),$j]+1) , ([int]$dist[$i,($j-1)]+1))
    $dist[$i,$j] = [System.Math]::Min($tempmin, ([int]$dist[($i-1),($j-1)] + $cost))
  }
}

# the actual distance is stored in the bottom right cell
return $dist[$len1, $len2];
}
# }}}
# {{{ Vi Help
function InvokeHelp {
    param($Command)
    $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" -or  $CmdType.CommandType -eq "Function") {
        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
            }
        }
    }elseif($CmdType.CommandType -eq 'Alias'){
        out-file -path c:\temp\logs.txt -inputobject $cmdtype.Definition -append
        InvokeHelp $CmdType.Definition
    }
}
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)
    InvokeHelp $Command
}
# }}}
# {{{ 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
# Case Replacement {{{

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 VIChangeCase {
    ($Cursor, $Replacement) = Get-Replacement
    $Replacement = @( $Replacement.toCharArray() | Foreach-Object {
        if( $_ -ge 'a' ){
            $_.toString().toUpper()
        }else {
            $_.toString().toLower()

        }
            }) -join ''
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace($Cursor,`
                $Replacement.Length, $Replacement)

}

#}}}

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()
}

function VIgPasteBefore {
    [Microsoft.PowerShell.PSConsoleReadLine]::PasteBefore()
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($Cursor+1)
}

function VIgPasteAfter {
    [Microsoft.PowerShell.PSConsoleReadLine]::PasteAfter()
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($Cursor+1)


}


# }}}
# {{{ 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)
    $Quotes['|'] = @('|', '|')
    $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 "'" -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()
        } 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"] = @(' ', ' ')
    $Quotes['|'] = @('|', '|')
    $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 "'" -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) )
        }
    }
}
# }}}
# z {{{

function VIZWordSubstitution     {
    $Line = $Null
    $Cursor = $Null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$Line,`
        [ref]$Cursor)
    $Tokens = [System.Management.Automation.PsParser]::Tokenize( `
                $Line, [ref] $null)
    $Token = $Tokens | Where-Object { $Cursor -ge $_.Start -and `
        $Cursor -lt ($_.Start + $_.Length)  }
    $Command = $Token.Content
    $Length = $Token.Length
    if( $Token.Type -eq 'Command'){
        $Commands = ( ( @(Get-Command)+ @(Get-Alias)) | select Name, @{
            N='LD';
            E={ LevenstienDistance $Command $_.Name -i
            }} | sort LD | select -First 20).Name
    }
    if(Get-Module PsFZF){
        $subst = $Commands | Invoke-Fzf -NoSort -Layout reverse
    }else{
        $subst = invoke-command -ScriptBlock $SBDisplayChoice `
            -ArgumentList $Commands, $null
    }
    if( $null -ne $subst){
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace($Token.Start, $Token.Length, $subst)
    }
    if(-not (Get-Module psFzf) ) {
        Clear-Host
    }
    [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
}
# }}}

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 #
# 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 #
# DONE: Add Description to defined chords #
# DONE: Call Help on Alias #
# DONE: Call Help on Function #
# DONE: Add gp and gP #
# DONE: Try to implement z= on command #
# DONE: z= must work without psfzf #
# DONE: z= should list alias also #
# DONE: add | to inner and outer Text #
# VERSION: 1.0.8 #
# TODO: Add g~ : problem with psreadline keyhandling #
# FIXME: B to not work #
# HEAD: #
################################################################################
# {{{CODING FORMAT #
################################################################################
# Variable = CamelCase #
# TabSpace = 4 #
# Max Line Width = 80 #
# Bracket Style : OTBS #
################################################################################