home *** CD-ROM | disk | FTP | other *** search
/ vim.ftp.fu-berlin.de / 2015-02-03.vim.ftp.fu-berlin.de.tar / vim.ftp.fu-berlin.de / runtime / indent / ruby.vim < prev    next >
Encoding:
Text File  |  2010-08-15  |  11.6 KB  |  379 lines

  1. " Vim indent file
  2. " Language:        Ruby
  3. " Maintainer:        Nikolai Weibull <now at bitwi.se>
  4. " Last Change:        2009 Dec 17
  5. " URL:            http://vim-ruby.rubyforge.org
  6. " Anon CVS:        See above site
  7. " Release Coordinator:    Doug Kearns <dougkearns@gmail.com>
  8.  
  9. " 0. Initialization {{{1
  10. " =================
  11.  
  12. " Only load this indent file when no other was loaded.
  13. if exists("b:did_indent")
  14.   finish
  15. endif
  16. let b:did_indent = 1
  17.  
  18. setlocal nosmartindent
  19.  
  20. " Now, set up our indentation expression and keys that trigger it.
  21. setlocal indentexpr=GetRubyIndent()
  22. setlocal indentkeys=0{,0},0),0],!^F,o,O,e
  23. setlocal indentkeys+==end,=elsif,=when,=ensure,=rescue,==begin,==end
  24.  
  25. " Only define the function once.
  26. if exists("*GetRubyIndent")
  27.   finish
  28. endif
  29.  
  30. let s:cpo_save = &cpo
  31. set cpo&vim
  32.  
  33. " 1. Variables {{{1
  34. " ============
  35.  
  36. " Regex of syntax group names that are or delimit string or are comments.
  37. let s:syng_strcom = '\<ruby\%(String\|StringEscape\|ASCIICode' .
  38.       \ '\|Interpolation\|NoInterpolation\|Comment\|Documentation\)\>'
  39.  
  40. " Regex of syntax group names that are strings.
  41. let s:syng_string =
  42.       \ '\<ruby\%(String\|Interpolation\|NoInterpolation\|StringEscape\)\>'
  43.  
  44. " Regex of syntax group names that are strings or documentation.
  45. let s:syng_stringdoc =
  46.   \'\<ruby\%(String\|Interpolation\|NoInterpolation\|StringEscape\|Documentation\)\>'
  47.  
  48. " Expression used to check whether we should skip a match with searchpair().
  49. let s:skip_expr =
  50.       \ "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
  51.  
  52. " Regex used for words that, at the start of a line, add a level of indent.
  53. let s:ruby_indent_keywords = '^\s*\zs\<\%(module\|class\|def\|if\|for' .
  54.       \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure' .
  55.       \ '\|rescue\)\>' .
  56.       \ '\|\%([*+/,=-]\|<<\|>>\|:\s\)\s*\zs' .
  57.       \    '\<\%(if\|for\|while\|until\|case\|unless\|begin\)\>'
  58.  
  59. " Regex used for words that, at the start of a line, remove a level of indent.
  60. let s:ruby_deindent_keywords =
  61.       \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|end\)\>'
  62.  
  63. " Regex that defines the start-match for the 'end' keyword.
  64. "let s:end_start_regex = '\%(^\|[^.]\)\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\|do\)\>'
  65. " TODO: the do here should be restricted somewhat (only at end of line)?
  66. let s:end_start_regex = '^\s*\zs\<\%(module\|class\|def\|if\|for' .
  67.       \ '\|while\|until\|case\|unless\|begin\)\>' .
  68.       \ '\|\%([*+/,=-]\|<<\|>>\|:\s\)\s*\zs' .
  69.       \    '\<\%(if\|for\|while\|until\|case\|unless\|begin\)\>' .
  70.       \ '\|\<do\>'
  71.  
  72. " Regex that defines the middle-match for the 'end' keyword.
  73. let s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue\>\|when\|elsif\)\>'
  74.  
  75. " Regex that defines the end-match for the 'end' keyword.
  76. let s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end\>'
  77.  
  78. " Expression used for searchpair() call for finding match for 'end' keyword.
  79. let s:end_skip_expr = s:skip_expr .
  80.       \ ' || (expand("<cword>") == "do"' .
  81.       \ ' && getline(".") =~ "^\\s*\\<\\(while\\|until\\|for\\)\\>")'
  82.  
  83. " Regex that defines continuation lines, not including (, {, or [.
  84. let s:continuation_regex = '\%([\\*+/.,:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
  85.  
  86. " Regex that defines continuation lines.
  87. " TODO: this needs to deal with if ...: and so on
  88. let s:continuation_regex2 =
  89.       \ '\%([\\*+/.,:({[]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
  90.  
  91. " Regex that defines blocks.
  92. let s:block_regex =
  93.       \ '\%(\<do\>\|{\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=\s*\%(#.*\)\=$'
  94.  
  95. " 2. Auxiliary Functions {{{1
  96. " ======================
  97.  
  98. " Check if the character at lnum:col is inside a string, comment, or is ascii.
  99. function s:IsInStringOrComment(lnum, col)
  100.   return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
  101. endfunction
  102.  
  103. " Check if the character at lnum:col is inside a string.
  104. function s:IsInString(lnum, col)
  105.   return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
  106. endfunction
  107.  
  108. " Check if the character at lnum:col is inside a string or documentation.
  109. function s:IsInStringOrDocumentation(lnum, col)
  110.   return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_stringdoc
  111. endfunction
  112.  
  113. " Find line above 'lnum' that isn't empty, in a comment, or in a string.
  114. function s:PrevNonBlankNonString(lnum)
  115.   let in_block = 0
  116.   let lnum = prevnonblank(a:lnum)
  117.   while lnum > 0
  118.     " Go in and out of blocks comments as necessary.
  119.     " If the line isn't empty (with opt. comment) or in a string, end search.
  120.     let line = getline(lnum)
  121.     if line =~ '^=begin$'
  122.       if in_block
  123.     let in_block = 0
  124.       else
  125.     break
  126.       endif
  127.     elseif !in_block && line =~ '^=end$'
  128.       let in_block = 1
  129.     elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
  130.       \ && s:IsInStringOrComment(lnum, strlen(line)))
  131.       break
  132.     endif
  133.     let lnum = prevnonblank(lnum - 1)
  134.   endwhile
  135.   return lnum
  136. endfunction
  137.  
  138. " Find line above 'lnum' that started the continuation 'lnum' may be part of.
  139. function s:GetMSL(lnum)
  140.   " Start on the line we're at and use its indent.
  141.   let msl = a:lnum
  142.   let lnum = s:PrevNonBlankNonString(a:lnum - 1)
  143.   while lnum > 0
  144.     " If we have a continuation line, or we're in a string, use line as MSL.
  145.     " Otherwise, terminate search as we have found our MSL already.
  146.     let line = getline(lnum)
  147.     let col = match(line, s:continuation_regex2) + 1
  148.     if (col > 0 && !s:IsInStringOrComment(lnum, col))
  149.       \ || s:IsInString(lnum, strlen(line))
  150.       let msl = lnum
  151.     else
  152.       break
  153.     endif
  154.     let lnum = s:PrevNonBlankNonString(lnum - 1)
  155.   endwhile
  156.   return msl
  157. endfunction
  158.  
  159. " Check if line 'lnum' has more opening brackets than closing ones.
  160. function s:LineHasOpeningBrackets(lnum)
  161.   let open_0 = 0
  162.   let open_2 = 0
  163.   let open_4 = 0
  164.   let line = getline(a:lnum)
  165.   let pos = match(line, '[][(){}]', 0)
  166.   while pos != -1
  167.     if !s:IsInStringOrComment(a:lnum, pos + 1)
  168.       let idx = stridx('(){}[]', line[pos])
  169.       if idx % 2 == 0
  170.     let open_{idx} = open_{idx} + 1
  171.       else
  172.     let open_{idx - 1} = open_{idx - 1} - 1
  173.       endif
  174.     endif
  175.     let pos = match(line, '[][(){}]', pos + 1)
  176.   endwhile
  177.   return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
  178. endfunction
  179.  
  180. function s:Match(lnum, regex)
  181.   let col = match(getline(a:lnum), '\C'.a:regex) + 1
  182.   return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
  183. endfunction
  184.  
  185. function s:MatchLast(lnum, regex)
  186.   let line = getline(a:lnum)
  187.   let col = match(line, '.*\zs' . a:regex)
  188.   while col != -1 && s:IsInStringOrComment(a:lnum, col)
  189.     let line = strpart(line, 0, col)
  190.     let col = match(line, '.*' . a:regex)
  191.   endwhile
  192.   return col + 1
  193. endfunction
  194.  
  195. " 3. GetRubyIndent Function {{{1
  196. " =========================
  197.  
  198. function GetRubyIndent()
  199.   " 3.1. Setup {{{2
  200.   " ----------
  201.  
  202.   " Set up variables for restoring position in file.  Could use v:lnum here.
  203.   let vcol = col('.')
  204.  
  205.   " 3.2. Work on the current line {{{2
  206.   " -----------------------------
  207.  
  208.   " Get the current line.
  209.   let line = getline(v:lnum)
  210.   let ind = -1
  211.  
  212.   " If we got a closing bracket on an empty line, find its match and indent
  213.   " according to it.  For parentheses we indent to its column - 1, for the
  214.   " others we indent to the containing line's MSL's level.  Return -1 if fail.
  215.   let col = matchend(line, '^\s*[]})]')
  216.   if col > 0 && !s:IsInStringOrComment(v:lnum, col)
  217.     call cursor(v:lnum, col)
  218.     let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
  219.     if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
  220.       if line[col-1]==')' && col('.') != col('$') - 1
  221.     let ind = virtcol('.')-1
  222.       else
  223.     let ind = indent(s:GetMSL(line('.')))
  224.       endif
  225.     endif
  226.     return ind
  227.   endif
  228.  
  229.   " If we have a =begin or =end set indent to first column.
  230.   if match(line, '^\s*\%(=begin\|=end\)$') != -1
  231.     return 0
  232.   endif
  233.  
  234.   " If we have a deindenting keyword, find its match and indent to its level.
  235.   " TODO: this is messy
  236.   if s:Match(v:lnum, s:ruby_deindent_keywords)
  237.     call cursor(v:lnum, 1)
  238.     if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
  239.         \ s:end_skip_expr) > 0
  240.       let line = getline('.')
  241.       if strpart(line, 0, col('.') - 1) =~ '=\s*$' &&
  242.        \ strpart(line, col('.') - 1, 2) !~ 'do'
  243.     let ind = virtcol('.') - 1
  244.       else
  245.     let ind = indent('.')
  246.       endif
  247.     endif
  248.     return ind
  249.   endif
  250.  
  251.   " If we are in a multi-line string or line-comment, don't do anything to it.
  252.   if s:IsInStringOrDocumentation(v:lnum, matchend(line, '^\s*') + 1)
  253.     return indent('.')
  254.   endif
  255.  
  256.   " 3.3. Work on the previous line. {{{2
  257.   " -------------------------------
  258.  
  259.   " Find a non-blank, non-multi-line string line above the current line.
  260.   let lnum = s:PrevNonBlankNonString(v:lnum - 1)
  261.  
  262.   " If the line is empty and inside a string, use the previous line.
  263.   if line =~ '^\s*$' && lnum != prevnonblank(v:lnum - 1)
  264.     return indent(prevnonblank(v:lnum))
  265.   endif
  266.  
  267.   " At the start of the file use zero indent.
  268.   if lnum == 0
  269.     return 0
  270.   endif
  271.  
  272.   " Set up variables for current line.
  273.   let line = getline(lnum)
  274.   let ind = indent(lnum)
  275.  
  276.   " If the previous line ended with a block opening, add a level of indent.
  277.   if s:Match(lnum, s:block_regex)
  278.     return indent(s:GetMSL(lnum)) + &sw
  279.   endif
  280.  
  281.   " If the previous line contained an opening bracket, and we are still in it,
  282.   " add indent depending on the bracket type.
  283.   if line =~ '[[({]'
  284.     let counts = s:LineHasOpeningBrackets(lnum)
  285.     if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
  286.       if col('.') + 1 == col('$')
  287.     return ind + &sw
  288.       else
  289.     return virtcol('.')
  290.       endif
  291.     elseif counts[1] == '1' || counts[2] == '1'
  292.       return ind + &sw
  293.     else
  294.       call cursor(v:lnum, vcol)
  295.     end
  296.   endif
  297.  
  298.   " If the previous line ended with an "end", match that "end"s beginning's
  299.   " indent.
  300.   let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
  301.   if col > 0
  302.     call cursor(lnum, col)
  303.     if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
  304.         \ s:end_skip_expr) > 0
  305.       let n = line('.')
  306.       let ind = indent('.')
  307.       let msl = s:GetMSL(n)
  308.       if msl != n
  309.     let ind = indent(msl)
  310.       end
  311.       return ind
  312.     endif
  313.   end
  314.  
  315.   let col = s:Match(lnum, s:ruby_indent_keywords)
  316.   if col > 0
  317.     call cursor(lnum, col)
  318.     let ind = virtcol('.') - 1 + &sw
  319. "    let ind = indent(lnum) + &sw
  320.     " TODO: make this better (we need to count them) (or, if a searchpair
  321.     " fails, we know that something is lacking an end and thus we indent a
  322.     " level
  323.     if s:Match(lnum, s:end_end_regex)
  324.       let ind = indent('.')
  325.     endif
  326.     return ind
  327.   endif
  328.  
  329.   " 3.4. Work on the MSL line. {{{2
  330.   " --------------------------
  331.  
  332.   " Set up variables to use and search for MSL to the previous line.
  333.   let p_lnum = lnum
  334.   let lnum = s:GetMSL(lnum)
  335.  
  336.   " If the previous line wasn't a MSL and is continuation return its indent.
  337.   " TODO: the || s:IsInString() thing worries me a bit.
  338.   if p_lnum != lnum
  339.     if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line))
  340.       return ind
  341.     endif
  342.   endif
  343.  
  344.   " Set up more variables, now that we know we wasn't continuation bound.
  345.   let line = getline(lnum)
  346.   let msl_ind = indent(lnum)
  347.  
  348.   " If the MSL line had an indenting keyword in it, add a level of indent.
  349.   " TODO: this does not take into account contrived things such as
  350.   " module Foo; class Bar; end
  351.   if s:Match(lnum, s:ruby_indent_keywords)
  352.     let ind = msl_ind + &sw
  353.     if s:Match(lnum, s:end_end_regex)
  354.       let ind = ind - &sw
  355.     endif
  356.     return ind
  357.   endif
  358.  
  359.   " If the previous line ended with [*+/.-=], indent one extra level.
  360.   if s:Match(lnum, s:continuation_regex)
  361.     if lnum == p_lnum
  362.       let ind = msl_ind + &sw
  363.     else
  364.       let ind = msl_ind
  365.     endif
  366.   endif
  367.  
  368.   " }}}2
  369.  
  370.   return ind
  371. endfunction
  372.  
  373. " }}}1
  374.  
  375. let &cpo = s:cpo_save
  376. unlet s:cpo_save
  377.  
  378. " vim:set sw=2 sts=2 ts=8 noet:
  379.