2

Recently I learned the awesome command cgn to repeat a change to a word. I’ve seen several answers advising its use, like this:

What is the best way to re-factor a variable name in vim?

and this:

How to find and replace in Vim without having to type the original word?

But the problem is that I can't add a repeat prefix to it. If I have, say, 10-20 occurrences, and I want to change them all, it's rather anoying to type 20 dots. If I add a prefix it jumps the lines and changes only one occurence, instead of repeating the whole change.

I can add a macro:

/foo<cr>qacgnbar<esc>.q20@a

But it's 5 keystrokes more. Well.. it's better than 10 or 20 keystrokes, but I thought there could be a better option.

How can I repeat cgn a number of times ?

1
  • :%s//\=@./g if you've already searched (/) for "foo" and changed (c) it to "bar" earlier.
    – Matt
    Commented Jun 20, 2020 at 8:37

3 Answers 3

1

How can I repeat cgn a number of times ?

Try this mapping:

nno <expr> <leader>. '<esc>' . repeat('.', v:count1)

The purpose of <esc> is to cancel the count. For example, if you press:

3 <leader>.

in the typeahead buffer, <leader>. is expanded into ..., and the resulting sequence is:

3...

3. makes Vim execute 3cgn; that is replace the third occurrence of the search register. This behavior is documented at :h .:

Simple changes can be repeated with the "." command. Without a count, the count of the last change is used. If you enter a count, it will replace the last one. |v:count| and |v:count1| will be set.

But:

  • that's not what you want
  • the count is not needed in that position, since repeat() already uses it later to repeat . as many times as desired

Btw, if <esc> triggers an audible/visual bell, try to replace it with <c-\><c-n> (see :h CTRL-\_CTRL-N):

nno <expr> <leader>. '<c-\><c-n>' . repeat('.', v:count1)

For more info, see:

:h map-<expr>
:h repeat()
:h v:count1
4
  • 1
    Thanks :) about the :hs but I already understood the whole line. We discussed your solution in a Telegram group about VIM that I'm in. You used '<esc>' to return to normal mode instead of :normal in order to avoid the substitution of count by a range. Then you included the first dot in order to be able to repeat the operation, but that moves the count value which were in v:count to v:count1, so you have to call repeat on v:count1. Is that it ? Commented Jun 22, 2020 at 18:15
  • Yes, that's more or less the explanation; I updated the answer to give more details.
    – user938271
    Commented Jun 22, 2020 at 18:31
  • Perfect. Thanks. About "Btw, if <esc> triggers an audible/visual bell", it doesn't but again thanks. Commented Jun 22, 2020 at 18:37
  • The only problem is that I was wrong: that version won't work with Vrapper. Only the first one I posted works Commented Jun 22, 2020 at 18:50
1

Your macro actually does unnecessary work, since it does cgn and . on every invocation.

/foo<CR>cgnfoo<Esc>qa.q

Isn’t really any shorter, but it does have the advantage of being easier to use with a count and also separating the action from the repeat.

1
  • Personally i don’t mind several dots but that’s me
    – D. Ben Knoble
    Commented Jun 20, 2020 at 12:07
0

A user in a Telegram group dedicated to VIM showed me a very good solution:

:nnoremap <leader>. :normal .<cr>

this remaps <leader>. to allow a prefix with repeat count. In my case is mapped to the default value \ So I just type say 10\. and it repeats cgn 10 times.

It even works in Eclipse's Vrapper.

But if anyone has a better solution I'd like to hear.

4
  • 1
    It won't work as expected when you have several matches on the same line. That's because when you hit a count, it's translated into a range. And when :norm is prefixed by a range, the cursor is automatically positioned on the first column of each line in the range. IOW, after executing each ., :norm repositions the cursor on the next line. See :h :normal-range. Also, even if it worked, you would probably want a bang: :norm!. In any case, try this instead: nno <expr> <leader>. '<esc>'..repeat('.', v:count1).
    – user938271
    Commented Jun 20, 2020 at 9:51
  • @user938271 I got an erro trying what you said: 5: Expressão inválida: .repeat('.', v:count1) E15: Expressão inválida: '^['..repeat('.', v:count1) . "Expressão inválida" is portuguese for "invalid expression". I don't have any experience in the keymap thing. If I needed to translate something, please clarify. Better still if you could create an answer with your solution. I confirm that it doesn't work with 2 targets in the same line. Commented Jun 21, 2020 at 3:13
  • You probably use an old Vim version which doesn't support the concatenation operator ..; replace it with a single dot ..
    – user938271
    Commented Jun 21, 2020 at 10:14
  • @user938271 worked fine. Including VRapper. Thanks. Don't you want to create an answer so I can accept/upvote it ? Commented Jun 22, 2020 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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