Why it doesn't work
The reason your command works that way is that all the lines are marked for action first, then the join is run (aside: you can use :v/.../j
to use the ex-command :join
instead of :normal! J
). Because the line numbers change, the execution gets off a bit.
Trying :vglobal
with a smarter range
One idea is to join from each non-blank line to the next blank line (something like .,/^$/join
if the current line is on the start of a paragraph). The problem is that we have to also consider the end of the file—in a range, that would usually be $
, but we need to match it in a pattern, so we use \%$
.
Trying it out:
vglobal /^$/ .,/^$\|\%$/ join
gives the rather erroneous
Some text that I want to transform.
And some more text that I want to transform.
Fixing a relative range & end-of-file issue
So we've accidentally eliminated the blank line... I thought we could fix that by using .,/pat/-1 join
to join from the start to the line above the next blank line, but alas it doesn't work on the sample very well.
So my solution is to double the blank lines and trust the original command to un-double them:
global /^$/ yank | put
vglobal // .,/^$\|\%$/ join
This time I use //
with :vglobal
because the last pattern is the one we want (and it saves typing). This gives
Some text that I want to transform.
And some more text that I want to transform.
Caveats
This method doesn't work too well when there are multiple blank lines in a row (it doesn't un-duplicate them properly; rather, it removes one from each set). It also doesn't handle the end-of-file already being a single line very well (the range gets confused).
The next section presents a slightly more robust method.
A better solution with pre- and post-processing at the end of the file
Alternately, we can add a blank line to the end of the file, process, and remove it:
$ put =''
vglobal /^$/ .,/^$/-1 join
$ delete
This time we don't even need the complicated pattern for the join range.
Shortening commands
In all cases, the commands I gave can be shortened considerably:
:vglobal
is the same as :v
;
:global
the same as :g
;
:join
is :j
;
:yank
is :y
;
:put
is :pu
; and
:delete
is :d
Also, I included lots of whitespace around ranges and commands and patterns; you can actually remove every bit of it and it will still work, so the options are
g/^$/y|pu
v//.,/^$\|\%$/j
or
$pu=''
v/^$/.,/^$/-1j
$d
But even most of the seasoned vimmers I know would struggle to decipher these at first glance.