1" Language: xml 2" Repository: https://github.com/chrisbra/vim-xml-ftplugin 3" Last Changed: July 27, 2019 4" Maintainer: Christian Brabandt <[email protected]> 5" Previous Maintainer: Johannes Zellner <[email protected]> 6" Last Change: 7" 20190726 - Correctly handle non-tagged data 8" 20190204 - correctly handle wrap tags 9" https://github.com/chrisbra/vim-xml-ftplugin/issues/5 10" 20190128 - Make sure to find previous tag 11" https://github.com/chrisbra/vim-xml-ftplugin/issues/4 12" 20181116 - Fix indentation when tags start with a colon or an underscore 13" https://github.com/vim/vim/pull/926 14" 20181022 - Do not overwrite indentkeys setting 15" https://github.com/chrisbra/vim-xml-ftplugin/issues/1 16" 20180724 - Correctly indent xml comments https://github.com/vim/vim/issues/3200 17" 18" Notes: 19" 1) does not indent pure non-xml code (e.g. embedded scripts) 20" 2) will be confused by unbalanced tags in comments 21" or CDATA sections. 22" 2009-05-26 patch by Nikolai Weibull 23" TODO: implement pre-like tags, see xml_indent_open / xml_indent_close 24 25" Only load this indent file when no other was loaded. 26if exists("b:did_indent") 27 finish 28endif 29let b:did_indent = 1 30let s:keepcpo= &cpo 31set cpo&vim 32 33" [-- local settings (must come before aborting the script) --] 34" Attention: Parameter use_syntax_check is used by the docbk.vim indent script 35setlocal indentexpr=XmlIndentGet(v:lnum,1) 36setlocal indentkeys=o,O,*<Return>,<>>,<<>,/,{,},!^F 37" autoindent: used when the indentexpr returns -1 38setlocal autoindent 39 40if !exists('b:xml_indent_open') 41 let b:xml_indent_open = '.\{-}<[:A-Z_a-z]' 42 " pre tag, e.g. <address> 43 " let b:xml_indent_open = '.\{-}<[/]\@!\(address\)\@!' 44endif 45 46if !exists('b:xml_indent_close') 47 let b:xml_indent_close = '.\{-}</' 48 " end pre tag, e.g. </address> 49 " let b:xml_indent_close = '.\{-}</\(address\)\@!' 50endif 51 52let &cpo = s:keepcpo 53unlet s:keepcpo 54 55" [-- finish, if the function already exists --] 56if exists('*XmlIndentGet') 57 finish 58endif 59 60let s:keepcpo= &cpo 61set cpo&vim 62 63fun! <SID>XmlIndentWithPattern(line, pat) 64 let s = substitute('x'.a:line, a:pat, "\1", 'g') 65 return strlen(substitute(s, "[^\1].*$", '', '')) 66endfun 67 68" [-- check if it's xml --] 69fun! <SID>XmlIndentSynCheck(lnum) 70 if &syntax != '' 71 let syn1 = synIDattr(synID(a:lnum, 1, 1), 'name') 72 let syn2 = synIDattr(synID(a:lnum, strlen(getline(a:lnum)) - 1, 1), 'name') 73 if syn1 != '' && syn1 !~ 'xml' && syn2 != '' && syn2 !~ 'xml' 74 " don't indent pure non-xml code 75 return 0 76 endif 77 endif 78 return 1 79endfun 80 81" [-- return the sum of indents of a:lnum --] 82fun! <SID>XmlIndentSum(line, style, add) 83 if <SID>IsXMLContinuation(a:line) && a:style == 0 84 " no complete tag, add one additional indent level 85 " but only for the current line 86 return a:add + shiftwidth() 87 elseif <SID>HasNoTagEnd(a:line) 88 " no complete tag, return initial indent 89 return a:add 90 endif 91 if a:style == match(a:line, '^\s*</') 92 return (shiftwidth() * 93 \ (<SID>XmlIndentWithPattern(a:line, b:xml_indent_open) 94 \ - <SID>XmlIndentWithPattern(a:line, b:xml_indent_close) 95 \ - <SID>XmlIndentWithPattern(a:line, '.\{-}/>'))) + a:add 96 else 97 return a:add 98 endif 99endfun 100 101" Main indent function 102fun! XmlIndentGet(lnum, use_syntax_check) 103 " Find a non-empty line above the current line. 104 if prevnonblank(a:lnum - 1) == 0 105 " Hit the start of the file, use zero indent. 106 return 0 107 endif 108 " Find previous line with a tag (regardless whether open or closed, 109 " but always restrict the match to a line before the current one 110 " Note: xml declaration: <?xml version="1.0"?> 111 " won't be found, as it is not a legal tag name 112 let ptag_pattern = '\%(.\{-}<[/:A-Z_a-z]\)'. '\%(\&\%<'. a:lnum .'l\)' 113 let ptag = search(ptag_pattern, 'bnW') 114 " no previous tag 115 if ptag == 0 116 return 0 117 endif 118 119 let pline = getline(ptag) 120 let pind = indent(ptag) 121 122 let syn_name_start = '' " Syntax element at start of line (excluding whitespace) 123 let syn_name_end = '' " Syntax element at end of line 124 let curline = getline(a:lnum) 125 if a:use_syntax_check 126 let check_lnum = <SID>XmlIndentSynCheck(ptag) 127 let check_alnum = <SID>XmlIndentSynCheck(a:lnum) 128 if check_lnum == 0 || check_alnum == 0 129 return indent(a:lnum) 130 endif 131 let syn_name_end = synIDattr(synID(a:lnum, strlen(curline) - 1, 1), 'name') 132 let syn_name_start = synIDattr(synID(a:lnum, match(curline, '\S') + 1, 1), 'name') 133 endif 134 135 if syn_name_end =~ 'Comment' && syn_name_start =~ 'Comment' 136 return <SID>XmlIndentComment(a:lnum) 137 elseif empty(syn_name_start) && empty(syn_name_end) 138 " non-xml tag content: use indent from 'autoindent' 139 return pind + shiftwidth() 140 endif 141 142 " Get indent from previous tag line 143 let ind = <SID>XmlIndentSum(pline, -1, pind) 144 " Determine indent from current line 145 let ind = <SID>XmlIndentSum(curline, 0, ind) 146 return ind 147endfun 148 149func! <SID>IsXMLContinuation(line) 150 " Checks, whether or not the line matches a start-of-tag 151 return a:line !~ '^\s*<' 152endfunc 153 154func! <SID>HasNoTagEnd(line) 155 " Checks whether or not the line matches '>' (so finishes a tag) 156 return a:line !~ '>\s*$' 157endfunc 158 159" return indent for a commented line, 160" the middle part might be indented one additional level 161func! <SID>XmlIndentComment(lnum) 162 let ptagopen = search(b:xml_indent_open, 'bnW') 163 let ptagclose = search(b:xml_indent_close, 'bnW') 164 if getline(a:lnum) =~ '<!--' 165 " if previous tag was a closing tag, do not add 166 " one additional level of indent 167 if ptagclose > ptagopen && a:lnum > ptagclose 168 return indent(ptagclose) 169 else 170 " start of comment, add one indentation level 171 return indent(ptagopen) + shiftwidth() 172 endif 173 elseif getline(a:lnum) =~ '-->' 174 " end of comment, same as start of comment 175 return indent(search('<!--', 'bnW')) 176 else 177 " middle part of comment, add one additional level 178 return indent(search('<!--', 'bnW')) + shiftwidth() 179 endif 180endfunc 181 182let &cpo = s:keepcpo 183unlet s:keepcpo 184 185" vim:ts=4 et sts=-1 sw=0 186