6

I have an adapted version of Damian Conway's Die Blinkënmatchen in my ~/.vimrc that "highlight-flashes" search matches as I step through them with n / N:

" 'hlsearch' makes it hard to tell which is the *current* match, so we
" do some trickery to highlight that one in a special way (flashing for
" 0.4 seconds).

" function to flash the current match a number of times
function! HLNext (blink)
    let [bufnum, lnum, col, off] = getpos('.')
    let matchlen = strlen(matchstr(strpart(getline('.'),col-1),@/))
    let target_pat = '\c\%#\%('.@/.'\)'
    let i = 1
    while i < a:blink
        exec 'sleep 50m'
        let ring = matchadd('Comment', target_pat, 101)
        redraw
        exec 'sleep 50m'
        call matchdelete(ring)
        redraw
        let i += 1
    endwhile
endfunction

" Rewire n and N to step-and-call-function
nnoremap <silent> n   n:call HLNext(5)<cr>
nnoremap <silent> N   N:call HLNext(5)<cr>

This works nice, but has one drawback -- skipping through many matches takes much longer, as the next match is not highlighted until the flashing of the current one has finished.

Is there a way to have the function interrupt the highlighting / flashing if n / N is pressed again while the function still runs?

I am afraid my knowledge of Vim programming is very limited (to copy & paste, mostly), so I hope to find the necessary brainpower among you lot.

12
  • For the credit part I think the original code was made by Damian Conway for his really good More instantly better Vim talk. A tarball is available here with the examples he shows in the talk. Now about your question, I might be wrong but the blinks comes from a sleep instruction and I don't think it is possible to interrupt such an instruction.
    – statox
    Jul 19, 2016 at 9:43
  • @statox: Thank you, that was indeed the source. I would settle for interrupting the loop after the current sleep expires.
    – DevSolar
    Jul 19, 2016 at 9:46
  • 1
    @statox I had the same problem when I first tried Conway's code, and reducing the blinking time actually worked fine for me. :)
    – Rich
    Jan 6, 2017 at 9:22
  • 2
    Now we have vim8, can't you register a timer and stop the highlight either when the timer expires or when you trigger the mapping before the expiration? Jan 6, 2017 at 10:04
  • 1
    Just check getchar(1) to see if there is a keypress waiting on the input buffer. @statox Sep 14, 2017 at 11:02

1 Answer 1

5

Here is a solution using Vim's recent +timers feature (which falls back to a non-blinking temporary highlight like Damian Conway's original code if you are a using an older version of Vim that predates the +timers feature).

It sets up two timers: one infinitely repeating timer that toggles the blink (s:blink_timer_id), and a second to cancel the blinking (s:blink_stop_id) after the specified time period (s:blink_length) has elapsed. (Initially I tried doing this with a single timer that repeated the correct number of times, but that didn't work well.)

This means that Vim is not blocking while match is blinking, so you can enter new commands immediately. If you press n again, it will reset the blink timers.

It also sets up two autocommands, to cancel the blinking immediately if you move the cursor or enter insert mode.

" Modified version of Damian Conway's Die Blinkënmatchen: highlight matches
"
" This is how long you want the blinking to last in milliseconds. If you're
" using an earlier Vim without the `+timers` feature, you need a much shorter
" blink time because Vim blocks while it waits for the blink to complete.
let s:blink_length = has("timers") ? 500 : 100

if has("timers")
  " This is the length of each blink in milliseconds. If you just want an
  " interruptible non-blinking highlight, set this to match s:blink_length
  " by uncommenting the line below
  let s:blink_freq = 50
  "let s:blink_freq = s:blink_length
  let s:blink_match_id = 0
  let s:blink_timer_id = 0
  let s:blink_stop_id = 0

  " Toggle the blink highlight. This is called many times repeatedly in order
  " to create the blinking effect.
  function! BlinkToggle(timer_id)
    if s:blink_match_id > 0
      " Clear highlight
      call BlinkClear()
    else
      " Set highlight
      let s:blink_match_id = matchadd('ErrorMsg', s:target_pat, 101)
      redraw
    endif
  endfunction

  " Remove the blink highlight
  function! BlinkClear()
    call matchdelete(s:blink_match_id)
    let s:blink_match_id = 0
    redraw
  endfunction

  " Stop blinking
  "
  " Cancels all the timers and removes the highlight if necessary.
  function! BlinkStop(timer_id)
    " Cancel timers
    if s:blink_timer_id > 0
      call timer_stop(s:blink_timer_id)
      let s:blink_timer_id = 0
    endif
    if s:blink_stop_id > 0
      call timer_stop(s:blink_stop_id)
      let s:blink_stop_id = 0
    endif
    " And clear blink highlight
    if s:blink_match_id > 0
      call BlinkClear()
    endif
  endfunction

  augroup die_blinkmatchen
    autocmd!
    autocmd CursorMoved * call BlinkStop(0)
    autocmd InsertEnter * call BlinkStop(0)
  augroup END
endif

function! HLNext(blink_length, blink_freq)
  let s:target_pat = '\c\%#'.@/
  if has("timers")
    " Reset any existing blinks
    call BlinkStop(0)
    " Start blinking. It is necessary to call this now so that the match is
    " highlighted initially (in case of large values of a:blink_freq)
    call BlinkToggle(0)
    " Set up blink timers.
    let s:blink_timer_id = timer_start(a:blink_freq, 'BlinkToggle', {'repeat': -1})
    let s:blink_stop_id = timer_start(a:blink_length, 'BlinkStop')
  else
    " Vim doesn't have the +timers feature. Just use Conway's original
    " code.
    " Highlight the match
    let ring = matchadd('ErrorMsg', s:target_pat, 101)
    redraw
    " Wait
    exec 'sleep ' . a:blink_length . 'm'
    " Remove the highlight
    call matchdelete(ring)
    redraw
  endif
endfunction

" Set up maps for n and N that blink the match
execute printf("nnoremap <silent> n n:call HLNext(%d, %d)<cr>", s:blink_length, has("timers") ? s:blink_freq : s:blink_length)
execute printf("nnoremap <silent> N N:call HLNext(%d, %d)<cr>", s:blink_length, has("timers") ? s:blink_freq : s:blink_length)
4
  • Very nice. I threw this in ~/.vim/plugin/searchblink.vim; no other setup was necessary.
    – tremby
    Sep 14, 2017 at 19:58
  • This script threw errors on my neovim 0.2.2 set up when using split panes.
    – Epimetheus
    Oct 3, 2018 at 11:31
  • @John Sorry to hear that. If you tell me what the errors are, I (or someone who uses Neovim) might be able to fix them.
    – Rich
    Oct 3, 2018 at 13:20
  • @Rich : I've just used your script and need to press two time control+[ to dismiss the highlight, how to have it just one press control+[?
    – Tuyen Pham
    May 7, 2022 at 14: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.