4

I want to replace each submatch with a string with an incrementing index that starts from 1 in the beginning of each line, so that replacement strings would be varargin{1}, varargin{2}, varargin{3}, etc. For bigger numbers, the number string would naturally need more than one character, e.g.: varargin{9}, varargin{10}, etc. The input data is MATLAB code; example inputs and desired outputs are presented below. I'm primarily looking for a Vim solution, but other ways to do this are also appreciated.

The regex below creates running indices beginning from 1, but those change for every line:

:let @a=1 | %s/\v.*'\zs.*\ze\);/\=substitute(submatch(0), '\s[a-zA-Z0-9{}_.]*', ' varargin{'.(@a+setreg('a',@a+1)).'}', 'g')/g

My question is:

How can I reset the index to 1 in the beginning of each line and increment the index by 1 between every submatch?

The code above is a modified version of the "Substitute with ascending numbers" example presented at http://vim.wikia.com/wiki/Substitute_with_incrementing_numbers:

:let @a=1 | %s/abc/\='xyz_'.(@a+setreg('a',@a+1))/g

Example input #1:

messages.msg1.English = xprintf('analysis directory is on %s\n', analysis_dir);

Desired output for example input #1:

messages.msg1.English = xprintf('analysis directory is on %s\n', varargin{1});

Example input #2:

messages.msg15.English = xprintf('the following sessions (%d pcs) have been approved: %s', handling_struct.n_of_accepted, handling_struct.accepted_sessions_vector);

Desired output for example input #2:

messages.msg15.English = xprintf('the following sessions (%d pcs) have been approved: %s', varargin{1}, varargin{2});

Example input #3:

messages.msg19.English = xprintf('looking for files ''%s'' in %d separate dirs', give_file_struct.regex, number_of_dirs);

Desired output for example input #3:

messages.msg19.English = xprintf('looking for files ''%s'' in %d separate dirs', varargin{1}, varargin{2});
ib.
  • 27,830
  • 11
  • 80
  • 100
nrz
  • 10,435
  • 4
  • 39
  • 71

2 Answers2

4

I would use the following command in this case:

:g/^/let n=[0] | s/abc/\='xyz_'.map(n,'v:val+1')[0]/g

See also my answer to the question “How to replace CSV column separators with numbered labels in Vim?”.

ib.
  • 27,830
  • 11
  • 80
  • 100
  • I checked your answer and the links and tried different ways to do it, but it seems to me that `map` cannot be combined with `\=substitute(submatch(0)`, and I need to change only those occurrences of `'\s[a-zA-Z0-9{}_.]*'` that occur after the last `'` (apostrophe) of each row. Is it possible to combine `map` and `\=substitute(submatch(0)` ? – nrz Nov 11 '12 at 20:13
  • @nrz: I cannot see any reason why the presented approach could not be combined with the `substitute()` call. Could you please elaborate on what exactly does not work as you expect? – ib. Nov 11 '12 at 21:36
  • 1
    @nrz: In order to substitute only the occurrences after the last apostrophe in a line, one could modify the command from the answer to be `:g/^/let n=[0]|s/abc\ze[^']*$/\='xyz_'.map(n,'v:val+1')[0]/g`. – ib. Nov 11 '12 at 21:37
  • Thanks, that's exactly what I was looking for. The regex that does the entire replacement I needed is this: `:g/^/let n=[0]|s/\v,\s*[a-zA-Z0-9{}_.]*\ze[^']*$/\=', varargin{'.map(n,'v:val+1')[0].'}'/g` . – nrz Nov 11 '12 at 22:42
  • With `\=substitute(submatch(0)` I tried this: `:g/^/let n=[0]|s/\v.*'\zs.*\ze\);/\=substitute(submatch(0), '\s[a-zA-Z0-9{}_.]*', ' varargin{'.map(n,'v:val+1')[0].'}', 'g')/g` but unfortunately it does not update the index between instances (submatches), so it replaces all instances with `varargin{1}`. Anyway, by using `\ze[^']*$` there is no need to use `\=substitute` here. – nrz Nov 11 '12 at 23:07
  • 1
    @nrz: The command from your last comment does not work as expected because function arguments are evaluated once before call. This means that the counter-increasing sub-expression is evaluated only once before replacements are done in the `substitute()` function. Usually, if you need to call `substitute()` from inside of `:substitute` replacement string, there is an equivalent `:s`-command without the `substitute()` call. – ib. Nov 13 '12 at 19:46
1

This may be a hacky solution, but if I wanted to do this quickly without a function then I would do something like this:

:let i=1<cr>                                         "set the index variable
qbf lcEvarargin{<C-r>=i<cr>},<esc>:let i+=1<cr>q     "record a macro
u                                                    "undo those changes
:%norm$F':let i=1<C-v><C-j>1000@b<cr>                "run the macro on each line
:%s/,$/);/<cr>                                       "clean up

Note that I use <> notation from vim to signify special keys, so <cr> means carriage return and <C-r> means CTRL-R (see :h i_CTRL-R). The <C-v><C-j> puts in a literal ^@ character on the command line. This allows normal mode to "press enter" without finishing the command. I use :norm instead of a macro here because when :norm throws an error it just moves on to the next line. When a macro throws an error (for example trying to do ft when there aren't any more t's in the line) then it just fails altogether. This way I can run the macro 1000 times and just keep on moving once it fails. Again, probably not the most elegant solution but it works and my laziness usually wins out.

Conner
  • 30,144
  • 8
  • 52
  • 73
  • +1 This isn't probably the most elegant solution (and probably cannot be used easily from bash command line like this: `find . -iname '*.m' | xargs -I abcdef vim -nes "+se ul=-1" 'argdo %s/foo/bar/ge|x' '+q' abcdef`), but it works. – nrz Nov 11 '12 at 20:11