xref: /vim-8.2.3635/runtime/indent/ruby.vim (revision ec7944aa)
1071d4279SBram Moolenaar" Vim indent file
2071d4279SBram Moolenaar" Language:		Ruby
3551dbcc9SBram Moolenaar" Maintainer:		Nikolai Weibull <now at bitwi.se>
4*ec7944aaSBram Moolenaar" URL:			https://github.com/vim-ruby/vim-ruby
5551dbcc9SBram Moolenaar" Release Coordinator:	Doug Kearns <[email protected]>
660a795aaSBram Moolenaar
760a795aaSBram Moolenaar" 0. Initialization {{{1
860a795aaSBram Moolenaar" =================
9071d4279SBram Moolenaar
10071d4279SBram Moolenaar" Only load this indent file when no other was loaded.
11071d4279SBram Moolenaarif exists("b:did_indent")
12071d4279SBram Moolenaar  finish
13071d4279SBram Moolenaarendif
14071d4279SBram Moolenaarlet b:did_indent = 1
15071d4279SBram Moolenaar
16551dbcc9SBram Moolenaarsetlocal nosmartindent
17551dbcc9SBram Moolenaar
1860a795aaSBram Moolenaar" Now, set up our indentation expression and keys that trigger it.
19*ec7944aaSBram Moolenaarsetlocal indentexpr=GetRubyIndent(v:lnum)
2060a795aaSBram Moolenaarsetlocal indentkeys=0{,0},0),0],!^F,o,O,e
21*ec7944aaSBram Moolenaarsetlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue,==begin,==end
22071d4279SBram Moolenaar
23071d4279SBram Moolenaar" Only define the function once.
24071d4279SBram Moolenaarif exists("*GetRubyIndent")
25071d4279SBram Moolenaar  finish
26071d4279SBram Moolenaarendif
27071d4279SBram Moolenaar
2860a795aaSBram Moolenaarlet s:cpo_save = &cpo
2960a795aaSBram Moolenaarset cpo&vim
3060a795aaSBram Moolenaar
3160a795aaSBram Moolenaar" 1. Variables {{{1
3260a795aaSBram Moolenaar" ============
3360a795aaSBram Moolenaar
34*ec7944aaSBram Moolenaar" Regex of syntax group names that are or delimit strings/symbols or are comments.
35*ec7944aaSBram Moolenaarlet s:syng_strcom = '\<ruby\%(Regexp\|RegexpDelimiter\|RegexpEscape' .
36*ec7944aaSBram Moolenaar      \ '\|Symbol\|String\|StringDelimiter\|StringEscape\|ASCIICode' .
37c236c16dSBram Moolenaar      \ '\|Interpolation\|NoInterpolation\|Comment\|Documentation\)\>'
3860a795aaSBram Moolenaar
3960a795aaSBram Moolenaar" Regex of syntax group names that are strings.
4060a795aaSBram Moolenaarlet s:syng_string =
41c236c16dSBram Moolenaar      \ '\<ruby\%(String\|Interpolation\|NoInterpolation\|StringEscape\)\>'
4260a795aaSBram Moolenaar
4360a795aaSBram Moolenaar" Regex of syntax group names that are strings or documentation.
4460a795aaSBram Moolenaarlet s:syng_stringdoc =
45c236c16dSBram Moolenaar      \'\<ruby\%(String\|Interpolation\|NoInterpolation\|StringEscape\|Documentation\)\>'
4660a795aaSBram Moolenaar
4760a795aaSBram Moolenaar" Expression used to check whether we should skip a match with searchpair().
4860a795aaSBram Moolenaarlet s:skip_expr =
49c236c16dSBram Moolenaar      \ "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
5060a795aaSBram Moolenaar
5160a795aaSBram Moolenaar" Regex used for words that, at the start of a line, add a level of indent.
5260a795aaSBram Moolenaarlet s:ruby_indent_keywords = '^\s*\zs\<\%(module\|class\|def\|if\|for' .
5360a795aaSBram Moolenaar      \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure' .
54*ec7944aaSBram Moolenaar      \ '\|rescue\):\@!\>' .
55*ec7944aaSBram Moolenaar      \ '\|\%([=,*/%+-]\|<<\|>>\|:\s\)\s*\zs' .
56*ec7944aaSBram Moolenaar      \    '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>'
5760a795aaSBram Moolenaar
5860a795aaSBram Moolenaar" Regex used for words that, at the start of a line, remove a level of indent.
5960a795aaSBram Moolenaarlet s:ruby_deindent_keywords =
60*ec7944aaSBram Moolenaar      \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|end\):\@!\>'
6160a795aaSBram Moolenaar
6260a795aaSBram Moolenaar" Regex that defines the start-match for the 'end' keyword.
6360a795aaSBram Moolenaar"let s:end_start_regex = '\%(^\|[^.]\)\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\|do\)\>'
6460a795aaSBram Moolenaar" TODO: the do here should be restricted somewhat (only at end of line)?
65*ec7944aaSBram Moolenaarlet s:end_start_regex =
66*ec7944aaSBram Moolenaar      \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
67*ec7944aaSBram Moolenaar      \ '\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\):\@!\>' .
68*ec7944aaSBram Moolenaar      \ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>'
6960a795aaSBram Moolenaar
7060a795aaSBram Moolenaar" Regex that defines the middle-match for the 'end' keyword.
71*ec7944aaSBram Moolenaarlet s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|elsif\):\@!\>'
7260a795aaSBram Moolenaar
7360a795aaSBram Moolenaar" Regex that defines the end-match for the 'end' keyword.
74*ec7944aaSBram Moolenaarlet s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end:\@!\>'
7560a795aaSBram Moolenaar
7660a795aaSBram Moolenaar" Expression used for searchpair() call for finding match for 'end' keyword.
7760a795aaSBram Moolenaarlet s:end_skip_expr = s:skip_expr .
7860a795aaSBram Moolenaar      \ ' || (expand("<cword>") == "do"' .
79*ec7944aaSBram Moolenaar      \ ' && getline(".") =~ "^\\s*\\<\\(while\\|until\\|for\\):\\@!\\>")'
8060a795aaSBram Moolenaar
8160a795aaSBram Moolenaar" Regex that defines continuation lines, not including (, {, or [.
82*ec7944aaSBram Moolenaarlet s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
8360a795aaSBram Moolenaar
8460a795aaSBram Moolenaar" Regex that defines continuation lines.
8560a795aaSBram Moolenaar" TODO: this needs to deal with if ...: and so on
86*ec7944aaSBram Moolenaarlet s:continuation_regex =
87*ec7944aaSBram Moolenaar      \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
88*ec7944aaSBram Moolenaar
89*ec7944aaSBram Moolenaar" Regex that defines bracket continuations
90*ec7944aaSBram Moolenaarlet s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$'
91*ec7944aaSBram Moolenaar
92*ec7944aaSBram Moolenaar" Regex that defines the first part of a splat pattern
93*ec7944aaSBram Moolenaarlet s:splat_regex = '[[,(]\s*\*\s*\%(#.*\)\=$'
9460a795aaSBram Moolenaar
9560a795aaSBram Moolenaar" Regex that defines blocks.
96*ec7944aaSBram Moolenaar"
97*ec7944aaSBram Moolenaar" Note that there's a slight problem with this regex and s:continuation_regex.
98*ec7944aaSBram Moolenaar" Code like this will be matched by both:
99*ec7944aaSBram Moolenaar"
100*ec7944aaSBram Moolenaar"   method_call do |(a, b)|
101*ec7944aaSBram Moolenaar"
102*ec7944aaSBram Moolenaar" The reason is that the pipe matches a hanging "|" operator.
103*ec7944aaSBram Moolenaar"
10460a795aaSBram Moolenaarlet s:block_regex =
105*ec7944aaSBram Moolenaar      \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$'
106*ec7944aaSBram Moolenaar
107*ec7944aaSBram Moolenaarlet s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex
10860a795aaSBram Moolenaar
10960a795aaSBram Moolenaar" 2. Auxiliary Functions {{{1
11060a795aaSBram Moolenaar" ======================
11160a795aaSBram Moolenaar
11260a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string, comment, or is ascii.
11360a795aaSBram Moolenaarfunction s:IsInStringOrComment(lnum, col)
114c236c16dSBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
11560a795aaSBram Moolenaarendfunction
11660a795aaSBram Moolenaar
11760a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string.
11860a795aaSBram Moolenaarfunction s:IsInString(lnum, col)
119c236c16dSBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
12060a795aaSBram Moolenaarendfunction
12160a795aaSBram Moolenaar
12260a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string or documentation.
12360a795aaSBram Moolenaarfunction s:IsInStringOrDocumentation(lnum, col)
124c236c16dSBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_stringdoc
12560a795aaSBram Moolenaarendfunction
12660a795aaSBram Moolenaar
127*ec7944aaSBram Moolenaar" Check if the character at lnum:col is inside a string delimiter
128*ec7944aaSBram Moolenaarfunction s:IsInStringDelimiter(lnum, col)
129*ec7944aaSBram Moolenaar  return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'rubyStringDelimiter'
130*ec7944aaSBram Moolenaarendfunction
131*ec7944aaSBram Moolenaar
13260a795aaSBram Moolenaar" Find line above 'lnum' that isn't empty, in a comment, or in a string.
13360a795aaSBram Moolenaarfunction s:PrevNonBlankNonString(lnum)
13460a795aaSBram Moolenaar  let in_block = 0
13560a795aaSBram Moolenaar  let lnum = prevnonblank(a:lnum)
13660a795aaSBram Moolenaar  while lnum > 0
13760a795aaSBram Moolenaar    " Go in and out of blocks comments as necessary.
13860a795aaSBram Moolenaar    " If the line isn't empty (with opt. comment) or in a string, end search.
13960a795aaSBram Moolenaar    let line = getline(lnum)
140*ec7944aaSBram Moolenaar    if line =~ '^=begin'
14160a795aaSBram Moolenaar      if in_block
14260a795aaSBram Moolenaar        let in_block = 0
14360a795aaSBram Moolenaar      else
14460a795aaSBram Moolenaar        break
14560a795aaSBram Moolenaar      endif
146*ec7944aaSBram Moolenaar    elseif !in_block && line =~ '^=end'
14760a795aaSBram Moolenaar      let in_block = 1
14860a795aaSBram Moolenaar    elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
14960a795aaSBram Moolenaar          \ && s:IsInStringOrComment(lnum, strlen(line)))
15060a795aaSBram Moolenaar      break
15160a795aaSBram Moolenaar    endif
15260a795aaSBram Moolenaar    let lnum = prevnonblank(lnum - 1)
15360a795aaSBram Moolenaar  endwhile
15460a795aaSBram Moolenaar  return lnum
15560a795aaSBram Moolenaarendfunction
15660a795aaSBram Moolenaar
15760a795aaSBram Moolenaar" Find line above 'lnum' that started the continuation 'lnum' may be part of.
15860a795aaSBram Moolenaarfunction s:GetMSL(lnum)
15960a795aaSBram Moolenaar  " Start on the line we're at and use its indent.
16060a795aaSBram Moolenaar  let msl = a:lnum
161*ec7944aaSBram Moolenaar  let msl_body = getline(msl)
16260a795aaSBram Moolenaar  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
16360a795aaSBram Moolenaar  while lnum > 0
16460a795aaSBram Moolenaar    " If we have a continuation line, or we're in a string, use line as MSL.
16560a795aaSBram Moolenaar    " Otherwise, terminate search as we have found our MSL already.
16660a795aaSBram Moolenaar    let line = getline(lnum)
167*ec7944aaSBram Moolenaar
168*ec7944aaSBram Moolenaar    if s:Match(lnum, s:splat_regex)
169*ec7944aaSBram Moolenaar      " If the above line looks like the "*" of a splat, use the current one's
170*ec7944aaSBram Moolenaar      " indentation.
171*ec7944aaSBram Moolenaar      "
172*ec7944aaSBram Moolenaar      " Example:
173*ec7944aaSBram Moolenaar      "   Hash[*
174*ec7944aaSBram Moolenaar      "     method_call do
175*ec7944aaSBram Moolenaar      "       something
176*ec7944aaSBram Moolenaar      "
177*ec7944aaSBram Moolenaar      return msl
178*ec7944aaSBram Moolenaar    elseif s:Match(line, s:non_bracket_continuation_regex) &&
179*ec7944aaSBram Moolenaar          \ s:Match(msl, s:non_bracket_continuation_regex)
180*ec7944aaSBram Moolenaar      " If the current line is a non-bracket continuation and so is the
181*ec7944aaSBram Moolenaar      " previous one, keep its indent and continue looking for an MSL.
182*ec7944aaSBram Moolenaar      "
183*ec7944aaSBram Moolenaar      " Example:
184*ec7944aaSBram Moolenaar      "   method_call one,
185*ec7944aaSBram Moolenaar      "     two,
186*ec7944aaSBram Moolenaar      "     three
187*ec7944aaSBram Moolenaar      "
188*ec7944aaSBram Moolenaar      let msl = lnum
189*ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
190*ec7944aaSBram Moolenaar          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
191*ec7944aaSBram Moolenaar      " If the current line is a bracket continuation or a block-starter, but
192*ec7944aaSBram Moolenaar      " the previous is a non-bracket one, respect the previous' indentation,
193*ec7944aaSBram Moolenaar      " and stop here.
194*ec7944aaSBram Moolenaar      "
195*ec7944aaSBram Moolenaar      " Example:
196*ec7944aaSBram Moolenaar      "   method_call one,
197*ec7944aaSBram Moolenaar      "     two {
198*ec7944aaSBram Moolenaar      "     three
199*ec7944aaSBram Moolenaar      "
200*ec7944aaSBram Moolenaar      return lnum
201*ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:bracket_continuation_regex) &&
202*ec7944aaSBram Moolenaar          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
203*ec7944aaSBram Moolenaar      " If both lines are bracket continuations (the current may also be a
204*ec7944aaSBram Moolenaar      " block-starter), use the current one's and stop here
205*ec7944aaSBram Moolenaar      "
206*ec7944aaSBram Moolenaar      " Example:
207*ec7944aaSBram Moolenaar      "   method_call(
208*ec7944aaSBram Moolenaar      "     other_method_call(
209*ec7944aaSBram Moolenaar      "       foo
210*ec7944aaSBram Moolenaar      return msl
211*ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:block_regex) &&
212*ec7944aaSBram Moolenaar          \ !s:Match(msl, s:continuation_regex) &&
213*ec7944aaSBram Moolenaar          \ !s:Match(msl, s:block_continuation_regex)
214*ec7944aaSBram Moolenaar      " If the previous line is a block-starter and the current one is
215*ec7944aaSBram Moolenaar      " mostly ordinary, use the current one as the MSL.
216*ec7944aaSBram Moolenaar      "
217*ec7944aaSBram Moolenaar      " Example:
218*ec7944aaSBram Moolenaar      "   method_call do
219*ec7944aaSBram Moolenaar      "     something
220*ec7944aaSBram Moolenaar      "     something_else
221*ec7944aaSBram Moolenaar      return msl
222*ec7944aaSBram Moolenaar    else
223*ec7944aaSBram Moolenaar      let col = match(line, s:continuation_regex) + 1
22460a795aaSBram Moolenaar      if (col > 0 && !s:IsInStringOrComment(lnum, col))
22560a795aaSBram Moolenaar            \ || s:IsInString(lnum, strlen(line))
22660a795aaSBram Moolenaar        let msl = lnum
22760a795aaSBram Moolenaar      else
22860a795aaSBram Moolenaar        break
22960a795aaSBram Moolenaar      endif
230*ec7944aaSBram Moolenaar    endif
231*ec7944aaSBram Moolenaar
232*ec7944aaSBram Moolenaar    let msl_body = getline(msl)
23360a795aaSBram Moolenaar    let lnum = s:PrevNonBlankNonString(lnum - 1)
23460a795aaSBram Moolenaar  endwhile
23560a795aaSBram Moolenaar  return msl
23660a795aaSBram Moolenaarendfunction
23760a795aaSBram Moolenaar
23860a795aaSBram Moolenaar" Check if line 'lnum' has more opening brackets than closing ones.
239*ec7944aaSBram Moolenaarfunction s:ExtraBrackets(lnum)
240*ec7944aaSBram Moolenaar  let opening = {'parentheses': [], 'braces': [], 'brackets': []}
241*ec7944aaSBram Moolenaar  let closing = {'parentheses': [], 'braces': [], 'brackets': []}
242*ec7944aaSBram Moolenaar
24360a795aaSBram Moolenaar  let line = getline(a:lnum)
24460a795aaSBram Moolenaar  let pos  = match(line, '[][(){}]', 0)
245*ec7944aaSBram Moolenaar
246*ec7944aaSBram Moolenaar  " Save any encountered opening brackets, and remove them once a matching
247*ec7944aaSBram Moolenaar  " closing one has been found. If a closing bracket shows up that doesn't
248*ec7944aaSBram Moolenaar  " close anything, save it for later.
24960a795aaSBram Moolenaar  while pos != -1
25060a795aaSBram Moolenaar    if !s:IsInStringOrComment(a:lnum, pos + 1)
251*ec7944aaSBram Moolenaar      if line[pos] == '('
252*ec7944aaSBram Moolenaar        call add(opening.parentheses, {'type': '(', 'pos': pos})
253*ec7944aaSBram Moolenaar      elseif line[pos] == ')'
254*ec7944aaSBram Moolenaar        if empty(opening.parentheses)
255*ec7944aaSBram Moolenaar          call add(closing.parentheses, {'type': ')', 'pos': pos})
25660a795aaSBram Moolenaar        else
257*ec7944aaSBram Moolenaar          let opening.parentheses = opening.parentheses[0:-2]
258*ec7944aaSBram Moolenaar        endif
259*ec7944aaSBram Moolenaar      elseif line[pos] == '{'
260*ec7944aaSBram Moolenaar        call add(opening.braces, {'type': '{', 'pos': pos})
261*ec7944aaSBram Moolenaar      elseif line[pos] == '}'
262*ec7944aaSBram Moolenaar        if empty(opening.braces)
263*ec7944aaSBram Moolenaar          call add(closing.braces, {'type': '}', 'pos': pos})
264*ec7944aaSBram Moolenaar        else
265*ec7944aaSBram Moolenaar          let opening.braces = opening.braces[0:-2]
266*ec7944aaSBram Moolenaar        endif
267*ec7944aaSBram Moolenaar      elseif line[pos] == '['
268*ec7944aaSBram Moolenaar        call add(opening.brackets, {'type': '[', 'pos': pos})
269*ec7944aaSBram Moolenaar      elseif line[pos] == ']'
270*ec7944aaSBram Moolenaar        if empty(opening.brackets)
271*ec7944aaSBram Moolenaar          call add(closing.brackets, {'type': ']', 'pos': pos})
272*ec7944aaSBram Moolenaar        else
273*ec7944aaSBram Moolenaar          let opening.brackets = opening.brackets[0:-2]
27460a795aaSBram Moolenaar        endif
27560a795aaSBram Moolenaar      endif
276*ec7944aaSBram Moolenaar    endif
277*ec7944aaSBram Moolenaar
27860a795aaSBram Moolenaar    let pos = match(line, '[][(){}]', pos + 1)
27960a795aaSBram Moolenaar  endwhile
280*ec7944aaSBram Moolenaar
281*ec7944aaSBram Moolenaar  " Find the rightmost brackets, since they're the ones that are important in
282*ec7944aaSBram Moolenaar  " both opening and closing cases
283*ec7944aaSBram Moolenaar  let rightmost_opening = {'type': '(', 'pos': -1}
284*ec7944aaSBram Moolenaar  let rightmost_closing = {'type': ')', 'pos': -1}
285*ec7944aaSBram Moolenaar
286*ec7944aaSBram Moolenaar  for opening in opening.parentheses + opening.braces + opening.brackets
287*ec7944aaSBram Moolenaar    if opening.pos > rightmost_opening.pos
288*ec7944aaSBram Moolenaar      let rightmost_opening = opening
289*ec7944aaSBram Moolenaar    endif
290*ec7944aaSBram Moolenaar  endfor
291*ec7944aaSBram Moolenaar
292*ec7944aaSBram Moolenaar  for closing in closing.parentheses + closing.braces + closing.brackets
293*ec7944aaSBram Moolenaar    if closing.pos > rightmost_closing.pos
294*ec7944aaSBram Moolenaar      let rightmost_closing = closing
295*ec7944aaSBram Moolenaar    endif
296*ec7944aaSBram Moolenaar  endfor
297*ec7944aaSBram Moolenaar
298*ec7944aaSBram Moolenaar  return [rightmost_opening, rightmost_closing]
29960a795aaSBram Moolenaarendfunction
30060a795aaSBram Moolenaar
30160a795aaSBram Moolenaarfunction s:Match(lnum, regex)
3021d68952aSBram Moolenaar  let col = match(getline(a:lnum), '\C'.a:regex) + 1
30360a795aaSBram Moolenaar  return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
30460a795aaSBram Moolenaarendfunction
30560a795aaSBram Moolenaar
30660a795aaSBram Moolenaarfunction s:MatchLast(lnum, regex)
30760a795aaSBram Moolenaar  let line = getline(a:lnum)
30860a795aaSBram Moolenaar  let col = match(line, '.*\zs' . a:regex)
30960a795aaSBram Moolenaar  while col != -1 && s:IsInStringOrComment(a:lnum, col)
31060a795aaSBram Moolenaar    let line = strpart(line, 0, col)
31160a795aaSBram Moolenaar    let col = match(line, '.*' . a:regex)
31260a795aaSBram Moolenaar  endwhile
31360a795aaSBram Moolenaar  return col + 1
31460a795aaSBram Moolenaarendfunction
31560a795aaSBram Moolenaar
31660a795aaSBram Moolenaar" 3. GetRubyIndent Function {{{1
31760a795aaSBram Moolenaar" =========================
31860a795aaSBram Moolenaar
319*ec7944aaSBram Moolenaarfunction GetRubyIndent(...)
32060a795aaSBram Moolenaar  " 3.1. Setup {{{2
32160a795aaSBram Moolenaar  " ----------
32260a795aaSBram Moolenaar
323*ec7944aaSBram Moolenaar  " For the current line, use the first argument if given, else v:lnum
324*ec7944aaSBram Moolenaar  let clnum = a:0 ? a:1 : v:lnum
325*ec7944aaSBram Moolenaar
326*ec7944aaSBram Moolenaar  " Set up variables for restoring position in file.  Could use clnum here.
32760a795aaSBram Moolenaar  let vcol = col('.')
32860a795aaSBram Moolenaar
32960a795aaSBram Moolenaar  " 3.2. Work on the current line {{{2
33060a795aaSBram Moolenaar  " -----------------------------
33160a795aaSBram Moolenaar
33260a795aaSBram Moolenaar  " Get the current line.
333*ec7944aaSBram Moolenaar  let line = getline(clnum)
33460a795aaSBram Moolenaar  let ind = -1
33560a795aaSBram Moolenaar
33660a795aaSBram Moolenaar  " If we got a closing bracket on an empty line, find its match and indent
33760a795aaSBram Moolenaar  " according to it.  For parentheses we indent to its column - 1, for the
33860a795aaSBram Moolenaar  " others we indent to the containing line's MSL's level.  Return -1 if fail.
33960a795aaSBram Moolenaar  let col = matchend(line, '^\s*[]})]')
340*ec7944aaSBram Moolenaar  if col > 0 && !s:IsInStringOrComment(clnum, col)
341*ec7944aaSBram Moolenaar    call cursor(clnum, col)
34260a795aaSBram Moolenaar    let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
34360a795aaSBram Moolenaar    if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
3449964e468SBram Moolenaar      if line[col-1]==')' && col('.') != col('$') - 1
3459964e468SBram Moolenaar        let ind = virtcol('.') - 1
3469964e468SBram Moolenaar      else
3479964e468SBram Moolenaar        let ind = indent(s:GetMSL(line('.')))
3489964e468SBram Moolenaar      endif
34960a795aaSBram Moolenaar    endif
35060a795aaSBram Moolenaar    return ind
35160a795aaSBram Moolenaar  endif
35260a795aaSBram Moolenaar
35360a795aaSBram Moolenaar  " If we have a =begin or =end set indent to first column.
35460a795aaSBram Moolenaar  if match(line, '^\s*\%(=begin\|=end\)$') != -1
35560a795aaSBram Moolenaar    return 0
35660a795aaSBram Moolenaar  endif
35760a795aaSBram Moolenaar
35860a795aaSBram Moolenaar  " If we have a deindenting keyword, find its match and indent to its level.
35960a795aaSBram Moolenaar  " TODO: this is messy
360*ec7944aaSBram Moolenaar  if s:Match(clnum, s:ruby_deindent_keywords)
361*ec7944aaSBram Moolenaar    call cursor(clnum, 1)
36260a795aaSBram Moolenaar    if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
36360a795aaSBram Moolenaar          \ s:end_skip_expr) > 0
364*ec7944aaSBram Moolenaar      let msl  = s:GetMSL(line('.'))
365*ec7944aaSBram Moolenaar      let line = getline(line('.'))
366*ec7944aaSBram Moolenaar
3671e015460SBram Moolenaar      if strpart(line, 0, col('.') - 1) =~ '=\s*$' &&
3681e015460SBram Moolenaar            \ strpart(line, col('.') - 1, 2) !~ 'do'
36960a795aaSBram Moolenaar        let ind = virtcol('.') - 1
370*ec7944aaSBram Moolenaar      elseif getline(msl) =~ '=\s*\(#.*\)\=$'
371*ec7944aaSBram Moolenaar        let ind = indent(line('.'))
37260a795aaSBram Moolenaar      else
373*ec7944aaSBram Moolenaar        let ind = indent(msl)
37460a795aaSBram Moolenaar      endif
37560a795aaSBram Moolenaar    endif
37660a795aaSBram Moolenaar    return ind
37760a795aaSBram Moolenaar  endif
37860a795aaSBram Moolenaar
37960a795aaSBram Moolenaar  " If we are in a multi-line string or line-comment, don't do anything to it.
380*ec7944aaSBram Moolenaar  if s:IsInStringOrDocumentation(clnum, matchend(line, '^\s*') + 1)
38160a795aaSBram Moolenaar    return indent('.')
38260a795aaSBram Moolenaar  endif
38360a795aaSBram Moolenaar
384*ec7944aaSBram Moolenaar  " If we are at the closing delimiter of a "<<" heredoc-style string, set the
385*ec7944aaSBram Moolenaar  " indent to 0.
386*ec7944aaSBram Moolenaar  if line =~ '^\k\+\s*$'
387*ec7944aaSBram Moolenaar        \ && s:IsInStringDelimiter(clnum, 1)
388*ec7944aaSBram Moolenaar        \ && search('\V<<'.line, 'nbW') > 0
389*ec7944aaSBram Moolenaar    return 0
390*ec7944aaSBram Moolenaar  endif
391*ec7944aaSBram Moolenaar
39260a795aaSBram Moolenaar  " 3.3. Work on the previous line. {{{2
39360a795aaSBram Moolenaar  " -------------------------------
39460a795aaSBram Moolenaar
39560a795aaSBram Moolenaar  " Find a non-blank, non-multi-line string line above the current line.
396*ec7944aaSBram Moolenaar  let lnum = s:PrevNonBlankNonString(clnum - 1)
397071d4279SBram Moolenaar
398c236c16dSBram Moolenaar  " If the line is empty and inside a string, use the previous line.
399*ec7944aaSBram Moolenaar  if line =~ '^\s*$' && lnum != prevnonblank(clnum - 1)
400*ec7944aaSBram Moolenaar    return indent(prevnonblank(clnum))
401c236c16dSBram Moolenaar  endif
402c236c16dSBram Moolenaar
403071d4279SBram Moolenaar  " At the start of the file use zero indent.
404071d4279SBram Moolenaar  if lnum == 0
405071d4279SBram Moolenaar    return 0
406071d4279SBram Moolenaar  endif
407071d4279SBram Moolenaar
408*ec7944aaSBram Moolenaar  " Set up variables for the previous line.
40960a795aaSBram Moolenaar  let line = getline(lnum)
410071d4279SBram Moolenaar  let ind = indent(lnum)
41160a795aaSBram Moolenaar
41260a795aaSBram Moolenaar  " If the previous line ended with a block opening, add a level of indent.
41360a795aaSBram Moolenaar  if s:Match(lnum, s:block_regex)
41460a795aaSBram Moolenaar    return indent(s:GetMSL(lnum)) + &sw
415071d4279SBram Moolenaar  endif
416071d4279SBram Moolenaar
417*ec7944aaSBram Moolenaar  " If the previous line ended with the "*" of a splat, add a level of indent
418*ec7944aaSBram Moolenaar  if line =~ s:splat_regex
419*ec7944aaSBram Moolenaar    return indent(lnum) + &sw
420*ec7944aaSBram Moolenaar  endif
421*ec7944aaSBram Moolenaar
422*ec7944aaSBram Moolenaar  " If the previous line contained unclosed opening brackets and we are still
423*ec7944aaSBram Moolenaar  " in them, find the rightmost one and add indent depending on the bracket
424*ec7944aaSBram Moolenaar  " type.
425*ec7944aaSBram Moolenaar  "
426*ec7944aaSBram Moolenaar  " If it contained hanging closing brackets, find the rightmost one, find its
427*ec7944aaSBram Moolenaar  " match and indent according to that.
428*ec7944aaSBram Moolenaar  if line =~ '[[({]' || line =~ '[])}]\s*\%(#.*\)\=$'
429*ec7944aaSBram Moolenaar    let [opening, closing] = s:ExtraBrackets(lnum)
430*ec7944aaSBram Moolenaar
431*ec7944aaSBram Moolenaar    if opening.pos != -1
432*ec7944aaSBram Moolenaar      if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
4339964e468SBram Moolenaar        if col('.') + 1 == col('$')
4349964e468SBram Moolenaar          return ind + &sw
4359964e468SBram Moolenaar        else
43660a795aaSBram Moolenaar          return virtcol('.')
4379964e468SBram Moolenaar        endif
43860a795aaSBram Moolenaar      else
439*ec7944aaSBram Moolenaar        let nonspace = matchend(line, '\S', opening.pos + 1) - 1
440*ec7944aaSBram Moolenaar        return nonspace > 0 ? nonspace : ind + &sw
441*ec7944aaSBram Moolenaar      endif
442*ec7944aaSBram Moolenaar    elseif closing.pos != -1
443*ec7944aaSBram Moolenaar      call cursor(lnum, closing.pos + 1)
444*ec7944aaSBram Moolenaar      normal! %
445*ec7944aaSBram Moolenaar
446*ec7944aaSBram Moolenaar      if s:Match(line('.'), s:ruby_indent_keywords)
447*ec7944aaSBram Moolenaar        return indent('.') + &sw
448*ec7944aaSBram Moolenaar      else
449*ec7944aaSBram Moolenaar        return indent('.')
450*ec7944aaSBram Moolenaar      endif
451*ec7944aaSBram Moolenaar    else
452*ec7944aaSBram Moolenaar      call cursor(clnum, vcol)
45360a795aaSBram Moolenaar    end
454071d4279SBram Moolenaar  endif
455071d4279SBram Moolenaar
45660a795aaSBram Moolenaar  " If the previous line ended with an "end", match that "end"s beginning's
45760a795aaSBram Moolenaar  " indent.
458720c7100SBram Moolenaar  let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
45960a795aaSBram Moolenaar  if col > 0
46060a795aaSBram Moolenaar    call cursor(lnum, col)
46160a795aaSBram Moolenaar    if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
46260a795aaSBram Moolenaar          \ s:end_skip_expr) > 0
46360a795aaSBram Moolenaar      let n = line('.')
46460a795aaSBram Moolenaar      let ind = indent('.')
46560a795aaSBram Moolenaar      let msl = s:GetMSL(n)
46660a795aaSBram Moolenaar      if msl != n
46760a795aaSBram Moolenaar        let ind = indent(msl)
46860a795aaSBram Moolenaar      end
46960a795aaSBram Moolenaar      return ind
47060a795aaSBram Moolenaar    endif
47160a795aaSBram Moolenaar  end
47260a795aaSBram Moolenaar
47360a795aaSBram Moolenaar  let col = s:Match(lnum, s:ruby_indent_keywords)
47460a795aaSBram Moolenaar  if col > 0
47560a795aaSBram Moolenaar    call cursor(lnum, col)
47660a795aaSBram Moolenaar    let ind = virtcol('.') - 1 + &sw
47760a795aaSBram Moolenaar    " TODO: make this better (we need to count them) (or, if a searchpair
47860a795aaSBram Moolenaar    " fails, we know that something is lacking an end and thus we indent a
47960a795aaSBram Moolenaar    " level
48060a795aaSBram Moolenaar    if s:Match(lnum, s:end_end_regex)
48160a795aaSBram Moolenaar      let ind = indent('.')
48260a795aaSBram Moolenaar    endif
48360a795aaSBram Moolenaar    return ind
48460a795aaSBram Moolenaar  endif
48560a795aaSBram Moolenaar
48660a795aaSBram Moolenaar  " 3.4. Work on the MSL line. {{{2
48760a795aaSBram Moolenaar  " --------------------------
48860a795aaSBram Moolenaar
48960a795aaSBram Moolenaar  " Set up variables to use and search for MSL to the previous line.
49060a795aaSBram Moolenaar  let p_lnum = lnum
49160a795aaSBram Moolenaar  let lnum = s:GetMSL(lnum)
49260a795aaSBram Moolenaar
49360a795aaSBram Moolenaar  " If the previous line wasn't a MSL and is continuation return its indent.
49460a795aaSBram Moolenaar  " TODO: the || s:IsInString() thing worries me a bit.
49560a795aaSBram Moolenaar  if p_lnum != lnum
496*ec7944aaSBram Moolenaar    if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
49760a795aaSBram Moolenaar      return ind
49860a795aaSBram Moolenaar    endif
49960a795aaSBram Moolenaar  endif
50060a795aaSBram Moolenaar
50160a795aaSBram Moolenaar  " Set up more variables, now that we know we wasn't continuation bound.
50260a795aaSBram Moolenaar  let line = getline(lnum)
50360a795aaSBram Moolenaar  let msl_ind = indent(lnum)
50460a795aaSBram Moolenaar
50560a795aaSBram Moolenaar  " If the MSL line had an indenting keyword in it, add a level of indent.
50660a795aaSBram Moolenaar  " TODO: this does not take into account contrived things such as
50760a795aaSBram Moolenaar  " module Foo; class Bar; end
50860a795aaSBram Moolenaar  if s:Match(lnum, s:ruby_indent_keywords)
50960a795aaSBram Moolenaar    let ind = msl_ind + &sw
51060a795aaSBram Moolenaar    if s:Match(lnum, s:end_end_regex)
511071d4279SBram Moolenaar      let ind = ind - &sw
512071d4279SBram Moolenaar    endif
51360a795aaSBram Moolenaar    return ind
51460a795aaSBram Moolenaar  endif
51560a795aaSBram Moolenaar
516*ec7944aaSBram Moolenaar  " If the previous line ended with [*+/.,-=], but wasn't a block ending or a
517*ec7944aaSBram Moolenaar  " closing bracket, indent one extra level.
518*ec7944aaSBram Moolenaar  if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)')
51960a795aaSBram Moolenaar    if lnum == p_lnum
52060a795aaSBram Moolenaar      let ind = msl_ind + &sw
52160a795aaSBram Moolenaar    else
52260a795aaSBram Moolenaar      let ind = msl_ind
52360a795aaSBram Moolenaar    endif
524*ec7944aaSBram Moolenaar    return ind
52560a795aaSBram Moolenaar  endif
52660a795aaSBram Moolenaar
52760a795aaSBram Moolenaar  " }}}2
528071d4279SBram Moolenaar
529071d4279SBram Moolenaar  return ind
530071d4279SBram Moolenaarendfunction
531071d4279SBram Moolenaar
53260a795aaSBram Moolenaar" }}}1
53360a795aaSBram Moolenaar
53460a795aaSBram Moolenaarlet &cpo = s:cpo_save
53560a795aaSBram Moolenaarunlet s:cpo_save
5369964e468SBram Moolenaar
537*ec7944aaSBram Moolenaar" vim:set sw=2 sts=2 ts=8 et:
538