The Versatility of Vim’s Global Command, g


I just had a powerful revelation; g is powerful! And in this post I’m going to share some of the nifty tricks that I use in my day to day work that increase my productivity ten fold! I’ll start out with some simple stuff and then progressively apply more and more concepts.

But What, Exactly, Is g

By definition, g is:


    :[range]g[lobal]/{pattern}/[cmd]

You can give it a range of lines to perform on, or leave it blank and allow it to work on the entire buffer. It will search for {pattern} which is just a normal regular expression and execute [cmd] on every line that it finds a match. In general, [cmd] is a regular ex mode command; however, you can also use the keyword normal to access all of Vim’s non-ex mode commands, as shown below.

My First Steps With g

I love whitespace and use { and } all the time to jump around a file. However, this really only works if the codebase is structured with relevant blank lines in the appropriate places. Unfortunately, I frequently run into nested arrays structured as such:

20    [
21        [
22            'key' => 'value',
23            'key' => 'value',
24        ],
25        [
26            'key' => 'value',
27            'key' => 'value',
28        ],
29    ],
30    [
31        [
32            'key' => 'value',
33            'key' => 'value',
34       ],
35        [
36            'key' => 'value',
37            'key' => 'value',
38        ],
39    ]

In this form, { and } will jump to the start or end of the entire array. I would much prefer those jumps to be to the start or end of each nested array. Rather than moving to each line with a ‘],‘ and pressing o to create a blank line below it, I’ve been using this:


    :20,39g/^\s*],$/normal O

This little gem of a command will find any line between line 20 and 39, inclusive, that starts and ends with a ‘],‘ (along with any possible whitespace at the beginning). On each matching line, it will execute normal o, which simply creates a blank line below the matching line, leaving me with a nested array that looks like this:

20    [
21        [
22            'key' => 'value',
23            'key' => 'value',
24        ],
25
26        [
27            'key' => 'value',
28            'key' => 'value',
29        ],
30
31    ],
32
33    [
34        [
35            'key' => 'value',
36            'key' => 'value',
37       ],
38
39        [
40            'key' => 'value',
41            'key' => 'value',
42        ],
43
44    ]

This is nice and all, however the extra lines at 30 and 43 aren’t really necessary. To fix this, I can just update my regex to not include lines that are followed by another closing brace. Running this command on the original nested array produces the desired results:


    :20,39g/^\s*],$\n\(^\s*],\=$\)\@!/normal O

After running this command, I’m left with this nested array structure:

20    [
21        [
22            'key' => 'value',
23            'key' => 'value',
24        ],
25
26        [
27            'key' => 'value',
28            'key' => 'value',
29        ],
30    ],
31
32    [
33        [
34            'key' => 'value',
35            'key' => 'value',
36       ],
37
38        [
39            'key' => 'value',
40            'key' => 'value',
41        ],
42    ]

This is a much more desirable structure for the nested arrays and allows me to use { and } to quickly move the cursor to where I need it to be.

This regex is a bit more complex, so I’m going to break it down piece by piece.

The first part:

^\s*],$

is the same bit from the first command and matches any line that contains only ], along with any amount of possible white space at the beginning.

The \n says that I now also want to match the newline character at the end of the line.

The next bit:

\(^\s*],\=$\)

creates an atom that basically matches the same thing again as the first part of the regex. The exception being the \= that tells it to match the comma either 0 or 1 times, which is necessary because of the very last line that does not include a comma.

And finally, the \@! tells it to not match the lines where the atom is true. Said another way and looking at the original nested array, don’t match line 28 because of line 29, and don’t match line 38 because of line 39.

In and of itself, this is a pretty neat trick, but this doesn’t even scratch the surface of what g can do!

Inspirations

The inspiration for this post came from another use case that I needed at work today. I was writing some test cases for a new feature. I had a repeated function call in numerous tests that I needed to updated. The call was structured something like this:


    $this->createCompany(
        1,           # company id
        'Test name', # company name = 'Test Name'
        25           # company markdown = 25
    );

The comments after each parameter indicated what the param was and what its default value was, if there was one. I wanted to change the company markdown to 30 and update the default value to match. Granted, I could just do,


    :%s/25/30/g

but the file contained several hundred values of 25, and this would not be a very efficient method to update the values that needed to be updated. Enter the power of g!

By simply issuing this:

 
    :g/company markdown/s/25/30/g

I was able to quickly update all of the values of 25 that I needed. In this case, {pattern} was simply the string, company markdown, and my command was a simple search and replace with the g modifier to indicate that I wanted to replace all values of 25 on the matching line with 30. The benefit here, of course, being that the search and replace was only executed on the lines that I needed it to be executed, instead of across a range of lines or the entire buffer.

