harry@binky.UUCP (Harry Weeks) (10/05/87)
There have been a few queries recently for enhancements to LISP mode that nicely indent LABELS and other complex constructions. I have an enhanced LISP mode that provides a generalized indenter for complex forms, hooks for indenting semicolon comments, and handling of nested comments and escaped symbol names. Since the file is large, I am providing a brief sketch of how the enhancements work, and anyone who is interested can write to me directly for the file. Harry Weeks Franz Inc. harry%franz.uucp@berkeley.edu -------- (defconst lisp-electric-semicolon nil "*If `t', semicolons that begin comments are indented as they are typed.") (defconst lisp-comment-indent-specification (list comment-column t nil 0) "*Specification list for indentations of semicolon comments. The nth element of the list specifies the indentation for a comment beginning with n semicolons (e.g. the first element of the list is the indentation for comments beginning with one semicolon). Each element of the list may be one of `t' (indent comment just like an s-expression), `nil' (don't change the indentation of the comment, i.e. leave the semicolon where it is), a non- negative integer (specifying the absolute column to which the comment is to be indented), or a negative integer (specifying a negative offset for the comment relative to the current column).") (defvar lisp-tag-indentation 1 "*Indentation of tags relative to containing list. This variable is used by the function `lisp-indent-tagbody' to indent tags that occur within special forms whose symbols have a 'lisp-indent-hook property of 'tag, 'tagbody, or 'keyword. The indentation is relative to the indentation of the parenthesis enclosing the special form.") (defvar lisp-tag-body-indentation 2 "*Indentation of non-tagged lines relative to containing list. This variable is used by the function `lisp-indent-tagbody' to indent normal lines (lines without tags) that occur within special forms whose symbols have a 'lisp-indent-hook property of 'tag, 'tagbody, or 'keyword. The indentation is relative to the indentation of the parenthesis enclosing the special form. If the value is T, the body of tags will be indented as a block at the same indentation as the first s-expression following the tag. In this case, the s-expressions before the first tag are indented as an undistinguished form.") (defvar lisp-tag-indentation-hook nil "*Name of function to apply to return indentation of tag. This variable may be bound to the name of a function to be applied (to three arguments: the character position of the beginning of the tag, the last parse state, and the indent point) to return the appropriate indentation for tags occurring within special forms whose symbols have a 'lisp-indent-hook property of 'tag, 'tagbody, or 'keyword. The inden- tation returned is absolute.") (defvar lisp-maximum-indent-struct-depth nil "*Maximum depth to backtrack out from a sublist for structured indentation. If this variable is NIL, no backtracking will occur and lists whose `car' is a symbol with a 'lisp-indent-hook property of 'label, 'labels, 'flet, 'macrolet, 'defun, or a list may not be indented properly. In addition, quoted lists will not be treated specially. If this variable is T, there is no limit placed on backtracking. A numeric value specifies the maximum depth to backtrack.") (defun lisp-indent-labels (depth count state indent-point) "Function to indent LABELS and related special forms. This function indents forms that have a 'lisp-indent-hook property equal to 'label,'labels, 'flet, or 'macrolet. It is equivalent to specifying a value of '((2 1 def) (0 t 1)) for the 'lisp-indent-hook property and is provided as a shorthand notation." . . . ) (defun lisp-indent-defun (depth count state indent-point) "Function to indent DEFUN and related special forms. This function indents forms that have a 'lisp-indent-hook property equal to 'defun. It is equivalent to specifying a value of '((1 2 quote) (0 t def)) for the 'lisp-indent-hook property and is provided as a shorthand notation." . . . ) (defun lisp-indent-struct (methods depth count state indent-point) "Function to indent Lisp special forms that have substructure. The METHODS is a list of triples, (Depth Count Method), where Depth and Count specify when special treatment is required of a sublist in a form, and Method is the method of indentation to apply to such a sublist. If Depth or Count is t or nil, Method is used for any Depth or Count. The Method will be applied to the form itself if the Depth is zero. The Method is interpreted identically to the 'lisp-indent-hook property of symbols and thus may be recursive (that is, itself a list of triples). If the Method is recursive, it is applied if DEPTH is greater or equal to Depth; this implies that triples should be ordered greatest depth first in lists. In recursive applications of this function to a Method, DEPTH is set to the depth of the current sublist relative to sublist associated with Method. The METHODS list should be the value of the 'lisp-indent-hook property of a symbol that is a Lisp special form having substructure. The DEPTH is the depth of the current sublist relative to the form. The COUNT argument is the number of the current s-expression in the form. As an example, giving LABELS the following property (put 'labels 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1))) will have the effect that s-expressions of the LABELS form itself are indented with 1 distinguished form using `lisp-indent-specform' (this is specified by '(0 t 1)). In addition, all sublists of the first s-expression, which is a list, of the LABELS special form (denoted by '(2 1 ...)) will be treated just like a LAMBDA (whose method is '((1 1 quote) (0 t 1)))." . . . ) (defun lisp-indent-tagbody (state indent-point &optional spec-count &rest keywords) "Function for indenting TAGBODY and related forms. This function indents special forms that have `tags' or `keywords' that should be treated specially. For example, TAGBODY forms have `tags' for GOTO, and IF* forms have `keywords' THEN, ELSE, etc. An optional argument SPEC-COUNT is accepted, specifying the number of distinguished s-expressions in the form. Any further arguments constitute the `keywords' or `tags' that are to be recognized. In the absence of an explicit list, any atomic expression is considered a keyword." . . . ) (defun lisp-indent-if (state indent-point &optional spec-count) "Function for indenting IF special forms. This function is dispatched to indent a form whose `car' is a symbol with a `lisp-indent-hook' property of `if'. This provides a shorthand for a property of '(lisp-indent-tagbody 2 \"then\" \"thenret\" \"else\" \"elseif\")." . . . ) ;; Examples. (put 'assert 'lisp-indent-hook '((1 2 quote) (0 t 2))) (put 'block 'lisp-indent-hook 1) (put 'compiler-let 'lisp-indent-hook '((1 1 quote) (0 t 1))) (put 'defflavor 'lisp-indent-hook '((1 2 quote) (1 3 quote) (0 t 3))) (put 'define-modify-macro 'lisp-indent-hook '((1 2 quote) (0 t 2))) (put 'defsetf 'lisp-indent-hook '((1 2 quote) (0 t 3))) (put 'defstruct 'lisp-indent-hook '((1 1 quote) (0 t 1))) (put 'do 'lisp-indent-hook '((1 1 1) (1 2 1) (0 t (lisp-indent-tagbody 2)))) (put 'flet 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1))) (put 'if 'lisp-indent-hook 'if) (put 'labels 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1))) (put 'lambda 'lisp-indent-hook '((1 1 quote) (0 t 1))) (put 'let 'lisp-indent-hook '((1 1 quote) (0 t 1))) (put 'loop 'lisp-indent-hook 'tagbody) (put 'multiple-value-bind 'lisp-indent-hook '((1 1 quote) (0 t 2)))
tsf@PROOF.ERGO.CS.CMU.EDU (Timothy Freeman) (05/17/88)
Here's code to indent Common Lisp's labels construct properly. It's a bit slow. The problem it solves is that a labels construct looks better when indented like this: (labels ((this-is-a-very-long-function-name () stuff))) than when indented like this: (labels ((this-is-a-very-long-function-name () stuff))) I hereby release this into the public domain. (defun containing-sexp (indent-point) (let (state (retry t) last-sexp containing-sexp) (save-excursion (beginning-of-defun) ;; Find outermost containing sexp (while (< (point) indent-point) (setq state (parse-partial-sexp (point) indent-point 0))) ;; Now state is the state of the parser starting at the ;; outermost containing sexp for indent-point and ending at indent-point. ;; This loop has no side effects on indent-point. (while (and retry (car state) (> (car state) 0)) (setq retry nil) (setq last-sexp (nth 2 state)) (setq containing-sexp (car (cdr state))) ;; Position following last unclosed open. (goto-char (1+ containing-sexp)) ;; Is there a complete sexp since then? (if (and last-sexp (> last-sexp (point))) ;; Yes, but is there a containing sexp after that? (let ((peek (parse-partial-sexp last-sexp indent-point 0))) (if (setq retry (car (cdr peek))) (setq state peek))))) ;; Now: ;; last-sexp is the start of the last sexp finished before point, ;; nil if none. ;; containing-sexp is the start of the innermost sexp ;; containing indent-point, nil if none. ;; state is the state of the parse from last-sexp to ;; indent-point. If last-sexp is nil, then state is ;; vacuous. ;; retry is t if indent-point is the first sexp in its ;; containing sexp. containing-sexp))) (defun in-labels-p (place) "Returns t if we are in the place where we need to indent specially for labels." (save-excursion (let ((cont1 (containing-sexp place))) (and cont1 (let ((cont2 (containing-sexp cont1))) (and cont2 (progn (goto-char cont2) (looking-at "((")) (let ((cont3 (containing-sexp cont2))) (goto-char cont3) (or (looking-at "(labels") (looking-at "(flet"))))))))) (defun new-lisp-indent-hook (indent-point state) (if (in-labels-p indent-point) (+ lisp-body-indent (save-excursion (goto-char (car (cdr state))) (current-column))) (lisp-indent-hook indent-point state))) (setq lisp-indent-hook 'new-lisp-indent-hook) -- Tim Freeman Arpanet: tsf@theory.cs.cmu.edu Uucp: ...!seismo.css.gov!proof.ergo.cs.cmu.edu!tsf