Search code examples
vimvi

Your problem with Vim is that you don't grok vi


We can use m to move lines around, and j to join lines. For example if you have a list and you want to separate all the stuff matching (or conversely NOT matching some pattern) without deleting them, then you can use something like: :% g/foo/m$ ... and all the "foo" lines will have been moved to the end of the file. (Note the other tip about using the end of your file as a scratch space). This will have preserved the relative order of all the "foo" lines while having extracted them from the rest of the list. (This would be equivalent to doing something like: 1G!GGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d (copy the file to its own tail, filter the tail through grep, and delete all the stuff from the head).

Going through the this legendary answer by Jim Dennis but I still can't get my head around this sequence:

1G!GGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d

Someone help elaborate, what is GGmap? Why's there a bang between 1G! GGmap? Does Ggrep come from vim-fugitive?


Solution

  • Everything makes sense in that command except the !GG bit. Let's deconstruct the whole thing:

    1G!GGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d
    
    1. 1G moves the cursor on line 1, see :help G,
    2. !GG makes no sense,
    3. ma creates mark a, see :help m,
    4. p puts the content of the unnamed register after the cursor, see :help p,
    5. !Ggrep foo<ENTER> filters the lines from the current line to the last line with the external command grep foo, see :help !,
    6. 1G moves the cursor on line 1,
    7. :1,'a g/foo'/d cuts every line matching foo' from line 1 to mark a, see :help :range, :help :g, and :help d.

    Step 2 is, I believe, a typo. !GG literally means "filter the text from the current line to the last line with external command G", which makes no sense because a) G is not a common Unix command (if it even exists), and b) it is not followed by <ENTER>, which is required for executing an Ex command.

    What that step is supposed to do is described in the answer's body: copy the content of the buffer. It can be done in many ways but:

    yG
    

    to yank from here to the last line, followed by:

    G
    

    to put the cursor on the last line, seems more fitting in this context. So the whole sequence should actually be:

    1GyGGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d<ENTER>
    

    which works in Vim and in vi.

    --- EDIT ---

    Note that there is still some room for improvement. The following sequence has the same outcome but without unnecessary cursor movements:

    :$ka|r!grep foo %<ENTER>:1,'ag/foo/d<ENTER>
    

    and it also works in ex ;-).

    Another optimised version that kind of does the opposite of what the answer tries to demonstrate (top of file as scratch area instead of bottom of file) but with the exact same outcome:

    :0r!grep -v foo %<ENTER>:+,$v/foo/d<ENTER>
    
    • no normal mode cursor motion,
    • no marks.

    But, to be honest, the problem doesn't really require such a convoluted solution as it can be done with a simple:

    :g/foo/m$
    

    which works in ex, vi, every vi clone, and even in ed.