5

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.

  • 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 '16 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 '16 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 '17 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? – Luc Hermitte Jan 6 '17 at 10:04
  • 1
    Just check getchar(1) to see if there is a keypress waiting on the input buffer. @statox – joeytwiddle Sep 14 '17 at 11:02
4

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)
  • Very nice. I threw this in ~/.vim/plugin/searchblink.vim; no other setup was necessary. – tremby Sep 14 '17 at 19:58
  • This script threw errors on my neovim 0.2.2 set up when using split panes. – John Oct 3 '18 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 '18 at 13:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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