vue.vim 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. " Since vue#Log and vue#GetConfig are always called
  2. " in syntax and indent files,
  3. " this file will be sourced on opening the first vue file
  4. function! s:GetConfig(name, default)
  5. let name = 'g:vim_vue_plugin_'.a:name
  6. let value = exists(name) ? eval(name) : a:default
  7. if a:name == 'config'
  8. let value = s:MergeUserConfigIntoDefault(value)
  9. endif
  10. return value
  11. endfunction
  12. function! s:MergeUserConfigIntoDefault(user)
  13. let default = {
  14. \'syntax': {
  15. \ 'template': ['html'],
  16. \ 'script': ['javascript'],
  17. \ 'style': ['css'],
  18. \},
  19. \'full_syntax': [],
  20. \'initial_indent': [],
  21. \'attribute': 0,
  22. \'keyword': 0,
  23. \'foldexpr': 0,
  24. \'debug': 0,
  25. \}
  26. let user = a:user
  27. for key in keys(default)
  28. if has_key(user, key)
  29. let default[key] = user[key]
  30. endif
  31. endfor
  32. " For backwards compatibility with 'init_indent'
  33. if !has_key(user, 'initial_indent')
  34. if has_key(user, 'init_indent')
  35. \ ? user.init_indent
  36. \ : expand('%:e') == 'wpy'
  37. let default.initial_indent = ['script', 'style']
  38. endif
  39. endif
  40. return default
  41. endfunction
  42. function! s:CheckVersion()
  43. if !exists('g:vim_vue_plugin_config')
  44. let prev_configs = [
  45. \'g:vim_vue_plugin_load_full_syntax',
  46. \'g:vim_vue_plugin_use_pug',
  47. \'g:vim_vue_plugin_use_coffee',
  48. \'g:vim_vue_plugin_use_typescript',
  49. \'g:vim_vue_plugin_use_less',
  50. \'g:vim_vue_plugin_use_sass',
  51. \'g:vim_vue_plugin_use_scss',
  52. \'g:vim_vue_plugin_use_stylus',
  53. \'g:vim_vue_plugin_has_init_indent',
  54. \'g:vim_vue_plugin_highlight_vue_attr',
  55. \'g:vim_vue_plugin_highlight_vue_keyword',
  56. \'g:vim_vue_plugin_use_foldexpr',
  57. \'g:vim_vue_plugin_custom_blocks',
  58. \'g:vim_vue_plugin_debug',
  59. \]
  60. let has_prev_config = 0
  61. for config in prev_configs
  62. if exists(config)
  63. let has_prev_config = 1
  64. break
  65. endif
  66. endfor
  67. if has_prev_config
  68. let message = 'Hey, it seems that you just upgraded. Please use `g:vim_vue_plugin_config` to replace previous configs'
  69. let message2 = 'For details, please check README.md ## Configuration or https://github.com/leafOfTree/vim-vue-plugin'
  70. echom '['.s:name.'] '.message
  71. echom '['.s:name.'] '.message2
  72. endif
  73. endif
  74. endfunction
  75. function! s:Main()
  76. let s:name = 'vim-vue-plugin'
  77. let s:config = s:GetConfig('config', {})
  78. let s:full_syntax = s:config.full_syntax
  79. let s:debug = s:config.debug
  80. call s:CheckVersion()
  81. endfunction
  82. function! vue#Log(msg)
  83. if s:debug
  84. echom '['.s:name.'] '.a:msg
  85. endif
  86. endfunction
  87. function! vue#LogWithLnum(msg)
  88. if s:debug
  89. echom '['.s:name.']['.v:lnum.'] '.a:msg
  90. endif
  91. endfunction
  92. function! vue#Warn(msg)
  93. if s:debug
  94. echohl WarningMsg
  95. echom '['.s:name.'] '.a:msg
  96. echohl None
  97. endif
  98. endfunction
  99. function! vue#GetConfig(name, default)
  100. return s:GetConfig(a:name, a:default)
  101. endfunction
  102. if exists('##CursorMoved') && (exists('*OnChangeVueSyntax') || exists('*OnChangeVueSubtype'))
  103. augroup vim_vue_plugin
  104. autocmd!
  105. autocmd CursorMoved,CursorMovedI,WinEnter *.vue,*.wpy
  106. \ call s:CheckSyntax()
  107. augroup END
  108. if exists('*OnChangeVueSyntax')
  109. let s:OnChangeListener = function('OnChangeVueSyntax')
  110. else
  111. let s:OnChangeListener = function('OnChangeVueSubtype')
  112. endif
  113. let s:syntax = ''
  114. function! s:CheckSyntax()
  115. let syntax = GetVueSyntax()
  116. if s:syntax != syntax
  117. call s:OnChangeListener(syntax)
  118. let s:syntax = syntax
  119. endif
  120. endfunction
  121. endif
  122. function! s:SyntaxListAtEnd(lnum)
  123. let plnum = prevnonblank(a:lnum)
  124. let pline = getline(plnum)
  125. " return [] if prevnonblank is an end tag
  126. if pline =~ '^<\/'
  127. return []
  128. endif
  129. let col = strlen(pline)
  130. return map(synstack(plnum, col), 'synIDattr(v:val, "name")')
  131. endfunction
  132. function! s:SyntaxAtEnd(lnum)
  133. let syns = s:SyntaxListAtEnd(a:lnum)
  134. let syntax_name = empty(syns) ? '' : get(syns, 0, '')
  135. let syntax_name = s:RemoveCssPrefix(syntax_name)
  136. return syntax_name
  137. endfunction
  138. function! vue#SyntaxSecondAtEnd(lnum)
  139. let syns = s:SyntaxListAtEnd(a:lnum)
  140. return get(syns, 1, '')
  141. endfunction
  142. function! s:GetBlockTag(lnum)
  143. let syntax_name = s:SyntaxAtEnd(a:lnum)
  144. let tag = matchstr(syntax_name, '\(\u\U\+\)\+\zeBlock')
  145. let tag = substitute(tag, '\U\zs\(\u\)', "-\\1", 'g')
  146. let tag = tolower(tag)
  147. return tag
  148. endfunction
  149. let s:style_with_css_prefix = ['scss', 'less', 'stylus']
  150. " Adjust syntax name to support emmet-vim by adding css prefix
  151. function! vue#AlterSyntaxForEmmetVim(name, syntax)
  152. let name = a:name
  153. if count(s:style_with_css_prefix, a:syntax)
  154. let name = 'css'.toupper(name[0]).name[1:]
  155. endif
  156. return name
  157. endfunction
  158. " Remove css prefix
  159. function! s:RemoveCssPrefix(syntax_name)
  160. let syntax_name = a:syntax_name
  161. let syntax = matchstr(syntax_name, '^\U\+')
  162. if syntax == 'css'
  163. let next_syntax = tolower(matchstr(syntax_name, '^\U\+\zs\u\U\+'))
  164. if count(s:style_with_css_prefix, next_syntax)
  165. let syntax_name = matchstr(syntax_name, '^\U\+\zs.*')
  166. let syntax_name = tolower(syntax_name[0]).syntax_name[1:]
  167. endif
  168. endif
  169. return syntax_name
  170. endfunction
  171. function! s:GetBlockSyntax(lnum)
  172. let syntax_name = s:SyntaxAtEnd(a:lnum)
  173. let syntax = matchstr(syntax_name, '^\U\+')
  174. return syntax
  175. endfunction
  176. function! vue#GetBlockTag(lnum)
  177. return s:GetBlockTag(a:lnum)
  178. endfunction
  179. function! vue#GetBlockSyntax(lnum)
  180. return s:GetBlockSyntax(a:lnum)
  181. endfunction
  182. function! GetVueSubtype()
  183. let syntax = vue#GetBlockSyntax(line('.'))
  184. return syntax
  185. endfunction
  186. function! GetVueSyntax()
  187. let syntax = vue#GetBlockSyntax(line('.'))
  188. return syntax
  189. endfunction
  190. function! GetVueTag(...)
  191. let lnum = a:0 > 0 ? a:1 : line('.')
  192. return vue#GetBlockTag(lnum)
  193. endfunction
  194. function! vue#LoadSyntax(group, syntax)
  195. let group = a:group
  196. let syntax = a:syntax
  197. if s:IncludeOrEqual(s:full_syntax, syntax)
  198. call vue#LoadFullSyntax(group, syntax)
  199. else
  200. let loaded = vue#LoadDefaultSyntax(group, syntax)
  201. if !loaded
  202. call vue#LoadFullSyntax(group, syntax)
  203. endif
  204. endif
  205. endfunction
  206. function! vue#LoadDefaultSyntax(group, syntax)
  207. unlet! b:current_syntax
  208. let loaded = 0
  209. let syntax_paths = ['$VIMRUNTIME', '$VIM/vimfiles', '$HOME/.vim']
  210. for path in syntax_paths
  211. let file = expand(path).'/syntax/'.a:syntax.'.vim'
  212. if filereadable(file)
  213. let loaded = 1
  214. execute 'syntax include '.a:group.' '.file
  215. endif
  216. endfor
  217. if loaded
  218. call vue#Log(a:syntax.': load default')
  219. else
  220. call vue#Warn(a:syntax.': syntax not found in '.string(syntax_paths))
  221. call vue#Warn(a:syntax.': load full instead')
  222. endif
  223. return loaded
  224. endfunction
  225. " Load all syntax files in 'runtimepath'
  226. " Useful if there is no default syntax file provided by vim
  227. function! vue#LoadFullSyntax(group, syntax)
  228. call vue#Log(a:syntax.': load full')
  229. call s:SetCurrentSyntax(a:syntax)
  230. execute 'syntax include '.a:group.' syntax/'.a:syntax.'.vim'
  231. endfunction
  232. " Settings to avoid syntax overload
  233. function! s:SetCurrentSyntax(type)
  234. if a:type == 'coffee'
  235. " Avoid `syntax/javascript.vim` in kchmck/vim-coffee-script
  236. let b:current_syntax = 'vue'
  237. syntax cluster coffeeJS contains=@javascript,@htmlJavaScript
  238. else
  239. unlet! b:current_syntax
  240. endif
  241. endfunction
  242. function! vue#GetSyntaxList(config_syntax)
  243. let syntax_list = []
  244. for syntax in values(a:config_syntax)
  245. let type = type(syntax)
  246. if type == v:t_string
  247. if !count(syntax_list, syntax)
  248. call add(syntax_list, syntax)
  249. endif
  250. elseif type == v:t_list
  251. for syn in syntax
  252. if !count(syntax_list, syn)
  253. call add(syntax_list, syn)
  254. endif
  255. endfor
  256. else
  257. echoerr '[vim-vue-plugin] syntax value type'
  258. \.' must be either string or list'
  259. endif
  260. endfor
  261. call s:ModifySyntaxOrder(syntax_list)
  262. return syntax_list
  263. endfunction
  264. function! s:IncludeOrEqual(listOrString, item)
  265. let listOrString = a:listOrString
  266. let item = a:item
  267. let type = type(listOrString)
  268. return (type == v:t_list && count(listOrString, item))
  269. \ || (type == v:t_string && listOrString == item)
  270. endfunction
  271. function! vue#IncludeOrEqual(listOrString, item)
  272. return s:IncludeOrEqual(a:listOrString, a:item)
  273. endfunction
  274. function! s:ModifySyntaxOrder(syntax_list)
  275. let syntax_list = a:syntax_list
  276. " Move basic syntax to the end of the list, so we can check
  277. " if they are already loaded by other syntax.
  278. " Order matters
  279. let load_last = ['html', 'javascript', 'css']
  280. for syntax in load_last
  281. let idx = index(syntax_list, syntax)
  282. if idx >= 0
  283. call remove(syntax_list, idx)
  284. call add(syntax_list, syntax)
  285. endif
  286. endfor
  287. endfunction
  288. call s:Main()