Support custom blocks syntax and indent

This commit is contained in:
leafOfTree
2021-03-11 14:52:38 +08:00
parent db71d38675
commit 9af7723bd1
6 changed files with 299 additions and 123 deletions

View File

@@ -76,6 +76,7 @@ let g:vim_vue_plugin_load_full_syntax = 1
| `g:vim_vue_plugin_highlight_vue_attr` | Highlight vue attribute value as expression instead of string. | 0 |
| `g:vim_vue_plugin_highlight_vue_keyword` | Highlight vue keyword like `data`, `methods`, ... | 0 |
| `g:vim_vue_plugin_use_foldexpr`\# | Enable builtin `foldexpr` foldmethod. | 0 |
| `g:vim_vue_plugin_custom_blocks` | Highlight custom blocks. Check the exmple bleow. | {} |
| `g:vim_vue_plugin_debug` | Echo debug messages in `messages` list. Useful to debug if unexpected indents occur. | 0 |
\*: Vim may be slow if the feature is enabled. Find a balance between syntax highlight and speed. By the way, custom syntax can be added in `~/.vim/syntax` or `$VIM/vimfiles/syntax`.
@@ -89,6 +90,32 @@ let g:vim_vue_plugin_load_full_syntax = 1
- `g:vim_vue_plugin_load_full_syntax` applies to other `HTML/Sass/Less` plugins.
- `filetype` is set to `vue` so autocmds and other settings for `javascript` have to be manually enabled for `vue`.
## Custom blocks
You could enable syntax highlighting in a custom block by setting `g:vim_vue_plugin_custom_blocks`.
Its structure is `{ block: filetype }` or `{ block: filetype[] }`. When providing a filetype list, you need to add `lang="..."` in the tag. Otherwise, the first one will be used.
```vim
let g:vim_vue_plugin_custom_blocks = {
\'docs': 'markdown',
\'i18n': ['json', 'yaml', 'json5'],
\}
```
```vue
<docs>
# This is the documentation for component.
</docs>
<i18n lang="yaml">
en:
hello: "Hello World!"
ja:
hello: "こんにちは、世界!"
</i18n>
```
## Context based behavior
As there are more than one language in `.vue` file, the different behaviors like mapping or completion and local options, may be required under different tags or subtypes(current language type).

View File

@@ -1,6 +1,15 @@
" Since vue#Log and vue#GetConfig are always called
" in syntax and indent files,
" this file will be sourced when opening the first vue file
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:GetConfig(name, default)
let name = 'g:vim_vue_plugin_'.a:name
return exists(name) ? eval(name) : a:default
endfunction
let s:name = 'vim-vue-plugin'
let s:debug = exists("g:vim_vue_plugin_debug")
\ && g:vim_vue_plugin_debug == 1
let s:load_full_syntax = s:GetConfig("load_full_syntax", 0)
let s:debug = s:GetConfig("debug", 0)
function! vue#Log(msg)
if s:debug
@@ -9,13 +18,9 @@ function! vue#Log(msg)
endfunction
function! vue#GetConfig(name, default)
let name = 'g:vim_vue_plugin_'.a:name
return exists(name) ? eval(name) : a:default
return s:GetConfig(a:name, a:default)
endfunction
" Since vue#Log and vue#GetConfig are always called
" in syntax and indent files,
" this file will be sourced when opening the first vue file
if exists('##CursorMoved') && exists('*OnChangeVueSubtype')
augroup vim_vue_plugin
autocmd!
@@ -70,3 +75,42 @@ function! GetVueTag(...)
return tag
endfunction
function! vue#LoadSyntax(group, type)
if s:load_full_syntax
call vue#LoadFullSyntax(a:group, a:type)
else
call vue#LoadDefaultSyntax(a:group, a:type)
endif
endfunction
function! vue#LoadDefaultSyntax(group, type)
unlet! b:current_syntax
let syntaxPaths = ['$VIMRUNTIME', '$VIM/vimfiles', '$HOME/.vim']
for path in syntaxPaths
let file = expand(path).'/syntax/'.a:type.'.vim'
if filereadable(file)
execute 'syntax include '.a:group.' '.file
endif
endfor
endfunction
" Load all syntax files in 'runtimepath'
" Useful if there is no default syntax file provided by vim
function! vue#LoadFullSyntax(group, type)
call s:SetCurrentSyntax(a:type)
execute 'syntax include '.a:group.' syntax/'.a:type.'.vim'
endfunction
" Settings to avoid syntax overload
function! s:SetCurrentSyntax(type)
if a:type == 'coffee'
syntax cluster coffeeJS contains=@htmlJavaScript
" Avoid overload of `javascript.vim`
let b:current_syntax = 'vue'
else
unlet! b:current_syntax
endif
endfunction
"}}}

