Search code examples
vim

Why does my macro stop at the end of file


I write a macro which adds a ; at the end of line and moves the cursor to next line. And I repeatly run this macro to add ;s to every line of the buffer.

Here is my input

qaA;ESCjq
1000@a

And here is my question. My file has only 5 lines, and I repeat the macro 1000 times. It should add a lot of ;s at the end of the last line, but what vim does is just stopping at the end of file. I want to know which mechanism in vim leads to this.

I believe it's a simple question, but I just cannot find any official doc to prove it. So I hope there are some doc that describe this mechanism formally.


Solution

  • In short…

    • j moves the cursor down one line.
    • This is impossible to do when the cursor is on the last line so Vim throws an error.
    • Your macro stops being executed because of that error.

    AFAIK, this is not documented in a very straightforward way. :help 10.1 is completely silent about error handling and :help @ is not really helpful either:

    […]
    The register is executed like a mapping, that means
    that the difference between 'wildchar' and 'wildcharm'
    applies, and undo might not be synced in the same way.
    […]
    

    That passing reference to mappings is the only line we can follow and, without a proper tag, it's a tiny one.

    Anyway, only after a tentative :help mapping and a lot of scrolling do we get to the bottom of it:

                                                            *map-error*
    Note that when an error is encountered (that causes an error message or might
    cause a beep) the rest of the mapping is not executed.  This is Vi-compatible.
    

    Now, using a large [count] to make sure a macro is executed as many times as necessary without having to count is akin to brute force. Effective, sort of, but not very efficient.

    A better way to use macros in this case would be to not include the motion in the recording:

    qaA;<ESC>q
    

    and play it back with :help :normal and a proper :help :range:

    :%normal @a
    

    But, frankly, why go through all that trouble when you can do:

    :%s/$/;
    

    ?