1

I'm trying to make a mapping which does one thing on the first press of a key, and also does something else if I press the key again within timeout. Specifically:

nnoremap <silent> <Esc><Esc> :set hls<CR>:let @/="\\<<C-R>=expand('<cword>')<CR>\\>"<CR>
nnoremap <silent> <Esc> :noh<CR>

…I want pressing Esc in normal mode to stop highlighting the current search (immediately, without waiting for timeout), and pressing Esc again to highlight the word under the cursor.

Things I've tried:

  • These mappings as-is: success, but there's a distracting delay when I press Esc once.

  • Adding <nowait> to the second mapping: it consumes the key press so the first mapping never happens.

  • Adding <nowait> to the second mapping, and <Esc> at the end: <Esc> does not put Vim in "waiting for another key" mode (presumably because its a non-recursive mapping).

  • Adding <nowait> and :call feedkeys("\<lt>esc>")<CR> (with various options) to the second mapping: hang.

Solutions that seem plausible:

  • Temporarily remove the <Esc> mapping, send <Esc>, and re-add it.
  • Have only one mapping which knows how to wait for a second Esc.
  • Somehow put Vim into a "waiting for more keys" mode with <Esc> in the buffer.
  • Having a mapping which behaves like <nowait> but doesn't "consume" the key press.

Any ideas?

5
  • Some options have toggling feature. Related one : stackoverflow.com/questions/762515/…
    – SibiCoder
    Mar 9, 2017 at 18:04
  • you can get the options set, for example whether highlight is set or not. You can code vimscript, like if else statements and react based on that.
    – SibiCoder
    Mar 9, 2017 at 18:05
  • I'm not sure what's your problem with your solution, because you would have the timeout delay anyway... So you can reduce the timeoutlen value to reduce the delay.
    – nobe4
    Mar 9, 2017 at 18:31
  • @SibiCoder Toggling isn't the right behavior. I want to be able to press Esc at any time to turn off highlighting, without waiting for timeout.
    – s4y
    Mar 9, 2017 at 18:45
  • 1
    @nobe4 I'm trying to get the first press to clear the highlight immediately, and a second press within timeout to highlight the word under the cursor.
    – s4y
    Mar 9, 2017 at 18:47

2 Answers 2

2

I found a way: use <Plug>, an un-typeable "key" intended for mappings to call each other, to let the single-Esc mapping prime the other one:

noremap <silent> <Plug><Esc> :set hls<CR>:let @/="\\<<C-R>=expand('<cword>')<CR>\\>"<CR>:call histadd("/", @/)<CR>
nmap <silent> <Esc> :noh<CR><Plug>

This could be enhanced by adding a name (<Plug><Plug>HLSearch) and/or <SID> to avoid a conflict with another mapping:

noremap <script> <silent> <SID><Plug><Esc> :set hls<CR>:let @/="\\<<C-R>=expand('<cword>')<CR>\\>"<CR>:call histadd("/", @/)<CR>
noremap <script> <silent> <Esc> :noh<CR><SID><Plug>
1
  • Adding the <SID> is important I think as it could otherwise collide with mapping by some other plugin. The only downside of this solution is that is sounds the bell when you type only one esc because it does not recognize that mapping.
    – Octaviour
    Jun 8, 2017 at 12:17
0

You can try toggling on/off. A sample vimscript like

func ToggleHLSearch()
  let hlsearch_opt = &hlsearch
  if(hlsearch_opt == 0)
     set hlsearch           "Sets hlsearch on
  else
     set nohlsearch
  endif
endfunc

can help.

Nw, map it to a key like F9

:nnoremap <F9> :call ToggleHLSearch()

(Haven't tested the code Yet. Just a sample)

2
  • Thanks, I appreciate the snippet! This isn't exactly what I want, see above.
    – s4y
    Mar 9, 2017 at 18:49
  • 1
    Wouldn't it be better to just toggle whether the 'hlsearch' highlighting is active instead of whether the option is active? That can trivially be done with :let v:hlsearch = !v:hlsearch.
    – jamessan
    Mar 9, 2017 at 21:31

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.