# HG changeset patch # User Steve Losh # Date 1456072649 0 # Node ID 99e032674ddd2d60239fc4e4bf9eb11c63b73024 # Parent 74729ab9307b1e1acd2b64b1a19deb393f60fb01 Indent common lisp correctly diff -r 74729ab9307b -r 99e032674ddd bin/lispindent --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/lispindent Sun Feb 21 16:37:29 2016 +0000 @@ -0,0 +1,190 @@ +":";if test -z "$LISP"; then +":"; if test "$USER" = evalwhen; then LISP=ecl +":"; elif test "$(arch 2>/dev/null)" = ppc; then LISP=clozure +":"; else LISP=sbcl +":"; fi; fi +":";if test "$LISP" = clisp; then exec clisp -q $0 +":";elif test "$LISP" = clozure; then exec ccl -b -Q -l $0 +":";elif test "$LISP" = ecl; then exec ecl -shell $0 +":";elif test "$LISP" = sbcl; then exec sbcl --script $0 +":";fi + +;Dorai Sitaram +;Oct 8, 1999 +;last change 2014-09-16 + +;this script takes lines of Lisp or Scheme code from its +;stdin and produces an indented version thereof on its +;stdout + +(defvar *lisp-keywords* '()) + +(defun define-with-lisp-indent-number (n syms) + (dolist (sym syms) + (let* ((x (symbol-name sym)) + (c (assoc x *lisp-keywords* :test #'string-equal))) + (unless c + (push (setq c (cons x nil)) *lisp-keywords*)) + (setf (cdr c) n)))) + +(define-with-lisp-indent-number 0 + '(block + handler-bind + loop)) + +(define-with-lisp-indent-number 1 + '(case + defpackage do-all-symbols do-external-symbols dolist do-symbols dotimes + ecase etypecase eval-when + flet + handler-case + labels lambda let let* let-values + macrolet + prog1 + typecase + unless unwind-protect + when with-input-from-string with-open-file with-open-socket + with-open-stream with-output-to-string)) + +(define-with-lisp-indent-number 2 + '(assert + defun destructuring-bind do do* + if + multiple-value-bind + with-slots)) + +(with-open-file (i (merge-pathnames ".lispwords" (user-homedir-pathname)) + :if-does-not-exist nil) + (when i + (loop + (let ((w (or (read i nil) (return)))) + (define-with-lisp-indent-number (car w) (cdr w)))))) + +(defun past-next-token (s i n) + (let ((escapep nil)) + (loop + (when (>= i n) (return i)) + (let ((c (char s i))) + (cond (escapep (setq escapep nil)) + ((char= c #\\) (setq escapep t)) + ((char= c #\#) + (let ((j (+ i 1))) + (if (>= j n) (return i) + (let ((c (char s j))) + (cond ((char= c #\\) (setq escapep t i j)) + (t (return i))))))) + ((member c '(#\space #\tab #\( #\) #\[ #\] #\" #\' #\` #\, #\;)) + (return i)))) + (incf i)))) + +(defun lisp-indent-number (s &optional (possible-keyword-p t)) + (or (cdr (assoc s *lisp-keywords* :test #'string-equal)) + (if (zerop (or (search "def" s :test #'char-equal) -1)) + 0 + (if possible-keyword-p + (let ((p (position #\: s :from-end t))) + (if p + (lisp-indent-number (subseq s (1+ p)) nil) + -1)) + -1)))) + +(defun literal-token-p (s) + (let ((colon-pos (position #\: s))) + (if colon-pos + (if (= colon-pos 0) t nil) + (let ((s (read-from-string s))) + (or (characterp s) (numberp s) (stringp s)))))) + +;(trace lisp-indent-number literal-token-p read-from-string past-next-token) + +(defstruct lparen + spaces-before + num-aligned-subforms + (num-finished-subforms 0)) + +(defun calc-subindent (s i n) + (let* ((j (past-next-token s i n)) + (num-aligned-subforms 0) + (left-indent + (if (= j i) 1 + (let ((w (subseq s i j))) + (if (and (>= i 2) (member (char s (- i 2)) '(#\' #\`))) 1 + (let ((nas (lisp-indent-number w))) + (cond ((>= nas 0) (setq num-aligned-subforms nas) + 2) + ((literal-token-p w) 1) + ((= j n) 1) + (t (+ (- j i) 2))))))))) + (values left-indent num-aligned-subforms (1- j)))) + +(defun num-leading-spaces (s) + (let ((n (length s)) + (i 0) (j 0)) + (loop + (when (>= i n) (return 0)) + (case (char s i) + (#\space (incf i) (incf j)) + (#\tab (incf i) (incf j 8)) + (t (return j)))))) + +(defun string-trim-blanks (s) + (string-trim '(#\space #\tab #\newline #\return) s)) + +(defun indent-lines () + (let ((left-i 0) (paren-stack '()) (stringp nil)) + (loop + (let* ((curr-line (or (read-line nil nil) (return))) + (leading-spaces (num-leading-spaces curr-line)) + (curr-left-i + (cond (stringp leading-spaces) + ((null paren-stack) + (when (= left-i 0) (setq left-i leading-spaces)) + left-i) + (t (let* ((lp (car paren-stack)) + (nas (lparen-num-aligned-subforms lp)) + (nfs (lparen-num-finished-subforms lp)) + (extra-w 0)) + (when (< nfs nas) ;(and (>= nas 0) (< nfs nas)) + (incf (lparen-num-finished-subforms lp)) + (setq extra-w 2)) + (+ (lparen-spaces-before lp) + extra-w)))))) + (setq curr-line (string-trim-blanks curr-line)) + (dotimes (k curr-left-i) (write-char #\space)) + (princ curr-line) (terpri) + ; + (let ((i 0) (n (length curr-line)) (escapep nil) + (inter-word-space-p nil)) + (loop + (when (>= i n) (return)) + (let ((c (char curr-line i))) + (cond (escapep (setq escapep nil)) + ((char= c #\\) (setq escapep t)) + (stringp (when (char= c #\") (setq stringp nil))) + ((char= c #\;) (return)) + ((char= c #\") (setq stringp t)) + ((member c '(#\space #\tab) :test #'char=) + (unless inter-word-space-p + (setq inter-word-space-p t) + (let ((lp (car paren-stack))) + (when lp + (incf (lparen-num-finished-subforms lp)))))) + ((member c '(#\( #\[) :test #'char=) + (setq inter-word-space-p nil) + (multiple-value-bind (left-indent num-aligned-subforms j) + (calc-subindent curr-line (1+ i) n) + (push + (make-lparen :spaces-before (+ i curr-left-i left-indent) + :num-aligned-subforms num-aligned-subforms) + paren-stack) + (setq i j))) + ((member c '(#\) #\]) :test #'char=) + (setq inter-word-space-p nil) + (cond (paren-stack (pop paren-stack)) + (t (setq left-i 0)))) + (t (setq inter-word-space-p nil))) + (incf i)))))))) + +(indent-lines) + +;eof diff -r 74729ab9307b -r 99e032674ddd vim/indent/lisp.vim --- a/vim/indent/lisp.vim Sat Feb 20 13:18:08 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,374 +0,0 @@ -" 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 - - if match( func, 'define-' ) >= 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) diff -r 74729ab9307b -r 99e032674ddd vim/vimrc --- a/vim/vimrc Sat Feb 20 13:18:08 2016 +0000 +++ b/vim/vimrc Sun Feb 21 16:37:29 2016 +0000 @@ -1022,9 +1022,12 @@ " )) " Indent top-level form. - au FileType lisp nmap = mz:silent normal99[(v%='z + " au FileType lisp nmap = mz:silent normal99[(v%='z + au FileType lisp nmap gi mz:silent normal99[(v%='z " ]) + au FileType lisp setlocal equalprg=LISP=sbcl\ lispindent + " s/it/happening/ au FileType lisp silent! call OozeMapKeys() augroup END