View File

@@ -0,0 +1,45 @@
if exists("b:did_indent")
finish
endif
let s:custom_blocks = vue#GetConfig("custom_blocks", {})
let s:indent = {}
function! s:GetSyntaxList()
let syntax_list = []
for syntax in values(s:custom_blocks)
let type = type(syntax)
if type == v:t_string
if !count(syntax_list, syntax)
call add(syntax_list, syntax)
endif
elseif type == v:t_list && len(syntax)
for syn in syntax
if !count(syntax_list, syn)
call add(syntax_list, syn)
endif
endfor
else
echoerr '[vim-vue-plugin] custom_blocks value type'
\.' must be either string or list'
endif
endfor
return syntax_list
endfunction
function! s:GetIndentExpr(syntax_list)
for syntax in a:syntax_list
unlet! b:did_indent
execute 'runtime indent/'.syntax.'.vim'
let s:indent[syntax] = &l:indentexpr
endfor
endfunction
function! GetVueCustomBlocksIndent(syn)
let syntax = matchstr(a:syn, '^\l\+')
call vue#Log('custom block syntax: '.syntax)
let ind = eval(s:indent[syntax])
return ind
endfunction
call s:GetIndentExpr(s:GetSyntaxList())

View File

@@ -11,6 +11,23 @@ if exists("b:did_indent")
finish
endif
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Config {{{
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let s:use_pug = vue#GetConfig("use_pug", 0)
let s:use_sass = vue#GetConfig("use_sass", 0)
let s:use_scss = vue#GetConfig("use_scss", 0)
let s:use_stylus = vue#GetConfig("use_stylus", 0)
let s:use_coffee = vue#GetConfig("use_coffee", 0)
let s:use_typescript = vue#GetConfig("use_typescript", 0)
let s:has_init_indent = vue#GetConfig("has_init_indent",
\ expand("%:e") == 'wpy' ? 1 : 0)
let s:custom_blocks = vue#GetConfig("custom_blocks", {})
let s:use_custom_blocks = !empty(s:custom_blocks)
"}}}
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Variables {{{
@@ -25,23 +42,9 @@ let s:empty_tag = '\v\<'.s:empty_tagname.'[^/]*\>'
let s:empty_tag_start = '\v\<'.s:empty_tagname.'[^\>]*$'
let s:empty_tag_end = '\v^\s*[^\<\>\/]*\/?\>\s*'
let s:tag_start = '\v^\s*\<\w*'
let s:tag_end = '\v^\s*\/?\>\s*'
let s:full_tag_end = '\v^\s*\<\/'
"}}}
let s:tag_end = '\v^\s*\/?\>\s*' " />
let s:full_tag_end = '\v^\s*\<\/' " </...>
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Config {{{
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let s:use_pug = vue#GetConfig("use_pug", 0)
let s:use_sass = vue#GetConfig("use_sass", 0)
let s:use_scss = vue#GetConfig("use_scss", 0)
let s:use_stylus = vue#GetConfig("use_stylus", 0)
let s:use_coffee = vue#GetConfig("use_coffee", 0)
let s:use_typescript = vue#GetConfig("use_typescript", 0)
let s:has_init_indent = vue#GetConfig("has_init_indent",
\ expand("%:e") == 'wpy' ? 1 : 0)
"}}}
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
@@ -64,6 +67,12 @@ unlet! b:did_indent
runtime! indent/javascript.vim
let b:javascript_indentexpr = &indentexpr
if s:use_custom_blocks
unlet! b:did_indent
runtime indent/vue-custom-blocks.vim
let s:vue_custom_blocks_tag = '<\/\?'.join(keys(s:custom_blocks), '\|')
endif
if s:use_pug
unlet! b:did_indent
let s:save_formatoptions = &formatoptions
@@ -118,42 +127,21 @@ setlocal indentexpr=GetVueIndent()
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! GetVueIndent()
let ind = s:GetIndentBySyntax()
let ind = s:AdjustIndent(ind)
call vue#Log('indent: '.ind)
return ind
endfunction
function! s:GetIndentBySyntax()
let prevlnum = prevnonblank(v:lnum - 1)
let prevline = getline(prevlnum)
let prevsyns = s:SynsEOL(prevlnum)
let prevsyn = get(prevsyns, 0, '')
let curline = getline(v:lnum)
let cursyns = s:SynsEOL(v:lnum)
let cursyn = get(cursyns, 0, '')
let cursyn = get(s:SynsEOL(v:lnum), 0, '')
if s:SynHTML(cursyn)
call vue#Log('syntax: html')
let ind = XmlIndentGet(v:lnum, 0)
if prevline =~? s:empty_tag
call vue#Log('previous line is an empty tag')
let ind = ind - &sw
endif
" Align '/>' and '>' with '<' for multiline tags.
if curline =~? s:tag_end
let ind = ind - &sw
endif
" Then correct the indentation of any element following '/>' or '>'.
if prevline =~? s:tag_end
let ind = ind + &sw
" Decrease indent if prevlines are a multiline empty tag
let [start, end] = s:PrevMultilineEmptyTag(v:lnum)
if end == prevlnum
call vue#Log('previous line is a multiline empty tag')
if curline =~? s:full_tag_end
let ind = indent(v:lnum - 1) - &sw
else
let ind = indent(v:lnum - 1)
endif
endif
endif
let ind = s:GetHTMLIndent(prevlnum, prevline, curline)
elseif s:SynPug(cursyn)
call vue#Log('syntax: pug')
let ind = GetPugIndent()
@@ -181,7 +169,11 @@ function! GetVueIndent()
elseif s:SynStyle(cursyn)
call vue#Log('syntax: css')
let ind = GetCSSIndent()
elseif s:use_custom_blocks && s:SynCustomBlocks(cursyn)
call vue#Log('syntax: custom blocks')
let ind = GetVueCustomBlocksIndent(cursyn)
else
" Default to JavaScript indent
call vue#Log('syntax: javascript')
if len(b:javascript_indentexpr)
let ind = eval(b:javascript_indentexpr)
@@ -189,28 +181,66 @@ function! GetVueIndent()
let ind = cindent(v:lnum)
endif
endif
return ind
endfunction
if curline =~? s:vue_tag_start || curline =~? s:vue_tag_end
\|| prevline =~? s:vue_tag_end
\|| (curline =~ s:template_tag && s:SynPug(cursyn))
function! s:AdjustIndent(ind)
let ind = a:ind
let prevline = getline(prevnonblank(v:lnum - 1))
let curline = getline(v:lnum)
let cursyn = get(s:SynsEOL(v:lnum), 0, '')
if curline =~? s:vue_tag_start
\ || curline =~? s:vue_tag_end
\ || prevline =~? s:vue_tag_end
\ || (curline =~ s:template_tag && s:SynPug(cursyn))
call vue#Log('current line is vue tag or previous line is vue end tag')
call vue#Log('... or current line is pug template tag')
call vue#Log(', or current line is pug template tag')
let ind = 0
elseif s:has_init_indent
if s:SynVueScriptOrStyle(cursyn) && ind < 1
elseif s:has_init_indent && ind < 1 && s:SynVueScriptOrStyle(cursyn)
call vue#Log('add initial indent')
let ind = &sw
endif
else
let prevlnum_noncomment = s:PrevNonBlacnkNonComment(v:lnum)
let prevline_noncomment = getline(prevlnum_noncomment)
if prevline_noncomment =~? s:vue_tag_start
call vue#Log('previous line is vue tag start')
let ind = 0
endif
elseif getline(s:PrevNonBlacnkNonComment(v:lnum)) =~? s:vue_tag_start
call vue#Log('previous line is vue tag start')
let ind = 0
elseif s:use_custom_blocks && curline =~ s:vue_custom_blocks_tag
call vue#Log('current line is vue custom blocks tag')
let ind = 0
endif
call vue#Log('indent: '.ind)
return ind
endfunction
function! s:GetHTMLIndent(prevlnum, prevline, curline)
let prevlnum = a:prevlnum
let prevline = a:prevline
let curline = a:curline
let ind = XmlIndentGet(v:lnum, 0)
if prevline =~? s:empty_tag
call vue#Log('previous line is an empty tag')
let ind = ind - &sw
endif
" Align '/>' and '>' with '<' for multiline tags.
if curline =~? s:tag_end
let ind = ind - &sw
endif
" Then correct the indentation of any element following '/>' or '>'.
if prevline =~? s:tag_end
let ind = ind + &sw
" Decrease indent if prevlines are a multiline empty tag
let [start, end] = s:PrevMultilineEmptyTag(v:lnum)
if end == prevlnum
call vue#Log('previous line is a multiline empty tag')
if curline =~? s:full_tag_end
let ind = indent(v:lnum - 1) - &sw
else
let ind = indent(v:lnum - 1)
endif
endif
endif
return ind
endfunction
@@ -252,6 +282,10 @@ function! s:SynStyle(syn)
return a:syn =~? 'VueStyle'
endfunction
function! s:SynCustomBlocks(syn)
return a:syn =~? 'Block'
endfunction
function! s:SynVueScriptOrStyle(syn)
return a:syn =~? '\v(VueStyle)|(VueScript)'
endfunction

View File

@@ -0,0 +1,64 @@
let s:custom_blocks = vue#GetConfig("custom_blocks", {})
if empty(s:custom_blocks)
finish
endif
function! s:LoadSyntax()
let syntax_list = []
for syntax in values(s:custom_blocks)
let type = type(syntax)
if type == v:t_string
if !count(syntax_list, syntax)
call add(syntax_list, syntax)
endif
elseif type == v:t_list && len(syntax)
for syn in syntax
if !count(syntax_list, syn)
call add(syntax_list, syn)
endif
endfor
else
echoerr '[vim-vue-plugin] custom_blocks value type'
\.' must be either string or list'
endif
endfor
for syntax in syntax_list
let syntaxGroup = '@'.syntax
call vue#LoadFullSyntax(syntaxGroup, syntax)
endfor
endfunction
function! s:SetSyntax(block, syntax, lang = 0)
let block = a:block
let syntax = a:syntax
let lang = a:lang
let region_name = syntax.toupper(block[0]).block[1:].'Block'
let syntax_lang = lang ? 'lang=["'']'.syntax.'["''][^>]*' : ''
let start = '<'.block.'[^>]*'.syntax_lang.'>'
let end = '</'.block.'>'
let syntaxGroup = '@'.syntax
execute 'syntax region '.region_name.' fold matchgroup=vueTag'
\.' start=+'.start.'+'
\.' end=+'.end.'+'
\.' keepend contains='.syntaxGroup
endfunction
function! s:Highlight()
for [block, syntax] in items(s:custom_blocks)
let type = type(syntax)
if type == v:t_string
call s:SetSyntax(block, syntax)
elseif type == v:t_list && len(syntax)
call s:SetSyntax(block, syntax[0])
for syn in syntax
call s:SetSyntax(block, syn, 1)
endfor
endif
endfor
endfunction
call s:LoadSyntax()
call s:Highlight()

View File

@@ -18,7 +18,6 @@ let b:current_loading_main_syntax = 'vue'
" Config {{{
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let s:load_full_syntax = vue#GetConfig("load_full_syntax", 0)
let s:use_pug = vue#GetConfig("use_pug", 0)
let s:use_less = vue#GetConfig("use_less", 0)
let s:use_sass = vue#GetConfig("use_sass", 0)
@@ -28,50 +27,6 @@ let s:use_coffee = vue#GetConfig("use_coffee", 0)
let s:use_typescript = vue#GetConfig("use_typescript", 0)
"}}}
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Functions {{{
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:LoadSyntax(group, type)
if s:load_full_syntax
call s:LoadFullSyntax(a:group, a:type)
else
call s:LoadDefaultSyntax(a:group, a:type)
endif
endfunction
function! s:LoadDefaultSyntax(group, type)
unlet! b:current_syntax
let syntaxPaths = ['$VIMRUNTIME', '$VIM/vimfiles', '$HOME/.vim']
for path in syntaxPaths
let file = expand(path).'/syntax/'.a:type.'.vim'
if filereadable(file)
execute 'syntax include '.a:group.' '.file
endif
endfor
endfunction
" Load all syntax files in 'runtimepath'
" Useful if there is no default syntax file provided by vim
function! s:LoadFullSyntax(group, type)
call s:SetCurrentSyntax(a:type)
execute 'syntax include '.a:group.' syntax/'.a:type.'.vim'
endfunction
" Settings to avoid syntax overload
function! s:SetCurrentSyntax(type)
if a:type == 'coffee'
syntax cluster coffeeJS contains=@htmlJavaScript
" Avoid overload of `javascript.vim`
let b:current_syntax = 'vue'
else
unlet! b:current_syntax
endif
endfunction
"}}}
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Load main syntax {{{
@@ -80,17 +35,17 @@ endfunction
" Load syntax/html.vim to syntax group, which loads full JavaScript and CSS
" syntax. It defines group @html, @htmlJavaScript, and @htmlCss.
call s:LoadSyntax('@html', 'html')
call vue#LoadSyntax('@html', 'html')
" Avoid overload
if !hlexists('cssTagName')
call s:LoadSyntax('@htmlCss', 'css')
call vue#LoadSyntax('@htmlCss', 'css')
endif
" Avoid overload
if !hlexists('javaScriptComment')
call vue#Log('load javascript cluster')
call s:LoadSyntax('@htmlJavaScript', 'javascript')
call vue#LoadSyntax('@htmlJavaScript', 'javascript')
endif
" Load vue-html syntax
@@ -107,43 +62,43 @@ runtime syntax/vue-javascript.vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" If pug is enabled, load vim-pug syntax
if s:use_pug
call s:LoadFullSyntax('@PugSyntax', 'pug')
call vue#LoadFullSyntax('@PugSyntax', 'pug')
syn cluster htmlJavascript remove=javascriptParenthesisBlock
endif
" If less is enabled, load less syntax
if s:use_less
call s:LoadSyntax('@LessSyntax', 'less')
call vue#LoadSyntax('@LessSyntax', 'less')
runtime! after/syntax/less.vim
endif
" If sass is enabled, load sass syntax
if s:use_sass
call s:LoadSyntax('@SassSyntax', 'sass')
call vue#LoadSyntax('@SassSyntax', 'sass')
runtime! after/syntax/sass.vim
endif
" If scss is enabled, load sass syntax
if s:use_scss
call s:LoadSyntax('@ScssSyntax', 'scss')
call vue#LoadSyntax('@ScssSyntax', 'scss')
runtime! after/syntax/scss.vim
endif
" If stylus is enabled, load stylus syntax
if s:use_stylus
call s:LoadFullSyntax('@StylusSyntax', 'stylus')
call vue#LoadFullSyntax('@StylusSyntax', 'stylus')
runtime! after/syntax/stylus.vim
endif
" If CoffeeScript is enabled, load the syntax. Keep name consistent with
" vim-coffee-script/after/html.vim
if s:use_coffee
call s:LoadFullSyntax('@htmlCoffeeScript', 'coffee')
call vue#LoadFullSyntax('@htmlCoffeeScript', 'coffee')
endif
" If TypeScript is enabled, load the syntax.
if s:use_typescript
call s:LoadFullSyntax('@TypeScript', 'typescript')
call vue#LoadFullSyntax('@TypeScript', 'typescript')
endif
"}}}
@@ -226,7 +181,14 @@ syntax region vueTag
highlight default link vueTag htmlTag
highlight default link cssUnitDecorators2 Number
highlight default link cssKeyFrameProp2 Constant
"}}}
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Custom blocks {{{
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
runtime syntax/vue-custom-blocks.vim
"}}}
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""