1

I want to join multiple consecutive blank lines into single blank line.

To do this, I use the following global command:

g/^\s*$/,/./-j

But this doesn't work in some exceptional cases. For example in the following scenario, 3 blank lines between "c" and "d" are reduced to 2 blank lines instead of 1.

Figure 1 Figure 2

In first figure, line 6 has no space. Line 7 has one space. Line 8 has no space.

Why does the above command not work in this case?

5

Why your command doesn't work

The simple answer is the . as the end range for your :j command will match the space in your offending line. This means that your nearly empty line will not be removed/compressed.

This can be solved by searching for \S (non-whitespace character) instead of .

:g/^\s*$/,/\S/-j

We can do better

The following command will work with your inconsistent whitespace issues and is much shorter:

:v/\S/,//-j

The fully expanded command is as follows:

:vglobal/\S/ .,/\S/-1join

Overview

For every blank line, :join the current line with the line above the next non-empty line, \S.

Glory of details

  • :v is short for :vglobal
  • :v/{pat}/{cmd} will run {cmd} on every line not matching {pat}
  • \S will match a non-whitespace character
  • :join (:j for short) command will join lines together. For this example you can think of it as compressing a block of blank lines into 1 blank line.
  • The :j command takes a range. e.g. .,//-
  • The range is .,/\S/-1
  • The start range is the current line, .. However . can be assumed so leave it blank.
  • The end of the range is /\S/-1. Which means find the next line with a non-whitespace character and then use the line right above it (-1)
  • Since the pattern is the same pattern used in the :v command we can reuse the pattern. // is shorthand to reuse the last search pattern.
  • -1 can be shorted to just - as the 1 can be assumed
  • .,/\S/-1 becomes ,//- after we shorten it

For more help see:

:h :j
:h :v
:h :g
:h /\S
:h :range
:h /
  • Cool (+1) -- vim is always surprising. – JJoao Dec 5 '15 at 0:45
1

Using substitution:

:%s/\v(\n\s*){2,}/\r\r/
  • substitute 2 or more newlines (\n){2,} with a empty line (\n\n)
  • \v (very magical) was used to simplify the necessity of protections (\{2,\})
0

Your global command is explained in details here.

g/^\s*$/,/./-j

If I understood the post correctly, I think your problem comes from this part:

,/./-

It defines the range of lines on which the join command has to operate.

The range begins with the current line (an empty line which can contain whitespace as described by the pattern ^\s*$) and ends one line above the next line containing any character (including a space).

So I think that in your example, when the global command g finds the empty line number 6, the join command j looks for a line containing any character (/./) and finds a space on line 7, then goes back one line above (- ; line 6 again) to determine the final line to collapse. Therefore j joins the lines between current line 6 and final line 6, which means it doesn't join anything.

After that it joins line 7 and 8.

If you want the join command to ignore the space when searching for the last line to collapse, you could replace . (any character) with \S (any non whitespace character).

It would give:

g/^\s*$/,/\S/-j

A more readable version of the same command could be:

global/^\s*$/ .,/\S/-1 join

The dot . in this last command is used in the range .,/\S/-1 and not in a search pattern, so this time it doesn't stand for any character but for the current line.
And .,/\S/-1 is a range like a,b where a = . and b=/\S/-1.

It could be read like this:

global command (global), whenever you find an empty line (/^\s*$/) execute the join command between the current line (.) and one line above (-1) the next line containing no whitespace (/\S/).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.