Files
vim-vue-plugin/indent/vue.vim
T
leafOfTree acdcadc39b Fix undefined a:block in indent
s:AdjustBlockIndent() had `let block = a:block` without such an argument,
so GetVueIndent() hit E121 for every line in template, script and style.
It was hidden as errors in 'indentexpr' are silent and the function goes
on, but showed up when calling GetVueIndent() directly.

Also return [0, 0] in s:PrevMultilineEmptyTag() when no tag start is
found, instead of no value.

Add ExpectIndentEachLine() for tests to call GetVueIndent() for each
line and check the result.
2026-10-21 12:11:30 +00:00

308 lines
8.1 KiB
VimL

" Language: Vue
" Maintainer: leaf <https://github.com/leafOfTree>
" Credits: Inspired by mxw/vim-jsx.
if exists('b:did_indent') | finish |endif
let s:test = exists('g:vim_vue_plugin_test')
function! s:Init()
""" Configs
let s:config = vue#GetConfig('config', {})
let s:config_syntax = s:config.syntax
let s:initial_indent = s:config.initial_indent
""" Variables
let s:indent = {}
let s:block_tag = '<\/\?\('.join(keys(s:config_syntax), '\|').'\)'
" To adjust HTML
let s:empty_tagname = '(area|base|br|col|embed|hr|input|img|'
\.'keygen|link|meta|param|source|track|wbr)'
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:ternary_q = '^\s\+?'
let s:ternary_e = '^\s\+:.*,\s*$'
endfunction
function! s:SetVueIndent()
""" Settings
" JavaScript indentkeys
setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e,:
" XML indentkeys
setlocal indentkeys+=*<Return>,<>>,<<>,/
setlocal indentexpr=GetVueIndent()
endfunction
function! s:GetIndentFile(syntax)
let syntax = a:syntax
" lib/indent/* files are perferred for better indent result
" from previous version Vim
if syntax == 'html'
let file = 'lib/indent/xml.vim'
elseif syntax == 'css'
let file = 'lib/indent/css.vim'
elseif syntax == 'javascript'
let file = 'lib/indent/typescript.vim'
else
let file = 'indent/'.syntax.'.vim'
endif
return file
endfunction
function! s:SetSyntaxIndentExpr(syntax_list)
let saved_shiftwidth = &shiftwidth
let saved_formatoptions = &formatoptions
for syntax in a:syntax_list
unlet! b:did_indent
let &l:indentexpr = ''
execute 'runtime '.s:GetIndentFile(syntax)
let s:indent[syntax] = &l:indentexpr
endfor
let &shiftwidth = saved_shiftwidth
let &formatoptions = saved_formatoptions
endfunction
function! s:GetBlockIndent(syntax)
let syntax = a:syntax
let indentexpr = get(s:indent, syntax)
if !empty(indentexpr)
let ind = eval(indentexpr)
else
call vue#LogWithLnum('indentexpr not found for '.syntax.', use cindent')
let ind = cindent(v:lnum)
endif
return ind
endfunction
function! s:GetIndentByContext(tag, syntax)
let ind = -1
let prevline = getline(s:PrevNonBlankNonComment(v:lnum))
let curline = getline(v:lnum)
if a:tag != 'template' && a:syntax == 'html'
" Set indent to 0 for custom tag with 'html' syntax
if curline =~ s:block_tag && empty(prevline)
let ind = 0
endif
elseif a:tag == 'template'
" When 'pug' syntax in <template>, set block tags indent to 0
if a:syntax == 'pug'
if curline =~ s:block_tag
let ind = 0
endif
endif
else
" When not in <template> and not 'html'
" Set indent to 0 if current or prev line is block tag
if curline =~ s:block_tag || prevline =~ s:block_tag
let ind = 0
endif
endif
return ind
endfunction
function! s:PrevNonBlankNonComment(lnum)
let lnum = a:lnum - 1
let prevlnum = prevnonblank(lnum)
let prevsyn = vue#SyntaxSecondAtEnd(prevlnum)
while prevsyn =~? 'comment' && lnum > 1
let lnum = lnum - 1
let prevlnum = prevnonblank(lnum)
let prevsyn = vue#SyntaxSecondAtEnd(prevlnum)
endwhile
return prevlnum
endfunction
function! s:AdjustBlockIndent(syntax, ind)
let syntax = a:syntax
let ind = a:ind
if syntax == 'html'
let ind = s:AdjustHTMLIndent(ind)
elseif syntax == 'javascript'
let ind = s:AdjustJavaScriptIndent(ind)
elseif syntax == 'css'
let ind = s:AdjustCSSIndent(ind)
endif
return ind
endfunction
function! s:CheckInitialIndent(tag, syntax, ind)
let ind = a:ind
let add = 0
if ind == 0 && getline(v:lnum) !~ s:block_tag
let add = vue#IncludeOrEqual(s:initial_indent, a:tag.'.'.a:syntax)
\ || vue#IncludeOrEqual(s:initial_indent, a:tag)
\ || vue#IncludeOrEqual(s:initial_indent, a:syntax)
endif
if add
call vue#LogWithLnum('add initial indent')
let ind = &sw
endif
return ind
endfunction
function! s:PrevMultilineEmptyTag(lnum)
let lnum = a:lnum - 1
let lnums = [0, 0]
while lnum > 0
let line = getline(lnum)
if line =~? s:empty_tag_end
let lnums[1] = lnum
endif
if line =~? s:tag_start
if line =~? s:empty_tag_start
let lnums[0] = lnum
return lnums
else
return [0, 0]
endif
endif
let lnum = lnum - 1
endwhile
return [0, 0]
endfunction
function! s:AdjustHTMLIndent(ind)
let ind = a:ind
let prevlnum = prevnonblank(v:lnum - 1)
let prevline = getline(prevlnum)
let curline = getline(v:lnum)
if prevline =~? s:empty_tag
call vue#LogWithLnum('previous line is an empty tag')
let ind = ind - &sw
endif
" Align '/>' and '>' with '<'
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 prevlnum == end
call vue#LogWithLnum('previous line is a multiline empty tag')
let ind = indent(v:lnum - 1)
if curline =~? s:full_tag_end
let ind = ind - &sw
endif
endif
endif
" Multiline array/object in attribute like v-*="[
" ...
" ]
if prevline =~ '[[{]\s*$'
call vue#LogWithLnum('previous line is an open bracket')
let ind = indent(prevlnum) + &sw
endif
if curline =~ '^\s*[]}][^"]*"\?\s*$'
call vue#LogWithLnum('current line is a closing bracket')
let ind = indent(prevlnum) - &sw
endif
" Multiline ternary 'a ? b : c' in attribute
if curline =~ s:ternary_q
call vue#LogWithLnum('current line is ?...')
let ind = indent(prevlnum) + &sw
endif
if curline =~ s:ternary_e && prevline =~ s:ternary_q
call vue#LogWithLnum('current line is :...')
let ind = indent(prevlnum)
endif
if prevline =~ s:ternary_e
call vue#LogWithLnum('prevline line is :...')
let ind = indent(prevlnum) - &sw
endif
" Angle bracket in attribute, like v-if="isEnabled('item.<name>')"
if prevline =~ '="[^"]*<[^"]*>[^"]*"'
call vue#LogWithLnum('prevline line is angle bracket in attribute')
let ind = ind - &sw
endif
return ind
endfunction
function! s:AdjustJavaScriptIndent(ind)
let ind = a:ind
let prevlnum = prevnonblank(v:lnum - 1)
let prevline = getline(prevlnum)
let curline = getline(v:lnum)
if prevline =~ '^\s*\w.*$' && curline =~ '^\s*\.'
call vue#LogWithLnum('current line is the first chain call')
let ind = indent(prevlnum) + &sw
endif
if prevline =~ '\s=>\s.*,\s*$' && curline !~ '^\s*[]}])\?,\?\s*$'
call vue#LogWithLnum('previous line is arrow function property')
let ind = indent(prevlnum)
endif
if prevline =~ '\s||\s*$'
call vue#LogWithLnum('previous line ends with "||"')
let ind = indent(prevlnum) + &sw
endif
return ind
endfunction
function! s:AdjustCSSIndent(ind)
let ind = a:ind
let prevlnum = prevnonblank(v:lnum - 1)
let prevline = getline(prevlnum)
let curline = getline(v:lnum)
if prevline =~ ':\s.*,\s*$'
call vue#LogWithLnum('previous line is css function')
let ind = indent(prevlnum) + &sw
endif
if curline =~ '^\s*);\?\s*$'
call vue#LogWithLnum('curline is closing round bracket')
let ind = indent(prevlnum) - &sw
endif
return ind
endfunction
function! GetVueIndent()
let tag = vue#GetBlockTag(v:lnum)
let syntax = vue#GetBlockSyntax(v:lnum)
let ind = s:GetIndentByContext(tag, syntax)
if ind == -1
let ind = s:GetBlockIndent(syntax)
let ind = s:AdjustBlockIndent(syntax, ind)
call vue#LogWithLnum('syntax '.syntax.', ind '.ind)
else
call vue#LogWithLnum('context, ind '.ind)
endif
let ind = s:CheckInitialIndent(tag, syntax, ind)
return ind
endfunction
function! VimVuePluginIndentMain(...)
call s:Init()
let syntax_list = vue#GetSyntaxList(s:config_syntax)
call s:SetSyntaxIndentExpr(syntax_list)
call s:SetVueIndent()
endfunction
call VimVuePluginIndentMain()
let b:did_indent = 1