I'm trying to make a vmap that takes a count and then indents all the highlighted lines so that the top one is more than the line above it, while keeping all the lines' relative indentation. I have made it to this function and I'm experiencing some behaviour I don't know how to figure out. For some reason, this function will only indent the top line of the selection. I've tested it using echomsg instead of execute, and then just pasting the commands in explicitly and it works just fine. But for some reason when it's in this for loop, it only executes the first one.

function! Test()
    let above = line("'<") - 1

    for line in range(line("'<"), line("'>"))
        execute line . "left " . (indent(above) + 4 + indent(line) - indent("'<"))

Also, if there's a plugin that'll already do this for me, feel free to let me know.

Edit: Putting this function at the top of the file, changing execute to echomsg, highlighting the function body, and then running :cal Test() give the following ouput

2left 4
3left 0
4left 4
5left 8
6left 4

Which is expected, so I don't understand what's going wrong when I use execute.

  • 2
    Can you show your vmap? Does adding range to the function definition (after the Test()) fix your issue?
    – Heptite
    Jun 24, 2021 at 17:35
  • @Heptite I haven't gotten that far. I'm just highlighting text and then doing :cal Test() with this exact function.
    – Mason
    Jun 24, 2021 at 20:53
  • When you do that, without defining the function as handling a range, Vim will call the function for each line in the highlighted region.
    – Heptite
    Jun 24, 2021 at 22:03
  • Well I can unhighlight the text and then call the function and I'll get the same result because the function just uses '< and '>. And to be clear, I ran literally :cal Test(), not :'<,'>call Test(). Also, like I mentioned, when using echomsg instead of execute the commands come out as expected.
    – Mason
    Jun 24, 2021 at 22:37
  • What does the output of ":messages" show? It could be that Vim is overlaying each echomsg with the next. I can't reproduce the behavior you're seeing but I also don't think the function is working right.
    – Heptite
    Jun 24, 2021 at 22:39

2 Answers 2


The problem is with the code indent("'<"). After your first execution, that value changes, causing the indentation of everything afterwards to not change. It can be fixed by setting it's value to a variable before you start the loop.


By default, a function called in visual mode is effectively once for every line in your range.

To get the behavior you want you need to add "range" to the function definition:

function! Test() range
   let above = line("'<") - 1
  • didn't change the behaviour :(
    – Mason
    Jun 25, 2021 at 5:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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