9

I'm reworking a section of logic in some C code to avoid a memory leak that looks like this:

switch (result) {
case CASE_1:
     return report(...);
case CASE_2:
     return report(...);
/*
 * More cases follow with the same pattern.
 */
}

I've used %s/return report/report/ to remove the return statements, but now I need to add a line with break; after that, so the final result resembles something like this:

switch (result) {
case CASE_1:
     report(...);
     break;
case CASE_2:
     report(...);
     break;
/*
 * More cases follow with the same pattern.
 */
}

I've tried using the global command to execute the append command with no success:

# Error "E448: Trailing characters" (potential bug in Vim?)
:g/report("/a\
     break;

# Appends nothing and does not wait for text to be input.
:g/report("/a

Trying the above in ex mode with Vim fails, and visual mode doesn't seem to work well with the insertion commands in general, regardless of the variant of vi. If I omit the global command, it will work: I can enter text and type a single "." character on a line by itself to return to command mode (or visual mode, depending on which mode I was in). However, this cannot be repeated using the . key in visual mode because it's an ex command, not a motion command.

In an unusual twist, the first attempt works in ex mode with the port of "The Traditional Vi" and even ed of all things, though nothing is auto-indented due to the usage of to the global command. That's fine anyway since the structure is predictable enough to manually insert indentation characters.

I've also attempted the following:

%s/report(".*$/&\
    break;/

This is the result of one line as displayed by Vim:

    report("No match");^@
break;

Can anybody explain how to use the global command with the append command to append one or even more than one line of text in Vim, even if I have to leave visual mode to do it?

3
  • 1
    Would you consider the following command as a potential solution to your problem : g/report("/execute "normal! obreak;\e" ?
    – saginaw
    Nov 16, 2015 at 7:51
  • @saginaw Yes, that works. It correctly auto-indents too! :-) Any other solutions are welcome as well, auto-indent or not.
    – user5316
    Nov 16, 2015 at 8:05
  • Also, go macros! The time to record the macro in this case is certainly less than thinking about how to do it with the global command, and pairing search and the dot command has the advantage that you can easily leave out some "report"-calls where you don't want to add a break statement. Might not apply here, but can be very handy in other cases. Nov 16, 2015 at 10:41

2 Answers 2

10

If I understand your post correctly, you had this snippet of C code :

switch (result) {
case CASE_1:
     return report("...");
case CASE_2:
     return report("...");
}

And you wanted to transform it to :

switch (result) {
case CASE_1:
     report("...");
     break;
case CASE_2:
     report("...");
     break;
}

I don't know all the details of the global command, but here's how I would do it :

g/return report/normal! _dawobreak;

Explanation :

The global command searches the pattern return report, then on each line that matches this pattern, it executes the following command :

normal! _dawobreak;

The normal command types every character that follows as if we were in normal mode. Here it reads _dawobreak; as a sequence of keystrokes that can be broken down like this :

  • _ = go to first non whitespace character
  • daw = delete a word (including the space after it)
  • o = open a new line under the current one
  • break; = insert break;

The exclamation mark after the normal command, is to prevent recursiveness.
It means : if inside the sequence of keystrokes you detect a subsequence corresponding to a mapping, don't interpret it.

6

One way to do it is to use the widely underappreciated \zs:

:%s/report.*\zs/\r     break;/

What this does is, look at lines containing report, find end of line, and add a new line with break.

You could also achieve the same thing with global, but that would just complicate the command for no reason.

4
  • 1
    You could also use a range to specify the line, and then just substitute the end-of-line by whatever you want in the next line :/report/s/$/\rbreak/. This doesn't add anything new in this case, but can often make it easier to find the right patterns. Nov 16, 2015 at 10:37
  • @PhilippFrank Sure, that's yet another valid solution. However, it has nothing to do with the point I was trying to make, about \zs. Nov 16, 2015 at 11:38
  • 1
    Right, it does not, it just felt right to put it here as a comment, since there's nothing new which warrants an answer and it's about finding the right patterns for the job. Nov 16, 2015 at 11:43
  • I like both solutions equally. Sep 18, 2017 at 19:54

Your Answer

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