# HG changeset patch # User Steve Losh # Date 1441025283 0 # Node ID a625ea2102024ae8e8e8a0e9f2981918cd3e54a3 # Parent d9910af7d3eee76cad24b4a7f5007f98380b11a6 Add jank-ass ripped slimv lisp indenter diff -r d9910af7d3ee -r a625ea210202 vim/indent/lisp.vim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/indent/lisp.vim Mon Aug 31 12:48:03 2015 +0000 @@ -0,0 +1,369 @@ +" lisp.vim: +" Lisp indent plugin for Slimv +" Version: 0.9.5 +" Last Change: 21 Feb 2012 +" Maintainer: Tamas Kovacs +" License: This file is placed in the public domain. +" No warranty, express or implied. +" *** *** Use At-Your-Own-Risk! *** *** +" +" ===================================================================== +" +" Load Once: +if exists("b:did_indent") + finish +endif + +" Handle cases when lisp dialects explicitly use the lisp indent plugins +if &ft == "clojure" && exists("g:slimv_disable_clojure") + finish +endif + +if &ft == "scheme" && exists("g:slimv_disable_scheme") + finish +endif + + +" Maximum number of lines searched backwards for indenting special forms +if !exists( 'g:slimv_indent_maxlines' ) + let g:slimv_indent_maxlines = 50 +endif + +" Special indentation for keyword lists +if !exists( 'g:slimv_indent_keylists' ) + let g:slimv_indent_keylists = 1 +endif + +let s:skip_sc = 'synIDattr(synID(line("."), col("."), 0), "name") =~ "[Ss]tring\\|[Cc]omment"' + " Skip matches inside string or comment +let s:skip_q = 'getline(".")[col(".")-2] == "\\"' " Skip escaped double quote characters in matches + +let s:indent = '' " Most recent indentation info +let s:spec_indent = 'flet\|labels\|macrolet\|symbol-macrolet' + " List of symbols need special indenting +let s:spec_param = 'defmacro' " List of symbols with special parameter list +let s:binding_form = 'let\|let\*' " List of symbols with binding list + +" Get the filetype (Lisp dialect) used by Slimv +function! SlimvGetFiletype() + if &ft != '' + " Return Vim filetype if defined + return &ft + endif + + if match( tolower( g:slimv_lisp ), 'clojure' ) >= 0 || match( tolower( g:slimv_lisp ), 'clj' ) >= 0 + " Must be Clojure + return 'clojure' + endif + + " We have no clue, guess its lisp + return 'lisp' +endfunction + +" Some multi-byte characters screw up the built-in lispindent() +" This function is a wrapper that tries to fix it +" TODO: implement custom indent procedure and omit lispindent() +function! SlimvLispindent( lnum ) + set lisp + let li = lispindent( a:lnum ) + set nolisp + let backline = max([a:lnum-g:slimv_indent_maxlines, 1]) + let oldpos = getpos( '.' ) + call cursor( oldpos[1], 1 ) + " Find containing form + let [lhead, chead] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if lhead == 0 + " No containing form, lispindent() is OK + call cursor( oldpos[1], oldpos[2] ) + return li + endif + " Find outer form + let [lparent, cparent] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + call cursor( oldpos[1], oldpos[2] ) + if lparent == 0 || lhead != lparent + " No outer form or starting above inner form, lispindent() is OK + return li + endif + " Count extra bytes before the function header + let header = strpart( getline( lparent ), 0 ) + let total_extra = 0 + let extra = 0 + let c = 0 + while a:lnum > 0 && c < chead-1 + let bytes = byteidx( header, c+1 ) - byteidx( header, c ) + if bytes > 1 + let total_extra = total_extra + bytes - 1 + if c >= cparent && extra < 10 + " Extra bytes in the outer function header + let extra = extra + bytes - 1 + endif + endif + let c = c + 1 + endwhile + if total_extra == 0 + " No multi-byte character, lispindent() is OK + return li + endif + " In some cases ending spaces add up to lispindent() if there are multi-byte characters + let ending_sp = len( matchstr( getline( lparent ), ' *$' ) ) + " Determine how wrong lispindent() is based on the number of extra bytes + " These values were determined empirically + if lparent == a:lnum - 1 + " Function header is in the previous line + if extra == 0 && total_extra > 1 + let ending_sp = ending_sp + 1 + endif + return li + [0, 1, 0, -3, -3, -3, -5, -5, -7, -7, -8][extra] - ending_sp + else + " Function header is in an upper line + if extra == 0 || total_extra == extra + let ending_sp = 0 + endif + return li + [0, 1, 0, -2, -2, -3, -3, -3, -3, -3, -3][extra] - ending_sp + endif +endfunction + +" Return Lisp source code indentation at the given line +function! SlimvIndent( lnum ) + if &autoindent == 0 || a:lnum <= 1 + " Start of the file + return 0 + endif + let pnum = prevnonblank(a:lnum - 1) + if pnum == 0 + " Hit the start of the file, use zero indent. + return 0 + endif + let oldpos = getpos( '.' ) + let linenum = a:lnum + + " Handle multi-line string + let plen = len( getline( pnum ) ) + if synIDattr( synID( pnum, plen, 0), 'name' ) =~ '[Ss]tring' && getline(pnum)[plen-1] != '"' + " Previous non-blank line ends with an unclosed string, so this is a multi-line string + let [l, c] = searchpairpos( '"', '', '"', 'bnW', s:skip_q ) + if l == pnum && c > 0 + " Indent to the opening double quote (if found) + return c + else + return SlimvLispindent( linenum ) + endif + endif + if synIDattr( synID( pnum, 1, 0), 'name' ) =~ '[Ss]tring' && getline(pnum)[0] != '"' + " Previous non-blank line is the last line of a multi-line string + call cursor( pnum, 1 ) + " First find the end of the multi-line string (omit \" characters) + let [lend, cend] = searchpos( '[^\\]"', 'nW' ) + if lend > 0 && strpart(getline(lend), cend+1) =~ '(\|)\|\[\|\]\|{\|}' + " Structural change after the string, no special handling + else + " Find the start of the multi-line string (omit \" characters) + let [l, c] = searchpairpos( '"', '', '"', 'bnW', s:skip_q ) + if l > 0 && strpart(getline(l), 0, c-1) =~ '^\s*$' + " Nothing else before the string: indent to the opening " + return c - 1 + endif + if l > 0 + " Pretend that we are really after the first line of the multi-line string + let pnum = l + let linenum = l + 1 + endif + endif + call cursor( oldpos[1], oldpos[2] ) + endif + + " Handle special indentation style for flet, labels, etc. + " When searching for containing forms, don't go back + " more than g:slimv_indent_maxlines lines. + let backline = max([pnum-g:slimv_indent_maxlines, 1]) + let indent_keylists = g:slimv_indent_keylists + + " Check if the previous line actually ends with a multi-line subform + let parent = pnum + let [l, c] = searchpos( ')', 'bW' ) + if l == pnum + let [l, c] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if l > 0 + " Make sure it is not a top level form and the containing form starts in the same line + let [l2, c2] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if l2 == l + " Remember the first line of the multi-line form + let parent = l + endif + endif + endif + + " Find beginning of the innermost containing form + call cursor( oldpos[1], 1 ) + let [l, c] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if l > 0 + if SlimvGetFiletype() =~ '.*\(clojure\|scheme\|racket\).*' + " Is this a clojure form with [] binding list? + call cursor( oldpos[1], oldpos[2] ) + let [lb, cb] = searchpairpos( '\[', '', '\]', 'bW', s:skip_sc, backline ) + if lb >= l && (lb > l || cb > c) + return cb + endif + endif + " Is this a form with special indentation? + let line = strpart( getline(l), c-1 ) + if match( line, '\c^(\s*\('.s:spec_indent.'\)\>' ) >= 0 + " Search for the binding list and jump to its end + if search( '(' ) > 0 + call searchpair( '(', '', ')', '', s:skip_sc ) + if line('.') == pnum + " We are indenting the first line after the end of the binding list + return c + 1 + endif + endif + elseif l == pnum + " If the containing form starts above this line then find the + " second outer containing form (possible start of the binding list) + let [l2, c2] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if l2 > 0 + let line2 = strpart( getline(l2), c2-1 ) + if match( line2, '\c^(\s*\('.s:spec_param.'\)\>' ) >= 0 + if search( '(' ) > 0 + if line('.') == l && col('.') == c + " This is the parameter list of a special form + return c + endif + endif + endif + if SlimvGetFiletype() !~ '.*clojure.*' + if l2 == l && match( line2, '\c^(\s*\('.s:binding_form.'\)\>' ) >= 0 + " Is this a lisp form with binding list? + return c + endif + if match( line2, '\c^(\s*cond\>' ) >= 0 && match( line, '\c^(\s*t\>' ) >= 0 + " Is this the 't' case for a 'cond' form? + return c + endif + if match( line2, '\c^(\s*defpackage\>' ) >= 0 + let indent_keylists = 0 + endif + endif + " Go one level higher and check if we reached a special form + let [l3, c3] = searchpairpos( '(', '', ')', 'bW', s:skip_sc, backline ) + if l3 > 0 + " Is this a form with special indentation? + let line3 = strpart( getline(l3), c3-1 ) + if match( line3, '\c^(\s*\('.s:spec_indent.'\)\>' ) >= 0 + " This is the first body-line of a binding + return c + 1 + endif + if match( line3, '\c^(\s*defsystem\>' ) >= 0 + let indent_keylists = 0 + endif + " Finally go to the topmost level to check for some forms with special keyword indenting + let [l4, c4] = searchpairpos( '(', '', ')', 'brW', s:skip_sc, backline ) + if l4 > 0 + let line4 = strpart( getline(l4), c4-1 ) + if match( line4, '\c^(\s*defsystem\>' ) >= 0 + let indent_keylists = 0 + endif + endif + endif + endif + endif + endif + + " Restore all cursor movements + call cursor( oldpos[1], oldpos[2] ) + + " Check if the current form started in the previous nonblank line + if l == parent + " Found opening paren in the previous line + let line = getline(l) + let form = strpart( line, c ) + " Determine the length of the function part up to the 1st argument + let funclen = matchend( form, '\s*\S*\s*' ) + 1 + " Contract strings, remove comments + let form = substitute( form, '".\{-}[^\\]"', '""', 'g' ) + let form = substitute( form, ';.*$', '', 'g' ) + " Contract subforms by replacing them with a single character + let f = '' + while form != f + let f = form + let form = substitute( form, '([^()]*)', '0', 'g' ) + let form = substitute( form, '([^()]*$', '0', 'g' ) + let form = substitute( form, '\[[^\[\]]*\]', '0', 'g' ) + let form = substitute( form, '\[[^\[\]]*$', '0', 'g' ) + let form = substitute( form, '{[^{}]*}', '0', 'g' ) + let form = substitute( form, '{[^{}]*$', '0', 'g' ) + endwhile + " Find out the function name + let func = matchstr( form, '\<\k*\>' ) + " If it's a keyword, keep the indentation straight + if indent_keylists && strpart(func, 0, 1) == ':' + if form =~ '^:\S*\s\+\S' + " This keyword has an associated value in the same line + return c + else + " The keyword stands alone in its line with no associated value + return c + 1 + endif + endif + if SlimvGetFiletype() =~ '.*clojure.*' + " Fix clojure specific indentation issues not handled by the default lisp.vim + if match( func, 'defn$' ) >= 0 + return c + 1 + endif + else + if match( func, 'defgeneric$' ) >= 0 || match( func, 'defsystem$' ) >= 0 || match( func, 'aif$' ) >= 0 + return c + 1 + endif + endif + " Remove package specification + let func = substitute(func, '^.*:', '', '') + if func != '' && s:swank_connected + " Look how many arguments are on the same line + " If an argument is actually a multi-line subform, then replace it with a single character + let form = substitute( form, "([^()]*$", '0', 'g' ) + let form = substitute( form, "[()\\[\\]{}#'`,]", '', 'g' ) + let args_here = len( split( form ) ) - 1 + " Get swank indent info + let s:indent = '' + silent execute 'python get_indent_info("' . func . '")' + if s:indent != '' && s:indent == args_here + " The next one is an &body argument, so indent by 2 spaces from the opening '(' + return c + 1 + endif + let llen = len( line ) + if synIDattr( synID( l, llen, 0), 'name' ) =~ '[Ss]tring' && line[llen-1] != '"' + " Parent line ends with a multi-line string + " lispindent() fails to handle it correctly + if s:indent == '' && args_here > 0 + " No &body argument, ignore lispindent() and indent to the 1st argument + return c + funclen - 1 + endif + endif + endif + endif + + " Use default Lisp indenting + let li = SlimvLispindent(linenum) + let line = strpart( getline(linenum-1), li-1 ) + let gap = matchend( line, '^(\s\+\S' ) + if gap >= 0 + " Align to the gap between the opening paren and the first atom + return li + gap - 2 + endif + return li +endfunction + +" Convert indent value to spaces or a mix of tabs and spaces +" depending on the value of 'expandtab' +function! s:MakeIndent( indent ) + if &expandtab + return repeat( ' ', a:indent ) + else + return repeat( "\", a:indent / &tabstop ) . repeat( ' ', a:indent % &tabstop ) + endif +endfunction + + +setlocal nolisp +setlocal autoindent +setlocal expandtab +setlocal indentexpr=SlimvIndent(v:lnum)