Created
November 1, 2021 21:53
-
-
Save imvaskii/878f7552bd7731036ae4ddf5465a257a to your computer and use it in GitHub Desktop.
The Insane Power of Vim's Global Command
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------------------------------------------------- | |
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