xref: /vim-8.2.3635/runtime/indent/scala.vim (revision 94688b8a)
1" Vim indent file
2" Language:             Scala (http://scala-lang.org/)
3" Original Author:      Stefan Matthias Aust
4" Modifications By:     Derek Wyatt
5" URL:                  https://github.com/derekwyatt/vim-scala
6" Last Change:          2016 Aug 26
7
8if exists("b:did_indent")
9  finish
10endif
11let b:did_indent = 1
12
13setlocal autoindent
14setlocal indentexpr=GetScalaIndent()
15setlocal indentkeys=0{,0},0),!^F,<>>,o,O,e,=case,<CR>
16
17if exists("*GetScalaIndent")
18  finish
19endif
20let s:keepcpo= &cpo
21set cpo&vim
22
23let s:defMatcher = '\%(\%(private\|protected\)\%(\[[^\]]*\]\)\?\s\+\|abstract\s\+\|override\s\+\)*\<def\>'
24let s:funcNameMatcher = '\w\+'
25let s:typeSpecMatcher = '\%(\s*\[\_[^\]]*\]\)'
26let s:defArgMatcher = '\%((\_.\{-})\)'
27let s:returnTypeMatcher = '\%(:\s*\w\+' . s:typeSpecMatcher . '\?\)'
28let g:fullDefMatcher = '^\s*' . s:defMatcher . '\s\+' . s:funcNameMatcher . '\s*' . s:typeSpecMatcher . '\?\s*' . s:defArgMatcher . '\?\s*' . s:returnTypeMatcher . '\?\s*[={]'
29
30function! scala#ConditionalConfirm(msg)
31  if 0
32    call confirm(a:msg)
33  endif
34endfunction
35
36function! scala#GetLine(lnum)
37  let line = substitute(getline(a:lnum), '//.*$', '', '')
38  let line = substitute(line, '"\(.\|\\"\)\{-}"', '""', 'g')
39  return line
40endfunction
41
42function! scala#CountBrackets(line, openBracket, closedBracket)
43  let line = substitute(a:line, '"\(.\|\\"\)\{-}"', '', 'g')
44  let open = substitute(line, '[^' . a:openBracket . ']', '', 'g')
45  let close = substitute(line, '[^' . a:closedBracket . ']', '', 'g')
46  return strlen(open) - strlen(close)
47endfunction
48
49function! scala#CountParens(line)
50  return scala#CountBrackets(a:line, '(', ')')
51endfunction
52
53function! scala#CountCurlies(line)
54  return scala#CountBrackets(a:line, '{', '}')
55endfunction
56
57function! scala#LineEndsInIncomplete(line)
58  if a:line =~ '[.,]\s*$'
59    return 1
60  else
61    return 0
62  endif
63endfunction
64
65function! scala#LineIsAClosingXML(line)
66  if a:line =~ '^\s*</\w'
67    return 1
68  else
69    return 0
70  endif
71endfunction
72
73function! scala#LineCompletesXML(lnum, line)
74  let savedpos = getpos('.')
75  call setpos('.', [savedpos[0], a:lnum, 0, savedpos[3]])
76  let tag = substitute(a:line, '^.*</\([^>]*\)>.*$', '\1', '')
77  let [lineNum, colnum] = searchpairpos('<' . tag . '>', '', '</' . tag . '>', 'Wbn')
78  call setpos('.', savedpos)
79  let pline = scala#GetLine(prevnonblank(lineNum - 1))
80  if pline =~ '=\s*$'
81    return 1
82  else
83    return 0
84  endif
85endfunction
86
87function! scala#IsParentCase()
88  let savedpos = getpos('.')
89  call setpos('.', [savedpos[0], savedpos[1], 0, savedpos[3]])
90  let [l, c] = searchpos('^\s*\%(' . s:defMatcher . '\|\%(\<case\>\)\)', 'bnW')
91  let retvalue = -1
92  if l != 0 && search('\%' . l . 'l\s*\<case\>', 'bnW')
93    let retvalue = l
94  endif
95  call setpos('.', savedpos)
96  return retvalue
97endfunction
98
99function! scala#CurlyMatcher()
100  let matchline = scala#GetLineThatMatchesBracket('{', '}')
101  if scala#CountParens(scala#GetLine(matchline)) < 0
102    let savedpos = getpos('.')
103    call setpos('.', [savedpos[0], matchline, 9999, savedpos[3]])
104    call searchpos('{', 'Wbc')
105    call searchpos(')', 'Wb')
106    let [lnum, colnum] = searchpairpos('(', '', ')', 'Wbn')
107    call setpos('.', savedpos)
108    let line = scala#GetLine(lnum)
109    if line =~ '^\s*' . s:defMatcher
110      return lnum
111    else
112      return matchline
113    endif
114  else
115    return matchline
116  endif
117endfunction
118
119function! scala#GetLineAndColumnThatMatchesCurly()
120  return scala#GetLineAndColumnThatMatchesBracket('{', '}')
121endfunction
122
123function! scala#GetLineAndColumnThatMatchesParen()
124  return scala#GetLineAndColumnThatMatchesBracket('(', ')')
125endfunction
126
127function! scala#GetLineAndColumnThatMatchesBracket(openBracket, closedBracket)
128  let savedpos = getpos('.')
129  let curline = scala#GetLine(line('.'))
130  if curline =~ a:closedBracket . '.*' . a:openBracket . '.*' . a:closedBracket
131    call setpos('.', [savedpos[0], savedpos[1], 0, savedpos[3]])
132    call searchpos(a:closedBracket . '\ze[^' . a:closedBracket . a:openBracket . ']*' . a:openBracket, 'W')
133  else
134    call setpos('.', [savedpos[0], savedpos[1], 9999, savedpos[3]])
135    call searchpos(a:closedBracket, 'Wbc')
136  endif
137  let [lnum, colnum] = searchpairpos(a:openBracket, '', a:closedBracket, 'Wbn')
138  call setpos('.', savedpos)
139  return [lnum, colnum]
140endfunction
141
142function! scala#GetLineThatMatchesCurly()
143  return scala#GetLineThatMatchesBracket('{', '}')
144endfunction
145
146function! scala#GetLineThatMatchesParen()
147  return scala#GetLineThatMatchesBracket('(', ')')
148endfunction
149
150function! scala#GetLineThatMatchesBracket(openBracket, closedBracket)
151  let [lnum, colnum] = scala#GetLineAndColumnThatMatchesBracket(a:openBracket, a:closedBracket)
152  return lnum
153endfunction
154
155function! scala#NumberOfBraceGroups(line)
156  let line = substitute(a:line, '[^()]', '', 'g')
157  if strlen(line) == 0
158    return 0
159  endif
160  let line = substitute(line, '^)*', '', 'g')
161  if strlen(line) == 0
162    return 0
163  endif
164  let line = substitute(line, '^(', '', 'g')
165  if strlen(line) == 0
166    return 0
167  endif
168  let c = 1
169  let counter = 0
170  let groupCount = 0
171  while counter < strlen(line)
172    let char = strpart(line, counter, 1)
173    if char == '('
174      let c = c + 1
175    elseif char == ')'
176      let c = c - 1
177    endif
178    if c == 0
179      let groupCount = groupCount + 1
180    endif
181    let counter = counter + 1
182  endwhile
183  return groupCount
184endfunction
185
186function! scala#MatchesIncompleteDefValr(line)
187  if a:line =~ '^\s*\%(' . s:defMatcher . '\|\<va[lr]\>\).*[=({]\s*$'
188    return 1
189  else
190    return 0
191  endif
192endfunction
193
194function! scala#LineIsCompleteIf(line)
195  if scala#CountBrackets(a:line, '{', '}') == 0 &&
196   \ scala#CountBrackets(a:line, '(', ')') == 0 &&
197   \ a:line =~ '^\s*\<if\>\s*([^)]*)\s*\S.*$'
198    return 1
199  else
200    return 0
201  endif
202endfunction
203
204function! scala#LineCompletesIfElse(lnum, line)
205  if a:line =~ '^\s*\%(\<if\>\|\%(}\s*\)\?\<else\>\)'
206    return 0
207  endif
208  let result = search('^\%(\s*\<if\>\s*(.*).*\n\|\s*\<if\>\s*(.*)\s*\n.*\n\)\%(\s*\<else\>\s*\<if\>\s*(.*)\s*\n.*\n\)*\%(\s*\<else\>\s*\n\|\s*\<else\>[^{]*\n\)\?\%' . a:lnum . 'l', 'Wbn')
209  if result != 0 && scala#GetLine(prevnonblank(a:lnum - 1)) !~ '{\s*$'
210    return result
211  endif
212  return 0
213endfunction
214
215function! scala#GetPrevCodeLine(lnum)
216  " This needs to skip comment lines
217  return prevnonblank(a:lnum - 1)
218endfunction
219
220function! scala#InvertBracketType(openBracket, closedBracket)
221  if a:openBracket == '('
222    return [ '{', '}' ]
223  else
224    return [ '(', ')' ]
225  endif
226endfunction
227
228function! scala#Testhelper(lnum, line, openBracket, closedBracket, iteration)
229  let bracketCount = scala#CountBrackets(a:line, a:openBracket, a:closedBracket)
230  " There are more '}' braces than '{' on this line so it may be completing the function definition
231  if bracketCount < 0
232    let [matchedLNum, matchedColNum] = scala#GetLineAndColumnThatMatchesBracket(a:openBracket, a:closedBracket)
233    if matchedLNum == a:lnum
234      return -1
235    endif
236    let matchedLine = scala#GetLine(matchedLNum)
237    if ! scala#MatchesIncompleteDefValr(matchedLine)
238      let bracketLine = substitute(substitute(matchedLine, '\%' . matchedColNum . 'c.*$', '', ''), '[^{}()]', '', 'g')
239      if bracketLine =~ '}$'
240        return scala#Testhelper(matchedLNum, matchedLine, '{', '}', a:iteration + 1)
241      elseif bracketLine =~ ')$'
242        return scala#Testhelper(matchedLNum, matchedLine, '(', ')', a:iteration + 1)
243      else
244        let prevCodeLNum = scala#GetPrevCodeLine(matchedLNum)
245        if scala#MatchesIncompleteDefValr(scala#GetLine(prevCodeLNum))
246          return prevCodeLNum
247        else
248          return -1
249        endif
250      endif
251    else
252      " return indent value instead
253      return matchedLNum
254    endif
255  " There's an equal number of '{' and '}' on this line so it may be a single line function definition
256  elseif bracketCount == 0
257    if a:iteration == 0
258      let otherBracketType = scala#InvertBracketType(a:openBracket, a:closedBracket)
259      return scala#Testhelper(a:lnum, a:line, otherBracketType[0], otherBracketType[1], a:iteration + 1)
260    else
261      let prevCodeLNum = scala#GetPrevCodeLine(a:lnum)
262      let prevCodeLine = scala#GetLine(prevCodeLNum)
263      if scala#MatchesIncompleteDefValr(prevCodeLine) && prevCodeLine !~ '{\s*$'
264        return prevCodeLNum
265      else
266        let possibleIfElse = scala#LineCompletesIfElse(a:lnum, a:line)
267        if possibleIfElse != 0
268          let defValrLine = prevnonblank(possibleIfElse - 1)
269          let possibleDefValr = scala#GetLine(defValrLine)
270          if scala#MatchesIncompleteDefValr(possibleDefValr) && possibleDefValr =~ '^.*=\s*$'
271            return possibleDefValr
272          else
273            return -1
274          endif
275        else
276          return -1
277        endif
278      endif
279    endif
280  else
281    return -1
282  endif
283endfunction
284
285function! scala#Test(lnum, line, openBracket, closedBracket)
286  return scala#Testhelper(a:lnum, a:line, a:openBracket, a:closedBracket, 0)
287endfunction
288
289function! scala#LineCompletesDefValr(lnum, line)
290  let bracketCount = scala#CountBrackets(a:line, '{', '}')
291  if bracketCount < 0
292    let matchedBracket = scala#GetLineThatMatchesBracket('{', '}')
293    if ! scala#MatchesIncompleteDefValr(scala#GetLine(matchedBracket))
294      let possibleDefValr = scala#GetLine(prevnonblank(matchedBracket - 1))
295      if matchedBracket != -1 && scala#MatchesIncompleteDefValr(possibleDefValr)
296        return 1
297      else
298        return 0
299      endif
300    else
301      return 0
302    endif
303  elseif bracketCount == 0
304    let bracketCount = scala#CountBrackets(a:line, '(', ')')
305    if bracketCount < 0
306      let matchedBracket = scala#GetLineThatMatchesBracket('(', ')')
307      if ! scala#MatchesIncompleteDefValr(scala#GetLine(matchedBracket))
308        let possibleDefValr = scala#GetLine(prevnonblank(matchedBracket - 1))
309        if matchedBracket != -1 && scala#MatchesIncompleteDefValr(possibleDefValr)
310          return 1
311        else
312          return 0
313        endif
314      else
315        return 0
316      endif
317    elseif bracketCount == 0
318      let possibleDefValr = scala#GetLine(prevnonblank(a:lnum - 1))
319      if scala#MatchesIncompleteDefValr(possibleDefValr) && possibleDefValr =~ '^.*=\s*$'
320        return 1
321      else
322        let possibleIfElse = scala#LineCompletesIfElse(a:lnum, a:line)
323        if possibleIfElse != 0
324          let possibleDefValr = scala#GetLine(prevnonblank(possibleIfElse - 1))
325          if scala#MatchesIncompleteDefValr(possibleDefValr) && possibleDefValr =~ '^.*=\s*$'
326            return 2
327          else
328            return 0
329          endif
330        else
331          return 0
332        endif
333      endif
334    else
335      return 0
336    endif
337  endif
338endfunction
339
340function! scala#SpecificLineCompletesBrackets(lnum, openBracket, closedBracket)
341  let savedpos = getpos('.')
342  call setpos('.', [savedpos[0], a:lnum, 9999, savedpos[3]])
343  let retv = scala#LineCompletesBrackets(a:openBracket, a:closedBracket)
344  call setpos('.', savedpos)
345
346  return retv
347endfunction
348
349function! scala#LineCompletesBrackets(openBracket, closedBracket)
350  let savedpos = getpos('.')
351  let offline = 0
352  while offline == 0
353    let [lnum, colnum] = searchpos(a:closedBracket, 'Wb')
354    let [lnumA, colnumA] = searchpairpos(a:openBracket, '', a:closedBracket, 'Wbn')
355    if lnum != lnumA
356      let [lnumB, colnumB] = searchpairpos(a:openBracket, '', a:closedBracket, 'Wbnr')
357      let offline = 1
358    endif
359  endwhile
360  call setpos('.', savedpos)
361  if lnumA == lnumB && colnumA == colnumB
362    return lnumA
363  else
364    return -1
365  endif
366endfunction
367
368function! GetScalaIndent()
369  " Find a non-blank line above the current line.
370  let prevlnum = prevnonblank(v:lnum - 1)
371
372  " Hit the start of the file, use zero indent.
373  if prevlnum == 0
374    return 0
375  endif
376
377  let ind = indent(prevlnum)
378  let originalIndentValue = ind
379  let prevline = scala#GetLine(prevlnum)
380  let curlnum = v:lnum
381  let curline = scala#GetLine(curlnum)
382  if get(g:, 'scala_scaladoc_indent', 0)
383    let star_indent = 2
384  else
385    let star_indent = 1
386  end
387
388  if prevline =~ '^\s*/\*\*'
389    if prevline =~ '\*/\s*$'
390      return ind
391    else
392      return ind + star_indent
393    endif
394  endif
395
396  if curline =~ '^\s*\*'
397    return cindent(curlnum)
398  endif
399
400  " If this line starts with a { then make it indent the same as the previous line
401  if curline =~ '^\s*{'
402    call scala#ConditionalConfirm("1")
403    " Unless, of course, the previous one is a { as well
404    if prevline !~ '^\s*{'
405      call scala#ConditionalConfirm("2")
406      return indent(prevlnum)
407    endif
408  endif
409
410  " '.' continuations
411  if curline =~ '^\s*\.'
412    if prevline =~ '^\s*\.'
413      return ind
414    else
415      return ind + shiftwidth()
416    endif
417  endif
418
419  " Indent html literals
420  if prevline !~ '/>\s*$' && prevline =~ '^\s*<[a-zA-Z][^>]*>\s*$'
421    call scala#ConditionalConfirm("3")
422    return ind + shiftwidth()
423  endif
424
425  " assumes curly braces around try-block
426  if curline =~ '^\s*}\s*\<catch\>'
427    return ind - shiftwidth()
428  elseif curline =~ '^\s*\<catch\>'
429    return ind
430  endif
431
432  " Add a shiftwidth()' after lines that start a block
433  " If 'if', 'for' or 'while' end with ), this is a one-line block
434  " If 'val', 'var', 'def' end with =, this is a one-line block
435  if (prevline =~ '^\s*\<\%(\%(}\?\s*else\s\+\)\?if\|for\|while\)\>.*[)=]\s*$' && scala#NumberOfBraceGroups(prevline) <= 1)
436        \ || prevline =~ '^\s*' . s:defMatcher . '.*=\s*$'
437        \ || prevline =~ '^\s*\<va[lr]\>.*[=]\s*$'
438        \ || prevline =~ '^\s*\%(}\s*\)\?\<else\>\s*$'
439        \ || prevline =~ '=\s*$'
440    call scala#ConditionalConfirm("4")
441    let ind = ind + shiftwidth()
442  elseif prevline =~ '^\s*\<\%(}\?\s*else\s\+\)\?if\>' && curline =~ '^\s*}\?\s*\<else\>'
443    return ind
444  endif
445
446  let lineCompletedBrackets = 0
447  let bracketCount = scala#CountBrackets(prevline, '{', '}')
448  if bracketCount > 0 || prevline =~ '.*{\s*$'
449    call scala#ConditionalConfirm("5b")
450    let ind = ind + shiftwidth()
451  elseif bracketCount < 0
452    call scala#ConditionalConfirm("6b")
453    " if the closing brace actually completes the braces entirely, then we
454    " have to indent to line that started the whole thing
455    let completeLine = scala#LineCompletesBrackets('{', '}')
456    if completeLine != -1
457      call scala#ConditionalConfirm("8b")
458      let prevCompleteLine = scala#GetLine(prevnonblank(completeLine - 1))
459      " However, what actually started this part looks like it was a function
460      " definition, so we need to indent to that line instead.  This is
461      " actually pretty weak at the moment.
462      if prevCompleteLine =~ '=\s*$'
463        call scala#ConditionalConfirm("9b")
464        let ind = indent(prevnonblank(completeLine - 1))
465      else
466        call scala#ConditionalConfirm("10b")
467        let ind = indent(completeLine)
468      endif
469    else
470      let lineCompletedBrackets = 1
471    endif
472  endif
473
474  if ind == originalIndentValue
475    let bracketCount = scala#CountBrackets(prevline, '(', ')')
476    if bracketCount > 0 || prevline =~ '.*(\s*$'
477      call scala#ConditionalConfirm("5a")
478      let ind = ind + shiftwidth()
479    elseif bracketCount < 0
480      call scala#ConditionalConfirm("6a")
481      " if the closing brace actually completes the braces entirely, then we
482      " have to indent to line that started the whole thing
483      let completeLine = scala#LineCompletesBrackets('(', ')')
484      if completeLine != -1 && prevline !~ '^.*{\s*$'
485        call scala#ConditionalConfirm("8a")
486        let prevCompleteLine = scala#GetLine(prevnonblank(completeLine - 1))
487        " However, what actually started this part looks like it was a function
488        " definition, so we need to indent to that line instead.  This is
489        " actually pretty weak at the moment.
490        if prevCompleteLine =~ '=\s*$'
491          call scala#ConditionalConfirm("9a")
492          let ind = indent(prevnonblank(completeLine - 1))
493        else
494          call scala#ConditionalConfirm("10a")
495          let ind = indent(completeLine)
496        endif
497      else
498        " This is the only part that's different from from the '{', '}' one below
499        " Yup... some refactoring is necessary at some point.
500        let ind = ind + (bracketCount * shiftwidth())
501        let lineCompletedBrackets = 1
502      endif
503    endif
504  endif
505
506  if curline =~ '^\s*}\?\s*\<else\>\%(\s\+\<if\>\s*(.*)\)\?\s*{\?\s*$' &&
507   \ ! scala#LineIsCompleteIf(prevline) &&
508   \ prevline !~ '^.*}\s*$'
509    let ind = ind - shiftwidth()
510  endif
511
512  " Subtract a shiftwidth()' on '}' or html
513  let curCurlyCount = scala#CountCurlies(curline)
514  if curCurlyCount < 0
515    call scala#ConditionalConfirm("14a")
516    let matchline = scala#CurlyMatcher()
517    return indent(matchline)
518  elseif curline =~ '^\s*</[a-zA-Z][^>]*>'
519    call scala#ConditionalConfirm("14c")
520    return ind - shiftwidth()
521  endif
522
523  let prevParenCount = scala#CountParens(prevline)
524  if prevline =~ '^\s*\<for\>.*$' && prevParenCount > 0
525    call scala#ConditionalConfirm("15")
526    let ind = indent(prevlnum) + 5
527  endif
528
529  let prevCurlyCount = scala#CountCurlies(prevline)
530  if prevCurlyCount == 0 && prevline =~ '^.*\%(=>\|⇒\)\s*$' && prevline !~ '^\s*this\s*:.*\%(=>\|⇒\)\s*$' && curline !~ '^\s*\<case\>'
531    call scala#ConditionalConfirm("16")
532    let ind = ind + shiftwidth()
533  endif
534
535  if ind == originalIndentValue && curline =~ '^\s*\<case\>'
536    call scala#ConditionalConfirm("17")
537    let parentCase = scala#IsParentCase()
538    if parentCase != -1
539      call scala#ConditionalConfirm("17a")
540      return indent(parentCase)
541    endif
542  endif
543
544  if prevline =~ '^\s*\*/'
545   \ || prevline =~ '*/\s*$'
546    call scala#ConditionalConfirm("18")
547    let ind = ind - star_indent
548  endif
549
550  if scala#LineEndsInIncomplete(prevline)
551    call scala#ConditionalConfirm("19")
552    return ind
553  endif
554
555  if scala#LineIsAClosingXML(prevline)
556    if scala#LineCompletesXML(prevlnum, prevline)
557      call scala#ConditionalConfirm("20a")
558      return ind - shiftwidth()
559    else
560      call scala#ConditionalConfirm("20b")
561      return ind
562    endif
563  endif
564
565  if ind == originalIndentValue
566    "let indentMultiplier = scala#LineCompletesDefValr(prevlnum, prevline)
567    "if indentMultiplier != 0
568    "  call scala#ConditionalConfirm("19a")
569    "  let ind = ind - (indentMultiplier * shiftwidth())
570    let defValrLine = scala#Test(prevlnum, prevline, '{', '}')
571    if defValrLine != -1
572      call scala#ConditionalConfirm("21a")
573      let ind = indent(defValrLine)
574    elseif lineCompletedBrackets == 0
575      call scala#ConditionalConfirm("21b")
576      if scala#GetLine(prevnonblank(prevlnum - 1)) =~ '^.*\<else\>\s*\%(//.*\)\?$'
577        call scala#ConditionalConfirm("21c")
578        let ind = ind - shiftwidth()
579      elseif scala#LineCompletesIfElse(prevlnum, prevline)
580        call scala#ConditionalConfirm("21d")
581        let ind = ind - shiftwidth()
582      elseif scala#CountParens(curline) < 0 && curline =~ '^\s*)' && scala#GetLine(scala#GetLineThatMatchesBracket('(', ')')) =~ '.*(\s*$'
583        " Handles situations that look like this:
584        "
585        "   val a = func(
586        "     10
587        "   )
588        "
589        " or
590        "
591        "   val a = func(
592        "     10
593        "   ).somethingHere()
594        call scala#ConditionalConfirm("21e")
595        let ind = ind - shiftwidth()
596      endif
597    endif
598  endif
599
600  call scala#ConditionalConfirm("returning " . ind)
601
602  return ind
603endfunction
604
605let &cpo = s:keepcpo
606unlet s:keepcpo
607
608" vim:set sw=2 sts=2 ts=8 et:
609" vim600:fdm=marker fdl=1 fdc=0:
610