Opposites Attract

It’s worth noting that there is also v, the inverse to g. Instead of executing [cmd] wherever {pattern} is found, v will execute [cmd] wherever {pattern} is not found. The two can even be used together!


    :g/{matchingPattern}/v/{nonMatchingPattern}/[cmd]

This command will execute [cmd] wherever {matchingPattern} is found, but only if {nonMatchingPattern} is not found, as well.

Putting It All Together

Let’s apply everything discussed here to a use case that I just needed earlier today at work.

As I mentioned earlier, I’m working on a series of unit tests, and I wanted to skip executing tests that I’m not focused on. Before this mini-revelation, I had been going to the start of each test that I wanted to skip and manually entering:


    $this->assertEquals(1,1);
    return;

Well, okay, not exactly manually. I had the two lines stored in register a and I was simply pasting them at the start of each unit test that I wanted to skip. However, this is still a giant pain! A much simpler approach, already having those two special lines in register a, is to simply issue:


    :g/public function/normal j"ap

With this command, the content of register a is pasted into the start of every function in the test class. Then, I only have to remove the two lines from the the unit test that I’m working on instead of having to add them to every unit test that I want to skip!

Note that the j is used because, in the codebase I was working on, the function declarations always have the opening brace on the following line. If the opening brace is on the same line as the function declaration (or if you’re writing in Python), the j is not needed. The lower-case p simply pastes the contents of register a below the current line. And of course, you’ll also want to update the {pattern} to work for whatever language you’re working in. In this case, it’s PHP.

But wait! My test class has a setup() method that I don’t want to skip. So I simply updated my command to be:


    :g/public function/v/setup/normal j"ap

Now, these two lines are added to every method except the setup() method!

Take it a little further, and I can do:


    :g/public function/v/\(setup\|unitTestIWantToRun\)/normal j"ap

And I don’t even have to worry about removing the two lines from the unit test that I’m currently working on, because they’re never added to it in the first place!

You may think that I would still have to manually delete these lines from each unit test once I’m ready to run all of the tests, and you’d be right…except for the ‘manual’ part! To remove all of the lines that the previous command added, I simply run:


    :g/public function/v/\(setup\|unitTestIWantToRun\)/normal 2j2dd

and the first two lines of every function not matching setup() and unitTestIWantToRun() will be deleted. Namely, the same two lines that the previous command added! Again, I’m using 2j here because the function’s opening brace is on the line below the function declaration.

Edit Note: Since writing this, I’ve learned more about PHPunit and discovered that there are much easier methods of running only specific tests, such as adding them to a group and only running that group. But I’m still leaving this section as a bit of contrived example to highlight a use case of g

A More Complex Use Case

I actually had to come back and edit this post to give one more neat example that I just used at work today!

I’m still working on some unit tests, only this time it was with the .xml files that make up our test database. Each .xml file represents a table in the database and is structured something like:


 <dataset>
    <table name="inventory_items">
        <column>inventory_item_id</column>
        <column>product_id</column>
        <column>department_id</column>
        <column>location_id</column>
        <column>expiration_date</column>
        <column>count</column>
        <column>page</column>
        <column>flag</column>
        <column>status</column>
        <column>amount</column>
        <column>created_at</column>
        <column>updated_at</column>

        <row>
            <value>1</value>
            <value>productId</value>
            <value>departmentIdParent</value>
            <value>locationId</value>
            <value>2020-10-19</value>
            <value>5</value>
            <value>6</value>
            <value>0</value>
            <value>0</value>
            <value>0</value>
            <value>2014-10-17</value>
            <value>2014-10-17</value>
        </row>

        <row>
            <value>2</value>
            <value>productId2</value>
            <value>departmentIdChild</value>
            <value>locationId2</value>
            <value>2020-10-19</value>
            <value>4</value>
            <value>3</value>
            <value>2</value>
            <value>1</value>
            <value>0</value>
            <value>2014-10-17</value>
            <value>2014-10-17</value>
        </row>
</dataset>

I was finding it rather cumbersome to keep track of which value went with which column and thought that it would be nice if each value entry had a comment following it of the column’s name. Some of these tables are 20-30 rows long and I simply wasn’t going to comment each value. I needed to figure out a more efficient method, so I turned to my new friend g to help me out!

I first needed to create a block of the commented column names. To do this, I moved my cursor to the first i of inventory_item_id. Then, I entered visual block mode and selected and yanked all of the column names. This created a block that looked like this:


    inventory_item_id
    product_id</colum
    department_id</co
    location_id</colu
    expiration_date</
    count</column>
    page</column>
    flag</column>
    status</column>
    amount</
    created_at</colum
    updated_at</colum

