Search code examples
xmlvimindentationneovim

Vim auto-indenting xml strangely


In vim (neovim), XML code is indenting strangely when attributes are put on their own lines, like so:

<test
    first="text"
    second="more text"
    third="last">
<foo
    a="a"
    b="b">
<bar />
</foo>
</test>

When I expect, and desire, this:

<test
    first="text"
    second="more text"
    third="last">
    <foo
        a="a"
        b="b">
        <bar />
    </foo>
</test>

This feels like none other than a bug to me. I could not find a plugin to address this. Perhaps there is something that I could add to my init.vim to correct this behavior?

EDIT: The patch below almost fixes this issue, but does not correctly handle the following case:

<test
    first="text"
    second="more text"
    third="last">
    <foobar>
    </foobar>
    <foobar>
    </foobar>
</test>

EDIT 2: Removing the + shiftwidth() on line 90 solves all but this case:

<test
    first="text">
    <foobar>
    </foobar>
</test>

<foobar> should indent as shown above, because the tag spanning the first two lines is an opening tag, indicating that the next lines should be indented.

EDIT 3: For some reason, <foo is indenting twice with the patch. Expected:

<test>
    <foo
        a="a">
    </foo>
</test>

I know regex enough to play with it, but vim script makes this tricky.

EDIT 4: Expected output should be:

<test>
    <foo
        a="a" />
    <foobar
        a="a" >
        <test>
        </test>
    </foobar>
</test>

/> should not indent the next line.


Solution

  • I'm not knowledgeable enough about XML to determine whether it's expected behavior nor can do proper tests, so I don't know if my solution is the best.

    But if you suspect it to be a bug, then the best action would be to report1 it on the official GitHub repository of XML runtime files: chrisbra/vim-xml-runtime.


    I managed to get your expected results by applying this patch2:

    @@ -81,11 +81,16 @@
     endfun
    
     " [-- return the sum of indents of a:lnum --]
    -fun! <SID>XmlIndentSum(line, style, add)
    +fun! <SID>XmlIndentSum(line, style, add, ...)
         if <SID>IsXMLContinuation(a:line) && a:style == 0 && !<SID>IsXMLEmptyClosingTag(a:line)
             " no complete tag, add one additional indent level
             " but only for the current line
             return a:add + shiftwidth()
    +    elseif a:0 && a:line =~ '^\s*<[^/]' && a:1 =~ '^\s*<[^/]' && a:1 !~ '>\s*$'
    +                \ && getline(prevnonblank(a:2-1)) !~ '/\s*>\s*$'
    +        return a:add + shiftwidth()
    +    elseif a:0 && a:line =~ '^\s*</' && a:1 =~ '^\s*<' && a:1 !~ '>\s*$'
    +        return a:add
         elseif <SID>HasNoTagEnd(a:line)
             " no complete tag, return initial indent
             return a:add
    @@ -156,7 +161,7 @@
         " Get indent from previous tag line
         let ind = <SID>XmlIndentSum(pline, -1, pind)
         " Determine indent from current line
    -    let ind = <SID>XmlIndentSum(curline, 0, ind)
    +    let ind = <SID>XmlIndentSum(curline, 0, ind, pline, a:lnum)
         return ind
     endfun
    

    Just copy the indent file to ~/.vim/indent/xml.vim 3 and apply the change.


    1 You may link this answer in the issue
    2 Which is of quality of just a temporary hack
    3 I believe it's $XDG_CONFIG_HOME/nvim/indent/xml.vim in case of Neovim