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