8

Occasionally I want to move all lines in a file to the top of that file. :g/PATTERN/m0 almost does what I want, but because :g operates one line at a time in order, it will also reverse the affected lines.


Example:

Consider this file. Say I want to separate lines containing a number and move them to the top of the file. :g/\d/m0 does almost what I want (fr4nk, car0l and b0b are moved to the top) -- but it reverses the order of the matched lines.

alice
b0b
car0l
dan
eve
fr4nk

Actual output:

fr4nk
car0l
b0b
alice
dan
eve

Desired output:

b0b
car0l
fr4nk
alice
dan
eve

One way of doing what I want is to use :g/PATTERN/m$ (which won't reverse lines) and then move the lines from the bottom of the file to the top. Is there anything simpler?

Pandu
  • 183
  • 3

5 Answers5

21

Instead of moving the target lines up (and reversing them), move all the non-target lines down. Since lines are processed top-to-bottom, the order of non-target lines will be preserved. Also, the target lines will remain in order at the top of the buffer.

:v/\d/m$

:v is the opposite of :g, it applies to all lines not matching a pattern.

Mass
  • 14,080
  • 1
  • 22
  • 47
  • That's really awesome, and seems very powerful. Where is the related documentation? :help v shows a lot of information, but it doesn't seem to described the above command. All I could find is https://vim.fandom.com/wiki/Power_of_g – Eric Duminil Sep 06 '21 at 12:45
  • 1
    @EricDuminil :help :v which refers to :g! in the previous paragraph, where it is described. – Mass Sep 06 '21 at 13:22
4

It's not as easy as I would like it to be, but you can do something like this:

:let @a='' | exe 'g/\d/d A' | 0put a

The breakdown is that it clears register a, executes a delete of all matching lines into register a (uppercase appends to the register instead of replacing it), then puts the contents of register a before the first line of the file.

Heptite
  • 1,046
  • 6
  • 14
  • If I were doing this, I'd probably just run the :global command in command-line mode, and perform the first and third steps with normal mode commands: qaq, and gg"aP – Rich Sep 02 '21 at 13:25
3

One more possibility :)

Add an empty line to the top. This will mark that line with the . mark. Then use the :g command and move all lines below that mark. Since the :g is making changes, the '.' mark will be updated to point to the latest moved line (and finally remove that empty line again).

0put=[]|g/\d/m '.
1d
Christian Brabandt
  • 25,820
  • 1
  • 52
  • 77
2

Here is another solution which is more complex. On the other hand, it leaves a nice separator between the target lines and the rest of the buffer.

:call append(0, '####')|1mark a|g/\d/m'a-1

It works like this:

  1. add a new line at the top of the buffer.
  2. create a mark called a on the first line (the newly added one)
  3. move matching lines before mark a

One thing to understand about vim marks is that when lines are added above, vim adjusts the mark's position lower. So address 'a-1 always refers to the line right above the #### line.

Mass
  • 14,080
  • 1
  • 22
  • 47
1

Personally, I would go with the :v approach mentioned by @Mass. Here is another solution. Move the matching lines to the bottom of the file and afterwards move those lines to the top:

exe 'g/\d/m$' | '.,$m0

You need to use exehere, because the | is otherwise seen as argument to the :g command. It makes use of the . mark for moving those lines of the latest change.

Christian Brabandt
  • 25,820
  • 1
  • 52
  • 77