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