mirror of
https://github.com/sheerun/vim-polyglot.git
synced 2025-11-11 13:03:50 -05:00
296 lines
8.9 KiB
VimL
296 lines
8.9 KiB
VimL
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1
|
|
|
|
" Only load this indent file when no other was loaded.
|
|
if exists('b:did_indent')
|
|
finish
|
|
endif
|
|
let b:did_indent = 1
|
|
|
|
setlocal nosmartindent
|
|
|
|
" Now, set up our indentation expression and keys that trigger it.
|
|
setlocal indentexpr=GetCrystalIndent(v:lnum)
|
|
setlocal indentkeys=0{,0},0),0],!^F,o,O,e,:,.
|
|
setlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue
|
|
setlocal indentkeys+==private,=protected
|
|
|
|
let s:cpo_save = &cpo
|
|
set cpo&vim
|
|
|
|
" Only define the function once.
|
|
if exists('*GetCrystalIndent')
|
|
finish
|
|
endif
|
|
|
|
" Return the value of a single shift-width
|
|
if exists('*shiftwidth')
|
|
let s:sw = function('shiftwidth')
|
|
else
|
|
function s:sw()
|
|
return &shiftwidth
|
|
endfunction
|
|
endif
|
|
|
|
" GetCrystalIndent Function {{{1
|
|
" =========================
|
|
|
|
function GetCrystalIndent(...)
|
|
" Setup {{{2
|
|
" -----
|
|
|
|
" For the current line, use the first argument if given, else v:lnum
|
|
let clnum = a:0 ? a:1 : v:lnum
|
|
|
|
" Set up variables for restoring position in file
|
|
let vcol = col('.')
|
|
|
|
" Work on the current line {{{2
|
|
" ------------------------
|
|
|
|
" Get the current line.
|
|
let line = getline(clnum)
|
|
let ind = -1
|
|
|
|
" 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.
|
|
let col = matchend(line, '^\s*[]})]')
|
|
if col > 0 && !crystal#indent#IsInStringOrComment(clnum, col)
|
|
call cursor(clnum, col)
|
|
let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
|
|
if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', g:crystal#indent#skip_expr)
|
|
if line[col-1] ==# ')' && col('.') != col('$') - 1
|
|
let ind = virtcol('.') - 1
|
|
else
|
|
let ind = indent(crystal#indent#GetMSL(line('.')))
|
|
endif
|
|
endif
|
|
return ind
|
|
endif
|
|
|
|
" If we have a deindenting keyword, find its match and indent to its level.
|
|
" TODO: this is messy
|
|
if crystal#indent#Match(clnum, g:crystal#indent#crystal_deindent_keywords)
|
|
call cursor(clnum, 1)
|
|
if searchpair(
|
|
\ g:crystal#indent#end_start_regex,
|
|
\ g:crystal#indent#end_middle_regex,
|
|
\ g:crystal#indent#end_end_regex,
|
|
\ 'bW', g:crystal#indent#skip_expr)
|
|
let msl = crystal#indent#GetMSL(line('.'))
|
|
let line = getline(line('.'))
|
|
|
|
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 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
|
|
return ind
|
|
endif
|
|
|
|
" If we are in a multi-line string, don't do anything to it.
|
|
if crystal#indent#IsInString(clnum, matchend(line, '^\s*') + 1)
|
|
return indent('.')
|
|
endif
|
|
|
|
" If we are at the closing delimiter of a "<<" heredoc-style string, set the
|
|
" indent to 0.
|
|
if line =~# '^\k\+\s*$'
|
|
\ && crystal#indent#IsInStringDelimiter(clnum, 1)
|
|
\ && search('\V<<'.line, 'nbW')
|
|
return 0
|
|
endif
|
|
|
|
" If the current line starts with a leading operator, add a level of indent.
|
|
if crystal#indent#Match(clnum, g:crystal#indent#leading_operator_regex)
|
|
return indent(crystal#indent#GetMSL(clnum)) + s:sw()
|
|
endif
|
|
|
|
" Work on the previous line. {{{2
|
|
" --------------------------
|
|
|
|
" Find a non-blank, non-multi-line string line above the current line.
|
|
let lnum = crystal#indent#PrevNonBlankNonString(clnum - 1)
|
|
|
|
" If the line is empty and inside a string, use the previous line.
|
|
if line =~# '^\s*$' && lnum != prevnonblank(clnum - 1)
|
|
return indent(prevnonblank(clnum))
|
|
endif
|
|
|
|
" At the start of the file use zero indent.
|
|
if lnum == 0
|
|
return 0
|
|
endif
|
|
|
|
" Set up variables for the previous line.
|
|
let line = getline(lnum)
|
|
let ind = indent(lnum)
|
|
|
|
if crystal#indent#Match(lnum, g:crystal#indent#continuable_regex) &&
|
|
\ crystal#indent#Match(lnum, g:crystal#indent#continuation_regex)
|
|
return indent(crystal#indent#GetMSL(lnum)) + s:sw() * 2
|
|
endif
|
|
|
|
" If the previous line ended with a block opening, add a level of indent.
|
|
if crystal#indent#Match(lnum, g:crystal#indent#block_regex)
|
|
let msl = crystal#indent#GetMSL(lnum)
|
|
|
|
if getline(msl) =~# '=\s*\(#.*\)\=$'
|
|
" in the case of assignment to the msl, align to the starting line,
|
|
" not to the msl
|
|
let ind = indent(lnum) + s:sw()
|
|
else
|
|
let ind = indent(msl) + s:sw()
|
|
endif
|
|
return ind
|
|
endif
|
|
|
|
" If the previous line started with a leading operator, use its MSL's level
|
|
" of indent
|
|
if crystal#indent#Match(lnum, g:crystal#indent#leading_operator_regex)
|
|
return indent(crystal#indent#GetMSL(lnum))
|
|
endif
|
|
|
|
" If the previous line ended with the "*" of a splat, add a level of indent
|
|
if line =~ g:crystal#indent#splat_regex
|
|
return indent(lnum) + s:sw()
|
|
endif
|
|
|
|
" If the previous line contained unclosed opening brackets and we are still
|
|
" in them, find the rightmost one and add indent depending on the bracket
|
|
" type.
|
|
"
|
|
" If it contained hanging closing brackets, find the rightmost one, find its
|
|
" match and indent according to that.
|
|
if line =~# '[[({]' || line =~# '[])]\s*\%(#.*\)\=$'
|
|
let [opening, closing] = crystal#indent#ExtraBrackets(lnum)
|
|
|
|
if opening.pos != -1
|
|
if opening.type ==# '(' && searchpair('(', '', ')', 'bW', g:crystal#indent#skip_expr)
|
|
if col('.') + 1 == col('$')
|
|
return ind + s:sw()
|
|
else
|
|
return virtcol('.')
|
|
endif
|
|
else
|
|
let nonspace = matchend(line, '\S', opening.pos + 1) - 1
|
|
return nonspace > 0 ? nonspace : ind + s:sw()
|
|
endif
|
|
elseif closing.pos != -1
|
|
call cursor(lnum, closing.pos + 1)
|
|
keepjumps normal! %
|
|
|
|
if crystal#indent#Match(line('.'), g:crystal#indent#crystal_indent_keywords)
|
|
return indent('.') + s:sw()
|
|
else
|
|
return indent('.')
|
|
endif
|
|
else
|
|
call cursor(clnum, vcol)
|
|
end
|
|
endif
|
|
|
|
" If the previous line ended with an "end", match that "end"s beginning's
|
|
" indent.
|
|
let col = crystal#indent#Match(lnum, g:crystal#indent#end_end_regex)
|
|
if col
|
|
call cursor(lnum, col)
|
|
if searchpair(
|
|
\ g:crystal#indent#end_start_regex,
|
|
\ g:crystal#indent#end_middle_regex,
|
|
\ g:crystal#indent#end_end_regex,
|
|
\ 'bW',
|
|
\ g:crystal#indent#skip_expr)
|
|
let n = line('.')
|
|
let ind = indent('.')
|
|
let msl = crystal#indent#GetMSL(n)
|
|
if msl != n
|
|
let ind = indent(msl)
|
|
end
|
|
return ind
|
|
endif
|
|
end
|
|
|
|
let col = crystal#indent#Match(lnum, g:crystal#indent#crystal_indent_keywords)
|
|
if col
|
|
call cursor(lnum, col)
|
|
let ind = virtcol('.') - 1 + s: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
|
|
if crystal#indent#Match(lnum, g:crystal#indent#end_end_regex)
|
|
let ind = indent('.')
|
|
endif
|
|
return ind
|
|
endif
|
|
|
|
" Work on the MSL line. {{{2
|
|
" ---------------------
|
|
|
|
" Set up variables to use and search for MSL to the previous line.
|
|
let p_lnum = lnum
|
|
let lnum = crystal#indent#GetMSL(lnum)
|
|
|
|
" If the previous line wasn't a MSL.
|
|
if p_lnum != lnum
|
|
" If previous line ends bracket and begins non-bracket continuation decrease indent by 1.
|
|
if crystal#indent#Match(p_lnum, g:crystal#indent#bracket_switch_continuation_regex)
|
|
return ind - 1
|
|
" If previous line is a continuation return its indent.
|
|
" TODO: the || crystal#indent#IsInString() thing worries me a bit.
|
|
elseif crystal#indent#Match(p_lnum, g:crystal#indent#non_bracket_continuation_regex) ||
|
|
\ crystal#indent#IsInString(p_lnum,strlen(line))
|
|
return ind
|
|
endif
|
|
endif
|
|
|
|
" Set up more variables, now that we know we wasn't continuation bound.
|
|
let line = getline(lnum)
|
|
let msl_ind = indent(lnum)
|
|
|
|
" If the MSL line had an indenting keyword in it, add a level of indent.
|
|
" TODO: this does not take into account contrived things such as
|
|
" module Foo; class Bar; end
|
|
if crystal#indent#Match(lnum, g:crystal#indent#crystal_indent_keywords)
|
|
let ind = msl_ind + s:sw()
|
|
if crystal#indent#Match(lnum, g:crystal#indent#end_end_regex)
|
|
let ind = ind - s:sw()
|
|
endif
|
|
return ind
|
|
endif
|
|
|
|
" If the previous line ended with an operator -- but wasn't a block
|
|
" ending, closing bracket, or type declaration -- indent one extra
|
|
" level.
|
|
if crystal#indent#Match(lnum, g:crystal#indent#non_bracket_continuation_regex) &&
|
|
\ !crystal#indent#Match(lnum, '^\s*\([\])}]\|end\)')
|
|
if lnum == p_lnum
|
|
let ind = msl_ind + s:sw()
|
|
else
|
|
let ind = msl_ind
|
|
endif
|
|
return ind
|
|
endif
|
|
|
|
" }}}2
|
|
|
|
return ind
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
let &cpo = s:cpo_save
|
|
unlet s:cpo_save
|
|
|
|
" vim:set sw=2 sts=2 ts=8 et:
|
|
|
|
endif
|