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 ?

D. Ben Knoble
  • 26,070
  • 3
  • 29
  • 65
Nelson Teixeira
  • 319
  • 2
  • 14
  • :%s//\=@./g if you've already searched (/) for "foo" and changed (c) it to "bar" earlier. – Matt Jun 20 '20 at 08:37

3 Answers3

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.

D. Ben Knoble
  • 26,070
  • 3
  • 29
  • 65
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
user938271
  • 5,947
  • 1
  • 15
  • 24
  • 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 '' 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 ? – Nelson Teixeira Jun 22 '20 at 18:15
  • Yes, that's more or less the explanation; I updated the answer to give more details. – user938271 Jun 22 '20 at 18:31
  • Perfect. Thanks. About "Btw, if triggers an audible/visual bell", it doesn't but again thanks. – Nelson Teixeira Jun 22 '20 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 – Nelson Teixeira Jun 22 '20 at 18:50
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.

Nelson Teixeira
  • 319
  • 2
  • 14
  • 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 Jun 20 '20 at 09: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. – Nelson Teixeira Jun 21 '20 at 03:13
  • You probably use an old Vim version which doesn't support the concatenation operator ..; replace it with a single dot .. – user938271 Jun 21 '20 at 10:14
  • @user938271 worked fine. Including VRapper. Thanks. Don't you want to create an answer so I can accept/upvote it ? – Nelson Teixeira Jun 22 '20 at 13:32