10

I like the behavior of the 'smartcase' option, which lets searches I type in by hand be case-insensitive by default, case-sensitive when there are any capital letters. I also like the behavior of the 'noignorecase' option, which lets searches generated by the editor (as when hitting * or #) be case-sensitive by default.

Unfortunately, 'smartcase' only does its magic when 'ignorecase' is on, so now I have a conflict: I can either get the nice behavior for hand-generated searches or the nice behavior for machine-generated searches, but not both.

How I can I get both nice behaviors?

2 Answers 2

13

You can fix that for the * command by putting the following mapping in your ~/.vimrc.

nnoremap * <silent> *N:let @/.='\C'<CR>n

When you type *, the mapping first executes the normal * command, then jumps back to the original word (N), then appends \C to the search expression (:let @/.='\C'<CR>), which makes it case-sensitive, and finally jumps forward again (n).

A complete compendium of bindings which doesn't clobber jumps (using Vitor's trick from the comments) and supports both forward and backwards search looks like this:

nnoremap <silent>  * :let @/='\C\<' . expand('<cword>') . '\>'<CR>:let v:searchforward=1<CR>n
nnoremap <silent>  # :let @/='\C\<' . expand('<cword>') . '\>'<CR>:let v:searchforward=0<CR>n
nnoremap <silent> g* :let @/='\C'   . expand('<cword>')       <CR>:let v:searchforward=1<CR>n
nnoremap <silent> g# :let @/='\C'   . expand('<cword>')       <CR>:let v:searchforward=0<CR>n
6
  • 1
    KISS: :nnoremap <silent> * :let @/='\C\<' . expand('<cword>') . '\>'<CR>n (the last S stands for "solution";)
    – Vitor
    Jul 29, 2015 at 13:17
  • @Vitor I like the idea of your solution because it doesn't clobber the jump registers, but it doesn't seem to work: after executing that nnoremap, the * key doesn't seem to do anything. Jul 29, 2015 at 21:27
  • @Vitor Oops, I put the <silent> in the wrong place -- as did garyjohn! Your code works well, thanks. Jul 29, 2015 at 21:35
  • How could this solution be implemented in Lua / Neovim.
    – 71GA
    Dec 19, 2022 at 9:51
  • @71GA I migrated this to my init.vim when I switched, and it has continued to work just fine. Dec 22, 2022 at 17:54
3

Here's a version of garyjohn/Vitor's solution that is a closer match to *'s behaviour in a few ways.

Making it an expr map fixes two things:

  • 'hlsearch' -- turns on highlight immediately instead of after next n
  • counts -- 3* skips the first two results

And explicitly handle some others:

  • # when in the middle of a word -- jump to previous match instead of start of current match
  • empty strings -- error
  • nonwords -- * on ) at end of line doesn't do wholeword
function! s:SearchCword(wholeword, direction) abort
    let query = expand('<cword>')
    if empty(query)
        echohl ErrorMsg
        echomsg "E348: No string under cursor"
        echohl None
        return ""
    endif
    " Doing * on a nonword character at end of line produces no word
    " characters so wholeword is invalid.
    if a:wholeword && query =~# '\w'
        let query = '\<'.. query ..'\>'
    endif
    let @/ = query ..'\C'
    let searchforward = a:direction ==# 'n'
    let suffix = ''
    if !searchforward
        " v:searchforward is reset after functions so it must be part of our
        " returned command instead.
        let suffix = ":let v:searchforward=0\<CR>"
    endif
    if searchforward || s:IsCursorAtStartOfWord(expand("<cword>"))
        return a:direction .. suffix
    endif
    return a:direction..a:direction .. suffix
endfunction
function! s:IsCursorAtStartOfWord(query) abort
    let index_in_word = match(getline('.'), '\%' . col('.') ..'c'.. a:query)
    return index_in_word > 0
endfunction
nnoremap <expr>  * <SID>SearchCword(1, "n")
nnoremap <expr>  # <SID>SearchCword(1, "N")
nnoremap <expr> g* <SID>SearchCword(0, "n")
nnoremap <expr> g# <SID>SearchCword(0, "N")

I've added SearchCword to vim-searchsavvy as searchsavvy#SearchCword(). It supports a few more options (like making * follow 'smartcase' rules instead of always case sensitive).

3
  • Cool. I wonder if the echoerr can be replaced by throw? Although honestly I’m pretty sure I have both smartcase and ignorecase set and don’t have this issue with *, but I could be misremembering.
    – D. Ben Knoble
    Sep 21, 2021 at 21:20
  • 1
    throw causes "Error detected while processing..." but we want an error message like the default behaviour. (You got my hopes up for a version of echoerr that's good for producing pretty error messages. : )
    – idbrii
    Sep 21, 2021 at 21:53
  • 1
    With set smartcase, * on "FizzBuzz" will match "Fizzbuzz". The smart logic only applies to user typed searches. The help suggests doing /<Up>
    – idbrii
    Sep 21, 2021 at 21:54

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.