summaryrefslogtreecommitdiff
path: root/runtime/indent/ruby.vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/indent/ruby.vim')
-rw-r--r--runtime/indent/ruby.vim229
1 files changed, 195 insertions, 34 deletions
diff --git a/runtime/indent/ruby.vim b/runtime/indent/ruby.vim
index 095b3a43c..4f87ec782 100644
--- a/runtime/indent/ruby.vim
+++ b/runtime/indent/ruby.vim
@@ -13,12 +13,23 @@ if exists("b:did_indent")
endif
let b:did_indent = 1
+if !exists('g:ruby_indent_access_modifier_style')
+ " Possible values: "normal", "indent", "outdent"
+ let g:ruby_indent_access_modifier_style = 'normal'
+endif
+
+if !exists('g:ruby_indent_block_style')
+ " Possible values: "expression", "do"
+ let g:ruby_indent_block_style = 'expression'
+endif
+
setlocal nosmartindent
" Now, set up our indentation expression and keys that trigger it.
setlocal indentexpr=GetRubyIndent(v:lnum)
-setlocal indentkeys=0{,0},0),0],!^F,o,O,e
+setlocal indentkeys=0{,0},0),0],!^F,o,O,e,:,.
setlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue,==begin,==end
+setlocal indentkeys+==private,=protected,=public
" Only define the function once.
if exists("*GetRubyIndent")
@@ -34,7 +45,7 @@ set cpo&vim
" Regex of syntax group names that are or delimit strings/symbols or are comments.
let s:syng_strcom = '\<ruby\%(Regexp\|RegexpDelimiter\|RegexpEscape' .
\ '\|Symbol\|String\|StringDelimiter\|StringEscape\|ASCIICode' .
- \ '\|Interpolation\|NoInterpolation\|Comment\|Documentation\)\>'
+ \ '\|Interpolation\|InterpolationDelimiter\|NoInterpolation\|Comment\|Documentation\)\>'
" Regex of syntax group names that are strings.
let s:syng_string =
@@ -49,9 +60,10 @@ let s:skip_expr =
\ "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
" Regex used for words that, at the start of a line, add a level of indent.
-let s:ruby_indent_keywords = '^\s*\zs\<\%(module\|class\|def\|if\|for' .
- \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure' .
- \ '\|rescue\):\@!\>' .
+let s:ruby_indent_keywords =
+ \ '^\s*\zs\<\%(module\|class\|if\|for' .
+ \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure\|rescue' .
+ \ '\|\%(public\|protected\|private\)\=\s*def\):\@!\>' .
\ '\|\%([=,*/%+-]\|<<\|>>\|:\s\)\s*\zs' .
\ '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>'
@@ -64,7 +76,8 @@ let s:ruby_deindent_keywords =
" TODO: the do here should be restricted somewhat (only at end of line)?
let s:end_start_regex =
\ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
- \ '\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\):\@!\>' .
+ \ '\<\%(module\|class\|if\|for\|while\|until\|case\|unless\|begin' .
+ \ '\|\%(public\|protected\|private\)\=\s*def\):\@!\>' .
\ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>'
" Regex that defines the middle-match for the 'end' keyword.
@@ -82,16 +95,35 @@ let s:end_skip_expr = s:skip_expr .
let s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
" Regex that defines continuation lines.
-" TODO: this needs to deal with if ...: and so on
let s:continuation_regex =
\ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
+" Regex that defines continuable keywords
+let s:continuable_regex =
+ \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
+ \ '\<\%(if\|for\|while\|until\|unless\):\@!\>'
+
" Regex that defines bracket continuations
let s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$'
+" Regex that defines dot continuations
+let s:dot_continuation_regex = '%\@<!\.\s*\%(#.*\)\=$'
+
+" Regex that defines backslash continuations
+let s:backslash_continuation_regex = '%\@<!\\\s*$'
+
+" Regex that defines end of bracket continuation followed by another continuation
+let s:bracket_switch_continuation_regex = '^\([^(]\+\zs).\+\)\+'.s:continuation_regex
+
" Regex that defines the first part of a splat pattern
let s:splat_regex = '[[,(]\s*\*\s*\%(#.*\)\=$'
+" Regex that describes all indent access modifiers
+let s:access_modifier_regex = '\C^\s*\%(public\|protected\|private\)\s*\%(#.*\)\=$'
+
+" Regex that describes the indent access modifiers (excludes public)
+let s:indent_access_modifier_regex = '\C^\s*\%(protected\|private\)\s*\%(#.*\)\=$'
+
" Regex that defines blocks.
"
" Note that there's a slight problem with this regex and s:continuation_regex.
@@ -102,10 +134,13 @@ let s:splat_regex = '[[,(]\s*\*\s*\%(#.*\)\=$'
" The reason is that the pipe matches a hanging "|" operator.
"
let s:block_regex =
- \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$'
+ \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|[^|]*|\)\=\s*\%(#.*\)\=$'
let s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex
+" Regex that describes a leading operator (only a method call's dot for now)
+let s:leading_operator_regex = '^\s*[.]'
+
" 2. Auxiliary Functions {{{1
" ======================
@@ -165,7 +200,21 @@ function s:GetMSL(lnum)
" Otherwise, terminate search as we have found our MSL already.
let line = getline(lnum)
- if s:Match(lnum, s:splat_regex)
+ if !s:Match(msl, s:backslash_continuation_regex) &&
+ \ s:Match(lnum, s:backslash_continuation_regex)
+ " If the current line doesn't end in a backslash, but the previous one
+ " does, look for that line's msl
+ "
+ " Example:
+ " foo = "bar" \
+ " "baz"
+ "
+ let msl = lnum
+ elseif s:Match(msl, s:leading_operator_regex)
+ " If the current line starts with a leading operator, keep its indent
+ " and keep looking for an MSL.
+ let msl = lnum
+ elseif s:Match(lnum, s:splat_regex)
" If the above line looks like the "*" of a splat, use the current one's
" indentation.
"
@@ -175,7 +224,7 @@ function s:GetMSL(lnum)
" something
"
return msl
- elseif s:Match(line, s:non_bracket_continuation_regex) &&
+ elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
\ s:Match(msl, s:non_bracket_continuation_regex)
" If the current line is a non-bracket continuation and so is the
" previous one, keep its indent and continue looking for an MSL.
@@ -186,6 +235,18 @@ function s:GetMSL(lnum)
" three
"
let msl = lnum
+ elseif s:Match(lnum, s:dot_continuation_regex) &&
+ \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
+ " If the current line is a bracket continuation or a block-starter, but
+ " the previous is a dot, keep going to see if the previous line is the
+ " start of another continuation.
+ "
+ " Example:
+ " parent.
+ " method_call {
+ " three
+ "
+ let msl = lnum
elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
\ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
" If the current line is a bracket continuation or a block-starter, but
@@ -299,18 +360,39 @@ function s:ExtraBrackets(lnum)
endfunction
function s:Match(lnum, regex)
- let col = match(getline(a:lnum), '\C'.a:regex) + 1
- return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
-endfunction
+ let line = getline(a:lnum)
+ let offset = match(line, '\C'.a:regex)
+ let col = offset + 1
-function s:MatchLast(lnum, regex)
- let line = getline(a:lnum)
- let col = match(line, '.*\zs' . a:regex)
- while col != -1 && s:IsInStringOrComment(a:lnum, col)
- let line = strpart(line, 0, col)
- let col = match(line, '.*' . a:regex)
+ while offset > -1 && s:IsInStringOrComment(a:lnum, col)
+ let offset = match(line, '\C'.a:regex, offset + 1)
+ let col = offset + 1
endwhile
- return col + 1
+
+ if offset > -1
+ return col
+ else
+ return 0
+ endif
+endfunction
+
+" Locates the containing class/module's definition line, ignoring nested classes
+" along the way.
+"
+function! s:FindContainingClass()
+ let saved_position = getpos('.')
+
+ while searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
+ \ s:end_skip_expr) > 0
+ if expand('<cword>') =~# '\<class\|module\>'
+ let found_lnum = line('.')
+ call setpos('.', saved_position)
+ return found_lnum
+ endif
+ endif
+
+ call setpos('.', saved_position)
+ return 0
endfunction
" 3. GetRubyIndent Function {{{1
@@ -320,6 +402,13 @@ function GetRubyIndent(...)
" 3.1. Setup {{{2
" ----------
+ " The value of a single shift-width
+ if exists('*shiftwidth')
+ let sw = shiftwidth()
+ else
+ let sw = &sw
+ endif
+
" For the current line, use the first argument if given, else v:lnum
let clnum = a:0 ? a:1 : v:lnum
@@ -333,6 +422,24 @@ function GetRubyIndent(...)
let line = getline(clnum)
let ind = -1
+ " If this line is an access modifier keyword, align according to the closest
+ " class declaration.
+ if g:ruby_indent_access_modifier_style == 'indent'
+ if s:Match(clnum, s:access_modifier_regex)
+ let class_line = s:FindContainingClass()
+ if class_line > 0
+ return indent(class_line) + sw
+ endif
+ endif
+ elseif g:ruby_indent_access_modifier_style == 'outdent'
+ if s:Match(clnum, s:access_modifier_regex)
+ let class_line = s:FindContainingClass()
+ if class_line > 0
+ return indent(class_line)
+ endif
+ endif
+ endif
+
" If we got a closing bracket on an empty line, find its match and indent
" according to it. For parentheses we indent to its column - 1, for the
" others we indent to the containing line's MSL's level. Return -1 if fail.
@@ -343,7 +450,9 @@ function GetRubyIndent(...)
if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
if line[col-1]==')' && col('.') != col('$') - 1
let ind = virtcol('.') - 1
- else
+ elseif g:ruby_indent_block_style == 'do'
+ let ind = indent(line('.'))
+ else " g:ruby_indent_block_style == 'expression'
let ind = indent(s:GetMSL(line('.')))
endif
endif
@@ -366,10 +475,17 @@ function GetRubyIndent(...)
if strpart(line, 0, col('.') - 1) =~ '=\s*$' &&
\ strpart(line, col('.') - 1, 2) !~ 'do'
+ " assignment to case/begin/etc, on the same line, hanging indent
let ind = virtcol('.') - 1
+ elseif g:ruby_indent_block_style == 'do'
+ " align to line of the "do", not to the MSL
+ let ind = indent(line('.'))
elseif getline(msl) =~ '=\s*\(#.*\)\=$'
+ " in the case of assignment to the MSL, align to the starting line,
+ " not to the MSL
let ind = indent(line('.'))
else
+ " align to the MSL
let ind = indent(msl)
endif
endif
@@ -389,6 +505,11 @@ function GetRubyIndent(...)
return 0
endif
+ " If the current line starts with a leading operator, add a level of indent.
+ if s:Match(clnum, s:leading_operator_regex)
+ return indent(s:GetMSL(clnum)) + sw
+ endif
+
" 3.3. Work on the previous line. {{{2
" -------------------------------
@@ -409,14 +530,50 @@ function GetRubyIndent(...)
let line = getline(lnum)
let ind = indent(lnum)
+ if g:ruby_indent_access_modifier_style == 'indent'
+ " If the previous line was a private/protected keyword, add a
+ " level of indent.
+ if s:Match(lnum, s:indent_access_modifier_regex)
+ return indent(lnum) + sw
+ endif
+ elseif g:ruby_indent_access_modifier_style == 'outdent'
+ " If the previous line was a private/protected/public keyword, add
+ " a level of indent, since the keyword has been out-dented.
+ if s:Match(lnum, s:access_modifier_regex)
+ return indent(lnum) + sw
+ endif
+ endif
+
+ if s:Match(lnum, s:continuable_regex) && s:Match(lnum, s:continuation_regex)
+ return indent(s:GetMSL(lnum)) + sw + sw
+ endif
+
" If the previous line ended with a block opening, add a level of indent.
if s:Match(lnum, s:block_regex)
- return indent(s:GetMSL(lnum)) + &sw
+ let msl = s:GetMSL(lnum)
+
+ if g:ruby_indent_block_style == 'do'
+ " don't align to the msl, align to the "do"
+ let ind = indent(lnum) + sw
+ elseif getline(msl) =~ '=\s*\(#.*\)\=$'
+ " in the case of assignment to the msl, align to the starting line,
+ " not to the msl
+ let ind = indent(lnum) + sw
+ else
+ let ind = indent(msl) + sw
+ endif
+ return ind
+ endif
+
+ " If the previous line started with a leading operator, use its MSL's level
+ " of indent
+ if s:Match(lnum, s:leading_operator_regex)
+ return indent(s:GetMSL(lnum))
endif
" If the previous line ended with the "*" of a splat, add a level of indent
if line =~ s:splat_regex
- return indent(lnum) + &sw
+ return indent(lnum) + sw
endif
" If the previous line contained unclosed opening brackets and we are still
@@ -431,22 +588,22 @@ function GetRubyIndent(...)
if opening.pos != -1
if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
if col('.') + 1 == col('$')
- return ind + &sw
+ return ind + sw
else
return virtcol('.')
endif
else
let nonspace = matchend(line, '\S', opening.pos + 1) - 1
- return nonspace > 0 ? nonspace : ind + &sw
+ return nonspace > 0 ? nonspace : ind + sw
endif
elseif closing.pos != -1
call cursor(lnum, closing.pos + 1)
normal! %
if s:Match(line('.'), s:ruby_indent_keywords)
- return indent('.') + &sw
+ return indent('.') + sw
else
- return indent('.')
+ return indent(s:GetMSL(line('.')))
endif
else
call cursor(clnum, vcol)
@@ -473,7 +630,7 @@ function GetRubyIndent(...)
let col = s:Match(lnum, s:ruby_indent_keywords)
if col > 0
call cursor(lnum, col)
- let ind = virtcol('.') - 1 + &sw
+ let ind = virtcol('.') - 1 + sw
" TODO: make this better (we need to count them) (or, if a searchpair
" fails, we know that something is lacking an end and thus we indent a
" level
@@ -490,10 +647,14 @@ function GetRubyIndent(...)
let p_lnum = lnum
let lnum = s:GetMSL(lnum)
- " If the previous line wasn't a MSL and is continuation return its indent.
- " TODO: the || s:IsInString() thing worries me a bit.
+ " If the previous line wasn't a MSL.
if p_lnum != lnum
- if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
+ " If previous line ends bracket and begins non-bracket continuation decrease indent by 1.
+ if s:Match(p_lnum, s:bracket_switch_continuation_regex)
+ return ind - 1
+ " If previous line is a continuation return its indent.
+ " TODO: the || s:IsInString() thing worries me a bit.
+ elseif s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
return ind
endif
endif
@@ -506,9 +667,9 @@ function GetRubyIndent(...)
" TODO: this does not take into account contrived things such as
" module Foo; class Bar; end
if s:Match(lnum, s:ruby_indent_keywords)
- let ind = msl_ind + &sw
+ let ind = msl_ind + sw
if s:Match(lnum, s:end_end_regex)
- let ind = ind - &sw
+ let ind = ind - sw
endif
return ind
endif
@@ -517,7 +678,7 @@ function GetRubyIndent(...)
" closing bracket, indent one extra level.
if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)')
if lnum == p_lnum
- let ind = msl_ind + &sw
+ let ind = msl_ind + sw
else
let ind = msl_ind
endif