Next, I cleaned this block up by selecting it in visual mode and running:


    :'<,'>s/<.*$//

This removes the < and everything after it on each of the visually selected lines, leaving me with:


    inventory_item_id
    product_id
    department_id
    location_id
    expiration_date
    count
    page
    flag
    status
    amount
    created_at
    updated_at

Then, I needed each column name wrapped in html comments. So, again, I moved the cursor to the first i of inventory_item_id and started recording my keystrokes into register a using qa in normal mode. Once the recording was started, I entered this sequence of key commands (where { and } denote a key press, i.e. spacebar and esc):


    i<!--{space}{esc}$a{space}-->{esc}j^q

This series of commands wraps the inventory_item_id string in <!-- and -->, moves the cursor to the first character on the next line, and then stops the recording. Then, I simply had to run:


    11@a

and the remaining 11 column names were automatically wrapped in html comments. Afterwards, I was left with this block:


    <!-- inventory_item_id -->
    <!-- product_id -->
    <!-- department_id -->
    <!-- location_id -->
    <!-- expiration_date -->
    <!-- count -->
    <!-- page -->
    <!-- flag -->
    <!-- status -->
    <!-- amount -->
    <!-- created_at -->
    <!-- updated_at -->

Next, I needed to yank all of these commented names into a visual block. Since they’re all different lengths, it’s difficult to select all of the characters in a block. So, for these purposes, I have this set in my .vimrc


    set virtualedit=block

This allows the cursor to extend past the end of the line whenever Vim is in visual block mode. After doing this, I can easily select the entire block in visual block mode and save it into register a.

Up until this point, it’s all been pretty straightforward, basic Vim-fu. The tricky and time consuming part was to now go to the inventory_item_id value of each row, move the cursor to the > at the end of the line, TAB out about 6 TAB lengths, and then paste from register a, which would leave me with this:


    <row>
        <value>1</value>                      <!-- inventory_item_id -->
        <value>productId</value>              <!-- product_id -->
        <value>departmentIdParent</value>     <!-- department_id -->
        <value>locationId</value>             <!-- location_id -->
        <value>2020-10-19</value>             <!-- expiration_date -->
        <value>5</value>                      <!-- count -->
        <value>6</value>                      <!-- page -->
        <value>0</value>                      <!-- flag -->
        <value>0</value>                      <!-- status -->
        <value>0</value>                      <!-- amount -->
        <value>2014-10-17</value>             <!-- created_at -->
        <value>2014-10-17</value>             <!-- updated_at -->
    </row>

For a row or two, that’s not too bad, but when you have 30-40 rows per file, it quickly becomes impractical. Again, enter the power of g!

I realized that this part could be easily automated with g. By simply issuing this:


    :g/<row>/exe "normal j$a\t\t\t\t\t\t" | normal "ap

every single row in the file was pasted with the commented column names! Easy as that! This command searches for each line that matches the string <row>. On a match, it will move the cursor down one line, jump to the last character of the line, enter into insert mode to the right of that last character, TAB out six TAB lengths, and then paste the contents of register a into position.

For those familiar with Bash, it’s worth noting here that Vim’s | is more akin to && in Bash and merely executes the next command in sequence. It does not behave like Bash’s pipe, |, which will pass the STDOUT from the first command into STDIN of the following command.

You might also be wondering about the extra steps in this command and why it wouldn’t work to simply issue:


    :g/<row>/normal j$a\t\t\t\t\t\t"ap

In this case, Vim’s exe command is needed in order to execute the normal mode commands correctly. In the second instance, the \t‘s would not be recognized as TAB key presses and would instead just be inserted into the file as a string.

Conclusions

It took me all of about 2 minutes, start to finish, to add the commented column names after each row value and has saved me from a countless amount of wasted time spent counting value entries to determine which value goes with which column, both today and at any point in the future when working with these files!

This, of course, all still barely scratches the surface of what can be done with g. It has, however, made me realize that I have often overlooked the versatility of this command, and I definitely look forward to using it more in my day to day work.

How do you use g in your day to day vimming. Let me know in the comments!


5 responses to “The Versatility of Vim’s Global Command, g”

  1. I think this is one of the most significant information for me.
    And i am glad reading your article. But want to remark on few general things,
    The site style is great, the articles is really
    great : D. Good job, cheers

  2. whoah this weblog is magnificent i really like reading your articles.

    Stay up the good work! You know, many persons are searching round for this information, you could help them greatly.

Leave a Reply

Your email address will not be published.