Why do I seem to get different results when I apply this function from the vim command line than when I apply it as part of a user-command?

" advance to next non-blank line only if current line is non-blank
function! NextNonBlankLine()
    if getline('.') =~ "^\s*$"
        :execute nextnonblank(line('.') + 1)

If I run this from the command line with :call NextNonBlankLine(), I get the result I expect: if I call the function while the cursor is on a blank line, it causes the cursor to advance to the next non-blank line; if I call it when the cursor is on a non-blank line, the cursor stays where it is.

I want to use run this function ahead of running a user command to collapse a range of lines, by replacing any blank lines with tabs.

Here's the user command:

:command! -range=0 CollapseLines <line1>,<line2>s/\n/\t/

It works as expected. Like this, for example, to collapse the next 4 lines inclusive:


So given lines:

2 one
4 two
6 three

... calling NextNonBlankLine() on line 2 with :.,.+3CollapseLines, creates:

2 one    two    three

But I want it to advance to the first non-blank line in the range before operating so it doesn't turn blank lines at the head of the range into preceding tabs.

So calling the same command while on the blank line on line 1, results in something like this:

1    one    two    
2 three

So I add a NextNonBlankLine() call to the user command:

:command! -range=0 CollapseLines :call NextNonBlankLine()|<line1>,<line2>s/\n/\t/

But this doesn't work. It does work if I call it from a non-blank line. But if I call it from a blank line, it does not advance as expected, but collapses from where it stands, replacing the blank line(s) at the head of the range with preceding tabs.

What is the cause of this inconsistency? How can I get the intended result?

1 Answer 1


Function that can handle a range for your case looks like this:

func! CollapseLines() range
    let firstline = nextnonblank(a:firstline)
    let lastline = a:lastline + (firstline - a:firstline)
    execute firstline . "," . lastline . 's/\n/\t'

:.,.+3call CollapseLines()<CR>

Command with a function:

func! CollapseLines(line1, line2)
    let firstline = nextnonblank(a:line1)
    let lastline = a:line2 + (firstline - a:line1)
    execute firstline . "," . lastline . 's/\n/\t'

command! -range CollapseLines :call CollapseLines(<line1>, <line2>)

  • Is the issue that <line1> is translated into an exact line number and not a general range element?
    – D. Ben Knoble
    Commented Dec 5, 2021 at 22:16
  • @D.BenKnoble not sure I got your question. But if you're about why line2 is adjusted -- to maintain the same line count in the initial given range
    – Maxim Kim
    Commented Dec 6, 2021 at 7:11
  • I’m sure the code works but I’m trying to figure out the answer to the OP’s question: “ What is the cause of this inconsistency?”
    – D. Ben Knoble
    Commented Dec 6, 2021 at 12:51
  • And I suspect based on your answer that <line 1> expands to a number and not the original range element given on the command line
    – D. Ben Knoble
    Commented Dec 6, 2021 at 12:52

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.