(in-package :swank) (defvar *application-hints-tables* '() "A list of hash tables mapping symbols to indentation hints (lists of symbols and numbers as per cl-indent.el). Applications can add hash tables to the list to change the auto indentation slime sends to emacs.") (defun has-application-indentation-hint-p (symbol) (let ((default (load-time-value (gensym)))) (dolist (table *application-hints-tables*) (let ((indentation (gethash symbol table default))) (unless (eq default indentation) (return-from has-application-indentation-hint-p (values indentation t)))))) (values nil nil)) (defun application-indentation-hint (symbol) (let ((indentation (has-application-indentation-hint-p symbol))) (labels ((walk (indentation-spec) (etypecase indentation-spec (null nil) (number indentation-spec) (symbol (string-downcase indentation-spec)) (cons (cons (walk (car indentation-spec)) (walk (cdr indentation-spec))))))) (walk indentation)))) ;;; override swank version of this function (defun symbol-indentation (symbol) "Return a form describing the indentation of SYMBOL. The form is to be used as the `common-lisp-indent-function' property in Emacs." (cond ((has-application-indentation-hint-p symbol) (application-indentation-hint symbol)) ((and (macro-function symbol) (not (known-to-emacs-p symbol))) (let ((arglist (arglist symbol))) (etypecase arglist ((member :not-available) nil) (list (macro-indentation arglist))))) (t nil))) ;;; More complex version. (defun macro-indentation (arglist) (labels ((frob (list &optional base) (if (every (lambda (x) (member x '(nil "&rest") :test #'equal)) list) ;; If there was nothing interesting, don't return anything. nil ;; Otherwise substitute leading NIL's with 4 or 1. (let ((ok t)) (substitute-if (if base 4 1) (lambda (x) (if (and ok (not x)) t (setf ok nil))) list)))) (walk (list level &optional firstp) (when (consp list) (let ((head (car list))) (if (consp head) (let ((indent (frob (walk head (+ level 1) t)))) (cons (list* "&whole" (if (zerop level) 4 1) indent) (walk (cdr list) level))) (case head ;; &BODY is &BODY, this is clear. (&body '("&body")) ;; &KEY is tricksy. If it's at the base level, we want ;; to indent them normally: ;; ;; (foo bar quux ;; :quux t ;; :zot nil) ;; ;; If it's at a destructuring level, we want indent of 1: ;; ;; (with-foo (var arg ;; :foo t ;; :quux nil) ;; ...) (&key (if (zerop level) '("&rest" nil) '("&rest" 1))) ;; &REST is tricksy. If it's at the front of ;; destructuring, we want to indent by 1, otherwise ;; normally: ;; ;; (foo (bar quux ;; zot) ;; ...) ;; ;; but ;; ;; (foo bar quux ;; zot) (&rest (if (and (plusp level) firstp) '("&rest" 1) '("&rest" nil))) ;; &WHOLE and &ENVIRONMENT are skipped as if they weren't there ;; at all. ((&whole &environment) (walk (cddr list) level firstp)) ;; &OPTIONAL is indented normally -- and the &OPTIONAL marker ;; itself is not counted. (&optional (walk (cdr list) level)) ;; Indent normally, walk the tail -- but ;; unknown lambda-list keywords terminate the walk. (otherwise (unless (member head lambda-list-keywords) (cons nil (walk (cdr list) level)))))))))) (frob (walk arglist 0 t) t))) #+nil (progn (assert (equal '(4 4 ("&whole" 4 "&rest" 1) "&body") (macro-indentation '(bar quux (&rest slots) &body body)))) (assert (equal nil (macro-indentation '(a b c &rest more)))) (assert (equal '(4 4 4 "&body") (macro-indentation '(a b c &body more)))) (assert (equal '(("&whole" 4 1 1 "&rest" 1) "&body") (macro-indentation '((name zot &key foo bar) &body body)))) (assert (equal nil (macro-indentation '(x y &key z))))) (provide :swank-indentation)