Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bhaskarkc/878f7552bd7731036ae4ddf5465a257a to your computer and use it in GitHub Desktop.
Save bhaskarkc/878f7552bd7731036ae4ddf5465a257a to your computer and use it in GitHub Desktop.
The Insane Power of Vim's Global Command
--------------------------------------------------------------------------------
THE INSANE POWER OF VIM'S GLOBAL COMMAND
Joe Ellis
Last edited: Sun 10 Oct 2021 21:18:45 BST
--------------------------------------------------------------------------------
If you're an advanced Vim user, you've probably already used the `:global`
command. It allows you to to execute an Ex command on a set of lines where a
pattern matches. Even basic usage of this command is seen as impressive online
-- so what if I told you that it's even more powerful than you think?
-----------------------------------------------------WHAT IS THE GLOBAL COMMAND?
The global command allows you to to execute an Ex command on the lines within a
range where a particular regex pattern match. Usually, the range given to the
global command is the whole file or a visual selection. So, in most use cases,
using the global command is saying something like "run this Ex command across
all matching lines in my visual selection" or "run this Ex command across all
matching lines in a file".
In a sentence: if a line within a range matches a regex, run an Ex command on
it.
The global command also has an inverse -- sometimes called vglobal -- whose
purpose is to run an Ex command across all lines in a range that do _not_ match
a regex.
---------------------------------------------------------------------BASIC USAGE
Here are a few basic examples of the global command.
:g/test (print all lines containing 'test')
:g/foo/d (delete all lines containing 'foo')
:g/bar/m$ (move all lines containing 'bar' to the bottom of the buffer)
:g/baz/t0 (copy all lines containing 'baz' to the top of the buffer)
:v/quux/d (delete all lines not containing the string quux)
These are fairly standard examples of the global command that you usually get
from most Vim blog posts. They are super useful, and honestly already quite
powerful.
The example that I use the most (by far) is the top one. Something that I find
myself regularly using is :g/^func -- it's really useful for getting a quick
preview of all of the functions defined in a file in a language like Go. I can
then jump to one of the definitions by typing :123 (or whatever the line number
is on screen). This is a really neat, quick, and clean way of getting an
overview of a file.
--------------------------------------------------------CHAINING GLOBAL COMMANDS
Global commands can be chained in Vim for more power. For example:
:g/test/g/run (print all lines containing both 'test' and 'run')
:g/foo/v/bar (print all lines containing 'foo', but not 'bar')
:g/^func/v/error (print all functions that don't return an error')
This already opens up some powerful possibilities. A few examples of how I've
used this in the past:
- Delete all debugging print statements that I've not explicitly marked with
the string `KEEPME` with something like :g/print/v/KEEPME/d
- Slowly whittle down what I'm grepping for in a file by chaining more global
commands -- I think this is a bit easier to cognitively manage than writing a
more complex regex
---------------------------------------------------NORMAL AND THE GLOBAL COMMAND
What if, for each of the matches the global command finds, you want to perform
some kind of more editing task? As a single example, what if you want to append
a string to each line that matches?
You can do something like this
:g/foo/norm!Ahello world!
... that is, for each line containing 'foo', send the keys `Ahello world` to
normal mode. This change could be done with a regex without too much trouble,
but it's much easier specifying an edit with this technique.
For more complex edits, you can record a macro and replay it on each matching
line with:
:g/foo/norm!@q
... that is, run the `q` macro on each line that matches. If you need to do a
complex edit on a bunch of lines, this is a really neat way of doing it.
--------------------------------THE GLOBAL COMMAND AS A POOR MAN'S QUICKFIX LIST
If you invoke the global command from within Ex mode (to enter Ex mode, it's Q
or gQ in normal mode) and specify `visual` as the command, like so:
:g/foo/visual
... you can use it as a poor man's quickfix list.
- Running the command for the first time will drop you in normal mode on the
first matching line
- Re-entering Ex mode with Q or gQ will automatically jump you to the next
match
- ... repeat until done!
I rarely ever use this. I haven't found many situations where this is
particularly useful, but I still think it's good to know about.
------------------------------------------------GETTING REALLY CRAZY WITH RANGES
The Ex command accepted by Vim's global command works in the exact same way as
if you had specified it on Vim's command line. That means it accepts ranges,
which allows us to do some really crazy stuff.
I won't go into the details of ranges here because it's beyond the scope of the
global command. However, you should be aware that ranges can seriously enhance
your usage of global, so I would totally suggest checking check them out. Use:
:help :range
... to read more.
In any case, here are a few examples of the crazy stuff you can do by combining
ranges and Vim's global command. These are a bit cryptic, so I'll leave an
explanation in each case.
:g/foo/'{+,'}d (delete all paragraphs containing the word 'foo')
EXPLANATION: the `:g/foo` part is saying 'find all of the lines that
contain foo'. The command part is using two of Vim's special
marks, '{ and '}, which specify the start and the end of the
current paragraph respectively. `'{+,'}` is specifying the
surrounding paragraph as range, then the `d` is deleting it.
:g/^func/,/^}$/p (print all functions in the current buffer)
EXPLANATION: the `:g/^func` part is saying 'find all of the lines that
start with func'. The `,/}$/` part is specifying a range that
captures from the current line to the next line that contains
only a `}` -- this range is approximating an entire function
definition. The `p` part is just saying 'print this range'.
:g/var/?^func?;p (print the signature of functions using 'var')
EXPLANATION: the `:g/var` part is saying 'find all of the lines that
contain var'. The `?^func?;` part is saying 'from those lines,
search backwards for the nearest line that starts with func
and change to it'. The `p` part is just saying 'print that
line'.
Note that if a function contains the string 'var' multiple
times, you'll get multiple entries in your output. If you care
about this, I use romainl's excellent Redir command to dump
the output into a new buffer, then :%!sort and :%!uniq.
https://gist.github.com/romainl/eae0a260ab9c135390c30cd370c20cd7
--------------------------------------------------------COLLAPSING BULLET POINTS
I'm going to talk a little about my note-taking process in Vim. This might not
sound super relevant right now, but it'll serve as a good example for how the
global command can do some really powerful things if you know how to wield it.
I take all of my notes in Vim, because nothing else allows me to write so
quickly. I take the majority of my notes in plaintext, because it means I can
afford worrying about formatting and just get the information I want on the
page. I use a particularly small font size, so I like to hard-wrap my text
(adding newlines at around the 80-character mark, just like in this file) so
that I can keep it all easily readable.
One last detail: I find that one of the best ways to take notes is to use
_nested bullet points_. Here's an example of what they look like:
..............................EXAMPLE OF MY NOTES...............................
- This is a toplevel bullet point. Typically these are quite short, but they
might span multiple lines, like this one.
- This is a nested bullet point.
- This is also a nested bullet point, but it spans multiple lines -- as you
can see.
- This is another toplevel bullet point, only spanning one line this time.
- Finally, this is another super-duper long nested bullet point. I'm just
making stuff up to get it to wrap over multiple lines at this point. I
promise that this happens all the time when I'm actually taking notes.
................................................................................
If I'm sending these notes to someone, of if I want to copy-paste to
Notion or something, the hard-wrapping causes problems. You end up pasting
the newlines, which can result in awkward formatting in certain tools.
What I want to do is 'flatten' the bullet points so that they are all on the
same line. Here's how we can do that with the global command:
:v/\v^\s*-/-j (flatten hard-wrapped bullet points onto a single line)
EXPLANATION: the `:v/\v^\s*-` part is saying 'find all of the lines whose
first non-whitespace character is NOT a dash'. The `/-j` part
is saying 'join this line onto the previous line'. The result
is that all of the lines that aren't a new bullet point are
joined into the line which 'starts' their bullet point. Give
it a go!
-------------------------------------TYING IT TOGETHER WITH AN ADVANCED WORKFLOW
Let's say I have a file with 100 functions in it, all randomly ordered. I want
to sort these functions lexicographically by their name. We can achieve this
with the global command!
It looks complicated. I'll put the commands here, but an explanation will
follow shortly after.
:'<,'>g/^func/,/^}$/s/$\n/%%%
:'<,'>sort
:'<,'>s/%%%/\r/g
That's basically saying:
- Find all lines in the visual range that start with 'func', and select from
that line down to a line that contains only a closing squiggle brace
- In each of those ranges, replace all newlines with `%%%`
- Sort the visual range, achieving the effect of sorting by function name
lexicographically
- Convert the `%%%` delimiters back to newlines
Of course, this is quite a specialised workflow. However, if you have a massive
test file that has a bunch of boilerplate-y function definitions, sometimes
it's nice to keep them sorted in lexicographical order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment