0

Using vim, I know that I can replace all occurrences of a string across multiple lines by specifying a count or a range, as documented in :h subst.

I have a markdown table which contains C function prototypes, as well as English prose. I would like to replace, for example, the word 'float', but only the first three occurrences.

If I were writing a program to do this, many languages have a .replace method on strings that will accept a howmany parameter that limits the number of times the replacement is made.

Can I do something like that with vim? Is there any way to say, for example, "replace float with long double at most 3 times"?

1
2

Taking a straight programmatic approach, we can simply implement this as a for loop:

:for x in range(3) | :%s/float/long double/ | endfor

Update: @PeterRincker offers this user command that you can wrap around the above for easy invocation:

command! -count=1 -nargs=+ -complete=command Times for _ in range(<count>) | execute '<args>' | endfor.

Then for the above example we'd just need...

:3Times %s/float/long double/ 
4
  • 2
    In case this is a common thing: command! -count=1 -nargs=+ -complete=command Times for _ in range(<count>) | execute '<args>' | endfor. Now they can do :3Times s/foo/bar/ May 6 at 13:18
  • Good stuff @PeterRincker. I added it to my answer. Cheers.
    – B Layer
    May 7 at 4:26
  • 1
    This is nicely pragmatic. And the extra Times command is just whipped cream on top of the cake. If I could give another +1 I would.
    – aghast
    May 7 at 17:21
  • @aghast LOL. I'm glad you're enjoying the dessert we served. ;)
    – B Layer
    May 7 at 18:38
3

Because I love macros, here's a simple macro based-solution:

  1. Record a macro to replace one instance of float on the current line:

     qq:s/float/long double<CR>q
                           ^^^^
                           Press Return here
    
  2. (If necessary) Undo the change you just made by hitting u

  3. Play the macro three times on each line in your file:

     :%norm! 3@q
    
1
  • I like this approach. It fits well with "normal" use and seems likely to be something one could stumble upon while floundering around looking for a better way. I had never thought of :%norm as a valid option before now, which means I learned something from this. Thanks!
    – aghast
    May 7 at 17:23
0

We can also prepare the repeated text with

:let list = repeat(['long double'], 3)

and then replace with:

:s/float/\=empty(l) ? submatch(0) : remove(list,0)/g

Alas, I'm not inspired at this time for solving the resetting of the list for each new line with a :%s/.... Well we could work with repeat([repeat(['long double'], 3)], line('$')) and then remove from list[line('.')]. It's a bit overkill, but quite possibly much more faster than :for based solutions with vimscript < 9.

Note: It can certainly be built into a command like (in other similar Q/A I've provided multiple examples of such encapsulation of :substitute (SO, ...)

:SubstituteFirst/3/float/long double
2
  • Perhaps use a :global to reset it? :g/float/ let list = repeat(['long double'], 3) | s//\=empty(l) ? submatch(0) : remove(list,0)/g
    – filbranden
    May 6 at 5:05
  • 1
    In a 5900 line text file a for loop substitution with a pattern that has 20K matches completes with no discernible delay on the two systems I tried it on.
    – B Layer
    May 6 at 6:54

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.