209

Can we do case-insensitive substitution and preserve existing case in Vim?

What I mean is: searching for BadJob and replacing with GoodJob would do the following replacements:

'badjob' -> 'goodjob'  
'BadJob' -> 'GoodJob'  
'badJob' -> 'goodJob'  
'BADJOB' -> 'GOODJOB'

Similar question for Visual Studio: Case preserving find/replace in Visual Studio

1
  • 36
    This should be a vim feature. It makes so much sense. Commented Jul 3, 2014 at 7:58

9 Answers 9

185

Use abolish.vim:

:%S/badjob/goodjob/g
Sign up to request clarification or add additional context in comments.

7 Comments

We do not deserve Tim Pope.
This plugin doesn't seem to work for me. If I a word like BadJob and I want to replace it with GoodJob, I can't use %S/badjob/goodjob/g. It fails to detect a match.
@Roymunson What you need to do is this: %S/BadJob/GoodJob/g, then the Subvert command will switch to mixed-case mode and will do all the substitions as given by OP.
@shivams Does the presence of mixed case anywhere in the arguments to %S activate mixed-case mode, or does the input argument BadJob need to literally match the thing you're trying to replace? The former seems like strange ux, and the latter seems to defeat the purpose.
With this answer, how would abolish know to capitalise the 'J' in GoodJob? I would imagine at the very least one needs %S/BadJob/GoodJob/g
|
29

I don't know if this is the kind of solution you're looking for... but i've used this: keepcase.vim

There's no support otherwise in vim...

Comments

19

What about

:%s/\Cbadjob/goodjob/
:%s/\CBadJob/GoodJob/
:%s/\CbadJob/goodJob/  
:%s/\CBADJOB/GOODJOB/

See: https://stackoverflow.com/a/2287449/5599687

3 Comments

What does the \C (or \c in other answers) do?
@AlastairG it forces case match search: linuxize.com/post/vim-find-replace/#case-sensitivity
So :%s/\Cbadjob/goodjob/ is the same as :%s/badjob/goodjob/c. I've never seen that syntax of adding flags before. Thanks.
14

sure u can

:s/\cbad/\= strpart(submatch(0), 0 ,1) == toupper(strpart(submatch(0), 0, 1)) ? "GOOD" : "good"/

ps. i'm guessing keepcase.vim encapsulates some similar logic :)

4 Comments

Yes it does since 2007: :%SubstituteCase/\cbadjob/GoodJob/g ^^
Meaning since gVim 7.2? I tried it in gVim 7.1 (12-May-2007) and it din't work :(
No, I've added the :SubstituteCase command to the plugin in 2007. That's all. The plugin is available on vim.org, and it is not shipped with vim as usual with with most plugins.
1. This fails when the user has :set ignorecase. 2. Bad will be substituted by GOOD instead of Good. 3. The "job" part of the question is ignored, so this will also replace lambadalamgooda. Fixes and explanations for these bugs and a few other things in my answer. (Also LOLOWLs!)
10

For most (non-complex) cases, i recommend @rampion’s answer over mine.
If you got a minute, my post might be still be worthwhile, though. Level up your awareness for scripting gotchas.



You can just paste and adapt this:
(Of course, if you do this from time to time, you will want a plugin instead of this monstrosity. But for some who are in a hurry and only need it once, this is a quick hack for your pasting pleasure:)

:%s/\cbad\zejob/= ( submatch(0)[0] is# toupper(submatch(0)[0]) ? 'G' : 'g' ) . ( submatch(0)[1] is# toupper(submatch(0)[1]) ? 'OOD' : 'ood' )

Apart from the search pattern, you have to edit the four 'strings' in the replacement code: Edit the parts in bold:

:%s/\cbad\zejob/=
( submatch(0)[0] is# toupper(submatch(0)[0]) ? 'G' : 'g' ) .
( submatch(0)[1] is# toupper(submatch(0)[1]) ? 'OOD' : 'ood' )

Don't use this 'orange' version for pasting, since its linebreak characters will also break the command.

/\ze is vim regex syntactic sugar for marking a positive lookahead: The pattern after \ze is checked for, but not substituted.


*`is#`*?? Let me explain… (If interested.)

# (also in ==# and others) enforces case sensitivity. Otherwise, with :set ignorecase (which I use, because that is required for the useful :set smartcase), vim will consider 'a' == 'A'!!

Crazy as it is, we really should account for it: Because it is user-settings-dependent, == should NEVAR be used! (Except where that would actually be what you want.) I will even follow the recommendation to use ==# when comparing integers: http://learnvimscriptthehardway.stevelosh.com/chapters/22.html#code-defensively

is# instead of ==# is another way of coding defensively: It improves type safety: http://google.github.io/styleguide/vimscriptguide.xml?showone=Type_checking#Type_checking
It should be used when comparing against a string literal.

'single-quoted' instead of "double quoted" strings are another good practice: http://google.github.io/styleguide/vimscriptguide.xml?showone=Strings#Strings


HT @fc. - this answer builds on their [answer](https://stackoverflow.com/questions/782511/case-preserving-substitute-in-vim/782617#782617), fixing a few shortcomings.

Comments

8

If you're only matching an exact (case-independent) string with a few possible capitalizations, another possibility is:

:s/abc/\={'abc':'xyz','Abc':'Xyz'}[submatch(0)]/i

Comments

2

An alternative to the keepcase plugin is SmartCase - replacing words while keeping original case. (Don't let yourself be discourage by the bad ratings.)

2 Comments

Is there a trick to shorten those hard to remember and tedious to type commands like :%s/file\A\?size/\=SmartCase("LastModifiedTime")/ig?
@MichaelHärtl: You can use the :SmartCase command. I've extended that in my own fork. Note that this requires ingo-library as a dependency.
2

All the problem is you have set: ignorecase in your .vimrc probably

You can program Vim to do sensitive/insensitive operation during both search and replace!

"Ctrl-h activating:
nnoremap <ctrl-h>  <Esc>:set noignorecase<CR><CR>:s%//<Right><Right><Right>

" Insensitive:
inoremap / <Esc>:set ignorecase<CR><CR>/<Right>
nnoremap / <Esc>:set ignorecase<CR><CR>/<Right>

" Case SENSITIVE search: (using key \ to search):
nnoremap \ <Esc>:set noignorecase<CR><CR>/<Right>

Comments

0

Of course, you can do this:

First declare some variables containing cases and replacements:

let a = ['badjob', 'BadJob', 'badJob', 'BADJOB']

let b = ['goodjob', 'GoodJob', 'goodJob', 'GOODJOB']

Second, apply a for loop as follow:

for i in range(0,3) | exec '%s/' . a[i] . '/' . b[i] . '/g' | endfor

Note that range function starts at 0.

Best regards.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.