xref: /vim-8.2.3635/runtime/indent/xml.vim (revision 23515b4e)
196f45c0bSBram Moolenaar" Language: XML
291f84f6eSBram Moolenaar" Maintainer: Christian Brabandt <[email protected]>
396f45c0bSBram Moolenaar" Repository: https://github.com/chrisbra/vim-xml-ftplugin
491f84f6eSBram Moolenaar" Previous Maintainer: Johannes Zellner <[email protected]>
5*23515b4eSBram Moolenaar" Last Changed: 2020 Nov 4th
69d87a37eSBram Moolenaar" Last Change:
7*23515b4eSBram Moolenaar" 20200529 - Handle empty closing tags correctly
84ceaa3a6SBram Moolenaar" 20191202 - Handle docbk filetype
95477506aSBram Moolenaar" 20190726 - Correctly handle non-tagged data
1063b74a83SBram Moolenaar" 20190204 - correctly handle wrap tags
1163b74a83SBram Moolenaar"            https://github.com/chrisbra/vim-xml-ftplugin/issues/5
12314dd79cSBram Moolenaar" 20190128 - Make sure to find previous tag
13314dd79cSBram Moolenaar"            https://github.com/chrisbra/vim-xml-ftplugin/issues/4
149d87a37eSBram Moolenaar" 20181116 - Fix indentation when tags start with a colon or an underscore
159d87a37eSBram Moolenaar"            https://github.com/vim/vim/pull/926
169d87a37eSBram Moolenaar" 20181022 - Do not overwrite indentkeys setting
17ba3ff539SBram Moolenaar"            https://github.com/chrisbra/vim-xml-ftplugin/issues/1
18ba3ff539SBram Moolenaar" 20180724 - Correctly indent xml comments https://github.com/vim/vim/issues/3200
199d87a37eSBram Moolenaar"
209d87a37eSBram Moolenaar" Notes:
219d87a37eSBram Moolenaar"   1) does not indent pure non-xml code (e.g. embedded scripts)
22071d4279SBram Moolenaar"       2) will be confused by unbalanced tags in comments
23071d4279SBram Moolenaar"       or CDATA sections.
245c73622aSBram Moolenaar"       2009-05-26 patch by Nikolai Weibull
25071d4279SBram Moolenaar" TODO:     implement pre-like tags, see xml_indent_open / xml_indent_close
26071d4279SBram Moolenaar
27071d4279SBram Moolenaar" Only load this indent file when no other was loaded.
28071d4279SBram Moolenaarif exists("b:did_indent")
29071d4279SBram Moolenaar    finish
30071d4279SBram Moolenaarendif
31071d4279SBram Moolenaarlet b:did_indent = 1
328e52a593SBram Moolenaarlet s:keepcpo= &cpo
338e52a593SBram Moolenaarset cpo&vim
34071d4279SBram Moolenaar
35071d4279SBram Moolenaar" [-- local settings (must come before aborting the script) --]
369d87a37eSBram Moolenaar" Attention: Parameter use_syntax_check is used by the docbk.vim indent script
37071d4279SBram Moolenaarsetlocal indentexpr=XmlIndentGet(v:lnum,1)
38ba3ff539SBram Moolenaarsetlocal indentkeys=o,O,*<Return>,<>>,<<>,/,{,},!^F
395477506aSBram Moolenaar" autoindent: used when the indentexpr returns -1
405477506aSBram Moolenaarsetlocal autoindent
41071d4279SBram Moolenaar
42071d4279SBram Moolenaarif !exists('b:xml_indent_open')
439d87a37eSBram Moolenaar    let b:xml_indent_open = '.\{-}<[:A-Z_a-z]'
44071d4279SBram Moolenaar    " pre tag, e.g. <address>
45071d4279SBram Moolenaar    " let b:xml_indent_open = '.\{-}<[/]\@!\(address\)\@!'
46071d4279SBram Moolenaarendif
47071d4279SBram Moolenaar
48071d4279SBram Moolenaarif !exists('b:xml_indent_close')
49*23515b4eSBram Moolenaar    let b:xml_indent_close = '.\{-}</\|/>.\{-}'
50071d4279SBram Moolenaar    " end pre tag, e.g. </address>
51071d4279SBram Moolenaar    " let b:xml_indent_close = '.\{-}</\(address\)\@!'
52071d4279SBram Moolenaarendif
53071d4279SBram Moolenaar
546c35beaaSBram Moolenaarlet &cpo = s:keepcpo
556c35beaaSBram Moolenaarunlet s:keepcpo
566c35beaaSBram Moolenaar
57071d4279SBram Moolenaar" [-- finish, if the function already exists --]
586c35beaaSBram Moolenaarif exists('*XmlIndentGet')
596c35beaaSBram Moolenaar    finish
606c35beaaSBram Moolenaarendif
616c35beaaSBram Moolenaar
626c35beaaSBram Moolenaarlet s:keepcpo= &cpo
636c35beaaSBram Moolenaarset cpo&vim
64071d4279SBram Moolenaar
65071d4279SBram Moolenaarfun! <SID>XmlIndentWithPattern(line, pat)
66071d4279SBram Moolenaar    let s = substitute('x'.a:line, a:pat, "\1", 'g')
67071d4279SBram Moolenaar    return strlen(substitute(s, "[^\1].*$", '', ''))
68071d4279SBram Moolenaarendfun
69071d4279SBram Moolenaar
70071d4279SBram Moolenaar" [-- check if it's xml --]
71071d4279SBram Moolenaarfun! <SID>XmlIndentSynCheck(lnum)
729d87a37eSBram Moolenaar    if &syntax != ''
73071d4279SBram Moolenaar        let syn1 = synIDattr(synID(a:lnum, 1, 1), 'name')
74071d4279SBram Moolenaar        let syn2 = synIDattr(synID(a:lnum, strlen(getline(a:lnum)) - 1, 1), 'name')
759d87a37eSBram Moolenaar        if syn1 != '' && syn1 !~ 'xml' && syn2 != '' && syn2 !~ 'xml'
76071d4279SBram Moolenaar            " don't indent pure non-xml code
77071d4279SBram Moolenaar            return 0
78071d4279SBram Moolenaar        endif
79071d4279SBram Moolenaar    endif
80071d4279SBram Moolenaar    return 1
81071d4279SBram Moolenaarendfun
82071d4279SBram Moolenaar
83071d4279SBram Moolenaar" [-- return the sum of indents of a:lnum --]
8463b74a83SBram Moolenaarfun! <SID>XmlIndentSum(line, style, add)
85*23515b4eSBram Moolenaar    if <SID>IsXMLContinuation(a:line) && a:style == 0 && !<SID>IsXMLEmptyClosingTag(a:line)
8663b74a83SBram Moolenaar        " no complete tag, add one additional indent level
8763b74a83SBram Moolenaar        " but only for the current line
8863b74a83SBram Moolenaar        return a:add + shiftwidth()
8963b74a83SBram Moolenaar    elseif <SID>HasNoTagEnd(a:line)
9063b74a83SBram Moolenaar        " no complete tag, return initial indent
9163b74a83SBram Moolenaar        return a:add
9263b74a83SBram Moolenaar    endif
9363b74a83SBram Moolenaar    if a:style == match(a:line, '^\s*</')
943ec574f2SBram Moolenaar        return (shiftwidth() *
9563b74a83SBram Moolenaar        \  (<SID>XmlIndentWithPattern(a:line, b:xml_indent_open)
9663b74a83SBram Moolenaar        \ - <SID>XmlIndentWithPattern(a:line, b:xml_indent_close)
9763b74a83SBram Moolenaar        \ - <SID>XmlIndentWithPattern(a:line, '.\{-}/>'))) + a:add
98071d4279SBram Moolenaar    else
99071d4279SBram Moolenaar        return a:add
100071d4279SBram Moolenaar    endif
101071d4279SBram Moolenaarendfun
102071d4279SBram Moolenaar
1039d87a37eSBram Moolenaar" Main indent function
104071d4279SBram Moolenaarfun! XmlIndentGet(lnum, use_syntax_check)
105071d4279SBram Moolenaar    " Find a non-empty line above the current line.
10663b74a83SBram Moolenaar    if prevnonblank(a:lnum - 1) == 0
107071d4279SBram Moolenaar        " Hit the start of the file, use zero indent.
108071d4279SBram Moolenaar        return 0
109071d4279SBram Moolenaar    endif
110314dd79cSBram Moolenaar    " Find previous line with a tag (regardless whether open or closed,
1115477506aSBram Moolenaar    " but always restrict the match to a line before the current one
11263b74a83SBram Moolenaar    " Note: xml declaration: <?xml version="1.0"?>
11363b74a83SBram Moolenaar    "       won't be found, as it is not a legal tag name
1145477506aSBram Moolenaar    let ptag_pattern = '\%(.\{-}<[/:A-Z_a-z]\)'. '\%(\&\%<'. a:lnum .'l\)'
11563b74a83SBram Moolenaar    let ptag = search(ptag_pattern, 'bnW')
11663b74a83SBram Moolenaar    " no previous tag
11763b74a83SBram Moolenaar    if ptag == 0
11863b74a83SBram Moolenaar        return 0
11963b74a83SBram Moolenaar    endif
120071d4279SBram Moolenaar
1215477506aSBram Moolenaar    let pline = getline(ptag)
1225477506aSBram Moolenaar    let pind  = indent(ptag)
1235477506aSBram Moolenaar
1245477506aSBram Moolenaar    let syn_name_start = '' " Syntax element at start of line (excluding whitespace)
1255477506aSBram Moolenaar    let syn_name_end = ''   " Syntax element at end of line
1265477506aSBram Moolenaar    let curline = getline(a:lnum)
127071d4279SBram Moolenaar    if a:use_syntax_check
12863b74a83SBram Moolenaar        let check_lnum = <SID>XmlIndentSynCheck(ptag)
1295c73622aSBram Moolenaar        let check_alnum = <SID>XmlIndentSynCheck(a:lnum)
1309d87a37eSBram Moolenaar        if check_lnum == 0 || check_alnum == 0
131071d4279SBram Moolenaar            return indent(a:lnum)
132071d4279SBram Moolenaar        endif
1335477506aSBram Moolenaar        let syn_name_end   = synIDattr(synID(a:lnum, strlen(curline) - 1, 1), 'name')
1345477506aSBram Moolenaar        let syn_name_start = synIDattr(synID(a:lnum, match(curline, '\S') + 1, 1), 'name')
135*23515b4eSBram Moolenaar        let prev_syn_name_end   = synIDattr(synID(ptag, strlen(pline) - 1, 1), 'name')
136*23515b4eSBram Moolenaar        " not needed (yet?)
137*23515b4eSBram Moolenaar        " let prev_syn_name_start = synIDattr(synID(ptag, match(pline, '\S') + 1, 1), 'name')
138071d4279SBram Moolenaar    endif
139071d4279SBram Moolenaar
1405477506aSBram Moolenaar    if syn_name_end =~ 'Comment' && syn_name_start =~ 'Comment'
1419d87a37eSBram Moolenaar        return <SID>XmlIndentComment(a:lnum)
1424ceaa3a6SBram Moolenaar    elseif empty(syn_name_start) && empty(syn_name_end) && a:use_syntax_check
1435477506aSBram Moolenaar        " non-xml tag content: use indent from 'autoindent'
144*23515b4eSBram Moolenaar        if pline =~ b:xml_indent_close
145*23515b4eSBram Moolenaar            return pind
146*23515b4eSBram Moolenaar        elseif !empty(prev_syn_name_end)
147*23515b4eSBram Moolenaar            " only indent by an extra shiftwidth, if the previous line ends
148*23515b4eSBram Moolenaar            " with an XML like tag
1495477506aSBram Moolenaar           return pind + shiftwidth()
150*23515b4eSBram Moolenaar        else
151*23515b4eSBram Moolenaar            " no extra indent, looks like a text continuation line
152*23515b4eSBram Moolenaar           return pind
153*23515b4eSBram Moolenaar        endif
1549d87a37eSBram Moolenaar    endif
1559d87a37eSBram Moolenaar
1569d87a37eSBram Moolenaar    " Get indent from previous tag line
15763b74a83SBram Moolenaar    let ind = <SID>XmlIndentSum(pline, -1, pind)
1589d87a37eSBram Moolenaar    " Determine indent from current line
1595477506aSBram Moolenaar    let ind = <SID>XmlIndentSum(curline, 0, ind)
160071d4279SBram Moolenaar    return ind
161071d4279SBram Moolenaarendfun
162071d4279SBram Moolenaar
16363b74a83SBram Moolenaarfunc! <SID>IsXMLContinuation(line)
16463b74a83SBram Moolenaar    " Checks, whether or not the line matches a start-of-tag
1654ceaa3a6SBram Moolenaar    return a:line !~ '^\s*<' && &ft is# 'xml'
16663b74a83SBram Moolenaarendfunc
16763b74a83SBram Moolenaar
16863b74a83SBram Moolenaarfunc! <SID>HasNoTagEnd(line)
16963b74a83SBram Moolenaar    " Checks whether or not the line matches '>' (so finishes a tag)
17063b74a83SBram Moolenaar    return a:line !~ '>\s*$'
17163b74a83SBram Moolenaarendfunc
17263b74a83SBram Moolenaar
173*23515b4eSBram Moolenaarfunc! <SID>IsXMLEmptyClosingTag(line)
174*23515b4eSBram Moolenaar    " Checks whether the line ends with an empty closing tag such as <lb/>
175*23515b4eSBram Moolenaar    return a:line =~? '<[^>]*/>\s*$'
176*23515b4eSBram Moolenaarendfunc
177*23515b4eSBram Moolenaar
1789d87a37eSBram Moolenaar" return indent for a commented line,
1795477506aSBram Moolenaar" the middle part might be indented one additional level
1809d87a37eSBram Moolenaarfunc! <SID>XmlIndentComment(lnum)
181*23515b4eSBram Moolenaar    let ptagopen = search('.\{-}<[:A-Z_a-z]\_[^/]\{-}>.\{-}', 'bnW')
18263b74a83SBram Moolenaar    let ptagclose = search(b:xml_indent_close, 'bnW')
1839d87a37eSBram Moolenaar    if getline(a:lnum) =~ '<!--'
1849d87a37eSBram Moolenaar        " if previous tag was a closing tag, do not add
1859d87a37eSBram Moolenaar        " one additional level of indent
1869d87a37eSBram Moolenaar        if ptagclose > ptagopen && a:lnum > ptagclose
187*23515b4eSBram Moolenaar            " If the previous tag was closed on the same line as it was
188*23515b4eSBram Moolenaar            " declared, we should indent with its indent level.
189*23515b4eSBram Moolenaar            if !<SID>IsXMLContinuation(getline(ptagclose))
190*23515b4eSBram Moolenaar                return indent(ptagclose)
191*23515b4eSBram Moolenaar            else
192*23515b4eSBram Moolenaar                return indent(ptagclose) - shiftwidth()
193*23515b4eSBram Moolenaar            endif
194*23515b4eSBram Moolenaar        elseif ptagclose == ptagopen
1959d87a37eSBram Moolenaar            return indent(ptagclose)
1969d87a37eSBram Moolenaar        else
1979d87a37eSBram Moolenaar            " start of comment, add one indentation level
1989d87a37eSBram Moolenaar            return indent(ptagopen) + shiftwidth()
1999d87a37eSBram Moolenaar        endif
2009d87a37eSBram Moolenaar    elseif getline(a:lnum) =~ '-->'
2019d87a37eSBram Moolenaar        " end of comment, same as start of comment
20263b74a83SBram Moolenaar        return indent(search('<!--', 'bnW'))
2039d87a37eSBram Moolenaar    else
2049d87a37eSBram Moolenaar        " middle part of comment, add one additional level
20563b74a83SBram Moolenaar        return indent(search('<!--', 'bnW')) + shiftwidth()
2069d87a37eSBram Moolenaar    endif
2079d87a37eSBram Moolenaarendfunc
2089d87a37eSBram Moolenaar
2098e52a593SBram Moolenaarlet &cpo = s:keepcpo
2108e52a593SBram Moolenaarunlet s:keepcpo
2118e52a593SBram Moolenaar
2129d87a37eSBram Moolenaar" vim:ts=4 et sts=-1 sw=0
213