4

What I want to do is have two mappings. One mapped to <s-k>, and one mapped to <s-k><s-k>. What happens right now is that if I press <s-k> vim delays for timeoutlenms, and if I don't press anything it does the <s-k> action, and if I press another <s-k> in that time it does the <s-k><s-k> action.

However, what I want it to do is do BOTH, i.e. if I press <s-k> it will do the <s-k> action immediately, and if I press another <s-k> in the timeoutlen period, it will do the second command as well.

3
  • 1
    I think you'll have to build that logic yourself. Write a single mapping for <s-k> (btw isn't that just K?) that controls whether it's called once or twice (hint: use a global or script variable) and acts accordingly. You can use the CursorHold or CursorMoved events to reset the variable, that's probably close enough to the timeoutlen behavior, if not exactly the same...
    – filbranden
    Jan 19, 2020 at 18:27
  • Just to clarify, if I press KK, should both mappings take effect? Or only the second?
    – D. Ben Knoble
    Jan 19, 2020 at 19:35
  • 1
    @D.BenKnoble both
    – Max Coplan
    Jan 19, 2020 at 20:00

2 Answers 2

4

It's not possible to achieve this with regular mappings of K and KK. If the K mapping fires then Vim is necessarily no longer waiting to see if KK is typed, and there are no configuration options that can change this behaviour.

There are ways to workaround this, however. filbranden suggested one possible method in the comments which D. Ben Knoble has implemented.

Rather than rely on autocommands to reset the state, I'd probably check the time since we last fired the mapping directly in the mapping itself by using reltime():

function KFunc() abort
  if !exists("s:last_press") || reltimefloat(reltime(s:last_press)) > &timeoutlen / 1000.0
    " do one

    " remember when we fired the mapping
    let s:last_press = reltime()

  else
    " do two

    " reset the time
    unlet s:last_press
  endif
endfunction

nnoremap K :call KFunc()<CR>
2
  • reltime was clever. +1 from me.
    – D. Ben Knoble
    Jan 20, 2020 at 23:26
  • @D.BenKnoble FWIW, I upvoted your answer too :).
    – Rich
    Jan 21, 2020 at 13:59
3

I think something like this would do:

let s:pressed = 0

function KFunc() abort
  let s:pressed += 1
  if s:pressed == 1
    " do one
  else if s:pressed == 2
    " do two
    let s:pressed = 0
  endif
endfunction

augroup ResetPressed
  au!
  au CursorMoved,CursorMovedI,CursorHold,CursorHoldI * let s:pressed = 0
augroup END

nnoremap K :call KFunc()<CR>

I included the insert mode variants of cursor moved/hold, but you probably don't need them unless it's an insert-mode map.

As @Rich points out in the comments, CursorHold and friends fire after 'updatetime', not 'timeoutlen'. Changing 'updatetime' has implications for swap files, and fixing the code seems tedious. That said, I changed 'updatetime' to 250ms (down from the default 4s) without any adverse effects.

3
  • 1
    Probably worth noting that the Hold events fire after 'updatetime' rather than 'timeoutlen': by default these are different values, (and updatetime also affects the swap file behaviour, which you should be aware of before changing it.)
    – Rich
    Jan 20, 2020 at 15:26
  • Thinking about it further, I guess if it were a problem, you could make the function/autocommand change updatetime to match timeoutlen only while it's waiting to see if the second part of the mapping is going to fire.
    – Rich
    Jan 20, 2020 at 17:20
  • 1
    @Rich interesting solution! Good points (ive edited them in).
    – D. Ben Knoble
    Jan 20, 2020 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.