From 9af7723bd1f588415bc3ed6810011f01ba3ecf1e Mon Sep 17 00:00:00 2001 From: leafOfTree Date: Thu, 11 Mar 2021 14:52:38 +0800 Subject: [PATCH] Support custom blocks syntax and indent --- README.md | 27 ++++++ autoload/vue.vim | 58 +++++++++++-- indent/vue-custom-blocks.vim | 45 ++++++++++ indent/vue.vim | 156 +++++++++++++++++++++-------------- syntax/vue-custom-blocks.vim | 64 ++++++++++++++ syntax/vue.vim | 72 ++++------------ 6 files changed, 299 insertions(+), 123 deletions(-) create mode 100644 indent/vue-custom-blocks.vim create mode 100644 syntax/vue-custom-blocks.vim diff --git a/README.md b/README.md index bf039a8..45b2a2b 100644 --- a/README.md +++ b/README.md @@ -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 + +# This is the documentation for component. + + + +en: + hello: "Hello World!" +ja: + hello: "こんにちは、世界!" + +``` + ## 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). diff --git a/autoload/vue.vim b/autoload/vue.vim index a516725..eafb99e 100644 --- a/autoload/vue.vim +++ b/autoload/vue.vim @@ -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 +"}}} diff --git a/indent/vue-custom-blocks.vim b/indent/vue-custom-blocks.vim new file mode 100644 index 0000000..703893f --- /dev/null +++ b/indent/vue-custom-blocks.vim @@ -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()) diff --git a/indent/vue.vim b/indent/vue.vim index ba72598..a65754d 100644 --- a/indent/vue.vim +++ b/indent/vue.vim @@ -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 diff --git a/syntax/vue-custom-blocks.vim b/syntax/vue-custom-blocks.vim new file mode 100644 index 0000000..cc761ce --- /dev/null +++ b/syntax/vue-custom-blocks.vim @@ -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 = '' + 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() diff --git a/syntax/vue.vim b/syntax/vue.vim index d50bc4e..b5801f6 100644 --- a/syntax/vue.vim +++ b/syntax/vue.vim @@ -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 "}}} """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""