xref: /vim-8.2.3635/runtime/indent/ruby.vim (revision 6c391a74)
1071d4279SBram Moolenaar" Vim indent file
2071d4279SBram Moolenaar" Language:		Ruby
3d09091d4SBram Moolenaar" Maintainer:		Andrew Radev <[email protected]>
4d09091d4SBram Moolenaar" Previous Maintainer:	Nikolai Weibull <now at bitwi.se>
5ec7944aaSBram Moolenaar" URL:			https://github.com/vim-ruby/vim-ruby
6551dbcc9SBram Moolenaar" Release Coordinator:	Doug Kearns <[email protected]>
74d8f4761SBram Moolenaar" Last Change:		2021 Feb 03
860a795aaSBram Moolenaar
960a795aaSBram Moolenaar" 0. Initialization {{{1
1060a795aaSBram Moolenaar" =================
11071d4279SBram Moolenaar
12071d4279SBram Moolenaar" Only load this indent file when no other was loaded.
13071d4279SBram Moolenaarif exists("b:did_indent")
14071d4279SBram Moolenaar  finish
15071d4279SBram Moolenaarendif
16071d4279SBram Moolenaarlet b:did_indent = 1
17071d4279SBram Moolenaar
1889bcfda6SBram Moolenaarif !exists('g:ruby_indent_access_modifier_style')
1989bcfda6SBram Moolenaar  " Possible values: "normal", "indent", "outdent"
2089bcfda6SBram Moolenaar  let g:ruby_indent_access_modifier_style = 'normal'
2189bcfda6SBram Moolenaarendif
2289bcfda6SBram Moolenaar
23d09091d4SBram Moolenaarif !exists('g:ruby_indent_assignment_style')
24d09091d4SBram Moolenaar  " Possible values: "variable", "hanging"
25d09091d4SBram Moolenaar  let g:ruby_indent_assignment_style = 'hanging'
26d09091d4SBram Moolenaarendif
27d09091d4SBram Moolenaar
2889bcfda6SBram Moolenaarif !exists('g:ruby_indent_block_style')
2989bcfda6SBram Moolenaar  " Possible values: "expression", "do"
30942db23cSBram Moolenaar  let g:ruby_indent_block_style = 'do'
31942db23cSBram Moolenaarendif
32942db23cSBram Moolenaar
33942db23cSBram Moolenaarif !exists('g:ruby_indent_hanging_elements')
34942db23cSBram Moolenaar  " Non-zero means hanging indents are enabled, zero means disabled
35942db23cSBram Moolenaar  let g:ruby_indent_hanging_elements = 1
3689bcfda6SBram Moolenaarendif
3789bcfda6SBram Moolenaar
38551dbcc9SBram Moolenaarsetlocal nosmartindent
39551dbcc9SBram Moolenaar
4060a795aaSBram Moolenaar" Now, set up our indentation expression and keys that trigger it.
41ec7944aaSBram Moolenaarsetlocal indentexpr=GetRubyIndent(v:lnum)
4289bcfda6SBram Moolenaarsetlocal indentkeys=0{,0},0),0],!^F,o,O,e,:,.
43ec7944aaSBram Moolenaarsetlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue,==begin,==end
4489bcfda6SBram Moolenaarsetlocal indentkeys+==private,=protected,=public
45071d4279SBram Moolenaar
46071d4279SBram Moolenaar" Only define the function once.
47071d4279SBram Moolenaarif exists("*GetRubyIndent")
48071d4279SBram Moolenaar  finish
49071d4279SBram Moolenaarendif
50071d4279SBram Moolenaar
5160a795aaSBram Moolenaarlet s:cpo_save = &cpo
5260a795aaSBram Moolenaarset cpo&vim
5360a795aaSBram Moolenaar
5460a795aaSBram Moolenaar" 1. Variables {{{1
5560a795aaSBram Moolenaar" ============
5660a795aaSBram Moolenaar
57d09091d4SBram Moolenaar" Syntax group names that are strings.
5860a795aaSBram Moolenaarlet s:syng_string =
592ed639abSBram Moolenaar      \ ['String', 'Interpolation', 'InterpolationDelimiter', 'StringEscape']
6060a795aaSBram Moolenaar
61d09091d4SBram Moolenaar" Syntax group names that are strings or documentation.
62d09091d4SBram Moolenaarlet s:syng_stringdoc = s:syng_string + ['Documentation']
63d09091d4SBram Moolenaar
64d09091d4SBram Moolenaar" Syntax group names that are or delimit strings/symbols/regexes or are comments.
652ed639abSBram Moolenaarlet s:syng_strcom = s:syng_stringdoc + [
662ed639abSBram Moolenaar      \ 'Character',
672ed639abSBram Moolenaar      \ 'Comment',
682ed639abSBram Moolenaar      \ 'HeredocDelimiter',
692ed639abSBram Moolenaar      \ 'PercentRegexpDelimiter',
702ed639abSBram Moolenaar      \ 'PercentStringDelimiter',
712ed639abSBram Moolenaar      \ 'PercentSymbolDelimiter',
722ed639abSBram Moolenaar      \ 'Regexp',
732ed639abSBram Moolenaar      \ 'RegexpCharClass',
742ed639abSBram Moolenaar      \ 'RegexpDelimiter',
752ed639abSBram Moolenaar      \ 'RegexpEscape',
762ed639abSBram Moolenaar      \ 'StringDelimiter',
772ed639abSBram Moolenaar      \ 'Symbol',
782ed639abSBram Moolenaar      \ 'SymbolDelimiter',
792ed639abSBram Moolenaar      \ ]
8060a795aaSBram Moolenaar
8160a795aaSBram Moolenaar" Expression used to check whether we should skip a match with searchpair().
8260a795aaSBram Moolenaarlet s:skip_expr =
83d09091d4SBram Moolenaar      \ 'index(map('.string(s:syng_strcom).',"hlID(''ruby''.v:val)"), synID(line("."),col("."),1)) >= 0'
8460a795aaSBram Moolenaar
8560a795aaSBram Moolenaar" Regex used for words that, at the start of a line, add a level of indent.
8689bcfda6SBram Moolenaarlet s:ruby_indent_keywords =
8789bcfda6SBram Moolenaar      \ '^\s*\zs\<\%(module\|class\|if\|for' .
8889bcfda6SBram Moolenaar      \   '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure\|rescue' .
892ed639abSBram Moolenaar      \   '\|\%(\K\k*[!?]\?\s\+\)\=def\):\@!\>' .
90ec7944aaSBram Moolenaar      \ '\|\%([=,*/%+-]\|<<\|>>\|:\s\)\s*\zs' .
91ec7944aaSBram Moolenaar      \    '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>'
9260a795aaSBram Moolenaar
9360a795aaSBram Moolenaar" Regex used for words that, at the start of a line, remove a level of indent.
9460a795aaSBram Moolenaarlet s:ruby_deindent_keywords =
95ec7944aaSBram Moolenaar      \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|end\):\@!\>'
9660a795aaSBram Moolenaar
9760a795aaSBram Moolenaar" Regex that defines the start-match for the 'end' keyword.
9860a795aaSBram Moolenaar"let s:end_start_regex = '\%(^\|[^.]\)\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\|do\)\>'
9960a795aaSBram Moolenaar" TODO: the do here should be restricted somewhat (only at end of line)?
100ec7944aaSBram Moolenaarlet s:end_start_regex =
101ec7944aaSBram Moolenaar      \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
10289bcfda6SBram Moolenaar      \ '\<\%(module\|class\|if\|for\|while\|until\|case\|unless\|begin' .
1032ed639abSBram Moolenaar      \   '\|\%(\K\k*[!?]\?\s\+\)\=def\):\@!\>' .
104ec7944aaSBram Moolenaar      \ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>'
10560a795aaSBram Moolenaar
10660a795aaSBram Moolenaar" Regex that defines the middle-match for the 'end' keyword.
107ec7944aaSBram Moolenaarlet s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|elsif\):\@!\>'
10860a795aaSBram Moolenaar
10960a795aaSBram Moolenaar" Regex that defines the end-match for the 'end' keyword.
110ec7944aaSBram Moolenaarlet s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end:\@!\>'
11160a795aaSBram Moolenaar
11260a795aaSBram Moolenaar" Expression used for searchpair() call for finding match for 'end' keyword.
11360a795aaSBram Moolenaarlet s:end_skip_expr = s:skip_expr .
11460a795aaSBram Moolenaar      \ ' || (expand("<cword>") == "do"' .
115ec7944aaSBram Moolenaar      \ ' && getline(".") =~ "^\\s*\\<\\(while\\|until\\|for\\):\\@!\\>")'
11660a795aaSBram Moolenaar
11760a795aaSBram Moolenaar" Regex that defines continuation lines, not including (, {, or [.
1184575876dSBram Moolenaarlet s:non_bracket_continuation_regex =
1194575876dSBram Moolenaar      \ '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|:\@<![^[:alnum:]:][|&?]\|||\|&&\)\s*\%(#.*\)\=$'
12060a795aaSBram Moolenaar
12160a795aaSBram Moolenaar" Regex that defines continuation lines.
122ec7944aaSBram Moolenaarlet s:continuation_regex =
1234575876dSBram Moolenaar      \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|:\@<![^[:alnum:]:][|&?]\|||\|&&\)\s*\%(#.*\)\=$'
124ec7944aaSBram Moolenaar
12589bcfda6SBram Moolenaar" Regex that defines continuable keywords
12689bcfda6SBram Moolenaarlet s:continuable_regex =
12789bcfda6SBram Moolenaar      \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
12889bcfda6SBram Moolenaar      \ '\<\%(if\|for\|while\|until\|unless\):\@!\>'
12989bcfda6SBram Moolenaar
130ec7944aaSBram Moolenaar" Regex that defines bracket continuations
131ec7944aaSBram Moolenaarlet s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$'
132ec7944aaSBram Moolenaar
13389bcfda6SBram Moolenaar" Regex that defines dot continuations
13489bcfda6SBram Moolenaarlet s:dot_continuation_regex = '%\@<!\.\s*\%(#.*\)\=$'
13589bcfda6SBram Moolenaar
13689bcfda6SBram Moolenaar" Regex that defines backslash continuations
13789bcfda6SBram Moolenaarlet s:backslash_continuation_regex = '%\@<!\\\s*$'
13889bcfda6SBram Moolenaar
13989bcfda6SBram Moolenaar" Regex that defines end of bracket continuation followed by another continuation
14089bcfda6SBram Moolenaarlet s:bracket_switch_continuation_regex = '^\([^(]\+\zs).\+\)\+'.s:continuation_regex
14189bcfda6SBram Moolenaar
142ec7944aaSBram Moolenaar" Regex that defines the first part of a splat pattern
143ec7944aaSBram Moolenaarlet s:splat_regex = '[[,(]\s*\*\s*\%(#.*\)\=$'
14460a795aaSBram Moolenaar
14589bcfda6SBram Moolenaar" Regex that describes all indent access modifiers
14689bcfda6SBram Moolenaarlet s:access_modifier_regex = '\C^\s*\%(public\|protected\|private\)\s*\%(#.*\)\=$'
14789bcfda6SBram Moolenaar
14889bcfda6SBram Moolenaar" Regex that describes the indent access modifiers (excludes public)
14989bcfda6SBram Moolenaarlet s:indent_access_modifier_regex = '\C^\s*\%(protected\|private\)\s*\%(#.*\)\=$'
15089bcfda6SBram Moolenaar
15160a795aaSBram Moolenaar" Regex that defines blocks.
152ec7944aaSBram Moolenaar"
153ec7944aaSBram Moolenaar" Note that there's a slight problem with this regex and s:continuation_regex.
154ec7944aaSBram Moolenaar" Code like this will be matched by both:
155ec7944aaSBram Moolenaar"
156ec7944aaSBram Moolenaar"   method_call do |(a, b)|
157ec7944aaSBram Moolenaar"
158ec7944aaSBram Moolenaar" The reason is that the pipe matches a hanging "|" operator.
159ec7944aaSBram Moolenaar"
16060a795aaSBram Moolenaarlet s:block_regex =
16189bcfda6SBram Moolenaar      \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|[^|]*|\)\=\s*\%(#.*\)\=$'
162ec7944aaSBram Moolenaar
163ec7944aaSBram Moolenaarlet s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex
16460a795aaSBram Moolenaar
16589bcfda6SBram Moolenaar" Regex that describes a leading operator (only a method call's dot for now)
1662ed639abSBram Moolenaarlet s:leading_operator_regex = '^\s*\%(&\=\.\)'
16789bcfda6SBram Moolenaar
168d09091d4SBram Moolenaar" 2. GetRubyIndent Function {{{1
169d09091d4SBram Moolenaar" =========================
170d09091d4SBram Moolenaar
171d09091d4SBram Moolenaarfunction! GetRubyIndent(...) abort
172d09091d4SBram Moolenaar  " 2.1. Setup {{{2
173d09091d4SBram Moolenaar  " ----------
174d09091d4SBram Moolenaar
175d09091d4SBram Moolenaar  let indent_info = {}
176d09091d4SBram Moolenaar
177d09091d4SBram Moolenaar  " The value of a single shift-width
178d09091d4SBram Moolenaar  if exists('*shiftwidth')
179d09091d4SBram Moolenaar    let indent_info.sw = shiftwidth()
180d09091d4SBram Moolenaar  else
181d09091d4SBram Moolenaar    let indent_info.sw = &sw
182d09091d4SBram Moolenaar  endif
183d09091d4SBram Moolenaar
184d09091d4SBram Moolenaar  " For the current line, use the first argument if given, else v:lnum
185d09091d4SBram Moolenaar  let indent_info.clnum = a:0 ? a:1 : v:lnum
186d09091d4SBram Moolenaar  let indent_info.cline = getline(indent_info.clnum)
187d09091d4SBram Moolenaar
188d09091d4SBram Moolenaar  " Set up variables for restoring position in file.  Could use clnum here.
189d09091d4SBram Moolenaar  let indent_info.col = col('.')
190d09091d4SBram Moolenaar
191d09091d4SBram Moolenaar  " 2.2. Work on the current line {{{2
192d09091d4SBram Moolenaar  " -----------------------------
193d09091d4SBram Moolenaar  let indent_callback_names = [
194d09091d4SBram Moolenaar        \ 's:AccessModifier',
195d09091d4SBram Moolenaar        \ 's:ClosingBracketOnEmptyLine',
196d09091d4SBram Moolenaar        \ 's:BlockComment',
197d09091d4SBram Moolenaar        \ 's:DeindentingKeyword',
198d09091d4SBram Moolenaar        \ 's:MultilineStringOrLineComment',
199d09091d4SBram Moolenaar        \ 's:ClosingHeredocDelimiter',
200d09091d4SBram Moolenaar        \ 's:LeadingOperator',
201d09091d4SBram Moolenaar        \ ]
202d09091d4SBram Moolenaar
203d09091d4SBram Moolenaar  for callback_name in indent_callback_names
204d09091d4SBram Moolenaar"    Decho "Running: ".callback_name
205d09091d4SBram Moolenaar    let indent = call(function(callback_name), [indent_info])
206d09091d4SBram Moolenaar
207d09091d4SBram Moolenaar    if indent >= 0
208d09091d4SBram Moolenaar"      Decho "Match: ".callback_name." indent=".indent." info=".string(indent_info)
209d09091d4SBram Moolenaar      return indent
210d09091d4SBram Moolenaar    endif
211d09091d4SBram Moolenaar  endfor
212d09091d4SBram Moolenaar
213d09091d4SBram Moolenaar  " 2.3. Work on the previous line. {{{2
214d09091d4SBram Moolenaar  " -------------------------------
215d09091d4SBram Moolenaar
216d09091d4SBram Moolenaar  " Special case: we don't need the real s:PrevNonBlankNonString for an empty
217d09091d4SBram Moolenaar  " line inside a string. And that call can be quite expensive in that
218d09091d4SBram Moolenaar  " particular situation.
219d09091d4SBram Moolenaar  let indent_callback_names = [
220d09091d4SBram Moolenaar        \ 's:EmptyInsideString',
221d09091d4SBram Moolenaar        \ ]
222d09091d4SBram Moolenaar
223d09091d4SBram Moolenaar  for callback_name in indent_callback_names
224d09091d4SBram Moolenaar"    Decho "Running: ".callback_name
225d09091d4SBram Moolenaar    let indent = call(function(callback_name), [indent_info])
226d09091d4SBram Moolenaar
227d09091d4SBram Moolenaar    if indent >= 0
228d09091d4SBram Moolenaar"      Decho "Match: ".callback_name." indent=".indent." info=".string(indent_info)
229d09091d4SBram Moolenaar      return indent
230d09091d4SBram Moolenaar    endif
231d09091d4SBram Moolenaar  endfor
232d09091d4SBram Moolenaar
233d09091d4SBram Moolenaar  " Previous line number
234d09091d4SBram Moolenaar  let indent_info.plnum = s:PrevNonBlankNonString(indent_info.clnum - 1)
235d09091d4SBram Moolenaar  let indent_info.pline = getline(indent_info.plnum)
236d09091d4SBram Moolenaar
237d09091d4SBram Moolenaar  let indent_callback_names = [
238d09091d4SBram Moolenaar        \ 's:StartOfFile',
239d09091d4SBram Moolenaar        \ 's:AfterAccessModifier',
240d09091d4SBram Moolenaar        \ 's:ContinuedLine',
241d09091d4SBram Moolenaar        \ 's:AfterBlockOpening',
242d09091d4SBram Moolenaar        \ 's:AfterHangingSplat',
243d09091d4SBram Moolenaar        \ 's:AfterUnbalancedBracket',
244d09091d4SBram Moolenaar        \ 's:AfterLeadingOperator',
245d09091d4SBram Moolenaar        \ 's:AfterEndKeyword',
246d09091d4SBram Moolenaar        \ 's:AfterIndentKeyword',
247d09091d4SBram Moolenaar        \ ]
248d09091d4SBram Moolenaar
249d09091d4SBram Moolenaar  for callback_name in indent_callback_names
250d09091d4SBram Moolenaar"    Decho "Running: ".callback_name
251d09091d4SBram Moolenaar    let indent = call(function(callback_name), [indent_info])
252d09091d4SBram Moolenaar
253d09091d4SBram Moolenaar    if indent >= 0
254d09091d4SBram Moolenaar"      Decho "Match: ".callback_name." indent=".indent." info=".string(indent_info)
255d09091d4SBram Moolenaar      return indent
256d09091d4SBram Moolenaar    endif
257d09091d4SBram Moolenaar  endfor
258d09091d4SBram Moolenaar
259d09091d4SBram Moolenaar  " 2.4. Work on the MSL line. {{{2
260d09091d4SBram Moolenaar  " --------------------------
261d09091d4SBram Moolenaar  let indent_callback_names = [
262d09091d4SBram Moolenaar        \ 's:PreviousNotMSL',
263d09091d4SBram Moolenaar        \ 's:IndentingKeywordInMSL',
264d09091d4SBram Moolenaar        \ 's:ContinuedHangingOperator',
265d09091d4SBram Moolenaar        \ ]
266d09091d4SBram Moolenaar
267d09091d4SBram Moolenaar  " Most Significant line based on the previous one -- in case it's a
268*6c391a74SBram Moolenaar  " continuation of something above
269d09091d4SBram Moolenaar  let indent_info.plnum_msl = s:GetMSL(indent_info.plnum)
270d09091d4SBram Moolenaar
271d09091d4SBram Moolenaar  for callback_name in indent_callback_names
272d09091d4SBram Moolenaar"    Decho "Running: ".callback_name
273d09091d4SBram Moolenaar    let indent = call(function(callback_name), [indent_info])
274d09091d4SBram Moolenaar
275d09091d4SBram Moolenaar    if indent >= 0
276d09091d4SBram Moolenaar"      Decho "Match: ".callback_name." indent=".indent." info=".string(indent_info)
277d09091d4SBram Moolenaar      return indent
278d09091d4SBram Moolenaar    endif
279d09091d4SBram Moolenaar  endfor
280d09091d4SBram Moolenaar
281d09091d4SBram Moolenaar  " }}}2
282d09091d4SBram Moolenaar
283d09091d4SBram Moolenaar  " By default, just return the previous line's indent
284d09091d4SBram Moolenaar"  Decho "Default case matched"
285d09091d4SBram Moolenaar  return indent(indent_info.plnum)
286d09091d4SBram Moolenaarendfunction
287d09091d4SBram Moolenaar
288d09091d4SBram Moolenaar" 3. Indenting Logic Callbacks {{{1
289d09091d4SBram Moolenaar" ============================
290d09091d4SBram Moolenaar
291d09091d4SBram Moolenaarfunction! s:AccessModifier(cline_info) abort
292d09091d4SBram Moolenaar  let info = a:cline_info
293d09091d4SBram Moolenaar
294d09091d4SBram Moolenaar  " If this line is an access modifier keyword, align according to the closest
295d09091d4SBram Moolenaar  " class declaration.
296d09091d4SBram Moolenaar  if g:ruby_indent_access_modifier_style == 'indent'
297d09091d4SBram Moolenaar    if s:Match(info.clnum, s:access_modifier_regex)
298d09091d4SBram Moolenaar      let class_lnum = s:FindContainingClass()
299d09091d4SBram Moolenaar      if class_lnum > 0
300d09091d4SBram Moolenaar        return indent(class_lnum) + info.sw
301d09091d4SBram Moolenaar      endif
302d09091d4SBram Moolenaar    endif
303d09091d4SBram Moolenaar  elseif g:ruby_indent_access_modifier_style == 'outdent'
304d09091d4SBram Moolenaar    if s:Match(info.clnum, s:access_modifier_regex)
305d09091d4SBram Moolenaar      let class_lnum = s:FindContainingClass()
306d09091d4SBram Moolenaar      if class_lnum > 0
307d09091d4SBram Moolenaar        return indent(class_lnum)
308d09091d4SBram Moolenaar      endif
309d09091d4SBram Moolenaar    endif
310d09091d4SBram Moolenaar  endif
311d09091d4SBram Moolenaar
312d09091d4SBram Moolenaar  return -1
313d09091d4SBram Moolenaarendfunction
314d09091d4SBram Moolenaar
315d09091d4SBram Moolenaarfunction! s:ClosingBracketOnEmptyLine(cline_info) abort
316d09091d4SBram Moolenaar  let info = a:cline_info
317d09091d4SBram Moolenaar
318d09091d4SBram Moolenaar  " If we got a closing bracket on an empty line, find its match and indent
319d09091d4SBram Moolenaar  " according to it.  For parentheses we indent to its column - 1, for the
320d09091d4SBram Moolenaar  " others we indent to the containing line's MSL's level.  Return -1 if fail.
321d09091d4SBram Moolenaar  let col = matchend(info.cline, '^\s*[]})]')
322d09091d4SBram Moolenaar
323d09091d4SBram Moolenaar  if col > 0 && !s:IsInStringOrComment(info.clnum, col)
324d09091d4SBram Moolenaar    call cursor(info.clnum, col)
325d09091d4SBram Moolenaar    let closing_bracket = info.cline[col - 1]
326d09091d4SBram Moolenaar    let bracket_pair = strpart('(){}[]', stridx(')}]', closing_bracket) * 2, 2)
327d09091d4SBram Moolenaar
328d09091d4SBram Moolenaar    if searchpair(escape(bracket_pair[0], '\['), '', bracket_pair[1], 'bW', s:skip_expr) > 0
329d09091d4SBram Moolenaar      if closing_bracket == ')' && col('.') != col('$') - 1
330942db23cSBram Moolenaar        if g:ruby_indent_hanging_elements
331d09091d4SBram Moolenaar          let ind = virtcol('.') - 1
332942db23cSBram Moolenaar        else
333942db23cSBram Moolenaar          let ind = indent(line('.'))
334942db23cSBram Moolenaar        end
335d09091d4SBram Moolenaar      elseif g:ruby_indent_block_style == 'do'
336d09091d4SBram Moolenaar        let ind = indent(line('.'))
337d09091d4SBram Moolenaar      else " g:ruby_indent_block_style == 'expression'
338d09091d4SBram Moolenaar        let ind = indent(s:GetMSL(line('.')))
339d09091d4SBram Moolenaar      endif
340d09091d4SBram Moolenaar    endif
341d09091d4SBram Moolenaar
342d09091d4SBram Moolenaar    return ind
343d09091d4SBram Moolenaar  endif
344d09091d4SBram Moolenaar
345d09091d4SBram Moolenaar  return -1
346d09091d4SBram Moolenaarendfunction
347d09091d4SBram Moolenaar
348d09091d4SBram Moolenaarfunction! s:BlockComment(cline_info) abort
349d09091d4SBram Moolenaar  " If we have a =begin or =end set indent to first column.
350d09091d4SBram Moolenaar  if match(a:cline_info.cline, '^\s*\%(=begin\|=end\)$') != -1
351d09091d4SBram Moolenaar    return 0
352d09091d4SBram Moolenaar  endif
353d09091d4SBram Moolenaar  return -1
354d09091d4SBram Moolenaarendfunction
355d09091d4SBram Moolenaar
356d09091d4SBram Moolenaarfunction! s:DeindentingKeyword(cline_info) abort
357d09091d4SBram Moolenaar  let info = a:cline_info
358d09091d4SBram Moolenaar
359d09091d4SBram Moolenaar  " If we have a deindenting keyword, find its match and indent to its level.
360d09091d4SBram Moolenaar  " TODO: this is messy
361d09091d4SBram Moolenaar  if s:Match(info.clnum, s:ruby_deindent_keywords)
362d09091d4SBram Moolenaar    call cursor(info.clnum, 1)
363d09091d4SBram Moolenaar
364d09091d4SBram Moolenaar    if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
365d09091d4SBram Moolenaar          \ s:end_skip_expr) > 0
366d09091d4SBram Moolenaar      let msl  = s:GetMSL(line('.'))
367d09091d4SBram Moolenaar      let line = getline(line('.'))
368d09091d4SBram Moolenaar
369d09091d4SBram Moolenaar      if s:IsAssignment(line, col('.')) &&
370d09091d4SBram Moolenaar            \ strpart(line, col('.') - 1, 2) !~ 'do'
371d09091d4SBram Moolenaar        " assignment to case/begin/etc, on the same line
372d09091d4SBram Moolenaar        if g:ruby_indent_assignment_style == 'hanging'
373d09091d4SBram Moolenaar          " hanging indent
374d09091d4SBram Moolenaar          let ind = virtcol('.') - 1
375d09091d4SBram Moolenaar        else
376d09091d4SBram Moolenaar          " align with variable
377d09091d4SBram Moolenaar          let ind = indent(line('.'))
378d09091d4SBram Moolenaar        endif
379d09091d4SBram Moolenaar      elseif g:ruby_indent_block_style == 'do'
380d09091d4SBram Moolenaar        " align to line of the "do", not to the MSL
381d09091d4SBram Moolenaar        let ind = indent(line('.'))
382d09091d4SBram Moolenaar      elseif getline(msl) =~ '=\s*\(#.*\)\=$'
383d09091d4SBram Moolenaar        " in the case of assignment to the MSL, align to the starting line,
384d09091d4SBram Moolenaar        " not to the MSL
385d09091d4SBram Moolenaar        let ind = indent(line('.'))
386d09091d4SBram Moolenaar      else
387d09091d4SBram Moolenaar        " align to the MSL
388d09091d4SBram Moolenaar        let ind = indent(msl)
389d09091d4SBram Moolenaar      endif
390d09091d4SBram Moolenaar    endif
391d09091d4SBram Moolenaar    return ind
392d09091d4SBram Moolenaar  endif
393d09091d4SBram Moolenaar
394d09091d4SBram Moolenaar  return -1
395d09091d4SBram Moolenaarendfunction
396d09091d4SBram Moolenaar
397d09091d4SBram Moolenaarfunction! s:MultilineStringOrLineComment(cline_info) abort
398d09091d4SBram Moolenaar  let info = a:cline_info
399d09091d4SBram Moolenaar
400d09091d4SBram Moolenaar  " If we are in a multi-line string or line-comment, don't do anything to it.
401d09091d4SBram Moolenaar  if s:IsInStringOrDocumentation(info.clnum, matchend(info.cline, '^\s*') + 1)
402d09091d4SBram Moolenaar    return indent(info.clnum)
403d09091d4SBram Moolenaar  endif
404d09091d4SBram Moolenaar  return -1
405d09091d4SBram Moolenaarendfunction
406d09091d4SBram Moolenaar
407d09091d4SBram Moolenaarfunction! s:ClosingHeredocDelimiter(cline_info) abort
408d09091d4SBram Moolenaar  let info = a:cline_info
409d09091d4SBram Moolenaar
410d09091d4SBram Moolenaar  " If we are at the closing delimiter of a "<<" heredoc-style string, set the
411d09091d4SBram Moolenaar  " indent to 0.
412d09091d4SBram Moolenaar  if info.cline =~ '^\k\+\s*$'
413d09091d4SBram Moolenaar        \ && s:IsInStringDelimiter(info.clnum, 1)
414d09091d4SBram Moolenaar        \ && search('\V<<'.info.cline, 'nbW') > 0
415d09091d4SBram Moolenaar    return 0
416d09091d4SBram Moolenaar  endif
417d09091d4SBram Moolenaar
418d09091d4SBram Moolenaar  return -1
419d09091d4SBram Moolenaarendfunction
420d09091d4SBram Moolenaar
421d09091d4SBram Moolenaarfunction! s:LeadingOperator(cline_info) abort
422d09091d4SBram Moolenaar  " If the current line starts with a leading operator, add a level of indent.
423d09091d4SBram Moolenaar  if s:Match(a:cline_info.clnum, s:leading_operator_regex)
424d09091d4SBram Moolenaar    return indent(s:GetMSL(a:cline_info.clnum)) + a:cline_info.sw
425d09091d4SBram Moolenaar  endif
426d09091d4SBram Moolenaar  return -1
427d09091d4SBram Moolenaarendfunction
428d09091d4SBram Moolenaar
429d09091d4SBram Moolenaarfunction! s:EmptyInsideString(pline_info) abort
430d09091d4SBram Moolenaar  " If the line is empty and inside a string (the previous line is a string,
431d09091d4SBram Moolenaar  " too), use the previous line's indent
432d09091d4SBram Moolenaar  let info = a:pline_info
433d09091d4SBram Moolenaar
434d09091d4SBram Moolenaar  let plnum = prevnonblank(info.clnum - 1)
435d09091d4SBram Moolenaar  let pline = getline(plnum)
436d09091d4SBram Moolenaar
437d09091d4SBram Moolenaar  if info.cline =~ '^\s*$'
438d09091d4SBram Moolenaar        \ && s:IsInStringOrComment(plnum, 1)
439d09091d4SBram Moolenaar        \ && s:IsInStringOrComment(plnum, strlen(pline))
440d09091d4SBram Moolenaar    return indent(plnum)
441d09091d4SBram Moolenaar  endif
442d09091d4SBram Moolenaar  return -1
443d09091d4SBram Moolenaarendfunction
444d09091d4SBram Moolenaar
445d09091d4SBram Moolenaarfunction! s:StartOfFile(pline_info) abort
446d09091d4SBram Moolenaar  " At the start of the file use zero indent.
447d09091d4SBram Moolenaar  if a:pline_info.plnum == 0
448d09091d4SBram Moolenaar    return 0
449d09091d4SBram Moolenaar  endif
450d09091d4SBram Moolenaar  return -1
451d09091d4SBram Moolenaarendfunction
452d09091d4SBram Moolenaar
453d09091d4SBram Moolenaarfunction! s:AfterAccessModifier(pline_info) abort
454d09091d4SBram Moolenaar  let info = a:pline_info
455d09091d4SBram Moolenaar
456d09091d4SBram Moolenaar  if g:ruby_indent_access_modifier_style == 'indent'
457d09091d4SBram Moolenaar    " If the previous line was a private/protected keyword, add a
458d09091d4SBram Moolenaar    " level of indent.
459d09091d4SBram Moolenaar    if s:Match(info.plnum, s:indent_access_modifier_regex)
460d09091d4SBram Moolenaar      return indent(info.plnum) + info.sw
461d09091d4SBram Moolenaar    endif
462d09091d4SBram Moolenaar  elseif g:ruby_indent_access_modifier_style == 'outdent'
463d09091d4SBram Moolenaar    " If the previous line was a private/protected/public keyword, add
464d09091d4SBram Moolenaar    " a level of indent, since the keyword has been out-dented.
465d09091d4SBram Moolenaar    if s:Match(info.plnum, s:access_modifier_regex)
466d09091d4SBram Moolenaar      return indent(info.plnum) + info.sw
467d09091d4SBram Moolenaar    endif
468d09091d4SBram Moolenaar  endif
469d09091d4SBram Moolenaar  return -1
470d09091d4SBram Moolenaarendfunction
471d09091d4SBram Moolenaar
472d09091d4SBram Moolenaar" Example:
473d09091d4SBram Moolenaar"
474d09091d4SBram Moolenaar"   if foo || bar ||
475d09091d4SBram Moolenaar"       baz || bing
476d09091d4SBram Moolenaar"     puts "foo"
477d09091d4SBram Moolenaar"   end
478d09091d4SBram Moolenaar"
479d09091d4SBram Moolenaarfunction! s:ContinuedLine(pline_info) abort
480d09091d4SBram Moolenaar  let info = a:pline_info
481d09091d4SBram Moolenaar
482d09091d4SBram Moolenaar  let col = s:Match(info.plnum, s:ruby_indent_keywords)
483d09091d4SBram Moolenaar  if s:Match(info.plnum, s:continuable_regex) &&
484d09091d4SBram Moolenaar        \ s:Match(info.plnum, s:continuation_regex)
485d09091d4SBram Moolenaar    if col > 0 && s:IsAssignment(info.pline, col)
486d09091d4SBram Moolenaar      if g:ruby_indent_assignment_style == 'hanging'
487d09091d4SBram Moolenaar        " hanging indent
488d09091d4SBram Moolenaar        let ind = col - 1
489d09091d4SBram Moolenaar      else
490d09091d4SBram Moolenaar        " align with variable
491d09091d4SBram Moolenaar        let ind = indent(info.plnum)
492d09091d4SBram Moolenaar      endif
493d09091d4SBram Moolenaar    else
494d09091d4SBram Moolenaar      let ind = indent(s:GetMSL(info.plnum))
495d09091d4SBram Moolenaar    endif
496d09091d4SBram Moolenaar    return ind + info.sw + info.sw
497d09091d4SBram Moolenaar  endif
498d09091d4SBram Moolenaar  return -1
499d09091d4SBram Moolenaarendfunction
500d09091d4SBram Moolenaar
501d09091d4SBram Moolenaarfunction! s:AfterBlockOpening(pline_info) abort
502d09091d4SBram Moolenaar  let info = a:pline_info
503d09091d4SBram Moolenaar
504d09091d4SBram Moolenaar  " If the previous line ended with a block opening, add a level of indent.
505d09091d4SBram Moolenaar  if s:Match(info.plnum, s:block_regex)
506d09091d4SBram Moolenaar    if g:ruby_indent_block_style == 'do'
507d09091d4SBram Moolenaar      " don't align to the msl, align to the "do"
508d09091d4SBram Moolenaar      let ind = indent(info.plnum) + info.sw
509d09091d4SBram Moolenaar    else
510d09091d4SBram Moolenaar      let plnum_msl = s:GetMSL(info.plnum)
511d09091d4SBram Moolenaar
512d09091d4SBram Moolenaar      if getline(plnum_msl) =~ '=\s*\(#.*\)\=$'
513d09091d4SBram Moolenaar        " in the case of assignment to the msl, align to the starting line,
514d09091d4SBram Moolenaar        " not to the msl
515d09091d4SBram Moolenaar        let ind = indent(info.plnum) + info.sw
516d09091d4SBram Moolenaar      else
517d09091d4SBram Moolenaar        let ind = indent(plnum_msl) + info.sw
518d09091d4SBram Moolenaar      endif
519d09091d4SBram Moolenaar    endif
520d09091d4SBram Moolenaar
521d09091d4SBram Moolenaar    return ind
522d09091d4SBram Moolenaar  endif
523d09091d4SBram Moolenaar
524d09091d4SBram Moolenaar  return -1
525d09091d4SBram Moolenaarendfunction
526d09091d4SBram Moolenaar
527d09091d4SBram Moolenaarfunction! s:AfterLeadingOperator(pline_info) abort
528d09091d4SBram Moolenaar  " If the previous line started with a leading operator, use its MSL's level
529d09091d4SBram Moolenaar  " of indent
530d09091d4SBram Moolenaar  if s:Match(a:pline_info.plnum, s:leading_operator_regex)
531d09091d4SBram Moolenaar    return indent(s:GetMSL(a:pline_info.plnum))
532d09091d4SBram Moolenaar  endif
533d09091d4SBram Moolenaar  return -1
534d09091d4SBram Moolenaarendfunction
535d09091d4SBram Moolenaar
536d09091d4SBram Moolenaarfunction! s:AfterHangingSplat(pline_info) abort
537d09091d4SBram Moolenaar  let info = a:pline_info
538d09091d4SBram Moolenaar
539d09091d4SBram Moolenaar  " If the previous line ended with the "*" of a splat, add a level of indent
540d09091d4SBram Moolenaar  if info.pline =~ s:splat_regex
541d09091d4SBram Moolenaar    return indent(info.plnum) + info.sw
542d09091d4SBram Moolenaar  endif
543d09091d4SBram Moolenaar  return -1
544d09091d4SBram Moolenaarendfunction
545d09091d4SBram Moolenaar
546d09091d4SBram Moolenaarfunction! s:AfterUnbalancedBracket(pline_info) abort
547d09091d4SBram Moolenaar  let info = a:pline_info
548d09091d4SBram Moolenaar
549d09091d4SBram Moolenaar  " If the previous line contained unclosed opening brackets and we are still
550d09091d4SBram Moolenaar  " in them, find the rightmost one and add indent depending on the bracket
551d09091d4SBram Moolenaar  " type.
552d09091d4SBram Moolenaar  "
553d09091d4SBram Moolenaar  " If it contained hanging closing brackets, find the rightmost one, find its
554d09091d4SBram Moolenaar  " match and indent according to that.
555d09091d4SBram Moolenaar  if info.pline =~ '[[({]' || info.pline =~ '[])}]\s*\%(#.*\)\=$'
556d09091d4SBram Moolenaar    let [opening, closing] = s:ExtraBrackets(info.plnum)
557d09091d4SBram Moolenaar
558d09091d4SBram Moolenaar    if opening.pos != -1
559942db23cSBram Moolenaar      if !g:ruby_indent_hanging_elements
560942db23cSBram Moolenaar        return indent(info.plnum) + info.sw
561942db23cSBram Moolenaar      elseif opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
562d09091d4SBram Moolenaar        if col('.') + 1 == col('$')
563d09091d4SBram Moolenaar          return indent(info.plnum) + info.sw
564d09091d4SBram Moolenaar        else
565d09091d4SBram Moolenaar          return virtcol('.')
566d09091d4SBram Moolenaar        endif
567d09091d4SBram Moolenaar      else
568d09091d4SBram Moolenaar        let nonspace = matchend(info.pline, '\S', opening.pos + 1) - 1
569d09091d4SBram Moolenaar        return nonspace > 0 ? nonspace : indent(info.plnum) + info.sw
570d09091d4SBram Moolenaar      endif
571d09091d4SBram Moolenaar    elseif closing.pos != -1
572d09091d4SBram Moolenaar      call cursor(info.plnum, closing.pos + 1)
573d09091d4SBram Moolenaar      normal! %
574d09091d4SBram Moolenaar
575d09091d4SBram Moolenaar      if s:Match(line('.'), s:ruby_indent_keywords)
576d09091d4SBram Moolenaar        return indent('.') + info.sw
577d09091d4SBram Moolenaar      else
578d09091d4SBram Moolenaar        return indent(s:GetMSL(line('.')))
579d09091d4SBram Moolenaar      endif
580d09091d4SBram Moolenaar    else
581d09091d4SBram Moolenaar      call cursor(info.clnum, info.col)
582d09091d4SBram Moolenaar    end
583d09091d4SBram Moolenaar  endif
584d09091d4SBram Moolenaar
585d09091d4SBram Moolenaar  return -1
586d09091d4SBram Moolenaarendfunction
587d09091d4SBram Moolenaar
588d09091d4SBram Moolenaarfunction! s:AfterEndKeyword(pline_info) abort
589d09091d4SBram Moolenaar  let info = a:pline_info
590d09091d4SBram Moolenaar  " If the previous line ended with an "end", match that "end"s beginning's
591d09091d4SBram Moolenaar  " indent.
592d09091d4SBram Moolenaar  let col = s:Match(info.plnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
593d09091d4SBram Moolenaar  if col > 0
594d09091d4SBram Moolenaar    call cursor(info.plnum, col)
595d09091d4SBram Moolenaar    if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
596d09091d4SBram Moolenaar          \ s:end_skip_expr) > 0
597d09091d4SBram Moolenaar      let n = line('.')
598d09091d4SBram Moolenaar      let ind = indent('.')
599d09091d4SBram Moolenaar      let msl = s:GetMSL(n)
600d09091d4SBram Moolenaar      if msl != n
601d09091d4SBram Moolenaar        let ind = indent(msl)
602d09091d4SBram Moolenaar      end
603d09091d4SBram Moolenaar      return ind
604d09091d4SBram Moolenaar    endif
605d09091d4SBram Moolenaar  end
606d09091d4SBram Moolenaar  return -1
607d09091d4SBram Moolenaarendfunction
608d09091d4SBram Moolenaar
609d09091d4SBram Moolenaarfunction! s:AfterIndentKeyword(pline_info) abort
610d09091d4SBram Moolenaar  let info = a:pline_info
611d09091d4SBram Moolenaar  let col = s:Match(info.plnum, s:ruby_indent_keywords)
612d09091d4SBram Moolenaar
613d09091d4SBram Moolenaar  if col > 0
614d09091d4SBram Moolenaar    call cursor(info.plnum, col)
615d09091d4SBram Moolenaar    let ind = virtcol('.') - 1 + info.sw
616d09091d4SBram Moolenaar    " TODO: make this better (we need to count them) (or, if a searchpair
617d09091d4SBram Moolenaar    " fails, we know that something is lacking an end and thus we indent a
618d09091d4SBram Moolenaar    " level
619d09091d4SBram Moolenaar    if s:Match(info.plnum, s:end_end_regex)
620d09091d4SBram Moolenaar      let ind = indent('.')
621d09091d4SBram Moolenaar    elseif s:IsAssignment(info.pline, col)
622d09091d4SBram Moolenaar      if g:ruby_indent_assignment_style == 'hanging'
623d09091d4SBram Moolenaar        " hanging indent
624d09091d4SBram Moolenaar        let ind = col + info.sw - 1
625d09091d4SBram Moolenaar      else
626d09091d4SBram Moolenaar        " align with variable
627d09091d4SBram Moolenaar        let ind = indent(info.plnum) + info.sw
628d09091d4SBram Moolenaar      endif
629d09091d4SBram Moolenaar    endif
630d09091d4SBram Moolenaar    return ind
631d09091d4SBram Moolenaar  endif
632d09091d4SBram Moolenaar
633d09091d4SBram Moolenaar  return -1
634d09091d4SBram Moolenaarendfunction
635d09091d4SBram Moolenaar
636d09091d4SBram Moolenaarfunction! s:PreviousNotMSL(msl_info) abort
637d09091d4SBram Moolenaar  let info = a:msl_info
638d09091d4SBram Moolenaar
639d09091d4SBram Moolenaar  " If the previous line wasn't a MSL
640d09091d4SBram Moolenaar  if info.plnum != info.plnum_msl
641d09091d4SBram Moolenaar    " If previous line ends bracket and begins non-bracket continuation decrease indent by 1.
642d09091d4SBram Moolenaar    if s:Match(info.plnum, s:bracket_switch_continuation_regex)
643d09091d4SBram Moolenaar      " TODO (2016-10-07) Wrong/unused? How could it be "1"?
644d09091d4SBram Moolenaar      return indent(info.plnum) - 1
645d09091d4SBram Moolenaar      " If previous line is a continuation return its indent.
646942db23cSBram Moolenaar    elseif s:Match(info.plnum, s:non_bracket_continuation_regex)
647d09091d4SBram Moolenaar      return indent(info.plnum)
648d09091d4SBram Moolenaar    endif
649d09091d4SBram Moolenaar  endif
650d09091d4SBram Moolenaar
651d09091d4SBram Moolenaar  return -1
652d09091d4SBram Moolenaarendfunction
653d09091d4SBram Moolenaar
654d09091d4SBram Moolenaarfunction! s:IndentingKeywordInMSL(msl_info) abort
655d09091d4SBram Moolenaar  let info = a:msl_info
656d09091d4SBram Moolenaar  " If the MSL line had an indenting keyword in it, add a level of indent.
657d09091d4SBram Moolenaar  " TODO: this does not take into account contrived things such as
658d09091d4SBram Moolenaar  " module Foo; class Bar; end
659d09091d4SBram Moolenaar  let col = s:Match(info.plnum_msl, s:ruby_indent_keywords)
660d09091d4SBram Moolenaar  if col > 0
661d09091d4SBram Moolenaar    let ind = indent(info.plnum_msl) + info.sw
662d09091d4SBram Moolenaar    if s:Match(info.plnum_msl, s:end_end_regex)
663d09091d4SBram Moolenaar      let ind = ind - info.sw
664d09091d4SBram Moolenaar    elseif s:IsAssignment(getline(info.plnum_msl), col)
665d09091d4SBram Moolenaar      if g:ruby_indent_assignment_style == 'hanging'
666d09091d4SBram Moolenaar        " hanging indent
667d09091d4SBram Moolenaar        let ind = col + info.sw - 1
668d09091d4SBram Moolenaar      else
669d09091d4SBram Moolenaar        " align with variable
670d09091d4SBram Moolenaar        let ind = indent(info.plnum_msl) + info.sw
671d09091d4SBram Moolenaar      endif
672d09091d4SBram Moolenaar    endif
673d09091d4SBram Moolenaar    return ind
674d09091d4SBram Moolenaar  endif
675d09091d4SBram Moolenaar  return -1
676d09091d4SBram Moolenaarendfunction
677d09091d4SBram Moolenaar
678d09091d4SBram Moolenaarfunction! s:ContinuedHangingOperator(msl_info) abort
679d09091d4SBram Moolenaar  let info = a:msl_info
680d09091d4SBram Moolenaar
681d09091d4SBram Moolenaar  " If the previous line ended with [*+/.,-=], but wasn't a block ending or a
682d09091d4SBram Moolenaar  " closing bracket, indent one extra level.
683d09091d4SBram Moolenaar  if s:Match(info.plnum_msl, s:non_bracket_continuation_regex) && !s:Match(info.plnum_msl, '^\s*\([\])}]\|end\)')
684d09091d4SBram Moolenaar    if info.plnum_msl == info.plnum
685d09091d4SBram Moolenaar      let ind = indent(info.plnum_msl) + info.sw
686d09091d4SBram Moolenaar    else
687d09091d4SBram Moolenaar      let ind = indent(info.plnum_msl)
688d09091d4SBram Moolenaar    endif
689d09091d4SBram Moolenaar    return ind
690d09091d4SBram Moolenaar  endif
691d09091d4SBram Moolenaar
692d09091d4SBram Moolenaar  return -1
693d09091d4SBram Moolenaarendfunction
694d09091d4SBram Moolenaar
695d09091d4SBram Moolenaar" 4. Auxiliary Functions {{{1
69660a795aaSBram Moolenaar" ======================
69760a795aaSBram Moolenaar
698d09091d4SBram Moolenaarfunction! s:IsInRubyGroup(groups, lnum, col) abort
699d09091d4SBram Moolenaar  let ids = map(copy(a:groups), 'hlID("ruby".v:val)')
700d09091d4SBram Moolenaar  return index(ids, synID(a:lnum, a:col, 1)) >= 0
701d09091d4SBram Moolenaarendfunction
702d09091d4SBram Moolenaar
70360a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string, comment, or is ascii.
704d09091d4SBram Moolenaarfunction! s:IsInStringOrComment(lnum, col) abort
705d09091d4SBram Moolenaar  return s:IsInRubyGroup(s:syng_strcom, a:lnum, a:col)
70660a795aaSBram Moolenaarendfunction
70760a795aaSBram Moolenaar
70860a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string.
709d09091d4SBram Moolenaarfunction! s:IsInString(lnum, col) abort
710d09091d4SBram Moolenaar  return s:IsInRubyGroup(s:syng_string, a:lnum, a:col)
71160a795aaSBram Moolenaarendfunction
71260a795aaSBram Moolenaar
71360a795aaSBram Moolenaar" Check if the character at lnum:col is inside a string or documentation.
714d09091d4SBram Moolenaarfunction! s:IsInStringOrDocumentation(lnum, col) abort
715d09091d4SBram Moolenaar  return s:IsInRubyGroup(s:syng_stringdoc, a:lnum, a:col)
71660a795aaSBram Moolenaarendfunction
71760a795aaSBram Moolenaar
718ec7944aaSBram Moolenaar" Check if the character at lnum:col is inside a string delimiter
719d09091d4SBram Moolenaarfunction! s:IsInStringDelimiter(lnum, col) abort
7202ed639abSBram Moolenaar  return s:IsInRubyGroup(
7212ed639abSBram Moolenaar        \ ['HeredocDelimiter', 'PercentStringDelimiter', 'StringDelimiter'],
7222ed639abSBram Moolenaar        \ a:lnum, a:col
7232ed639abSBram Moolenaar        \ )
724d09091d4SBram Moolenaarendfunction
725d09091d4SBram Moolenaar
726d09091d4SBram Moolenaarfunction! s:IsAssignment(str, pos) abort
727d09091d4SBram Moolenaar  return strpart(a:str, 0, a:pos - 1) =~ '=\s*$'
728ec7944aaSBram Moolenaarendfunction
729ec7944aaSBram Moolenaar
73060a795aaSBram Moolenaar" Find line above 'lnum' that isn't empty, in a comment, or in a string.
731d09091d4SBram Moolenaarfunction! s:PrevNonBlankNonString(lnum) abort
73260a795aaSBram Moolenaar  let in_block = 0
73360a795aaSBram Moolenaar  let lnum = prevnonblank(a:lnum)
73460a795aaSBram Moolenaar  while lnum > 0
73560a795aaSBram Moolenaar    " Go in and out of blocks comments as necessary.
73660a795aaSBram Moolenaar    " If the line isn't empty (with opt. comment) or in a string, end search.
73760a795aaSBram Moolenaar    let line = getline(lnum)
738ec7944aaSBram Moolenaar    if line =~ '^=begin'
73960a795aaSBram Moolenaar      if in_block
74060a795aaSBram Moolenaar        let in_block = 0
74160a795aaSBram Moolenaar      else
74260a795aaSBram Moolenaar        break
74360a795aaSBram Moolenaar      endif
744ec7944aaSBram Moolenaar    elseif !in_block && line =~ '^=end'
74560a795aaSBram Moolenaar      let in_block = 1
74660a795aaSBram Moolenaar    elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
74760a795aaSBram Moolenaar          \ && s:IsInStringOrComment(lnum, strlen(line)))
74860a795aaSBram Moolenaar      break
74960a795aaSBram Moolenaar    endif
75060a795aaSBram Moolenaar    let lnum = prevnonblank(lnum - 1)
75160a795aaSBram Moolenaar  endwhile
75260a795aaSBram Moolenaar  return lnum
75360a795aaSBram Moolenaarendfunction
75460a795aaSBram Moolenaar
75560a795aaSBram Moolenaar" Find line above 'lnum' that started the continuation 'lnum' may be part of.
756d09091d4SBram Moolenaarfunction! s:GetMSL(lnum) abort
75760a795aaSBram Moolenaar  " Start on the line we're at and use its indent.
75860a795aaSBram Moolenaar  let msl = a:lnum
75960a795aaSBram Moolenaar  let lnum = s:PrevNonBlankNonString(a:lnum - 1)
76060a795aaSBram Moolenaar  while lnum > 0
76160a795aaSBram Moolenaar    " If we have a continuation line, or we're in a string, use line as MSL.
76260a795aaSBram Moolenaar    " Otherwise, terminate search as we have found our MSL already.
76360a795aaSBram Moolenaar    let line = getline(lnum)
764ec7944aaSBram Moolenaar
76589bcfda6SBram Moolenaar    if !s:Match(msl, s:backslash_continuation_regex) &&
76689bcfda6SBram Moolenaar          \ s:Match(lnum, s:backslash_continuation_regex)
76789bcfda6SBram Moolenaar      " If the current line doesn't end in a backslash, but the previous one
76889bcfda6SBram Moolenaar      " does, look for that line's msl
76989bcfda6SBram Moolenaar      "
77089bcfda6SBram Moolenaar      " Example:
77189bcfda6SBram Moolenaar      "   foo = "bar" \
77289bcfda6SBram Moolenaar      "     "baz"
77389bcfda6SBram Moolenaar      "
77489bcfda6SBram Moolenaar      let msl = lnum
77589bcfda6SBram Moolenaar    elseif s:Match(msl, s:leading_operator_regex)
77689bcfda6SBram Moolenaar      " If the current line starts with a leading operator, keep its indent
77789bcfda6SBram Moolenaar      " and keep looking for an MSL.
77889bcfda6SBram Moolenaar      let msl = lnum
77989bcfda6SBram Moolenaar    elseif s:Match(lnum, s:splat_regex)
780ec7944aaSBram Moolenaar      " If the above line looks like the "*" of a splat, use the current one's
781ec7944aaSBram Moolenaar      " indentation.
782ec7944aaSBram Moolenaar      "
783ec7944aaSBram Moolenaar      " Example:
784ec7944aaSBram Moolenaar      "   Hash[*
785ec7944aaSBram Moolenaar      "     method_call do
786ec7944aaSBram Moolenaar      "       something
787ec7944aaSBram Moolenaar      "
788ec7944aaSBram Moolenaar      return msl
78989bcfda6SBram Moolenaar    elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
790ec7944aaSBram Moolenaar          \ s:Match(msl, s:non_bracket_continuation_regex)
791ec7944aaSBram Moolenaar      " If the current line is a non-bracket continuation and so is the
792ec7944aaSBram Moolenaar      " previous one, keep its indent and continue looking for an MSL.
793ec7944aaSBram Moolenaar      "
794ec7944aaSBram Moolenaar      " Example:
795ec7944aaSBram Moolenaar      "   method_call one,
796ec7944aaSBram Moolenaar      "     two,
797ec7944aaSBram Moolenaar      "     three
798ec7944aaSBram Moolenaar      "
799ec7944aaSBram Moolenaar      let msl = lnum
80089bcfda6SBram Moolenaar    elseif s:Match(lnum, s:dot_continuation_regex) &&
80189bcfda6SBram Moolenaar          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
80289bcfda6SBram Moolenaar      " If the current line is a bracket continuation or a block-starter, but
80389bcfda6SBram Moolenaar      " the previous is a dot, keep going to see if the previous line is the
80489bcfda6SBram Moolenaar      " start of another continuation.
80589bcfda6SBram Moolenaar      "
80689bcfda6SBram Moolenaar      " Example:
80789bcfda6SBram Moolenaar      "   parent.
80889bcfda6SBram Moolenaar      "     method_call {
80989bcfda6SBram Moolenaar      "     three
81089bcfda6SBram Moolenaar      "
81189bcfda6SBram Moolenaar      let msl = lnum
812ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
813ec7944aaSBram Moolenaar          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
814ec7944aaSBram Moolenaar      " If the current line is a bracket continuation or a block-starter, but
815ec7944aaSBram Moolenaar      " the previous is a non-bracket one, respect the previous' indentation,
816ec7944aaSBram Moolenaar      " and stop here.
817ec7944aaSBram Moolenaar      "
818ec7944aaSBram Moolenaar      " Example:
819ec7944aaSBram Moolenaar      "   method_call one,
820ec7944aaSBram Moolenaar      "     two {
821ec7944aaSBram Moolenaar      "     three
822ec7944aaSBram Moolenaar      "
823ec7944aaSBram Moolenaar      return lnum
824ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:bracket_continuation_regex) &&
825ec7944aaSBram Moolenaar          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
826ec7944aaSBram Moolenaar      " If both lines are bracket continuations (the current may also be a
827ec7944aaSBram Moolenaar      " block-starter), use the current one's and stop here
828ec7944aaSBram Moolenaar      "
829ec7944aaSBram Moolenaar      " Example:
830ec7944aaSBram Moolenaar      "   method_call(
831ec7944aaSBram Moolenaar      "     other_method_call(
832ec7944aaSBram Moolenaar      "       foo
833ec7944aaSBram Moolenaar      return msl
834ec7944aaSBram Moolenaar    elseif s:Match(lnum, s:block_regex) &&
835ec7944aaSBram Moolenaar          \ !s:Match(msl, s:continuation_regex) &&
836ec7944aaSBram Moolenaar          \ !s:Match(msl, s:block_continuation_regex)
837ec7944aaSBram Moolenaar      " If the previous line is a block-starter and the current one is
838ec7944aaSBram Moolenaar      " mostly ordinary, use the current one as the MSL.
839ec7944aaSBram Moolenaar      "
840ec7944aaSBram Moolenaar      " Example:
841ec7944aaSBram Moolenaar      "   method_call do
842ec7944aaSBram Moolenaar      "     something
843ec7944aaSBram Moolenaar      "     something_else
844ec7944aaSBram Moolenaar      return msl
845ec7944aaSBram Moolenaar    else
846ec7944aaSBram Moolenaar      let col = match(line, s:continuation_regex) + 1
84760a795aaSBram Moolenaar      if (col > 0 && !s:IsInStringOrComment(lnum, col))
84860a795aaSBram Moolenaar            \ || s:IsInString(lnum, strlen(line))
84960a795aaSBram Moolenaar        let msl = lnum
85060a795aaSBram Moolenaar      else
85160a795aaSBram Moolenaar        break
85260a795aaSBram Moolenaar      endif
853ec7944aaSBram Moolenaar    endif
854ec7944aaSBram Moolenaar
85560a795aaSBram Moolenaar    let lnum = s:PrevNonBlankNonString(lnum - 1)
85660a795aaSBram Moolenaar  endwhile
85760a795aaSBram Moolenaar  return msl
85860a795aaSBram Moolenaarendfunction
85960a795aaSBram Moolenaar
86060a795aaSBram Moolenaar" Check if line 'lnum' has more opening brackets than closing ones.
861d09091d4SBram Moolenaarfunction! s:ExtraBrackets(lnum) abort
862ec7944aaSBram Moolenaar  let opening = {'parentheses': [], 'braces': [], 'brackets': []}
863ec7944aaSBram Moolenaar  let closing = {'parentheses': [], 'braces': [], 'brackets': []}
864ec7944aaSBram Moolenaar
86560a795aaSBram Moolenaar  let line = getline(a:lnum)
86660a795aaSBram Moolenaar  let pos  = match(line, '[][(){}]', 0)
867ec7944aaSBram Moolenaar
868ec7944aaSBram Moolenaar  " Save any encountered opening brackets, and remove them once a matching
869ec7944aaSBram Moolenaar  " closing one has been found. If a closing bracket shows up that doesn't
870ec7944aaSBram Moolenaar  " close anything, save it for later.
87160a795aaSBram Moolenaar  while pos != -1
87260a795aaSBram Moolenaar    if !s:IsInStringOrComment(a:lnum, pos + 1)
873ec7944aaSBram Moolenaar      if line[pos] == '('
874ec7944aaSBram Moolenaar        call add(opening.parentheses, {'type': '(', 'pos': pos})
875ec7944aaSBram Moolenaar      elseif line[pos] == ')'
876ec7944aaSBram Moolenaar        if empty(opening.parentheses)
877ec7944aaSBram Moolenaar          call add(closing.parentheses, {'type': ')', 'pos': pos})
87860a795aaSBram Moolenaar        else
879ec7944aaSBram Moolenaar          let opening.parentheses = opening.parentheses[0:-2]
880ec7944aaSBram Moolenaar        endif
881ec7944aaSBram Moolenaar      elseif line[pos] == '{'
882ec7944aaSBram Moolenaar        call add(opening.braces, {'type': '{', 'pos': pos})
883ec7944aaSBram Moolenaar      elseif line[pos] == '}'
884ec7944aaSBram Moolenaar        if empty(opening.braces)
885ec7944aaSBram Moolenaar          call add(closing.braces, {'type': '}', 'pos': pos})
886ec7944aaSBram Moolenaar        else
887ec7944aaSBram Moolenaar          let opening.braces = opening.braces[0:-2]
888ec7944aaSBram Moolenaar        endif
889ec7944aaSBram Moolenaar      elseif line[pos] == '['
890ec7944aaSBram Moolenaar        call add(opening.brackets, {'type': '[', 'pos': pos})
891ec7944aaSBram Moolenaar      elseif line[pos] == ']'
892ec7944aaSBram Moolenaar        if empty(opening.brackets)
893ec7944aaSBram Moolenaar          call add(closing.brackets, {'type': ']', 'pos': pos})
894ec7944aaSBram Moolenaar        else
895ec7944aaSBram Moolenaar          let opening.brackets = opening.brackets[0:-2]
89660a795aaSBram Moolenaar        endif
89760a795aaSBram Moolenaar      endif
898ec7944aaSBram Moolenaar    endif
899ec7944aaSBram Moolenaar
90060a795aaSBram Moolenaar    let pos = match(line, '[][(){}]', pos + 1)
90160a795aaSBram Moolenaar  endwhile
902ec7944aaSBram Moolenaar
903ec7944aaSBram Moolenaar  " Find the rightmost brackets, since they're the ones that are important in
904ec7944aaSBram Moolenaar  " both opening and closing cases
905ec7944aaSBram Moolenaar  let rightmost_opening = {'type': '(', 'pos': -1}
906ec7944aaSBram Moolenaar  let rightmost_closing = {'type': ')', 'pos': -1}
907ec7944aaSBram Moolenaar
908ec7944aaSBram Moolenaar  for opening in opening.parentheses + opening.braces + opening.brackets
909ec7944aaSBram Moolenaar    if opening.pos > rightmost_opening.pos
910ec7944aaSBram Moolenaar      let rightmost_opening = opening
911ec7944aaSBram Moolenaar    endif
912ec7944aaSBram Moolenaar  endfor
913ec7944aaSBram Moolenaar
914ec7944aaSBram Moolenaar  for closing in closing.parentheses + closing.braces + closing.brackets
915ec7944aaSBram Moolenaar    if closing.pos > rightmost_closing.pos
916ec7944aaSBram Moolenaar      let rightmost_closing = closing
917ec7944aaSBram Moolenaar    endif
918ec7944aaSBram Moolenaar  endfor
919ec7944aaSBram Moolenaar
920ec7944aaSBram Moolenaar  return [rightmost_opening, rightmost_closing]
92160a795aaSBram Moolenaarendfunction
92260a795aaSBram Moolenaar
923d09091d4SBram Moolenaarfunction! s:Match(lnum, regex) abort
92489bcfda6SBram Moolenaar  let line   = getline(a:lnum)
92589bcfda6SBram Moolenaar  let offset = match(line, '\C'.a:regex)
92689bcfda6SBram Moolenaar  let col    = offset + 1
92789bcfda6SBram Moolenaar
92889bcfda6SBram Moolenaar  while offset > -1 && s:IsInStringOrComment(a:lnum, col)
92989bcfda6SBram Moolenaar    let offset = match(line, '\C'.a:regex, offset + 1)
93089bcfda6SBram Moolenaar    let col = offset + 1
93189bcfda6SBram Moolenaar  endwhile
93289bcfda6SBram Moolenaar
93389bcfda6SBram Moolenaar  if offset > -1
93489bcfda6SBram Moolenaar    return col
93589bcfda6SBram Moolenaar  else
93689bcfda6SBram Moolenaar    return 0
93789bcfda6SBram Moolenaar  endif
93860a795aaSBram Moolenaarendfunction
93960a795aaSBram Moolenaar
94089bcfda6SBram Moolenaar" Locates the containing class/module's definition line, ignoring nested classes
94189bcfda6SBram Moolenaar" along the way.
94289bcfda6SBram Moolenaar"
943d09091d4SBram Moolenaarfunction! s:FindContainingClass() abort
94489bcfda6SBram Moolenaar  let saved_position = getpos('.')
94589bcfda6SBram Moolenaar
94689bcfda6SBram Moolenaar  while searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
94789bcfda6SBram Moolenaar        \ s:end_skip_expr) > 0
94889bcfda6SBram Moolenaar    if expand('<cword>') =~# '\<class\|module\>'
94989bcfda6SBram Moolenaar      let found_lnum = line('.')
95089bcfda6SBram Moolenaar      call setpos('.', saved_position)
95189bcfda6SBram Moolenaar      return found_lnum
95289bcfda6SBram Moolenaar    endif
9534575876dSBram Moolenaar  endwhile
95489bcfda6SBram Moolenaar
95589bcfda6SBram Moolenaar  call setpos('.', saved_position)
95689bcfda6SBram Moolenaar  return 0
95760a795aaSBram Moolenaarendfunction
95860a795aaSBram Moolenaar
95960a795aaSBram Moolenaar" }}}1
96060a795aaSBram Moolenaar
96160a795aaSBram Moolenaarlet &cpo = s:cpo_save
96260a795aaSBram Moolenaarunlet s:cpo_save
9639964e468SBram Moolenaar
964ec7944aaSBram Moolenaar" vim:set sw=2 sts=2 ts=8 et:
965