I have a line that fits a "template", and multiple pieces of "data". For each piece of data, I want to copy the template and replace all occurrences of a pattern in that line with the data. I'm using the method below, but I want to know if there's a better (shorter, easier) way.

Given this buffer (cursor is at |):

|Hello, world!

And these commands:

:for data in ['one', 'two', 'three'] | exe 'norm! "ap' | exe 's/world/' . data . '/' | endfor

The buffer becomes:

Hello, world!
Hello, one!
Hello, two!
|Hello, three!
  • Not exactly sure what you're after, but, perhaps this could help: vi.stackexchange.com/a/3927/1800 (instead of chars, strings) – VanLaser Sep 13 '16 at 21:44
  • 1
    You should give a sample of the file you're trying to manipulate, because your question isn't really easy to understand. – statox Sep 14 '16 at 8:23
  • Edited my question with a full, self-contained example. – John Freeman Sep 14 '16 at 17:52
  • why do you expect the cursor to move? – Christian Brabandt Sep 14 '16 at 19:30
  • I don't. I just indicated where it started and where it ended up. – John Freeman Sep 15 '16 at 14:15

If I am correct in thinking you are after a way to do the same thing, but to make it easier to do or to execute over and over?

This is exactly what custom commands are for, but yours is a little tricky to make into a single command. Instead we can build a command that chains to a function, and put the meat of the code in that function.

function HandleMultiReplace(...)
    exe 'normal! "ayy'
    for data in a:000 
        exe 'norm! "ap'
        exe 's/world/' . data . '/'

This function does exactly the same thing as your long line of code, but now it is parameterized with var args and is easy to call.

Don't get too hung up on a:000 - it is basically the list of all variable arguments that were passed into the function. It only works if we specify varargs (...) in the parameter list, as we've done here.

Next we will add a command that calls our new function:

command -nargs=* MultiReplace :call HandleMultiReplace( <f-args> )

To use the new command, put the above lines of code into your vimrc. Once you've done that, and have reloaded any vim windows that were open, you can just type the following and hit enter:

:MultiReplace 'a', 'b', 'c'

The nice thing is that with var args, we can pass a list of any size we want.

Now let's say you wanted to improve your function and make the first argument the part you want to search for (to replace). This can be done by changing only the function as follows:

function HandleMultiReplace(text, ...)
    exe 'normal! "ayy'
    for data in a:000
        exe 'norm! "ap'
        exe 's/' . a:text . '/' . data . '/'

Now, when using your command, pass the replace text as the first parameter, and the list that follows it can still be as long as we want:

:MultiReplace 'world', 'a', 'b', 'c'
| improve this answer | |

What about

" untested
let res = map(copy(['a', 'b', 'c']), 'substitute(@a, "evenmoreescapedpattern", v:val, "g")')
" or with vim8 lambdas
let res = map(copy(['a', 'b', 'c']), {idx,val -> substitute(@a, 'pattern', val, 'g')})
put=res " or call append('.', res)


| improve this answer | |
  • 1
    Can you explain what this does? I edited my question with a full example. Please let me know if your answer addresses it. – John Freeman Sep 14 '16 at 17:53
  • I've used list functions instead of plain loops. Map applies a transformation on all elements, here substitute. Then :put puts all the elements. Each of its line – Luc Hermitte Sep 16 '16 at 10:10

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.