5

In the screencast below I searched statusline and then cycle through the results with n.

Is it possible to highlight the selected match with a different color than the other matches, instead of having them all highlighted in yellow and relying only on the user to notice that the very first character of the match has the usual background color (purple in my case) instead of yellow?

enter image description here

3

4 Answers 4

3

TL;DR For those in a hurry, not interested in the process/context, etc...jump to the last two code blocks, augroup procsearch and function ProcSearch. Copy those to your vimrc for a complete solution that works seamlessly with native search (as demonstrated in the GIF).

Haven't considered this before but I think I have the seed of a solution...

If you run this command after you've started the search then the match that the cursor is on should be highlighted differently (I've tested that this is so while cycling through matches with n/N)...

:exec 'match HI /\%#' . @/ . '/'

The key is \%# which is pattern representing cursor position. (Note: if you enable 'ignorecase' I think you'll need to add \c to the beginning of this pattern.)

Replace HI with whatever highlight group you prefer. (For testing with something highly visible you can try Error.)

Disable the behavior with

:match none

Obviously you don't want to type that all in whenever you need it. The easiest thing to do is map it. Something like

:nnoremap <leader>n  exec 'match HI /\%#' . @/ . '/'<CR>

Okay so we've established that you want this behavior every time you do a search. We need to overload the search operation so that the match command is invoked immediately after the usual business.

...

I removed my previous attempts at a solution since I've got a much better approach and this thing had gotten too long. Feel free to look at the history...if you're so inclined. ;)

...

Though I earlier bemoaned the lack of an autocommand event for search turns out there is such a thing it's just more general: CmdLineEnter/CmdLineLeave. And there is a way, once those events trigger, to distinguish between : commands and search commands (/ or ?) using special string <afile>.

So now all we need is an autocommand that will call a function each time a search command is completed:

augroup procsearch
  autocmd!
  au CmdLineLeave * let b:cmdtype = expand('<afile>') | if (b:cmdtype == '/' || b:cmdtype == '?') | call timer_start(200, 'ProcessSearch') | endif
augroup END

I did the call asynchronously (timer_start()) because otherwise the / register wasn't set yet.

Here's the callback that calls the :match command per usual, including proper 'ignorecase' handling:

function! ProcessSearch(timerid)
    let l:patt = '\%#' . @/
    if &ic | let l:patt = '\c' . l:patt | endif
    exe 'match SpellRare /' . l:patt . '/'
endfunc

And you can unmap <leader>/...we don't need it anymore.

I don't always use the functional enhancements that I post as answers in my own config but this is useful enough that I believe I will. :)

demo video

Note: next steps would be to make this a little more robust. For instance, the event is triggered even when user aborts out of search with, e.g. Ctrl-C, so we should check for that. Also, using a delay before calling the function is a little sketchy so that could be tightened up a bit. I'll probably leave these to readers...StackExchange is not about posting production-ready code it's about providing specific answers to specific question, IMO.

8
  • Thanks, that's looks awesome succinct, I'll try it out tomorrow. I would indeed like this after each search and after each n. I thought I'd just remap "/" and "n" to first do their normal job and then run your command?
    – Bananach
    Jan 12, 2019 at 21:56
  • The current command maintains the highlighting through every n and N. The command only needs to be run once after the initial search. But then, yes, somehow / needs to be overloaded with search and my command. I don't know that it's as easy as it sounds. I'll try to work something out by the time you check it tomorrow.
    – B Layer
    Jan 12, 2019 at 21:59
  • Too bad there arent any autocmd events for searches...
    – D. Ben Knoble
    Jan 12, 2019 at 22:25
  • @D.BenKnoble Ha, yes, I checked help to make sure I hadn't forgot about the existence of such events.
    – B Layer
    Jan 12, 2019 at 22:29
  • @BLayer you might be interested in the solution that I just posted. It's piecemeal from previous stackexchange questions but I think it pieces together quite nicely.
    – Bananach
    Jan 13, 2019 at 17:00
3

I wrote a plugin for this: vim-current-search-match.

2
  • Nice one as always! Is there a particular reason why you'd choose to default to PMenuSel rather than IncSearch (which is the default while typing a search)?
    – Biggybi
    Feb 11, 2021 at 8:42
  • 1
    Thanks! No strong reason; in the colorscheme I like to use, IncSearch is the same as Search. Also I thought PMenuSel was sort-of appropriate, representing the selected option among several. Feb 11, 2021 at 12:07
1

Here is my solution, based on the answer of B Layer and those of many other stackexchange heroes.

It supports incremental search highlighting as well and keeps the highlight color of the incremental search highlight after pressing Enter and after pressing n and N.

enter image description here

This is the responsible code. (It includes changing the statusline color according to whether there are any matches at all or not. For example, you can see it switch to red when I type statuslim accidentally. Not included is the code that highlights the linenumber column in red and the fact that I am using the plugin romainl/vim-cool to remove search highlights when the cursor isn't on a match)

function! HighlightSearch(timer)
    if (g:firstCall)
        let g:originalStatusLineHLGroup = execute("hi StatusLine")
        let g:firstCall = 0
    endif
    if (exists("g:searching") && g:searching)
        let searchString = getcmdline()
        if searchString == "" 
            let searchString = "."
        endif
        let newBG = search(searchString) != 0 ? "green" : "red"
        if searchString == "."
            set whichwrap+=h
            normal h
            set whichwrap-=h
        endif
        execute("hi StatusLine ctermfg=" . newBG)
        let g:highlightTimer = timer_start(50, 'HighlightSearch')
        let g:searchString = searchString
    else
        let originalBG = matchstr(g:originalStatusLineHLGroup, 'ctermfg=\zs[^ ]\+')
        execute("hi StatusLine ctermfg=" . originalBG)
        if exists("g:highlightTimer")
            call timer_stop(g:highlightTimer)
            call HighlightCursorMatch()
        endif
    endif
endfunction
function! HighlightCursorMatch() 
    try
        let l:patt = '\%#'
        if &ic | let l:patt = '\c' . l:patt | endif
        exec 'match IncSearch /' . l:patt . g:searchString . '/'
    endtry
endfunction
nnoremap n n:call HighlightCursorMatch()<CR>
nnoremap N N:call HighlightCursorMatch()<CR>
augroup betterSeachHighlighting
    autocmd!
    autocmd CmdlineEnter * if (index(['?', '/'], getcmdtype()) >= 0) | let g:searching = 1 | let g:firstCall = 1 | call timer_start(1, 'HighlightSearch') | endif
    autocmd CmdlineLeave * let g:searching = 0
augroup END
1

I just wrote a vim plugin for this: https://github.com/borisbrodski/vim-highlight-hero

Highlight current word using default color, color1, color2, ...

  • :HH
  • :1HH
  • :2HH

Highlight "string" using default color, color1, color2, ...

  • :HH string
  • :1HH string
  • :2HH string

Help:

  • :help highlight-hero

Bonus: Turn on auto-highlighing! (Navigate around to see the impact):

  • :HHauto

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