vue.vim 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. " Language: Vue
  2. " Maintainer: leaf <https://github.com/leafOfTree>
  3. " Credits: Inspired by mxw/vim-jsx.
  4. if exists('b:did_indent') | finish |endif
  5. let s:test = exists('g:vim_vue_plugin_test')
  6. function! s:Init()
  7. """ Configs
  8. let s:config = vue#GetConfig('config', {})
  9. let s:config_syntax = s:config.syntax
  10. let s:initial_indent = s:config.initial_indent
  11. """ Variables
  12. let s:indent = {}
  13. let s:block_tag = '<\/\?\('.join(keys(s:config_syntax), '\|').'\)'
  14. " To adjust HTML
  15. let s:empty_tagname = '(area|base|br|col|embed|hr|input|img|'
  16. \.'keygen|link|meta|param|source|track|wbr)'
  17. let s:empty_tag = '\v\<'.s:empty_tagname.'.*(/)@<!\>'
  18. let s:empty_tag_start = '\v\<'.s:empty_tagname.'[^>]*$'
  19. let s:empty_tag_end = '\v^\s*[^<>/]*\/?\>\s*'
  20. let s:tag_start = '\v^\s*\<\w*' " <
  21. let s:tag_end = '\v^\s*\/?\>\s*' " />
  22. let s:full_tag_end = '\v^\s*\<\/' " </...>
  23. let s:ternary_q = '^\s\+?'
  24. let s:ternary_e = '^\s\+:.*,\s*$'
  25. endfunction
  26. function! s:SetVueIndent()
  27. """ Settings
  28. " JavaScript indentkeys
  29. setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e,:
  30. " XML indentkeys
  31. setlocal indentkeys+=*<Return>,<>>,<<>,/
  32. setlocal indentexpr=GetVueIndent()
  33. endfunction
  34. function! s:GetIndentFile(syntax)
  35. let syntax = a:syntax
  36. " lib/indent/* files are perferred for better indent result
  37. " from previous version Vim
  38. if syntax == 'html'
  39. let file = 'lib/indent/xml.vim'
  40. elseif syntax == 'css'
  41. let file = 'lib/indent/css.vim'
  42. elseif syntax == 'javascript'
  43. let file = 'lib/indent/typescript.vim'
  44. else
  45. let file = 'indent/'.syntax.'.vim'
  46. endif
  47. return file
  48. endfunction
  49. function! s:SetSyntaxIndentExpr(syntax_list)
  50. let saved_shiftwidth = &shiftwidth
  51. let saved_formatoptions = &formatoptions
  52. for syntax in a:syntax_list
  53. unlet! b:did_indent
  54. let &l:indentexpr = ''
  55. execute 'runtime '.s:GetIndentFile(syntax)
  56. let s:indent[syntax] = &l:indentexpr
  57. endfor
  58. let &shiftwidth = saved_shiftwidth
  59. let &formatoptions = saved_formatoptions
  60. endfunction
  61. function! s:GetBlockIndent(syntax)
  62. let syntax = a:syntax
  63. let indentexpr = get(s:indent, syntax)
  64. if !empty(indentexpr)
  65. let ind = eval(indentexpr)
  66. else
  67. call vue#LogWithLnum('indentexpr not found for '.syntax.', use cindent')
  68. let ind = cindent(v:lnum)
  69. endif
  70. return ind
  71. endfunction
  72. function! s:GetIndentByContext(tag, syntax)
  73. let ind = -1
  74. let prevline = getline(s:PrevNonBlankNonComment(v:lnum))
  75. let curline = getline(v:lnum)
  76. if a:tag != 'template' && a:syntax == 'html'
  77. " Set indent to 0 for custom tag with 'html' syntax
  78. if curline =~ s:block_tag && empty(prevline)
  79. let ind = 0
  80. endif
  81. elseif a:tag == 'template'
  82. " When 'pug' syntax in <template>, set block tags indent to 0
  83. if a:syntax == 'pug'
  84. if curline =~ s:block_tag
  85. let ind = 0
  86. endif
  87. endif
  88. else
  89. " When not in <template> and not 'html'
  90. " Set indent to 0 if current or prev line is block tag
  91. if curline =~ s:block_tag || prevline =~ s:block_tag
  92. let ind = 0
  93. endif
  94. endif
  95. return ind
  96. endfunction
  97. function! s:PrevNonBlankNonComment(lnum)
  98. let lnum = a:lnum - 1
  99. let prevlnum = prevnonblank(lnum)
  100. let prevsyn = vue#SyntaxSecondAtEnd(prevlnum)
  101. while prevsyn =~? 'comment' && lnum > 1
  102. let lnum = lnum - 1
  103. let prevlnum = prevnonblank(lnum)
  104. let prevsyn = vue#SyntaxSecondAtEnd(prevlnum)
  105. endwhile
  106. return prevlnum
  107. endfunction
  108. function! s:AdjustBlockIndent(syntax, ind)
  109. let block = a:block
  110. let syntax = a:syntax
  111. let ind = a:ind
  112. if syntax == 'html'
  113. let ind = s:AdjustHTMLIndent(ind)
  114. elseif syntax == 'javascript'
  115. let ind = s:AdjustJavaScriptIndent(ind)
  116. elseif syntax == 'css'
  117. let ind = s:AdjustCSSIndent(ind)
  118. endif
  119. return ind
  120. endfunction
  121. function! s:CheckInitialIndent(tag, syntax, ind)
  122. let ind = a:ind
  123. let add = 0
  124. if ind == 0 && getline(v:lnum) !~ s:block_tag
  125. let add = vue#IncludeOrEqual(s:initial_indent, a:tag.'.'.a:syntax)
  126. \ || vue#IncludeOrEqual(s:initial_indent, a:tag)
  127. \ || vue#IncludeOrEqual(s:initial_indent, a:syntax)
  128. endif
  129. if add
  130. call vue#LogWithLnum('add initial indent')
  131. let ind = &sw
  132. endif
  133. return ind
  134. endfunction
  135. function! s:PrevMultilineEmptyTag(lnum)
  136. let lnum = a:lnum - 1
  137. let lnums = [0, 0]
  138. while lnum > 0
  139. let line = getline(lnum)
  140. if line =~? s:empty_tag_end
  141. let lnums[1] = lnum
  142. endif
  143. if line =~? s:tag_start
  144. if line =~? s:empty_tag_start
  145. let lnums[0] = lnum
  146. return lnums
  147. else
  148. return [0, 0]
  149. endif
  150. endif
  151. let lnum = lnum - 1
  152. endwhile
  153. endfunction
  154. function! s:AdjustHTMLIndent(ind)
  155. let ind = a:ind
  156. let prevlnum = prevnonblank(v:lnum - 1)
  157. let prevline = getline(prevlnum)
  158. let curline = getline(v:lnum)
  159. if prevline =~? s:empty_tag
  160. call vue#LogWithLnum('previous line is an empty tag')
  161. let ind = ind - &sw
  162. endif
  163. " Align '/>' and '>' with '<'
  164. if curline =~? s:tag_end
  165. let ind = ind - &sw
  166. endif
  167. " Then correct the indentation of any element following '/>' or '>'.
  168. if prevline =~? s:tag_end
  169. let ind = ind + &sw
  170. " Decrease indent if prevlines are a multiline empty tag
  171. let [start, end] = s:PrevMultilineEmptyTag(v:lnum)
  172. if prevlnum == end
  173. call vue#LogWithLnum('previous line is a multiline empty tag')
  174. let ind = indent(v:lnum - 1)
  175. if curline =~? s:full_tag_end
  176. let ind = ind - &sw
  177. endif
  178. endif
  179. endif
  180. " Multiline array/object in attribute like v-*="[
  181. " ...
  182. " ]
  183. if prevline =~ '[[{]\s*$'
  184. call vue#LogWithLnum('previous line is an open bracket')
  185. let ind = indent(prevlnum) + &sw
  186. endif
  187. if curline =~ '^\s*[]}][^"]*"\?\s*$'
  188. call vue#LogWithLnum('current line is a closing bracket')
  189. let ind = indent(prevlnum) - &sw
  190. endif
  191. " Multiline ternary 'a ? b : c' in attribute
  192. if curline =~ s:ternary_q
  193. call vue#LogWithLnum('current line is ?...')
  194. let ind = indent(prevlnum) + &sw
  195. endif
  196. if curline =~ s:ternary_e && prevline =~ s:ternary_q
  197. call vue#LogWithLnum('current line is :...')
  198. let ind = indent(prevlnum)
  199. endif
  200. if prevline =~ s:ternary_e
  201. call vue#LogWithLnum('prevline line is :...')
  202. let ind = indent(prevlnum) - &sw
  203. endif
  204. " Angle bracket in attribute, like v-if="isEnabled('item.<name>')"
  205. if prevline =~ '="[^"]*<[^"]*>[^"]*"'
  206. call vue#LogWithLnum('prevline line is angle bracket in attribute')
  207. let ind = ind - &sw
  208. endif
  209. return ind
  210. endfunction
  211. function! s:AdjustJavaScriptIndent(ind)
  212. let ind = a:ind
  213. let prevlnum = prevnonblank(v:lnum - 1)
  214. let prevline = getline(prevlnum)
  215. let curline = getline(v:lnum)
  216. if prevline =~ '^\s*\w.*$' && curline =~ '^\s*\.'
  217. call vue#LogWithLnum('current line is the first chain call')
  218. let ind = indent(prevlnum) + &sw
  219. endif
  220. if prevline =~ '\s=>\s.*,\s*$' && curline !~ '^\s*[]}])\?,\?\s*$'
  221. call vue#LogWithLnum('previous line is arrow function property')
  222. let ind = indent(prevlnum)
  223. endif
  224. if prevline =~ '\s||\s*$'
  225. call vue#LogWithLnum('previous line ends with "||"')
  226. let ind = indent(prevlnum) + &sw
  227. endif
  228. return ind
  229. endfunction
  230. function! s:AdjustCSSIndent(ind)
  231. let ind = a:ind
  232. let prevlnum = prevnonblank(v:lnum - 1)
  233. let prevline = getline(prevlnum)
  234. let curline = getline(v:lnum)
  235. if prevline =~ ':\s.*,\s*$'
  236. call vue#LogWithLnum('previous line is css function')
  237. let ind = indent(prevlnum) + &sw
  238. endif
  239. if curline =~ '^\s*);\?\s*$'
  240. call vue#LogWithLnum('curline is closing round bracket')
  241. let ind = indent(prevlnum) - &sw
  242. endif
  243. return ind
  244. endfunction
  245. function! GetVueIndent()
  246. let tag = vue#GetBlockTag(v:lnum)
  247. let syntax = vue#GetBlockSyntax(v:lnum)
  248. let ind = s:GetIndentByContext(tag, syntax)
  249. if ind == -1
  250. let ind = s:GetBlockIndent(syntax)
  251. let ind = s:AdjustBlockIndent(syntax, ind)
  252. call vue#LogWithLnum('syntax '.syntax.', ind '.ind)
  253. else
  254. call vue#LogWithLnum('context, ind '.ind)
  255. endif
  256. let ind = s:CheckInitialIndent(tag, syntax, ind)
  257. return ind
  258. endfunction
  259. function! VimVuePluginIndentMain(...)
  260. call s:Init()
  261. let syntax_list = vue#GetSyntaxList(s:config_syntax)
  262. call s:SetSyntaxIndentExpr(syntax_list)
  263. call s:SetVueIndent()
  264. endfunction
  265. let s:timer = exists('*timer_start') && !exists('SessionLoad') && !s:test
  266. if s:timer
  267. call timer_start(200, 'VimVuePluginIndentMain')
  268. else
  269. call VimVuePluginIndentMain()
  270. endif
  271. let b:did_indent = 1