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