PSReadLine icon indicating copy to clipboard operation
PSReadLine copied to clipboard

Allow a key sequence binding like `j,k` to not prevent user from insert `j`

Open dhatch48 opened this issue 5 years ago • 9 comments

Description of the new feature/enhancement

You can bind a key sequence like j,k to a function or script block by Set-PSReadLineKeyHandler -Key 'j,k' -ViMode Insert -Function ViCommandMode, but a user cannot insert a character j after that.

Proposed technical implementation details (optional)

Quoted from https://github.com/PowerShell/PSReadLine/issues/1701#issuecomment-664020465

In vim, jk or jj to exit insert mode is super common. Usually it works like this:

  1. You press j
  2. j is written to the buffer
  3. A timer starts
  4. If you press the next key in the sequence before the timer is up, the j is removed from the buffer and the command fires
  5. If the key you press next isn't in the sequence, or the timer is up then it's treated like a literal

dhatch48 avatar Jul 26 '20 05:07 dhatch48

@dhatch48 You can do that by Set-PSReadLineKeyHandler -Key 'j,k' -ViMode Insert -Function ViCommandMode

daxian-dbw avatar Jul 26 '20 06:07 daxian-dbw

@daxian-dbw I can't get that to work right. I mean it works, but you can't type j anymore.

SeeminglyScience avatar Jul 26 '20 13:07 SeeminglyScience

Yeah, j will be bound after that and won't be treated as a literal character. I think that's just an example. Key sequences should be chosen to not interfere with the regular input, like Set-PSReadlineKeyHandler -Chord 'Ctrl+d,Ctrl+c' -Function CaptureScreen

daxian-dbw avatar Jul 26 '20 17:07 daxian-dbw

Yeah, j will be bound after that and won't be treated as a literal character. I think that's just an example.

Nah jk or jj (what I use) to exit insert mode is super common. Usually it works like this:

  1. You press j
  2. j is written to the buffer
  3. A timer starts
  4. If you press the next key in the sequence before the timer is up, the j is removed from the buffer and the command fires
  5. If the key you press next isn't in the sequence, or the timer is up then it's treated like a literal

SeeminglyScience avatar Jul 26 '20 17:07 SeeminglyScience

Yeah, j will be bound after that and won't be treated as a literal character. I think that's just an example. Key sequences should be chosen to not interfere with the regular input, like Set-PSReadlineKeyHandler -Chord 'Ctrl+d,Ctrl+c' -Function CaptureScreen

I think @SeeminglyScience explained it nicely. Many vim users have bindings of jj or jk to exit insert mode because it is so common to switch modes and this way it keeps your fingers on the home row vs. reaching for Esc key every time.

Anyway, I suppose a sequence timer/timeout is needed to make this work.

dhatch48 avatar Jul 27 '20 04:07 dhatch48

I see. Thanks for clarifying it. I have changed the title and PR description to reflect what is really asked for.

daxian-dbw avatar Jul 27 '20 04:07 daxian-dbw

This is what I added to my profile to make jk go to comand mode:

$j_timer = New-Object System.Diagnostics.Stopwatch

Set-PSReadLineKeyHandler -Key j -ViMode Insert -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert("j")
    $j_timer.Restart()
}

Set-PSReadLineKeyHandler -Key k -ViMode Insert -ScriptBlock {
    if (!$j_timer.IsRunning -or $j_timer.ElapsedMilliseconds -gt 1000) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert("k")
    } else {
        [Microsoft.PowerShell.PSConsoleReadLine]::ViCommandMode()
        $line = $null
        $cursor = $null
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
        [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor, 1)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor-1)
    }
}

Drllap avatar Jan 23 '22 00:01 Drllap

This is what I added to my profile to make jk go to comand mode:

$j_timer = New-Object System.Diagnostics.Stopwatch

Set-PSReadLineKeyHandler -Key j -ViMode Insert -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert("j")
    $j_timer.Restart()
}

Set-PSReadLineKeyHandler -Key k -ViMode Insert -ScriptBlock {
    if (!$j_timer.IsRunning -or $j_timer.ElapsedMilliseconds -gt 1000) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert("k")
    } else {
        [Microsoft.PowerShell.PSConsoleReadLine]::ViCommandMode()
        $line = $null
        $cursor = $null
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
        [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor, 1)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor-1)
    }
}

Since this is under not-planned, it seems, I'm trying to figure out how to change this so it's jj instead of jk but I can't figure it out. Do you have any ideas how I could do that?

slacksystem avatar Jan 06 '23 18:01 slacksystem

@slacksystem try this:

$j_timer = New-Object System.Diagnostics.Stopwatch
Set-PSReadLineKeyHandler -Key j -ViMode Insert -ScriptBlock {
    if (!$j_timer.IsRunning -or $j_timer.ElapsedMilliseconds -gt 1000) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert("j")
        $j_timer.Restart()
        return
    }

    [Microsoft.PowerShell.PSConsoleReadLine]::Insert("j")
    [Microsoft.PowerShell.PSConsoleReadLine]::ViCommandMode()
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
    [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor-1, 2)
    [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor-2)
}

Drllap avatar Feb 25 '23 15:02 Drllap