summaryrefslogtreecommitdiffstats
path: root/runtime/indent/xml.vim
blob: 29069bab8402c2065faaf30b8b92b5ec93cc2b54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"     Language: xml
"   Repository: https://github.com/chrisbra/vim-xml-ftplugin
" Last Changed: Jan 28, 2019
"   Maintainer: Christian Brabandt <cb@256bit.org>
" Previous Maintainer:  Johannes Zellner <johannes@zellner.org>
" Last Change:
" 20190128 - Make sure to find previous tag
"            https://github.com/chrisbra/vim-xml-ftplugin/issues/4
" 20181116 - Fix indentation when tags start with a colon or an underscore
"            https://github.com/vim/vim/pull/926
" 20181022 - Do not overwrite indentkeys setting
"            https://github.com/chrisbra/vim-xml-ftplugin/issues/1
" 20180724 - Correctly indent xml comments https://github.com/vim/vim/issues/3200
"
" Notes:
"   1) does not indent pure non-xml code (e.g. embedded scripts)
"       2) will be confused by unbalanced tags in comments
"       or CDATA sections.
"       2009-05-26 patch by Nikolai Weibull
" TODO:     implement pre-like tags, see xml_indent_open / xml_indent_close

" Only load this indent file when no other was loaded.
if exists("b:did_indent")
    finish
endif
let b:did_indent = 1
let s:keepcpo= &cpo
set cpo&vim

" [-- local settings (must come before aborting the script) --]
" Attention: Parameter use_syntax_check is used by the docbk.vim indent script
setlocal indentexpr=XmlIndentGet(v:lnum,1)
setlocal indentkeys=o,O,*<Return>,<>>,<<>,/,{,},!^F

if !exists('b:xml_indent_open')
    let b:xml_indent_open = '.\{-}<[:A-Z_a-z]'
    " pre tag, e.g. <address>
    " let b:xml_indent_open = '.\{-}<[/]\@!\(address\)\@!'
endif

if !exists('b:xml_indent_close')
    let b:xml_indent_close = '.\{-}</'
    " end pre tag, e.g. </address>
    " let b:xml_indent_close = '.\{-}</\(address\)\@!'
endif

let &cpo = s:keepcpo
unlet s:keepcpo

" [-- finish, if the function already exists --]
if exists('*XmlIndentGet')
    finish
endif

let s:keepcpo= &cpo
set cpo&vim

fun! <SID>XmlIndentWithPattern(line, pat)
    let s = substitute('x'.a:line, a:pat, "\1", 'g')
    return strlen(substitute(s, "[^\1].*$", '', ''))
endfun

" [-- check if it's xml --]
fun! <SID>XmlIndentSynCheck(lnum)
    if &syntax != ''
        let syn1 = synIDattr(synID(a:lnum, 1, 1), 'name')
        let syn2 = synIDattr(synID(a:lnum, strlen(getline(a:lnum)) - 1, 1), 'name')
        if syn1 != '' && syn1 !~ 'xml' && syn2 != '' && syn2 !~ 'xml'
            " don't indent pure non-xml code
            return 0
        endif
    endif
    return 1
endfun

" [-- return the sum of indents of a:lnum --]
fun! <SID>XmlIndentSum(lnum, style, add)
    let line = getline(a:lnum)
    if a:style == match(line, '^\s*</')
        return (shiftwidth() *
        \  (<SID>XmlIndentWithPattern(line, b:xml_indent_open)
        \ - <SID>XmlIndentWithPattern(line, b:xml_indent_close)
        \ - <SID>XmlIndentWithPattern(line, '.\{-}/>'))) + a:add
    else
        return a:add
    endif
endfun

" Main indent function
fun! XmlIndentGet(lnum, use_syntax_check)
    " Find a non-empty line above the current line.
    let plnum = prevnonblank(a:lnum - 1)
    " Hit the start of the file, use zero indent.
    if plnum == 0
        return 0
    endif
    " Find previous line with a tag (regardless whether open or closed,
    " but always start restrict the match to a line before the current one
    let ptag_pattern = '\%(.\{-}<[/:A-Z_a-z]\)'. '\%(\&\%<'. line('.').'l\)'
    let ptag = search(ptag_pattern, 'bnw')

    let syn_name = ''
    if a:use_syntax_check
        let check_lnum = <SID>XmlIndentSynCheck(plnum)
        let check_alnum = <SID>XmlIndentSynCheck(a:lnum)
        if check_lnum == 0 || check_alnum == 0
            return indent(a:lnum)
        endif
        let syn_name = synIDattr(synID(a:lnum, strlen(getline(a:lnum)) - 1, 1), 'name')
    endif

    if syn_name =~ 'Comment'
        return <SID>XmlIndentComment(a:lnum)
    endif

    " Get indent from previous tag line
    let ind = <SID>XmlIndentSum(ptag, -1, indent(ptag))
    " Determine indent from current line
    let ind = <SID>XmlIndentSum(a:lnum, 0, ind)
    return ind
endfun

" return indent for a commented line,
" the middle part might be indented on additional level
func! <SID>XmlIndentComment(lnum)
    let ptagopen = search(b:xml_indent_open, 'bnw')
    let ptagclose = search(b:xml_indent_close, 'bnw')
    if getline(a:lnum) =~ '<!--'
        " if previous tag was a closing tag, do not add
        " one additional level of indent
        if ptagclose > ptagopen && a:lnum > ptagclose
            return indent(ptagclose)
        else
            " start of comment, add one indentation level
            return indent(ptagopen) + shiftwidth()
        endif
    elseif getline(a:lnum) =~ '-->'
        " end of comment, same as start of comment
        return indent(search('<!--', 'bnw'))
    else
        " middle part of comment, add one additional level
        return indent(search('<!--', 'bnw')) + shiftwidth()
    endif
endfunc

let &cpo = s:keepcpo
unlet s:keepcpo

" vim:ts=4 et sts=-1 sw=0