It really is quite ingenious. Let's break it down. The ex
command
g/^$/xyzzy
will search for all empty lines and execute the xyzzy
command (an arbitrary ex
command) on each of them. Note that xyzzy
is not a real ex
command, it's only there as a placeholder, as per the next paragraph.
The tricky bit here is that the xyzzy
"command" in your case is yet another search command:
,/./-j
The ,/./-
specifies a range. This is of the form <start>,<end>
and, because there's nothing before the comma, it assumes the current line (the one where you found the blank line) is the start.
After the comma is /./-
which means search for the next character (.
means any character) then back up one line (/./-
is short for /./-1
since the one is implied if no value is given). You'll find that pattern .
on the first non-blank line following the one you're operating on.
In other words, the end of the range is the last blank line after or at the one you're currently operating on.
Then you execute a join over that range.
If the start and the end of the range were equal (only one blank line was in the section), join does nothing. If they're not equal, join will join them all up.
That's the way in which it combines multiple blank lines into one.
Lets look at an example (the line numbers are for explanation only, they are not in the actual file):
1 Line 1
2
3 Line 3
4 Line 4
5
6
7
8
9 Line 9
The g/^$/
command will find all empty lines and perform its operation on them (these are the lines 2, 5, 6, 7 and 8).
For line 2, ,/./-j
will set up a range from 2 to 2 (the next .
is found on line 3 then subtract 1 to get 2). A join on the range 2,2 does nothing.
For line 5, ,/./-j
will set up a range from 5 to 8 (next .
found on line 9 then subtract 1). A join on the range 5,8 will join all those lines together.
The operation is not performed on lines that disappear as part of an earlier operation. That's because it would make no sense to process lines that have been deleted earlier in the cycle.
In other words, because lines 6 through 8 are deleted (combined with line 5), the global command doesn't operate on them after that.
That's based on the fact that the vim
documentation states a two-pass algorithm, one to mark the lines, one to perform the operation.