1" Vim indent file 2" Language: Javascript 3" Maintainer: Chris Paul ( https://github.com/bounceme ) 4" URL: https://github.com/pangloss/vim-javascript 5" Last Change: August 25, 2016 6 7" Only load this indent file when no other was loaded. 8if exists('b:did_indent') 9 finish 10endif 11let b:did_indent = 1 12 13" Now, set up our indentation expression and keys that trigger it. 14setlocal indentexpr=GetJavascriptIndent() 15setlocal nolisp noautoindent nosmartindent 16setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e 17setlocal cinoptions+=j1,J1 18 19let b:undo_indent = 'setlocal indentexpr< smartindent< autoindent< indentkeys< cinoptions<' 20 21" Only define the function once. 22if exists('*GetJavascriptIndent') 23 finish 24endif 25 26let s:cpo_save = &cpo 27set cpo&vim 28 29" Get shiftwidth value 30if exists('*shiftwidth') 31 function s:sw() 32 return shiftwidth() 33 endfunction 34else 35 function s:sw() 36 return &sw 37 endfunction 38endif 39 40let s:line_pre = '^\s*\%(\%(\%(\/\*.\{-}\)\=\*\+\/\s*\)\=\)\@>' 41let s:expr_case = s:line_pre . '\%(\%(case\>.\+\)\|default\)\s*:' 42" Regex of syntax group names that are or delimit string or are comments. 43let s:syng_strcom = '\%(s\%(tring\|pecial\)\|comment\|regex\|doc\|template\)' 44 45" Regex of syntax group names that are strings or documentation. 46let s:syng_comment = '\%(comment\|doc\)' 47 48" Expression used to check whether we should skip a match with searchpair(). 49let s:skip_expr = "synIDattr(synID(line('.'),col('.'),0),'name') =~? '".s:syng_strcom."'" 50 51if has('reltime') 52 function s:GetPair(start,end,flags,time) 53 return searchpair(a:start,'',a:end,a:flags,s:skip_expr,max([prevnonblank(v:lnum) - 2000,0]),a:time) 54 endfunction 55else 56 function s:GetPair(start,end,flags,n) 57 return searchpair(a:start,'',a:end,a:flags,0,max([prevnonblank(v:lnum) - 2000,0])) 58 endfunction 59endif 60 61let s:line_term = '\s*\%(\%(\/\%(\%(\*.\{-}\*\/\)\|\%(\*\+\)\)\)\s*\)\=$' 62 63" configurable regexes that define continuation lines, not including (, {, or [. 64if !exists('g:javascript_opfirst') 65 let g:javascript_opfirst = '\%([<>,:?^%|*&]\|\/[^/*]\|\([-.+]\)\1\@!\|=>\@!\|in\%(stanceof\)\=\>\)' 66endif 67if !exists('g:javascript_continuation') 68 let g:javascript_continuation = '\%([<=,.?/*:^%|&]\|+\@<!+\|-\@<!-\|=\@<!>\|\<in\%(stanceof\)\=\)' 69endif 70 71let g:javascript_opfirst = s:line_pre . g:javascript_opfirst 72let g:javascript_continuation .= s:line_term 73 74function s:OneScope(lnum,text,add) 75 return a:text =~# '\%(\<else\|\<do\|=>\)' . s:line_term ? 'no b' : 76 \ ((a:add && a:text =~ s:line_pre . '$' && search('\%' . s:PrevCodeLine(a:lnum - 1) . 'l.)' . s:line_term)) || 77 \ cursor(a:lnum, match(a:text, ')' . s:line_term)) > -1) && 78 \ s:GetPair('(', ')', 'cbW', 100) > 0 && search('\C\l\+\_s*\%#','bW') && 79 \ (a:add || ((expand('<cword>') !=# 'while' || !s:GetPair('\C\<do\>', '\C\<while\>','nbW',100)) && 80 \ (expand('<cword>') !=# 'each' || search('\C\<for\_s\+\%#','nbW')))) ? expand('<cword>') : '' 81endfunction 82 83" https://github.com/sweet-js/sweet.js/wiki/design#give-lookbehind-to-the-reader 84function s:IsBlock() 85 return getline(line('.'))[col('.')-1] == '{' && !search( 86 \ '\C\%(\<return\s*\|\%([-=~!<*+,.?^%|&\[(]\|=\@<!>\|\*\@<!\/\|\<\%(var\|const\|let\|import\|export\%(\_s\+default\)\=\|yield\|delete\|void\|t\%(ypeof\|hrow\)\|new\|in\%(stanceof\)\=\)\)\_s*\)\%#','bnW') && 87 \ (!search(':\_s*\%#','bW') || (!s:GetPair('[({[]','[])}]','bW',200) || s:IsBlock())) 88endfunction 89 90" Auxiliary Functions {{{2 91 92" Find line above 'lnum' that isn't empty, in a comment, or in a string. 93function s:PrevCodeLine(lnum) 94 let l:lnum = prevnonblank(a:lnum) 95 while l:lnum 96 if synIDattr(synID(l:lnum,matchend(getline(l:lnum), '^\s*[^''"]'),0),'name') !~? s:syng_strcom 97 return l:lnum 98 endif 99 let l:lnum = prevnonblank(l:lnum - 1) 100 endwhile 101endfunction 102 103" Check if line 'lnum' has a balanced amount of parentheses. 104function s:Balanced(lnum) 105 let [open_0,open_2,open_4] = [0,0,0] 106 let l:line = getline(a:lnum) 107 let pos = match(l:line, '[][(){}]', 0) 108 while pos != -1 109 if synIDattr(synID(a:lnum,pos + 1,0),'name') !~? s:syng_strcom 110 let idx = stridx('(){}[]', l:line[pos]) 111 if idx % 2 == 0 112 let open_{idx} = open_{idx} + 1 113 else 114 let open_{idx - 1} = open_{idx - 1} - 1 115 endif 116 endif 117 let pos = match(l:line, '[][(){}]', pos + 1) 118 endwhile 119 return (!open_4 + !open_2 + !open_0) - 2 120endfunction 121" }}} 122 123function GetJavascriptIndent() 124 if !exists('b:js_cache') 125 let b:js_cache = [0,0,0] 126 endif 127 " Get the current line. 128 let l:line = getline(v:lnum) 129 let syns = synIDattr(synID(v:lnum, 1, 0), 'name') 130 131 " start with strings,comments,etc.{{{2 132 if (l:line !~ '^[''"`]' && syns =~? '\%(string\|template\)') || 133 \ (l:line !~ '^\s*[/*]' && syns =~? s:syng_comment) 134 return -1 135 endif 136 if l:line !~ '^\%(\/\*\|\s*\/\/\)' && syns =~? s:syng_comment 137 return cindent(v:lnum) 138 endif 139 let l:lnum = s:PrevCodeLine(v:lnum - 1) 140 if l:lnum == 0 141 return 0 142 endif 143 144 if (l:line =~# s:expr_case) 145 let cpo_switch = &cpo 146 set cpo+=% 147 let ind = cindent(v:lnum) 148 let &cpo = cpo_switch 149 return ind 150 endif 151 "}}} 152 153 " the containing paren, bracket, curly. Memoize, last lineNr either has the 154 " same scope or starts a new one, unless if it closed a scope. 155 call cursor(v:lnum,1) 156 if b:js_cache[0] >= l:lnum && b:js_cache[0] < v:lnum && b:js_cache[0] && 157 \ (b:js_cache[0] > l:lnum || s:Balanced(l:lnum) > 0) 158 let num = b:js_cache[1] 159 elseif syns != '' && l:line[0] =~ '\s' 160 let pattern = syns =~? 'block' ? ['{','}'] : syns =~? 'jsparen' ? ['(',')'] : 161 \ syns =~? 'jsbracket'? ['\[','\]'] : ['[({[]','[])}]'] 162 let num = s:GetPair(pattern[0],pattern[1],'bW',2000) 163 else 164 let num = s:GetPair('[({[]','[])}]','bW',2000) 165 endif 166 let b:js_cache = [v:lnum,num,line('.') == v:lnum ? b:js_cache[2] : col('.')] 167 168 if l:line =~ s:line_pre . '[])}]' 169 return indent(num) 170 endif 171 172 call cursor(b:js_cache[1],b:js_cache[2]) 173 174 let swcase = getline(l:lnum) =~# s:expr_case 175 let pline = swcase ? getline(l:lnum) : substitute(getline(l:lnum), '\%(:\@<!\/\/.*\)$', '','') 176 let inb = num == 0 || num < l:lnum && ((l:line !~ s:line_pre . ',' && pline !~ ',' . s:line_term) || s:IsBlock()) 177 let switch_offset = num == 0 || s:OneScope(num, strpart(getline(num),0,b:js_cache[2] - 1),1) !=# 'switch' ? 0 : 178 \ &cino !~ ':' || !has('float') ? s:sw() : 179 \ float2nr(str2float(matchstr(&cino,'.*:\zs[-0-9.]*')) * (&cino =~# '.*:[^,]*s' ? s:sw() : 1)) 180 181 " most significant, find the indent amount 182 if inb && !swcase && ((l:line =~# g:javascript_opfirst || pline =~# g:javascript_continuation) || 183 \ num < l:lnum && s:OneScope(l:lnum,pline,0) =~# '\<\%(for\|each\|if\|let\|no\sb\|w\%(hile\|ith\)\)\>' && 184 \ l:line !~ s:line_pre . '{') 185 return (num > 0 ? indent(num) : -s:sw()) + (s:sw() * 2) + switch_offset 186 elseif num > 0 187 return indent(num) + s:sw() + switch_offset 188 endif 189 190endfunction 191 192 193let &cpo = s:cpo_save 194unlet s:cpo_save 195