if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'pony') != -1 finish endif " Vim plugin file " Language: Pony " Maintainer: Jak Wings " TODO: Make sure echomsg is off for release. let s:cpo_save = &cpo set cpo&vim "let s:skip = 'InCommentOrLiteral(line("."), col("."))' let s:skip2 = 'InLiteral(line("."), col(".")) || InComment(line("."), col(".")) == 1' let s:skip3 = '!InKeyword(line("."), col("."))' let s:skip4 = '!InBracket(line("."), col("."))' let s:cfstart = '\v<%(ifdef|if|match|while|for|repeat|try|with|recover|object|lambda|iftype)>' let s:cfmiddle = '\v<%(then|elseif|else|until|do|in|elseiftype)>|\|' let s:cfend = '\v' let s:bstartp = '\v<%(ifdef|if|then|elseif|else|(match)|while|for|in|do|try|with|recover|repeat|until|(object)|lambda|iftype|elseiftype)>' function! pony#Indent() if v:lnum <= 1 return 0 endif call cursor(v:lnum, 1) let l:pnzpos = searchpos('.', 'cbnW') if l:pnzpos == [0, 0] return 0 endif if s:InComment2(l:pnzpos) > 1 "echomsg 'Comment' (l:pnzpos[0] . '-' . v:lnum) -1 return cindent(v:lnum) endif if s:InLiteral2(l:pnzpos) "echomsg 'String' (l:pnzpos[0] . '-' . v:lnum) -1 return -1 endif unlet! l:pnzpos " NOTE: Lines started in comments and strings are checked already. let l:pnblnum = s:PrevNonblank(v:lnum - 1) if l:pnblnum < 1 return 0 endif let l:pnbline = getline(l:pnblnum) let l:pnbindent = indent(l:pnblnum) let l:line = getline(v:lnum) let l:indent = l:pnbindent let l:shiftwidth = shiftwidth() " FIXME? let l:continuing = 0 " If the previous line ends with a unary or binary operator, if s:IsContinued(l:pnblnum) let l:contlnum = l:pnblnum let l:ppcontinued = 0 let l:ppnblnum = s:PrevNonblank(l:pnblnum - 1) while s:IsContinued(l:ppnblnum) let l:ppcontinued += 1 let l:contlnum = l:ppnblnum let l:ppnblnum = s:PrevNonblank(l:ppnblnum - 1) endwhile "echomsg 'Continued1' l:pnblnum l:contlnum " If the previous line is also continuing another line, if l:ppcontinued let l:continuing = 1 if getline(l:contlnum) =~# '\v^\s*%(actor|class|struct|primitive|trait|type|interface)>' " reset the indent level. "echomsg 'Continuing0' (l:contlnum . '-' . v:lnum) (l:shiftwidth * 2) let l:indent = l:shiftwidth * 2 else " keep using the previous indent. "echomsg 'Continuing1' (l:pnblnum . '-' . v:lnum) l:pnbindent let l:indent = l:pnbindent endif " if the previous line is part of the definition of a class, elseif l:pnbline =~# '\v^\s*%(actor|class|struct|primitive|trait|type|interface)>' " reset the indent level. "echomsg 'Continuing2' (l:pnblnum . '-' . v:lnum) (l:shiftwidth * 2) let l:continuing = 1 let l:indent = l:shiftwidth * 2 " if the previous line is part of the definition of a method, elseif l:pnbline =~# '\v^\s*%(fun|new|be)>' " reset the indent level. "echomsg 'Continuing3' (l:pnblnum . '-' . v:lnum) (l:pnbindent + l:shiftwidth) let l:continuing = 1 let l:indent = l:pnbindent + l:shiftwidth " if the previous line is the start of a definition body, elseif l:pnbline =~# '=>\s*$' " indent this line. "echomsg 'Continuing4' (l:pnblnum . '-' . v:lnum) (l:pnbindent + l:shiftwidth) let l:continuing = 1 let l:indent = l:pnbindent + l:shiftwidth else " indent this line twice as far. "echomsg 'Continuing5' (l:pnblnum . '-' . v:lnum) (l:pnbindent + l:shiftwidth * 2) let l:continuing = 1 let l:indent = l:pnbindent + l:shiftwidth * 2 endif unlet! l:contlnum l:ppnblnum l:ppcontinued endif " If this line starts a document string, if !l:continuing && l:line =~# '^\s*"""' let l:ppnblnum = s:PrevNonblank(l:pnblnum - 1) if s:IsContinued(l:ppnblnum) let l:contlnum = l:ppnblnum while s:IsContinued(l:ppnblnum) let l:contlnum = l:ppnblnum let l:ppnblnum = s:PrevNonblank(l:ppnblnum - 1) endwhile if getline(l:contlnum) =~# '\v^\s*%(actor|class|struct|primitive|trait|type|interface)>' " reset the indent level. "echomsg 'DocString' (l:contlnum . '-' . v:lnum) l:shiftwidth return l:shiftwidth endif endif unlet! l:contlnum l:ppnblnum endif " If the previous line contains an unmatched opening bracket if !l:continuing && l:pnbline =~# '[{[(]' " if the line ends with an opening bracket, if l:pnbline =~# '[{[(]\s*$' && !s:InCommentOrLiteral(l:pnblnum, col([l:pnblnum, '$']) - 1) " indent this line. let l:indent += l:shiftwidth else " find the unmatched opening bracket, let l:start = [0, 0] let l:end = col([l:pnblnum, '$']) - 1 call cursor(l:pnblnum, l:end) while l:end > 0 let l:start = s:OuterPos(l:start, searchpairpos('(', '', ')', 'bnW', s:skip4, l:pnblnum)) let l:start = s:OuterPos(l:start, searchpairpos('\[', '', '\]', 'bnW', s:skip4, l:pnblnum)) let l:start = s:OuterPos(l:start, searchpairpos('{', '', '}', 'bnW', s:skip4, l:pnblnum)) if l:start == [0, 0] break endif " find the matched closing bracket on the same line, call cursor(l:start[0], l:start[1]) let l:c = s:CharAtCursor(l:start[0], l:start[1]) if searchpair(escape(l:c, '['), '', escape(tr(l:c, '([{', ')]}'), ']'), \ 'znW', s:skip4, l:pnblnum) < 1 " the unmatched opening bracket is found, break endif let l:end = l:start[1] let l:start = [0, 0] endwhile if l:start != [0, 0] " indent this line. "echomsg 'Open bracket' (l:pnblnum . '-' . v:lnum) (l:indent + l:shiftwidth) let l:indent += l:shiftwidth endif endif unlet! l:start l:end l:c endif " If there is a matched closing bracket on the previous line, " NOTE: " >|[ " | (1 - " | 1) * 2] " | command " ^ if !l:continuing call cursor(l:pnblnum, 1) " find the last closing bracket, let l:end = [0, 0] let l:end = s:OuterPos(l:end, searchpairpos('(', '', ')', 'zncr', s:skip4, l:pnblnum)) let l:end = s:OuterPos(l:end, searchpairpos('\[', '', '\]', 'zncr', s:skip4, l:pnblnum)) let l:end = s:OuterPos(l:end, searchpairpos('{', '', '}', 'zncr', s:skip4, l:pnblnum)) if l:end != [0, 0] " find the matched opening bracket on another line, let l:c = s:CharAtCursor(l:end[0], l:end[1]) let l:start = searchpairpos(escape(tr(l:c, ')]}', '([{'), '['), '', escape(l:c, ']'), 'nbW', s:skip4) if l:start[0] != l:end[0] " and then this line has the same indent as the line the matched bracket stays. "echomsg 'Matched bracket' (l:start[0] . '-' . v:lnum) indent(l:start[0]) let l:indent = indent(l:start[0]) endif endif unlet! l:start l:end l:c endif " If there is a matched closing bracket on this line, " NOTE: " |[ " >| (1 - " | 1) * 2 " |] " ^ ^ if l:line =~# '^\s*[)\]}]' " find the first closing bracket, call cursor(v:lnum, 1) let l:end = [0, 0] let l:end = s:InnerPos(l:end, searchpairpos('(', '', ')', 'zncW', s:skip4, v:lnum)) let l:end = s:InnerPos(l:end, searchpairpos('\[', '', '\]', 'zncW', s:skip4, v:lnum)) let l:end = s:InnerPos(l:end, searchpairpos('{', '', '}', 'zncW', s:skip4, v:lnum)) if l:end != [0, 0] " find the matched opening bracket on another line, let l:c = s:CharAtCursor(l:end[0], l:end[1]) let l:start = searchpairpos(escape(tr(l:c, ')]}', '([{'), '['), '', escape(l:c, ']'), 'nbW', s:skip4) if l:start[0] != l:end[0] " and then this line has the same indent as the line the matched bracket stays. "echomsg 'Closing Bracket' (l:start[0] . '-' . v:lnum) indent(l:start[0]) let l:indent = indent(l:start[0]) endif endif unlet! l:start l:end l:c endif " If this line starts the definition of a method, closure or match case, if l:line =~# '^\s*=>' " find the start of the definition, call cursor(v:lnum, 1) let l:start = searchpairpos('\v<%(new|be|fun|lambda)>|\|', '', '=>\zs', 'bnW', s:skip3) if l:start != [0, 0] " then this line has the same indent as the start. "echomsg 'Method body' (l:start[0] . '-' . v:lnum) indent(l:start[0]) return indent(l:start[0]) endif unlet! l:start endif " If this line starts a class definition or starts an alias, if l:line =~# '\v^\s*%(actor|class|struct|primitive|trait|interface|use|type)>' " reset the indent level. return 0 endif " If this line starts a method definition, if l:line =~# '\v^\s*%(new|be|fun)>' call cursor(v:lnum, 1) let l:start = searchpairpos(s:cfstart, s:cfmiddle, s:cfend, 'bW', s:skip3) if l:start != [0, 0] let l:start = searchpos(s:bstartp, 'zcnpW', l:start[0]) " see if it is in an object block, if l:start[2] == 3 "echomsg 'Method in object' (l:start[0] . '-' . v:lnum) (l:shiftwidth + indent(l:start[0])) return l:shiftwidth + indent(l:start[0]) endif endif return l:shiftwidth endif " If this line starts a match case, call cursor(v:lnum, 1) if l:line =~# '^\s*|' && s:InKeyword(searchpos('|', 'znW', v:lnum)) " find the start or the previous case of the match block, let l:start = searchpairpos(s:cfstart, s:cfmiddle, s:cfend, 'bnW', s:skip3) if l:start != [0, 0] " then this line has the same indent as the start. "echomsg 'Match case' (l:start[0] . '-' . v:lnum) indent(l:start[0]) return indent(l:start[0]) endif unlet! l:start endif " If this line ends (part of) a control flow, if l:line =~# '\v^\s*%(end|elseif|else|then|in|do|until|elseiftype)>' " find the start or middle of the control block, call cursor(v:lnum, 1) let l:start = searchpairpos(s:cfstart, s:cfmiddle, s:cfend, 'bnW', s:skip3) if l:start != [0, 0] " then this line has the same indent as the start. "echomsg 'Block end' (l:start[0] . '-' . v:lnum) indent(l:start[0]) return indent(l:start[0]) endif unlet! l:start endif " If the previous line starts a class definition, if l:pnbline =~# '\v^\s*%(actor|class|struct|primitive|trait|type|interface)>' " reset the indent level. if s:IsContinued(l:pnblnum) return l:shiftwidth * 2 else return l:shiftwidth endif endif " If the previous line starts a method definition, if l:pnbline =~# '\v^\s*%(new|be|fun)>' return l:pnbindent + l:shiftwidth endif " If the previous line starts (part of) a control flow, call cursor(l:pnblnum, 1) while 1 " find the start of the control block, let l:start = searchpos(s:bstartp, 'zcepW', l:pnblnum) if l:start[2] == 0 break endif if !s:InKeyword(l:start[0:1]) call cursor(l:pnblnum, l:start[1] + 3) continue endif let l:index = l:start[2] " find the end of the control block on the same line, let l:end = searchpair(s:cfstart, '', s:cfend, 'znW', s:skip3, l:pnblnum) " if the control block is not ended, if l:end < 1 " if this line is a case for a match, if l:index == 2 && l:line =~# '^\s*|' " then this line has the same indent as the start of the match block. return l:pnbindent else " then indent this line. "echomsg 'Block start' (l:pnblnum . '-' . v:lnum) (l:pnbindent + l:shiftwidth) return l:pnbindent + l:shiftwidth endif endif endwhile unlet! l:start l:end l:index return l:indent endfunction function! s:PrevNonblank(lnum) let l:lnum = prevnonblank(a:lnum) while l:lnum > 0 && (s:InComment2(l:lnum, 1) || s:InLiteral2(l:lnum, 1)) let l:lnum = prevnonblank(l:lnum - 1) endwhile return l:lnum endfunction " NOTE: " v " |1 /* comment */ " |2 function! s:IsContinued(lnum) let l:lnum = s:PrevNonblank(a:lnum) if l:lnum < 1 return 0 endif let l:line = getline(l:lnum) let l:width = strwidth(substitute(l:line, '\s*$', '', '')) " FIXME? " | 1 + // " | // " | 2 return !s:InCommentOrLiteral(a:lnum, l:width) \ && (l:line =~# '\v<%(and|or|xor|is|isnt|as|not|consume|addressof|digestof)\s*$' \ || l:line =~# '\v%([=\-.]\>|[]\=\~?|\<\<\~?|\>\>\~?|\<:|[+\-*/%<>]\~?|[.,|:@~])\s*$' \ ) endfunction function! s:InCommentOrLiteral(...) return call('s:InComment', a:000) || call('s:InLiteral', a:000) endfunction function! s:InKeyword(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) for id in s:Or(synstack(l:lnum, l:col), []) if synIDattr(id, 'name') =~# '^ponyKw' return 1 endif endfor return 0 endfunction function! s:InBracket(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) for id in s:Or(synstack(l:lnum, l:col), []) if synIDattr(id, 'name') ==# 'ponyBracket' return 1 endif endfor return 0 endfunction function! s:InComment(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) let l:stack = synstack(l:lnum, l:col) let l:i = len(l:stack) while l:i > 0 let l:sname = synIDattr(l:stack[l:i - 1], 'name') if l:sname =~# '^ponyNestedCommentX\?$' return 1 + l:i - (l:sname =~# 'X$') elseif l:sname =~# '^ponyCommentX\?$' return 1 endif let l:i -= 1 endwhile return 0 endfunction function! s:InLiteral(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) let l:stack = synstack(l:lnum, l:col) let l:i = len(l:stack) while l:i > 0 let l:sname = synIDattr(l:stack[l:i - 1], 'name') if l:sname =~# '^ponyDocumentStringX\?$' return 3 elseif l:sname =~# '^ponyStringX\?$' return 2 elseif l:sname =~# '^ponyCharacterX\?$' return 1 endif let l:i -= 1 endwhile return 0 endfunction " NOTE: " |// //inside " ^^^^^^^^^^ " |/* /*inside*/ */ " ^^^^^^^^^^^^^^ function! s:InComment2(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) let l:stack = synstack(l:lnum, l:col) let l:i = len(l:stack) while l:i > 0 let l:sname = synIDattr(l:stack[l:i - 1], 'name') if l:sname ==# 'ponyNestedComment' return 1 + l:i elseif l:sname ==# 'ponyComment' return 1 elseif l:sname =~# '\v^pony%(Nested)?CommentX$' return 0 endif let l:i -= 1 endwhile return 0 endfunction " NOTE: " |"inside" " ^^^^^^ " |"""inside""""" " ^^^^^^^^^^^^ function! s:InLiteral2(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) let l:stack = synstack(l:lnum, l:col) let l:i = len(l:stack) while l:i > 0 let l:sname = synIDattr(l:stack[l:i - 1], 'name') if l:sname ==# 'ponyDocumentString' return 3 elseif l:sname ==# 'ponyString' return 2 elseif l:sname ==# 'ponyCharacter' return 1 elseif l:sname =~# '\v^pony%(DocumentString|String|Character)X$' return 0 endif let l:i -= 1 endwhile return 0 endfunction function! s:CharAtCursor(...) let [l:lnum, l:col] = (type(a:1) == type([]) ? a:1 : a:000) return matchstr(getline(l:lnum), '\%' . l:col . 'c.') endfunction function! s:Or(x, y) return !empty(a:x) ? a:x : a:y endfunction function! s:InnerPos(x, y) if a:x == [0, 0] return a:y elseif a:y == [0, 0] return a:x else return a:x[1] < a:y[1] ? a:x : a:y end endfunction function! s:OuterPos(x, y) if a:x == [0, 0] return a:y elseif a:y == [0, 0] return a:x else return a:x[1] > a:y[1] ? a:x : a:y end endfunction function! pony#ClearTrailingSpace(all, alt, ...) let l:force = (a:0 > 0 ? a:1 : 0) if !l:force && (&readonly || !&modifiable || !&modified) return endif if a:all for lnum in range(1, line('$')) let l:line = getline(lnum) let l:end = col([lnum, '$']) - 1 if l:end > 0 && l:line =~# '\s$' && !s:InLiteral(lnum, l:end) if a:alt call setline(lnum, substitute(l:line, '\S\@<=\s\s*$', '', '')) else call setline(lnum, substitute(l:line, '\s\+$', '', '')) endif endif endfor else let l:lnum = line('.') let l:end = col('$') - 1 let l:line = getline(l:lnum) if l:line =~# '\s$' && !s:InLiteral(l:lnum, l:end) if a:alt call setline(l:lnum, substitute(l:line, '\s\+$', '', '')) else call setline(l:lnum, substitute(l:line, '\S\@<=\s\s*$', '', '')) endif endif endif endfunction let &cpo = s:cpo_save unlet s:cpo_save