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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
" Language: XML
" Maintainer: Christian Brabandt <cb@256bit.org>
" Repository: https://github.com/chrisbra/vim-xml-ftplugin
" Previous Maintainer: Johannes Zellner <johannes@zellner.org>
" Last Changed: 2020 Nov 4th
" Last Change:
" 20200529 - Handle empty closing tags correctly
" 20191202 - Handle docbk filetype
" 20190726 - Correctly handle non-tagged data
" 20190204 - correctly handle wrap tags
" https://github.com/chrisbra/vim-xml-ftplugin/issues/5
" 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
" autoindent: used when the indentexpr returns -1
setlocal autoindent
let b:undo_indent = "setl ai< inde< indk<"
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
if !exists('b:xml_indent_continuation_filetype')
let b:xml_indent_continuation_filetype = 'xml'
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(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 <SID>HasNoTagEnd(a:line)
" no complete tag, return initial indent
return a:add
endif
if a:style == match(a:line, '^\s*</')
return (shiftwidth() *
\ (<SID>XmlIndentWithPattern(a:line, b:xml_indent_open)
\ - <SID>XmlIndentWithPattern(a:line, b:xml_indent_close)
\ - <SID>XmlIndentWithPattern(a: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.
if prevnonblank(a:lnum - 1) == 0
" Hit the start of the file, use zero indent.
return 0
endif
" Find previous line with a tag (regardless whether open or closed,
" but always restrict the match to a line before the current one
" Note: xml declaration: <?xml version="1.0"?>
" won't be found, as it is not a legal tag name
let ptag_pattern = '\%(.\{-}<[/:A-Z_a-z]\)'. '\%(\&\%<'. a:lnum .'l\)'
let ptag = search(ptag_pattern, 'bnW')
" no previous tag
if ptag == 0
return 0
endif
let pline = getline(ptag)
let pind = indent(ptag)
let syn_name_start = '' " Syntax element at start of line (excluding whitespace)
let syn_name_end = '' " Syntax element at end of line
let curline = getline(a:lnum)
if a:use_syntax_check
let check_lnum = <SID>XmlIndentSynCheck(ptag)
let check_alnum = <SID>XmlIndentSynCheck(a:lnum)
if check_lnum == 0 || check_alnum == 0
return indent(a:lnum)
endif
let syn_name_end = synIDattr(synID(a:lnum, strlen(curline) - 1, 1), 'name')
let syn_name_start = synIDattr(synID(a:lnum, match(curline, '\S') + 1, 1), 'name')
let prev_syn_name_end = synIDattr(synID(ptag, strlen(pline) - 1, 1), 'name')
" not needed (yet?)
" let prev_syn_name_start = synIDattr(synID(ptag, match(pline, '\S') + 1, 1), 'name')
endif
if syn_name_end =~ 'Comment' && syn_name_start =~ 'Comment'
return <SID>XmlIndentComment(a:lnum)
elseif empty(syn_name_start) && empty(syn_name_end) && a:use_syntax_check
" non-xml tag content: use indent from 'autoindent'
if pline =~ b:xml_indent_close
return pind
elseif !empty(prev_syn_name_end)
" only indent by an extra shiftwidth, if the previous line ends
" with an XML like tag
return pind + shiftwidth()
else
" no extra indent, looks like a text continuation line
return pind
endif
endif
" 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)
return ind
endfun
func! <SID>IsXMLContinuation(line)
" Checks, whether or not the line matches a start-of-tag
return a:line !~ '^\s*<' && &ft =~# b:xml_indent_continuation_filetype
endfunc
func! <SID>HasNoTagEnd(line)
" Checks whether or not the line matches '>' (so finishes a tag)
return a:line !~ '>\s*$'
endfunc
func! <SID>IsXMLEmptyClosingTag(line)
" Checks whether the line ends with an empty closing tag such as <lb/>
return a:line =~? '<[^>]*/>\s*$'
endfunc
" return indent for a commented line,
" the middle part might be indented one additional level
func! <SID>XmlIndentComment(lnum)
let ptagopen = search('.\{-}<[:A-Z_a-z]\_[^/]\{-}>.\{-}', '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
" If the previous tag was closed on the same line as it was
" declared, we should indent with its indent level.
if !<SID>IsXMLContinuation(getline(ptagclose))
return indent(ptagclose)
else
return indent(ptagclose) - shiftwidth()
endif
elseif ptagclose == ptagopen
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
|