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