1

I've found this little trick in vim wiki.

nnoremap <Leader>s :%s/\<<C-r><C-w>\>//g<Left><Left>

Immediately I thought that in some cases it would be beneficial to use word in substitution, so I've added capturing parentheses for back reference.

nnoremap <leader>* *:%s/\v(<<C-r><C-w>>)//g<Left><Left>

I'm using this primarily as a replacement for rename refactoring, so I thought: "You know what? In all those IDEs when I've renamed things it's always been a little pain to change one or two words in multiword variable".

I mean something like first in first_second_third or FirstSecondThird or firstSecondThird. Thanks to extensibility of vim this should be easy to do. I just need to split the word under cursor by rule that depends of whether its a camel case word of snake case word (easy to determine if we would take as a rule, that the word either a snake case or camel case, but not both). And then to produce the argument to the substitute command with each actual word in variable name surrounded by capturing parens, like so.

%s/\v(first)_(second)_(third)//g

With that if I need to change second word all I need to do is enter

\1something_other\2

Which is pretty neat in my opinion. The problem is that I don't know how to make a mapping call substitute with dynamic parameters.

  • Do you want to substitute A with B irrespective of its lower,upper and Snake case? Many plugins do that. – SibiCoder Jun 9 '16 at 5:17
  • 1
    No, the point here is to substitute part of the word under cursor faster. – user1685095 Jun 9 '16 at 5:21
3

I'm not sure but maybe the following code could work:

nnoremap <expr> <Leader>s <SID>change_word_under_cursor()
fu! s:change_word_under_cursor() abort
    let word = expand('<cword>')
    let subwords = split(word, word =~# '_' ? '_' : '\ze\u')
    return ':%s/\v' . join(map(subwords, '"(" . v:val . ")"'), word =~# '_' ? '_' : '') . '//g' . "\<Left>\<Left>"
endfu

<expr> is an argument that you can pass to a mapping command such as :nnoremap. It tells Vim to not consider the {rhs} as keys to type, but as an expression to evaluate, and then type the evaluation as keys.

Here the expression is the function s:change_word_under_cursor.
In order to not pollute your global namespace, it's local to the script where you'll write the code.
The problem is that when you'll hit <Leader>s, the function will be called from outside the script, and Vim won't know where to look for it.
The solution is to add <SID> in front of the name of the function. When your mapping will be sourced and add an entry in the mappings table, <SID> will be translated into something like <SNR>{some_number}_. {some_number} will tell Vim in which script to look for the function.


The function does the following thing:

let word = expand('<cword>')

Store the word under the cursor inside the variable word.

let subwords = split(word, word =~# '_' ? '_' : '\ze\u')

word =~# '_' ? '_' : '\ze\u' is an expression using the ternary operator ?: which checks whether the word contains an underscore. If it does, its evaluation is the string '_' otherwise the string '\ze\u'. This string will be used by the split() function to know where to split the word.

split(word, …) splits the word every time an underscore or an uppercase letter is found, and returns a list containing the result.

return ':%s/\v' . join(map(subwords, '"(" . v:val . ")"'), word =~# '_' ? '_' : '') . '//g' . "\<Left>\<Left>"

Return the keys to type.

map(subwords, '…') applies a transformation on each word inside the list subwords, which surrounds them with parentheses.
And join(map(…), word =~# '_' ? '_' : '') concatenates the items inside the list by adding an underscore or nothing between 2 consecutive items.


You can find more information on this with:

:h <expr>
:h <sid>
:h /\ze
:h split()
:h map()
:h join()
  • It basically works! Thanks! Can you explain what all this <expr and <SID> is doing? Like what should I search in help? – user1685095 Jun 8 '16 at 19:55
  • 1
    I wonder if you can use input here too, and just run the :s command directly. – muru Jun 8 '16 at 20:00
2

Plugins like KeepCase already defines commands to substitute things keeping the case.

However, unlike what you have specified, in foo_foingFoo, :SubstituteCase/\cfoo/Bar/g will transform the word into bar_baringBar.

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.