xml.vim 7.6 KB

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