25

With the current colourscheme I have been using, it is hard to find where the cursor is positioned when navigating through search results. The highlighting colour and the cursor colour are blending in making it hard to find the cursor.

How do I set the cursor colour different when it is on a highlighted word?

Screenshot of the problem

6 Answers 6

17

I use this snippet from Damian Conway's fantastic talk, More Instantly Better Vim (at 4m 59s). It causes the entire highlight to blink briefly when you leap between search results.

" Damian Conway's Die Blinkënmatchen: highlight matches
nnoremap <silent> n n:call HLNext(0.1)<cr>
nnoremap <silent> N N:call HLNext(0.1)<cr>

function! HLNext (blinktime)
  let target_pat = '\c\%#'.@/
  let ring = matchadd('ErrorMsg', target_pat, 101)
  redraw
  exec 'sleep ' . float2nr(a:blinktime * 1000) . 'm'
  call matchdelete(ring)
  redraw
endfunction

If you want the change in highlighting to be more persistent, you could tweak this to call matchdelete at some other time (e.g. when the cursor moves).

UPDATE:

I recently wrote this more advanced version in response to another question. It has the following changes::

  1. (Optionally) blinks repeatedly: Conway's original version just blinks once: on and then off,
  2. Allows you to interrupt the blinking by typing other commands (such as pressing n again to jump to the next match.) This allows you to set a longer time

UPDATE 2:

The vim-searchhi plugin provides the originally requested feature.

5
  • Works great. I will be using this from now. I will try to implement the persistent one sometime later.
    – ma08
    Mar 27, 2015 at 18:02
  • What do the first 2 lines do? As far as I can see, the assigned variables aren't used, and if I remove those lines, it still works. May 3, 2015 at 23:02
  • @Carpetsmoker Those lines (now deleted) were good old-fashioned cruft. If you watch the talk, you'll see that Conway arrives at this solution after a few iterations. The unnecessary variables were used in the earlier variations, but not in this one. I copied it over into my .vimrc before I really started learning vimscript, and obviously never looked at the code again, even when pasting it into my answer! Well spotted.
    – Rich
    May 5, 2015 at 11:29
  • I recently replaced the vim-pulse (i think it's called) addon with this snippet and it's much better/faster
    – craigp
    May 7, 2015 at 7:49
  • @Badger Glad to hear it. :)
    – Rich
    Jun 26, 2015 at 9:35
7

Based on Rich's answer here with some differences:

You can clear the highlighting with <C-l>.

fun! SearchHighlight()
    silent! call matchdelete(b:ring)
    let b:ring = matchadd('ErrorMsg', '\c\%#' . @/, 101)
endfun

fun! SearchNext()
    try
        execute 'normal! ' . 'Nn'[v:searchforward]
    catch /E385:/
        echohl ErrorMsg | echo "E385: search hit BOTTOM without match for: " . @/ | echohl None
    endtry
    call SearchHighlight()
endfun

fun! SearchPrev()
    try
        execute 'normal! ' . 'nN'[v:searchforward]
    catch /E384:/
        echohl ErrorMsg | echo "E384: search hit TOP without match for: " . @/ | echohl None
    endtry
    call SearchHighlight()
endfun

" Highlight entry
nnoremap <silent> n :call SearchNext()<CR>
nnoremap <silent> N :call SearchPrev()<CR>

" Use <C-L> to clear some highlighting
nnoremap <silent> <C-L> :silent! call matchdelete(b:ring)<CR>:nohlsearch<CR>:set nolist nospell<CR><C-L>
3

You can choose a better color. Just put this on your vimrc:

hi Cursor guifg=#121212 guibg=#afd700
2
  • That's always an option, I am just looking to see if it's possible to set colour according to the context.
    – ma08
    Mar 27, 2015 at 13:55
  • I don't think that is usefull, but you can detect if the search pattern (on / or #) is the same as the word under cursor and change the Cursor highlight
    – adelarsq
    Mar 27, 2015 at 14:10
3

The only way I can think of is by using the CursorMoved and CursorMovedI autocmds, these are invoked every time the cursor is moved...

Here is some code which does exactly that:

fun! CursorColour()
    if !g:searching | return | endif

    let l:prev_end = 0

    " Loop until we've found all matches of the current search pattern
    while 1
        let l:start = match(getline('.'), @/, l:prev_end)

        " No more matches: stop looping
        if l:start == -1 | break | endif

        let l:cursor = col('.')
        " Check if the cursor is inside the string
        if l:cursor > l:start && l:cursor <= l:start + strlen(@/)
            hi Cursor guifg=#121212 guibg=#afd700
            return
        endif

        " Set start location for next loop iteration
        let l:prev_end = l:start + strlen(@/)
    endwhile

    " We return if we're on a highlighted string, if we're here we didn't
    " match anything
    call ResetCursorColour()
endfun

fun! ResetCursorColour()
    hi Cursor guifg=#ffffff guibg=#000000
endfun

autocmd CursorMoved,CursorMovedI * call CursorColour()

let g:searching=0
nnoremap <silent> <C-L> :nohlsearch<CR>:let g:searching=0<CR>:call ResetCursorColour()<CR><C-L>
nnoremap / :let g:searching=1<CR>/

There are some caveats:

  • This only works for gVim, as changing the cursor colour isn't possible in terminal Vim (this is something you configure in your terminal emulator, and not something Vim can control in most terminals).

  • I can't find an obvious way to check if the character I'm on is part of the current search term and/or is being highlighted; the only way is to get the line, and regexp that (again) with @/. There may be some minor differences on regexp interpretation between / and match() depending on your settings (see :help match()).

  • I can't find out a way to check if we're currently actually highlighting something, the @/ register contains the last used search, regardless of what we're highlighting. So what I did is remap / to first set the g:searching variable to indicate we're doing an active search, and with <C-l> we call :nohlsearch to clear the highlighting, and set g:searching to false to indicate that we're not searching...

  • This is run every time the cursor moves, it may not be very fast especially for large files/lines, slow systems or complicated regexps (it seems to work okay for me though).

1

To keep things minimal, but still works perfectly for me, I have this, inspired from above : plain highlight until CursorMoved :

function! HLNext()
  let l:higroup = matchend(getline('.'), '\c'.@/, col('.')-1) == col('.')
              \ ? 'SpellRare' : 'IncSearch'
  let b:cur_match = matchadd(l:higroup, '\c\%#'.@/, 101)
  redraw
  augroup HLNext
    autocmd CursorMoved <buffer>
                \   execute 'silent! call matchdelete('.b:cur_match.')'
                \ | redraw
                \ | autocmd! HLNext
  augroup END
endfunction
nnoremap <silent> * *:call HLNext()<CR>
nnoremap <silent> # #:call HLNext()<CR>
nnoremap <silent> n n:call HLNext()<cr>
nnoremap <silent> N N:call HLNext()<cr>

Now, n even without hlsearch shows me the where I landed until I move the cursor. The SpellRare is used to make it more ovbious when only a single character matches, otherwise it's the smooth IncSearch

0

I tried some of the other answers listed here and the solution here but found them distracting and prone to error messages. timakro has written a plugin that solves the problem and is working well for me so far.

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.