diff options
Diffstat (limited to 'lisp/emacs-lisp/lisp-mode.el')
-rw-r--r-- | lisp/emacs-lisp/lisp-mode.el | 1574 |
1 files changed, 1574 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el new file mode 100644 index 00000000000..7e39a77aed5 --- /dev/null +++ b/lisp/emacs-lisp/lisp-mode.el @@ -0,0 +1,1574 @@ +;;; lisp-mode.el --- Lisp mode, and its idiosyncratic commands -*- lexical-binding:t -*- + +;; Copyright (C) 1985-1986, 1999-2022 Free Software Foundation, Inc. + +;; Maintainer: emacs-devel@gnu.org +;; Keywords: lisp, languages +;; Package: emacs + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; The base major mode for editing Lisp code (used also for Emacs Lisp). +;; This mode is documented in the Emacs manual. + +;;; Code: + +(eval-when-compile (require 'cl-lib)) +(eval-when-compile (require 'subr-x)) + +(defvar font-lock-comment-face) +(defvar font-lock-doc-face) +(defvar font-lock-keywords-case-fold-search) +(defvar font-lock-string-face) + +(define-abbrev-table 'lisp-mode-abbrev-table () + "Abbrev table for Lisp mode.") + +(defvar lisp-data-mode-syntax-table + (let ((table (make-syntax-table)) + (i 0)) + (while (< i ?0) + (modify-syntax-entry i "_ " table) + (setq i (1+ i))) + (setq i (1+ ?9)) + (while (< i ?A) + (modify-syntax-entry i "_ " table) + (setq i (1+ i))) + (setq i (1+ ?Z)) + (while (< i ?a) + (modify-syntax-entry i "_ " table) + (setq i (1+ i))) + (setq i (1+ ?z)) + (while (< i 128) + (modify-syntax-entry i "_ " table) + (setq i (1+ i))) + (modify-syntax-entry ?\s " " table) + ;; Non-break space acts as whitespace. + (modify-syntax-entry ?\xa0 " " table) + (modify-syntax-entry ?\t " " table) + (modify-syntax-entry ?\f " " table) + (modify-syntax-entry ?\n "> " table) + (modify-syntax-entry ?\; "< " table) + (modify-syntax-entry ?` "' " table) + (modify-syntax-entry ?' "' " table) + (modify-syntax-entry ?, "' " table) + (modify-syntax-entry ?@ "_ p" table) + ;; Used to be singlequote; changed for flonums. + (modify-syntax-entry ?. "_ " table) + (modify-syntax-entry ?# "' " table) + (modify-syntax-entry ?\" "\" " table) + (modify-syntax-entry ?\\ "\\ " table) + (modify-syntax-entry ?\( "() " table) + (modify-syntax-entry ?\) ")( " table) + (modify-syntax-entry ?\[ "(]" table) + (modify-syntax-entry ?\] ")[" table) + table) + "Parent syntax table used in Lisp modes.") + +(defvar lisp-mode-syntax-table + (let ((table (make-syntax-table lisp-data-mode-syntax-table))) + (modify-syntax-entry ?\[ "_ " table) + (modify-syntax-entry ?\] "_ " table) + (modify-syntax-entry ?# "' 14" table) + (modify-syntax-entry ?| "\" 23bn" table) + table) + "Syntax table used in `lisp-mode'.") + +(rx-define lisp-mode-symbol (+ (| (syntax word) + (syntax symbol) + (: "\\" nonl)))) + +(eval-and-compile + (defconst lisp-mode-symbol-regexp (rx lisp-mode-symbol))) + +(defvar lisp-imenu-generic-expression + (list + (list nil + (purecopy (concat "^\\s-*(" + (regexp-opt + '("defun" "defmacro" + ;; Elisp. + "defun*" "defsubst" "define-inline" + "define-advice" "defadvice" "define-skeleton" + "define-compilation-mode" "define-minor-mode" + "define-global-minor-mode" + "define-globalized-minor-mode" + "define-derived-mode" "define-generic-mode" + "ert-deftest" + "cl-defun" "cl-defsubst" "cl-defmacro" + "cl-define-compiler-macro" "cl-defgeneric" + "cl-defmethod" + ;; CL. + "define-compiler-macro" "define-modify-macro" + "defsetf" "define-setf-expander" + "define-method-combination" + ;; CLOS and EIEIO + "defgeneric" "defmethod") + t) + "\\s-+\\(" (rx lisp-mode-symbol) "\\)")) + 2) + ;; Like the previous, but uses a quoted symbol as the name. + (list nil + (purecopy (concat "^\\s-*(" + (regexp-opt + '("defalias" "define-obsolete-function-alias") + t) + "\\s-+'\\(" (rx lisp-mode-symbol) "\\)")) + 2) + (list (purecopy "Variables") + (purecopy (concat "^\\s-*(" + (regexp-opt + '(;; Elisp + "defconst" "defcustom" + ;; CL + "defconstant" + "defparameter" "define-symbol-macro") + t) + "\\s-+\\(" (rx lisp-mode-symbol) "\\)")) + 2) + ;; For `defvar'/`defvar-local', we ignore (defvar FOO) constructs. + (list (purecopy "Variables") + (purecopy (concat "^\\s-*(defvar\\(?:-local\\)?\\s-+\\(" + (rx lisp-mode-symbol) "\\)" + "[[:space:]\n]+[^)]")) + 1) + (list (purecopy "Types") + (purecopy (concat "^\\s-*(" + (regexp-opt + '(;; Elisp + "defgroup" "deftheme" + "define-widget" "define-error" + "defface" "cl-deftype" "cl-defstruct" + ;; CL + "deftype" "defstruct" + "define-condition" "defpackage" + ;; CLOS and EIEIO + "defclass") + t) + "\\s-+'?\\(" (rx lisp-mode-symbol) "\\)")) + 2)) + + "Imenu generic expression for Lisp mode. See `imenu-generic-expression'.") + +(defconst lisp-mode-autoload-regexp + "^;;;###\\(\\([-[:alnum:]]+?\\)-\\)?\\(autoload\\)" + "Regexp to match autoload cookies. +The second group matches package names used to redirect autoloads +to a package-local <package>-loaddefs.el file.") + +;; This was originally in autoload.el and is still used there. +(put 'autoload 'doc-string-elt 3) +(put 'defmethod 'doc-string-elt 3) +(put 'defvar 'doc-string-elt 3) +(put 'defconst 'doc-string-elt 3) +(put 'defalias 'doc-string-elt 3) +(put 'defvaralias 'doc-string-elt 3) +(put 'define-category 'doc-string-elt 2) +;; CL +(put 'defconstant 'doc-string-elt 3) +(put 'defparameter 'doc-string-elt 3) + +(defvar lisp-doc-string-elt-property 'doc-string-elt + "The symbol property that holds the docstring position info.") + +(defconst lisp-prettify-symbols-alist '(("lambda" . ?λ)) + "Alist of symbol/\"pretty\" characters to be displayed.") + +;;;; Font-lock support. + +(defun lisp--match-hidden-arg (limit) + (let ((res nil)) + (forward-line 0) + (while + (let ((ppss (parse-partial-sexp (point) + (line-end-position) + -1))) + (skip-syntax-forward " )") + (if (or (>= (car ppss) 0) + (eolp) + (looking-at ";") + (nth 8 (syntax-ppss))) ;Within a string or comment. + (progn + (forward-line 1) + (< (point) limit)) + (looking-at ".*") ;Set the match-data. + (forward-line 1) + (setq res (point)) + nil))) + res)) + +(defun lisp--el-non-funcall-position-p (pos) + "Heuristically determine whether POS is an evaluated position." + (declare (obsolete lisp--el-funcall-position-p "28.1")) + (not (lisp--el-funcall-position-p pos))) + +(defun lisp--el-funcall-position-p (pos) + "Heuristically determine whether POS is an evaluated position." + (save-match-data + (save-excursion + (ignore-errors + (goto-char pos) + ;; '(lambda ..) is not a funcall position, but #'(lambda ...) is. + (if (eql (char-before) ?\') + (eql (char-before (1- (point))) ?#) + (let* ((ppss (syntax-ppss)) + (paren-posns (nth 9 ppss)) + (parent + (when paren-posns + (goto-char (car (last paren-posns))) ;(up-list -1) + (cond + ((ignore-errors + (and (eql (char-after) ?\() + (when (cdr paren-posns) + (goto-char (car (last paren-posns 2))) + (looking-at "(\\_<let\\*?\\_>")))) + (goto-char (match-end 0)) + 'let) + ((looking-at + (rx "(" + (group-n 1 (+ (or (syntax w) (syntax _)))) + symbol-end)) + (prog1 (intern-soft (match-string-no-properties 1)) + (goto-char (match-end 1)))))))) + (pcase parent + ('declare nil) + ('let + (forward-sexp 1) + (>= pos (point))) + ((or 'defun 'defmacro 'cl-defmethod 'cl-defun) + (forward-sexp 2) + (>= pos (point))) + ('condition-case + ;; If (cdr paren-posns), then we're in the BODY + ;; of HANDLERS. + (or (cdr paren-posns) + (progn + (forward-sexp 1) + ;; If we're in the second form, then we're in + ;; a funcall position. + (< (point) pos (progn (forward-sexp 1) + (point)))))) + (_ t)))))))) + +(defun lisp--el-match-keyword (limit) + ;; FIXME: Move to elisp-mode.el. + (catch 'found + (while (re-search-forward + (concat "(\\(" (rx lisp-mode-symbol) "\\)\\_>") + limit t) + (let ((sym (intern-soft (match-string 1)))) + (when (and (or (special-form-p sym) (macrop sym)) + (not (get sym 'no-font-lock-keyword)) + (lisp--el-funcall-position-p (match-beginning 0))) + (throw 'found t)))))) + +(defmacro let-when-compile (bindings &rest body) + "Like `let*', but allow for compile time optimization. +Use BINDINGS as in regular `let*', but in BODY each usage should +be wrapped in `eval-when-compile'. +This will generate compile-time constants from BINDINGS." + (declare (indent 1) (debug let)) + (letrec ((loop + (lambda (bindings) + (if (null bindings) + (macroexpand-all (macroexp-progn body) + macroexpand-all-environment) + (let ((binding (pop bindings))) + (cl-progv (list (car binding)) + (list (eval (nth 1 binding) t)) + (funcall loop bindings))))))) + (funcall loop bindings))) + +(defun elisp--font-lock-backslash () + (let* ((beg0 (match-beginning 0)) + (end0 (match-end 0)) + (ppss (save-excursion (syntax-ppss beg0)))) + (and (nth 3 ppss) ;Inside a string. + (not (nth 5 ppss)) ;The \ is not itself \-escaped. + ;; Don't highlight the \( introduced because of + ;; `open-paren-in-column-0-is-defun-start'. + (not (eq ?\n (char-before beg0))) + (equal (ignore-errors + (car (read-from-string + (format "\"%s\"" + (buffer-substring-no-properties + beg0 end0))))) + (buffer-substring-no-properties (1+ beg0) end0)) + `(face ,font-lock-warning-face + help-echo "This \\ has no effect")))) + +(defun lisp--match-confusable-symbol-character (limit) + ;; Match a confusable character within a Lisp symbol. + (catch 'matched + (while t + (if (re-search-forward help-uni-confusables-regexp limit t) + ;; Skip confusables which are backslash escaped, or inside + ;; strings or comments. + (save-match-data + (unless (or (eq (char-before (match-beginning 0)) ?\\) + (nth 8 (syntax-ppss))) + (throw 'matched t))) + (throw 'matched nil))))) + +(defun lisp-mode--search-key (char bound) + (catch 'found + (while (re-search-forward + (concat "\\_<" char (rx lisp-mode-symbol) "\\_>") + bound t) + (when (or (< (match-beginning 0) (+ (point-min) 2)) + ;; A quoted white space before the &/: means that this + ;; is not the start of a :keyword or an &option. + (not (eql (char-after (- (match-beginning 0) 2)) + ?\\)) + (not (memq (char-after (- (match-beginning 0) 1)) + '(?\s ?\n ?\t)))) + (throw 'found t))))) + +(let-when-compile + ((lisp-fdefs '("defmacro" "defun")) + (lisp-vdefs '("defvar")) + (lisp-kw '("cond" "if" "while" "let" "let*" "progn" "prog1" + "prog2" "lambda" "unwind-protect" "condition-case" + "when" "unless" "with-output-to-string" + "ignore-errors" "dotimes" "dolist" "declare")) + (lisp-errs '("warn" "error" "signal")) + ;; Elisp constructs. Now they are update dynamically + ;; from obarray but they are also used for setting up + ;; the keywords for Common Lisp. + (el-fdefs '("defsubst" "cl-defsubst" "define-inline" + "define-advice" "defadvice" "defalias" + "define-derived-mode" "define-minor-mode" + "define-generic-mode" "define-global-minor-mode" + "define-globalized-minor-mode" "define-skeleton" + "define-widget" "ert-deftest")) + (el-vdefs '("defconst" "defcustom" "defvaralias" "defvar-local" + "defface")) + (el-tdefs '("defgroup" "deftheme")) + (el-errs '("user-error")) + ;; Common-Lisp constructs supported by EIEIO. FIXME: namespace. + (eieio-fdefs '("defgeneric" "defmethod")) + (eieio-tdefs '("defclass")) + ;; Common-Lisp constructs supported by cl-lib. + (cl-lib-fdefs '("defmacro" "defsubst" "defun" "defmethod" "defgeneric")) + (cl-lib-tdefs '("defstruct" "deftype")) + (cl-lib-errs '("assert" "check-type")) + ;; Common-Lisp constructs not supported by cl-lib. + (cl-fdefs '("defsetf" "define-method-combination" + "define-condition" "define-setf-expander" + ;; "define-function"?? + "define-compiler-macro" "define-modify-macro")) + (cl-vdefs '("define-symbol-macro" "defconstant" "defparameter")) + (cl-tdefs '("defpackage" "defstruct" "deftype")) + (cl-kw '("block" "break" "case" "ccase" "compiler-let" "ctypecase" + "declaim" "destructuring-bind" "do" "do*" + "ecase" "etypecase" "eval-when" "flet" "flet*" + "go" "handler-case" "handler-bind" "in-package" ;; "inline" + "labels" "letf" "locally" "loop" + "macrolet" "multiple-value-bind" "multiple-value-prog1" + "proclaim" "prog" "prog*" "progv" + "restart-case" "restart-bind" "return" "return-from" + "symbol-macrolet" "tagbody" "the" "typecase" + "with-accessors" "with-compilation-unit" + "with-condition-restarts" "with-hash-table-iterator" + "with-input-from-string" "with-open-file" + "with-open-stream" "with-package-iterator" + "with-simple-restart" "with-slots" "with-standard-io-syntax")) + (cl-errs '("abort" "cerror"))) + (let ((vdefs (eval-when-compile + (append lisp-vdefs el-vdefs cl-vdefs))) + (tdefs (eval-when-compile + (append el-tdefs eieio-tdefs cl-tdefs cl-lib-tdefs + (mapcar (lambda (s) (concat "cl-" s)) cl-lib-tdefs)))) + ;; Elisp and Common Lisp definers. + (el-defs-re (eval-when-compile + (regexp-opt (append lisp-fdefs lisp-vdefs + el-fdefs el-vdefs el-tdefs + (mapcar (lambda (s) (concat "cl-" s)) + (append cl-lib-fdefs cl-lib-tdefs)) + eieio-fdefs eieio-tdefs) + t))) + (cl-defs-re (eval-when-compile + (regexp-opt (append lisp-fdefs lisp-vdefs + cl-lib-fdefs cl-lib-tdefs + eieio-fdefs eieio-tdefs + cl-fdefs cl-vdefs cl-tdefs) + t))) + ;; Common Lisp keywords (Elisp keywords are handled dynamically). + (cl-kws-re (eval-when-compile + (regexp-opt (append lisp-kw cl-kw) t))) + ;; Elisp and Common Lisp "errors". + (el-errs-re (eval-when-compile + (regexp-opt (append (mapcar (lambda (s) (concat "cl-" s)) + cl-lib-errs) + lisp-errs el-errs) + t))) + (cl-errs-re (eval-when-compile + (regexp-opt (append lisp-errs cl-lib-errs cl-errs) t)))) + (dolist (v vdefs) + (put (intern v) 'lisp-define-type 'var)) + (dolist (v tdefs) + (put (intern v) 'lisp-define-type 'type)) + + (define-obsolete-variable-alias 'lisp-font-lock-keywords-1 + 'lisp-el-font-lock-keywords-1 "24.4") + (defconst lisp-el-font-lock-keywords-1 + `( ;; Definitions. + (,(concat "(" el-defs-re "\\_>" + ;; Any whitespace and defined object. + "[ \t']*" + "\\(([ \t']*\\)?" ;; An opening paren. + "\\(\\(setf\\)[ \t]+" (rx lisp-mode-symbol) + "\\|" (rx lisp-mode-symbol) "\\)?") + (1 font-lock-keyword-face) + (3 (let ((type (get (intern-soft (match-string 1)) 'lisp-define-type))) + (cond ((eq type 'var) font-lock-variable-name-face) + ((eq type 'type) font-lock-type-face) + ;; If match-string 2 is non-nil, we encountered a + ;; form like (defalias (intern (concat s "-p"))), + ;; unless match-string 4 is also there. Then its a + ;; defmethod with (setf foo) as name. + ((or (not (match-string 2)) ;; Normal defun. + (and (match-string 2) ;; Setf method. + (match-string 4))) + font-lock-function-name-face))) + nil t)) + ;; Emacs Lisp autoload cookies. Supports the slightly different + ;; forms used by mh-e, calendar, etc. + (,lisp-mode-autoload-regexp (3 font-lock-warning-face prepend) + (2 font-lock-function-name-face prepend t))) + "Subdued level highlighting for Emacs Lisp mode.") + + (defconst lisp-cl-font-lock-keywords-1 + `( ;; Definitions. + (,(concat "(" cl-defs-re "\\_>" + ;; Any whitespace and defined object. + "[ \t']*" + "\\(([ \t']*\\)?" ;; An opening paren. + "\\(\\(setf\\)[ \t]+" (rx lisp-mode-symbol) + "\\|" (rx lisp-mode-symbol) "\\)?") + (1 font-lock-keyword-face) + (3 (let ((type (get (intern-soft (match-string 1)) 'lisp-define-type))) + (cond ((eq type 'var) font-lock-variable-name-face) + ((eq type 'type) font-lock-type-face) + ((or (not (match-string 2)) ;; Normal defun. + (and (match-string 2) ;; Setf function. + (match-string 4))) + font-lock-function-name-face))) + nil t))) + "Subdued level highlighting for Lisp modes.") + + (define-obsolete-variable-alias 'lisp-font-lock-keywords-2 + 'lisp-el-font-lock-keywords-2 "24.4") + (defconst lisp-el-font-lock-keywords-2 + (append + lisp-el-font-lock-keywords-1 + `( ;; Regexp negated char group. + ("\\[\\(\\^\\)" 1 font-lock-negation-char-face prepend) + ;; Erroneous structures. + (,(concat "(" el-errs-re "\\_>") + (1 font-lock-warning-face)) + ;; Control structures. Common Lisp forms. + (lisp--el-match-keyword . 1) + ;; Exit/Feature symbols as constants. + (,(concat "(\\(catch\\|throw\\|featurep\\|provide\\|require\\)\\_>" + "[ \t']*\\(" (rx lisp-mode-symbol) "\\)?") + (1 font-lock-keyword-face) + (2 font-lock-constant-face nil t)) + ;; Words inside \\[], \\<>, \\{} or \\`' tend to be for + ;; `substitute-command-keys'. + (,(rx "\\\\" (or (seq "[" (group-n 1 lisp-mode-symbol) "]") + (seq "`" (group-n 1 + ;; allow multiple words, e.g. "C-x a" + lisp-mode-symbol (* " " lisp-mode-symbol)) + "'"))) + (1 font-lock-constant-face prepend)) + (,(rx "\\\\" (or (seq "<" (group-n 1 lisp-mode-symbol) ">") + (seq "{" (group-n 1 lisp-mode-symbol) "}"))) + (1 font-lock-variable-name-face prepend)) + ;; Ineffective backslashes (typically in need of doubling). + ("\\(\\\\\\)\\([^\"\\]\\)" + (1 (elisp--font-lock-backslash) prepend)) + ;; Words inside ‘’, '' and `' tend to be symbol names. + (,(concat "[`‘']\\(" (rx lisp-mode-symbol) "\\)['’]") + (1 font-lock-constant-face prepend)) + ;; \\= tends to be an escape in doc strings. + (,(rx "\\\\=") + (0 font-lock-builtin-face prepend)) + ;; Constant values. + (,(lambda (bound) (lisp-mode--search-key ":" bound)) + (0 font-lock-builtin-face)) + ;; ELisp and CLisp `&' keywords as types. + (,(lambda (bound) (lisp-mode--search-key "&" bound)) + (0 font-lock-builtin-face)) + ;; ELisp regexp grouping constructs + (,(lambda (bound) + (catch 'found + ;; The following loop is needed to continue searching after matches + ;; that do not occur in strings. The associated regexp matches one + ;; of `\\\\' `\\(' `\\(?:' `\\|' `\\)'. `\\\\' has been included to + ;; avoid highlighting, for example, `\\(' in `\\\\('. + (while (re-search-forward "\\(\\\\\\\\\\)\\(?:\\(\\\\\\\\\\)\\|\\((\\(?:\\?[0-9]*:\\)?\\|[|)]\\)\\)" bound t) + (unless (match-beginning 2) + (let ((face (get-text-property (1- (point)) 'face))) + (when (or (and (listp face) + (memq 'font-lock-string-face face)) + (eq 'font-lock-string-face face)) + (throw 'found t))))))) + (1 'font-lock-regexp-grouping-backslash prepend) + (3 'font-lock-regexp-grouping-construct prepend)) + (lisp--match-hidden-arg + (0 '(face font-lock-warning-face + help-echo "Easy to misread; consider moving the element to the next line") + prepend)) + (lisp--match-confusable-symbol-character + 0 '(face font-lock-warning-face + help-echo "Confusable character")) + )) + "Gaudy level highlighting for Emacs Lisp mode.") + + (defconst lisp-cl-font-lock-keywords-2 + (append + lisp-cl-font-lock-keywords-1 + `( ;; Regexp negated char group. + ("\\[\\(\\^\\)" 1 font-lock-negation-char-face prepend) + ;; Control structures. Common Lisp forms. + (,(concat "(" cl-kws-re "\\_>") . 1) + ;; Exit/Feature symbols as constants. + (,(concat "(\\(catch\\|throw\\|provide\\|require\\)\\_>" + "[ \t']*\\(" (rx lisp-mode-symbol) "\\)?") + (1 font-lock-keyword-face) + (2 font-lock-constant-face nil t)) + ;; Erroneous structures. + (,(concat "(" cl-errs-re "\\_>") + (1 font-lock-warning-face)) + ;; Words inside ‘’ and `' tend to be symbol names. + (,(concat "[`‘]\\(" (rx lisp-mode-symbol) "\\)['’]") + (1 font-lock-constant-face prepend)) + ;; Uninterned symbols, e.g., (defpackage #:my-package ...) + ;; must come before keywords below to have effect + (,(concat "#:" (rx lisp-mode-symbol) "") 0 font-lock-builtin-face) + ;; Constant values. + (,(lambda (bound) (lisp-mode--search-key ":" bound)) + (0 font-lock-builtin-face)) + ;; ELisp and CLisp `&' keywords as types. + (,(lambda (bound) (lisp-mode--search-key "&" bound)) + (0 font-lock-builtin-face)) + ;; ELisp regexp grouping constructs + ;; This is too general -- rms. + ;; A user complained that he has functions whose names start with `do' + ;; and that they get the wrong color. + ;; That user has violated the https://www.cliki.net/Naming+conventions: + ;; CL (but not EL!) `with-' (context) and `do-' (iteration) + (,(concat "(\\(\\(do-\\|with-\\)" (rx lisp-mode-symbol) "\\)") + (1 font-lock-keyword-face)) + (lisp--match-hidden-arg + (0 '(face font-lock-warning-face + help-echo "Easy to misread; consider moving the element to the next line") + prepend)) + )) + "Gaudy level highlighting for Lisp modes."))) + +(define-obsolete-variable-alias 'lisp-font-lock-keywords + 'lisp-el-font-lock-keywords "24.4") +(defvar lisp-el-font-lock-keywords lisp-el-font-lock-keywords-1 + "Default expressions to highlight in Emacs Lisp mode.") +(defvar lisp-cl-font-lock-keywords lisp-cl-font-lock-keywords-1 + "Default expressions to highlight in Lisp modes.") + +;; Support backtrace mode. +(defconst lisp-el-font-lock-keywords-for-backtraces lisp-el-font-lock-keywords + "Default highlighting from Emacs Lisp mode used in Backtrace mode.") +(defconst lisp-el-font-lock-keywords-for-backtraces-1 lisp-el-font-lock-keywords-1 + "Subdued highlighting from Emacs Lisp mode used in Backtrace mode.") +(defconst lisp-el-font-lock-keywords-for-backtraces-2 + (remove (assoc 'lisp--match-hidden-arg lisp-el-font-lock-keywords-2) + lisp-el-font-lock-keywords-2) + "Gaudy highlighting from Emacs Lisp mode used in Backtrace mode.") + +(defun lisp-string-in-doc-position-p (listbeg startpos) + "Return non-nil if a doc string may occur at STARTPOS inside a list. +LISTBEG is the position of the start of the innermost list +containing STARTPOS." + (let* ((firstsym (and listbeg + (save-excursion + (goto-char listbeg) + (and (looking-at + (concat "([ \t\n]*\\(" + (rx lisp-mode-symbol) "\\)")) + (match-string 1))))) + (docelt (and firstsym + (function-get (intern-soft firstsym) + lisp-doc-string-elt-property)))) + (and docelt + ;; It's a string in a form that can have a docstring. + ;; Check whether it's in docstring position. + (save-excursion + (when (functionp docelt) + (goto-char (match-end 1)) + (setq docelt (funcall docelt))) + (goto-char listbeg) + (forward-char 1) + (condition-case nil + (while (and (> docelt 0) (< (point) startpos) + (progn (forward-sexp 1) t)) + (setq docelt (1- docelt))) + (error nil)) + (and (zerop docelt) (<= (point) startpos) + (progn (forward-comment (point-max)) t) + (= (point) startpos)))))) + +(defun lisp-string-after-doc-keyword-p (listbeg startpos) + "Return non-nil if `:documentation' symbol ends at STARTPOS inside a list. +`:doc' can also be used. + +LISTBEG is the position of the start of the innermost list +containing STARTPOS." + (and listbeg ; We are inside a Lisp form. + (save-excursion + (goto-char startpos) + (ignore-errors + (progn (backward-sexp 1) + (looking-at ":documentation\\_>\\|:doc\\_>")))))) + +(defun lisp-font-lock-syntactic-face-function (state) + "Return syntactic face function for the position represented by STATE. +STATE is a `parse-partial-sexp' state, and the returned function is the +Lisp font lock syntactic face function." + (if (nth 3 state) + ;; This might be a (doc)string or a |...| symbol. + (let ((startpos (nth 8 state))) + (if (eq (char-after startpos) ?|) + ;; This is not a string, but a |...| symbol. + nil + (let ((listbeg (nth 1 state))) + (if (or (lisp-string-in-doc-position-p listbeg startpos) + (lisp-string-after-doc-keyword-p listbeg startpos)) + font-lock-doc-face + font-lock-string-face)))) + font-lock-comment-face)) + +(defun lisp-adaptive-fill () + "Return fill prefix found at point. +Value for `adaptive-fill-function'." + ;; Adaptive fill mode gets the fill wrong for a one-line paragraph made of + ;; a single docstring. Let's fix it here. + (if (looking-at "\\s-+\"[^\n\"]+\"\\s-*$") "")) + +;; Maybe this should be discouraged/obsoleted and users should be +;; encouraged to use 'lisp-data-mode' instead. +(defun lisp-mode-variables (&optional lisp-syntax keywords-case-insensitive + elisp) + "Common initialization routine for Lisp modes. +The LISP-SYNTAX argument is used by code in inf-lisp.el and is +\(uselessly) passed from pp.el, chistory.el, gnus-kill.el and +score-mode.el. KEYWORDS-CASE-INSENSITIVE non-nil means that for +font-lock keywords will not be case sensitive." + (when lisp-syntax + (set-syntax-table lisp-mode-syntax-table)) + (setq-local paragraph-ignore-fill-prefix t) + (setq-local fill-paragraph-function 'lisp-fill-paragraph) + (setq-local adaptive-fill-function #'lisp-adaptive-fill) + ;; Adaptive fill mode gets in the way of auto-fill, + ;; and should make no difference for explicit fill + ;; because lisp-fill-paragraph should do the job. + ;; I believe that newcomment's auto-fill code properly deals with it -stef + ;;(setq-local adaptive-fill-mode nil) + (setq-local indent-line-function 'lisp-indent-line) + (setq-local indent-region-function 'lisp-indent-region) + (setq-local comment-indent-function #'lisp-comment-indent) + (setq-local outline-regexp (concat ";;;;* [^ \t\n]\\|(\\|\\(" + lisp-mode-autoload-regexp + "\\)")) + (setq-local outline-level 'lisp-outline-level) + (setq-local add-log-current-defun-function #'lisp-current-defun-name) + (setq-local comment-start ";") + (setq-local comment-start-skip ";+ *") + (setq-local comment-add 1) ;default to `;;' in comment-region + (setq-local comment-column 40) + (setq-local comment-use-syntax t) + (setq-local imenu-generic-expression lisp-imenu-generic-expression) + (setq-local multibyte-syntax-as-symbol t) + ;; (setq-local syntax-begin-function 'beginning-of-defun) ;;Bug#16247. + (setq font-lock-defaults + `(,(if elisp '(lisp-el-font-lock-keywords + lisp-el-font-lock-keywords-1 + lisp-el-font-lock-keywords-2) + '(lisp-cl-font-lock-keywords + lisp-cl-font-lock-keywords-1 + lisp-cl-font-lock-keywords-2)) + nil ,keywords-case-insensitive nil nil + (font-lock-mark-block-function . mark-defun) + (font-lock-extra-managed-props help-echo) + (font-lock-syntactic-face-function + . lisp-font-lock-syntactic-face-function))) + (setq-local prettify-symbols-alist lisp-prettify-symbols-alist) + (setq-local electric-pair-skip-whitespace 'chomp) + (setq-local electric-pair-open-newline-between-pairs nil)) + +;;;###autoload +(define-derived-mode lisp-data-mode prog-mode "Lisp-Data" + "Major mode for buffers holding data written in Lisp syntax." + :group 'lisp + (lisp-mode-variables nil t nil) + (setq-local electric-quote-string t) + (setq imenu-case-fold-search nil)) + +(defun lisp-outline-level () + "Lisp mode `outline-level' function." + ;; Expects outline-regexp is ";;;\\(;* [^ \t\n]\\|###autoload\\)\\|(" + ;; and point is at the beginning of a matching line. + (let ((len (- (match-end 0) (match-beginning 0)))) + (cond ((or (looking-at-p "(") + (looking-at-p lisp-mode-autoload-regexp)) + 1000) + ((looking-at ";;\\(;+\\) ") + (- (match-end 1) (match-beginning 1))) + ;; Above should match everything but just in case. + (t + len)))) + +(defun lisp-current-defun-name () + "Return the name of the defun at point, or nil." + (save-excursion + (let ((location (point))) + ;; If we are now precisely at the beginning of a defun, make sure + ;; beginning-of-defun finds that one rather than the previous one. + (or (eobp) (forward-char 1)) + (beginning-of-defun) + ;; Make sure we are really inside the defun found, not after it. + (when (and (looking-at "\\s(") + (progn (end-of-defun) + (< location (point))) + (progn (forward-sexp -1) + (>= location (point)))) + (if (looking-at "\\s(") + (forward-char 1)) + ;; Skip the defining construct name, typically "defun" or + ;; "defvar". + (forward-sexp 1) + ;; The second element is usually a symbol being defined. If it + ;; is not, use the first symbol in it. + (skip-chars-forward " \t\n'(") + (buffer-substring-no-properties (point) + (progn (forward-sexp 1) + (point))))))) + +(defvar-keymap lisp-mode-shared-map + :doc "Keymap for commands shared by all sorts of Lisp modes." + :parent prog-mode-map + "C-M-q" #'indent-sexp + "DEL" #'backward-delete-char-untabify + ;; This gets in the way when viewing a Lisp file in view-mode. As + ;; long as [backspace] is mapped into DEL via the + ;; function-key-map, this should remain disabled!! + ;;;"<backspace>" #'backward-delete-char-untabify + ) + +(defcustom lisp-mode-hook nil + "Hook run when entering Lisp mode." + :options '(imenu-add-menubar-index) + :type 'hook + :group 'lisp) + +(defcustom lisp-interaction-mode-hook nil + "Hook run when entering Lisp Interaction mode." + :options '(eldoc-mode) + :type 'hook + :group 'lisp) + +;;; Generic Lisp mode. + +(defvar-keymap lisp-mode-map + :doc "Keymap for ordinary Lisp mode. +All commands in `lisp-mode-shared-map' are inherited by this map." + :parent lisp-mode-shared-map + "C-M-x" #'lisp-eval-defun + "C-c C-z" #'run-lisp) + +(easy-menu-define lisp-mode-menu lisp-mode-map + "Menu for ordinary Lisp mode." + '("Lisp" + ["Indent sexp" indent-sexp + :help "Indent each line of the list starting just after point"] + ["Eval defun" lisp-eval-defun + :help "Send the current defun to the Lisp process made by M-x run-lisp"] + ["Run inferior Lisp" run-lisp + :help "Run an inferior Lisp process, input and output via buffer `*inferior-lisp*'"])) + +(define-derived-mode lisp-mode lisp-data-mode "Lisp" + "Major mode for editing programs in Common Lisp and other similar Lisps. +Commands: +Delete converts tabs to spaces as it moves back. +Blank lines separate paragraphs. Semicolons start comments. + +\\{lisp-mode-map} +Note that `run-lisp' may be used either to start an inferior Lisp job +or to switch back to an existing one." + (setq-local lisp-indent-function 'common-lisp-indent-function) + (setq-local find-tag-default-function 'lisp-find-tag-default) + (setq-local comment-start-skip + "\\(\\(^\\|[^\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *") + (setq-local comment-end-skip "[ \t]*\\(\\s>\\||#\\)") + (setq-local font-lock-comment-end-skip "|#") + (setq imenu-case-fold-search t)) + +(defun lisp-find-tag-default () + (let ((default (find-tag-default))) + (when (stringp default) + (if (string-match ":+" default) + (substring default (match-end 0)) + default)))) + +;; Used in old LispM code. +(defalias 'common-lisp-mode 'lisp-mode) + +(autoload 'lisp-eval-defun "inf-lisp" nil t) + +(defun lisp-comment-indent () + "Like `comment-indent-default', but don't put space after open paren." + (or (when (looking-at "\\s<\\s<") + (let ((pt (point))) + (skip-syntax-backward " ") + (if (eq (preceding-char) ?\() + (cons (current-column) (current-column)) + (goto-char pt) + nil))) + (comment-indent-default))) + +(defcustom lisp-indent-offset nil + "If non-nil, indent second line of expressions that many more columns." + :group 'lisp + :type '(choice (const nil) integer) + :safe (lambda (x) (or (null x) (integerp x)))) + +(defcustom lisp-indent-function 'lisp-indent-function + "A function to be called by `calculate-lisp-indent'. +It indents the arguments of a Lisp function call. This function +should accept two arguments: the indent-point, and the +`parse-partial-sexp' state at that position. One option for this +function is `common-lisp-indent-function'." + :type 'function + :group 'lisp) + +(defun lisp-ppss (&optional pos) + "Return Parse-Partial-Sexp State at POS, defaulting to point. +Like `syntax-ppss' but includes the character address of the last +complete sexp in the innermost containing list at position +2 (counting from 0). This is important for Lisp indentation." + (unless pos (setq pos (point))) + (let ((pss (syntax-ppss pos))) + (if (nth 9 pss) + (let ((sexp-start (car (last (nth 9 pss))))) + (parse-partial-sexp sexp-start pos nil nil (syntax-ppss sexp-start))) + pss))) + +(cl-defstruct (lisp-indent-state + (:constructor nil) + (:constructor lisp-indent-initial-state + (&aux (ppss (lisp-ppss)) + (ppss-point (point)) + (stack (make-list (1+ (car ppss)) nil))))) + stack ;; Cached indentation, per depth. + ppss + ppss-point) + +(defun lisp-indent-calc-next (state) + "Move to next line and return calculated indent for it. +STATE is updated by side effect, the first state should be +created by `lisp-indent-initial-state'. This function may move +by more than one line to cross a string literal." + (pcase-let* (((cl-struct lisp-indent-state + (stack indent-stack) ppss ppss-point) + state) + (indent-depth (car ppss)) ; Corresponding to indent-stack. + (depth indent-depth)) + ;; Parse this line so we can learn the state to indent the + ;; next line. + (while (let ((last-sexp (nth 2 ppss))) + (setq ppss (parse-partial-sexp + ppss-point (progn (end-of-line) (point)) + nil nil ppss)) + ;; Preserve last sexp of state (position 2) for + ;; `calculate-lisp-indent', if we're at the same depth. + (if (and (not (nth 2 ppss)) (= depth (car ppss))) + (setf (nth 2 ppss) last-sexp) + (setq last-sexp (nth 2 ppss))) + (setq depth (car ppss)) + ;; Skip over newlines within strings. + (and (not (eobp)) (nth 3 ppss))) + (let ((string-start (nth 8 ppss))) + (setq ppss (parse-partial-sexp (point) (point-max) + nil nil ppss 'syntax-table)) + (setf (nth 2 ppss) string-start) ; Finished a complete string. + (setq depth (car ppss))) + (setq ppss-point (point))) + (setq ppss-point (point)) + (let* ((depth-delta (- depth indent-depth))) + (cond ((< depth-delta 0) + (setq indent-stack (nthcdr (- depth-delta) indent-stack))) + ((> depth-delta 0) + (setq indent-stack (nconc (make-list depth-delta nil) + indent-stack))))) + (prog1 + (let (indent) + (cond ((= (forward-line 1) 1) + ;; Can't move to the next line, apparently end of buffer. + nil) + ((null indent-stack) + ;; Negative depth, probably some kind of syntax + ;; error. Reset the state. + (setq ppss (parse-partial-sexp (point) (point)))) + ((car indent-stack)) + ((integerp (setq indent (calculate-lisp-indent ppss))) + (setf (car indent-stack) indent)) + ((consp indent) ; (COLUMN CONTAINING-SEXP-START) + (car indent)) + ;; This only happens if we're in a string, but the + ;; loop should always skip over strings (unless we hit + ;; end of buffer, which is taken care of by the first + ;; clause). + (t (error "This shouldn't happen")))) + (setf (lisp-indent-state-stack state) indent-stack) + (setf (lisp-indent-state-ppss-point state) ppss-point) + (setf (lisp-indent-state-ppss state) ppss)))) + +(defun lisp-indent-region (start end) + "Indent region as Lisp code, efficiently." + (save-excursion + (setq end (copy-marker end)) + (goto-char start) + (beginning-of-line) + ;; The default `indent-region-line-by-line' doesn't hold a running + ;; parse state, which forces each indent call to reparse from the + ;; beginning. That has O(n^2) complexity. + (let* ((parse-state (lisp-indent-initial-state)) + (pr (unless (minibufferp) + (make-progress-reporter "Indenting region..." (point) end)))) + (let ((ppss (lisp-indent-state-ppss parse-state))) + (unless (or (and (bolp) (eolp)) (nth 3 ppss)) + (lisp-indent-line (calculate-lisp-indent ppss)))) + (let ((indent nil)) + (while (progn (setq indent (lisp-indent-calc-next parse-state)) + (< (point) end)) + (unless (or (and (bolp) (eolp)) (not indent)) + (lisp-indent-line indent)) + (and pr (progress-reporter-update pr (point))))) + (and pr (progress-reporter-done pr)) + (move-marker end nil)))) + +(defun lisp-indent-line (&optional indent) + "Indent current line as Lisp code." + (interactive) + (let ((pos (- (point-max) (point))) + (indent (progn (beginning-of-line) + (or indent (calculate-lisp-indent (lisp-ppss)))))) + (skip-chars-forward " \t") + (if (or (null indent) (looking-at "\\s<\\s<\\s<")) + ;; Don't alter indentation of a ;;; comment line + ;; or a line that starts in a string. + ;; FIXME: inconsistency: comment-indent moves ;;; to column 0. + (goto-char (- (point-max) pos)) + (if (and (looking-at "\\s<") (not (looking-at "\\s<\\s<"))) + ;; Single-semicolon comment lines should be indented + ;; as comment lines, not as code. + (progn (indent-for-comment) (forward-char -1)) + (if (listp indent) (setq indent (car indent))) + (indent-line-to indent)) + ;; If initial point was within line's indentation, + ;; position after the indentation. Else stay at same point in text. + (if (> (- (point-max) pos) (point)) + (goto-char (- (point-max) pos)))))) + +(defvar calculate-lisp-indent-last-sexp) + +(defun calculate-lisp-indent (&optional parse-start) + "Return appropriate indentation for current line as Lisp code. +In usual case returns an integer: the column to indent to. +If the value is nil, that means don't change the indentation +because the line starts inside a string. + +PARSE-START may be a buffer position to start parsing from, or a +parse state as returned by calling `parse-partial-sexp' up to the +beginning of the current line. + +The value can also be a list of the form (COLUMN CONTAINING-SEXP-START). +This means that following lines at the same level of indentation +should not necessarily be indented the same as this line. +Then COLUMN is the column to indent to, and CONTAINING-SEXP-START +is the buffer position of the start of the containing expression." + (save-excursion + (beginning-of-line) + (let ((indent-point (point)) + state + ;; setting this to a number inhibits calling hook + (desired-indent nil) + (retry t) + whitespace-after-open-paren + calculate-lisp-indent-last-sexp containing-sexp) + (cond ((or (markerp parse-start) (integerp parse-start)) + (goto-char parse-start)) + ((null parse-start) (beginning-of-defun)) + (t (setq state parse-start))) + (unless state + ;; Find outermost containing sexp + (while (< (point) indent-point) + (setq state (parse-partial-sexp (point) indent-point 0)))) + ;; Find innermost containing sexp + (while (and retry + state + (> (elt state 0) 0)) + (setq retry nil) + (setq calculate-lisp-indent-last-sexp (elt state 2)) + (setq containing-sexp (elt state 1)) + ;; Position following last unclosed open. + (goto-char (1+ containing-sexp)) + ;; Is there a complete sexp since then? + (if (and calculate-lisp-indent-last-sexp + (> calculate-lisp-indent-last-sexp (point))) + ;; Yes, but is there a containing sexp after that? + (let ((peek (parse-partial-sexp calculate-lisp-indent-last-sexp + indent-point 0))) + (if (setq retry (car (cdr peek))) (setq state peek))))) + (if retry + nil + ;; Innermost containing sexp found + (goto-char (1+ containing-sexp)) + (setq whitespace-after-open-paren (looking-at (rx whitespace))) + (if (not calculate-lisp-indent-last-sexp) + ;; indent-point immediately follows open paren. + ;; Don't call hook. + (setq desired-indent (current-column)) + ;; Find the start of first element of containing sexp. + (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) + (cond ((looking-at "\\s(") + ;; First element of containing sexp is a list. + ;; Indent under that list. + ) + ((> (save-excursion (forward-line 1) (point)) + calculate-lisp-indent-last-sexp) + ;; This is the first line to start within the containing sexp. + ;; It's almost certainly a function call. + (if (or (= (point) calculate-lisp-indent-last-sexp) + whitespace-after-open-paren) + ;; Containing sexp has nothing before this line + ;; except the first element, or the first element is + ;; preceded by whitespace. Indent under that element. + nil + ;; Skip the first element, find start of second (the first + ;; argument of the function call) and indent under. + (progn (forward-sexp 1) + (parse-partial-sexp (point) + calculate-lisp-indent-last-sexp + 0 t))) + (backward-prefix-chars)) + (t + ;; Indent beneath first sexp on same line as + ;; `calculate-lisp-indent-last-sexp'. Again, it's + ;; almost certainly a function call. + (goto-char calculate-lisp-indent-last-sexp) + (beginning-of-line) + (parse-partial-sexp (point) calculate-lisp-indent-last-sexp + 0 t) + (backward-prefix-chars))))) + ;; Point is at the point to indent under unless we are inside a string. + ;; Call indentation hook except when overridden by lisp-indent-offset + ;; or if the desired indentation has already been computed. + (let ((normal-indent (current-column))) + (cond ((elt state 3) + ;; Inside a string, don't change indentation. + nil) + ((and (integerp lisp-indent-offset) containing-sexp) + ;; Indent by constant offset + (goto-char containing-sexp) + (+ (current-column) lisp-indent-offset)) + ;; in this case calculate-lisp-indent-last-sexp is not nil + (calculate-lisp-indent-last-sexp + (or + ;; try to align the parameters of a known function + (and lisp-indent-function + (not retry) + (funcall lisp-indent-function indent-point state)) + ;; If the function has no special alignment + ;; or it does not apply to this argument, + ;; try to align a constant-symbol under the last + ;; preceding constant symbol, if there is such one of + ;; the last 2 preceding symbols, in the previous + ;; uncommented line. + (and (save-excursion + (goto-char indent-point) + (skip-chars-forward " \t") + (looking-at ":")) + ;; The last sexp may not be at the indentation + ;; where it begins, so find that one, instead. + (save-excursion + (goto-char calculate-lisp-indent-last-sexp) + ;; Handle prefix characters and whitespace + ;; following an open paren. (Bug#1012) + (backward-prefix-chars) + (while (not (save-excursion + (skip-chars-backward " \t") + (or (= (point) (line-beginning-position)) + (and containing-sexp + (= (point) (1+ containing-sexp)))))) + (forward-sexp -1) + (backward-prefix-chars)) + (setq calculate-lisp-indent-last-sexp (point))) + (> calculate-lisp-indent-last-sexp + (save-excursion + (goto-char (1+ containing-sexp)) + (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) + (point))) + (let ((parse-sexp-ignore-comments t) + indent) + (goto-char calculate-lisp-indent-last-sexp) + (or (and (looking-at ":") + (setq indent (current-column))) + (and (< (line-beginning-position) + (prog2 (backward-sexp) (point))) + (looking-at ":") + (setq indent (current-column)))) + indent)) + ;; another symbols or constants not preceded by a constant + ;; as defined above. + normal-indent)) + ;; in this case calculate-lisp-indent-last-sexp is nil + (desired-indent) + (t + normal-indent)))))) + +(defun lisp--local-defform-body-p (state) + "Return non-nil when at local definition body according to STATE. +STATE is the `parse-partial-sexp' state for current position." + (when-let ((start-of-innermost-containing-list (nth 1 state))) + (let* ((parents (nth 9 state)) + (first-cons-after (cdr parents)) + (second-cons-after (cdr first-cons-after)) + first-order-parent second-order-parent) + (while second-cons-after + (when (= start-of-innermost-containing-list + (car second-cons-after)) + (setq second-order-parent (pop parents) + first-order-parent (pop parents) + ;; Leave the loop. + second-cons-after nil)) + (pop second-cons-after) + (pop parents)) + (when second-order-parent + (let (local-definitions-starting-point) + (and (save-excursion + (goto-char (1+ second-order-parent)) + (when-let ((head (ignore-errors + ;; FIXME: This does not distinguish + ;; between reading nil and a read error. + ;; We don't care but still, better fix this. + (read (current-buffer))))) + (when (memq head '( cl-flet cl-labels cl-macrolet cl-flet* + cl-symbol-macrolet)) + ;; In what follows, we rely on (point) returning non-nil. + (setq local-definitions-starting-point + (progn + (parse-partial-sexp + (point) first-order-parent nil + ;; From docstring of `parse-partial-sexp': + ;; Fourth arg non-nil means stop + ;; when we come to any character + ;; that starts a sexp. + t) + (point)))))) + (save-excursion + (when (ignore-errors + ;; We rely on `backward-up-list' working + ;; even when sexp is incomplete “to the right”. + (backward-up-list 2) + t) + (= local-definitions-starting-point (point)))))))))) + +(defun lisp-indent-function (indent-point state) + "This function is the normal value of the variable `lisp-indent-function'. +The function `calculate-lisp-indent' calls this to determine +if the arguments of a Lisp function call should be indented specially. + +INDENT-POINT is the position at which the line being indented begins. +Point is located at the point to indent under (for default indentation); +STATE is the `parse-partial-sexp' state for that position. + +If the current line is in a call to a Lisp function that has a non-nil +property `lisp-indent-function' (or the deprecated `lisp-indent-hook'), +it specifies how to indent. The property value can be: + +* `defun', meaning indent `defun'-style + (this is also the case if there is no property and the function + has a name that begins with \"def\", and three or more arguments); + +* an integer N, meaning indent the first N arguments specially + (like ordinary function arguments), and then indent any further + arguments like a body; + +* a function to call that returns the indentation (or nil). + `lisp-indent-function' calls this function with the same two arguments + that it itself received. + +This function returns either the indentation to use, or nil if the +Lisp function does not specify a special indentation." + (let ((normal-indent (current-column))) + (goto-char (1+ (elt state 1))) + (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) + (if (and (elt state 2) + (not (looking-at "\\sw\\|\\s_"))) + ;; car of form doesn't seem to be a symbol + (if (lisp--local-defform-body-p state) + ;; We nevertheless check whether we are in flet-like form + ;; as we presume local function names could be non-symbols. + (lisp-indent-defform state indent-point) + (if (not (> (save-excursion (forward-line 1) (point)) + calculate-lisp-indent-last-sexp)) + (progn (goto-char calculate-lisp-indent-last-sexp) + (beginning-of-line) + (parse-partial-sexp (point) + calculate-lisp-indent-last-sexp 0 t))) + ;; Indent under the list or under the first sexp on the same + ;; line as calculate-lisp-indent-last-sexp. Note that first + ;; thing on that line has to be complete sexp since we are + ;; inside the innermost containing sexp. + (backward-prefix-chars) + (current-column)) + (let ((function (buffer-substring (point) + (progn (forward-sexp 1) (point)))) + method) + (setq method (or (function-get (intern-soft function) + 'lisp-indent-function) + (get (intern-soft function) 'lisp-indent-hook))) + (cond ((or (eq method 'defun) + ;; Check whether we are in flet-like form. + (lisp--local-defform-body-p state)) + (lisp-indent-defform state indent-point)) + ((integerp method) + (lisp-indent-specform method state + indent-point normal-indent)) + (method + (funcall method indent-point state))))))) + +(defcustom lisp-body-indent 2 + "Number of columns to indent the second line of a `(def...)' form." + :group 'lisp + :type 'integer + :safe #'integerp) + +(defun lisp-indent-specform (count state indent-point normal-indent) + (let ((containing-form-start (elt state 1)) + (i count) + body-indent containing-form-column) + ;; Move to the start of containing form, calculate indentation + ;; to use for non-distinguished forms (> count), and move past the + ;; function symbol. lisp-indent-function guarantees that there is at + ;; least one word or symbol character following open paren of containing + ;; form. + (goto-char containing-form-start) + (setq containing-form-column (current-column)) + (setq body-indent (+ lisp-body-indent containing-form-column)) + (forward-char 1) + (forward-sexp 1) + ;; Now find the start of the last form. + (parse-partial-sexp (point) indent-point 1 t) + (while (and (< (point) indent-point) + (condition-case () + (progn + (setq count (1- count)) + (forward-sexp 1) + (parse-partial-sexp (point) indent-point 1 t)) + (error nil)))) + ;; Point is sitting on first character of last (or count) sexp. + (if (> count 0) + ;; A distinguished form. If it is the first or second form use double + ;; lisp-body-indent, else normal indent. With lisp-body-indent bound + ;; to 2 (the default), this just happens to work the same with if as + ;; the older code, but it makes unwind-protect, condition-case, + ;; with-output-to-temp-buffer, et. al. much more tasteful. The older, + ;; less hacked, behavior can be obtained by replacing below with + ;; (list normal-indent containing-form-start). + (if (<= (- i count) 1) + (list (+ containing-form-column (* 2 lisp-body-indent)) + containing-form-start) + (list normal-indent containing-form-start)) + ;; A non-distinguished form. Use body-indent if there are no + ;; distinguished forms and this is the first undistinguished form, + ;; or if this is the first undistinguished form and the preceding + ;; distinguished form has indentation at least as great as body-indent. + (if (or (and (= i 0) (= count 0)) + (and (= count 0) (<= body-indent normal-indent))) + body-indent + normal-indent)))) + +(defun lisp-indent-defform (state _indent-point) + (goto-char (car (cdr state))) + (forward-line 1) + (if (> (point) (car (cdr (cdr state)))) + (progn + (goto-char (car (cdr state))) + (+ lisp-body-indent (current-column))))) + + +;; (put 'progn 'lisp-indent-function 0), say, causes progn to be indented +;; like defun if the first form is placed on the next line, otherwise +;; it is indented like any other form (i.e. forms line up under first). + +(put 'autoload 'lisp-indent-function 'defun) ;Elisp +(put 'progn 'lisp-indent-function 0) +(put 'defvar 'lisp-indent-function 'defun) +(put 'defalias 'lisp-indent-function 'defun) +(put 'defvaralias 'lisp-indent-function 'defun) +(put 'defconst 'lisp-indent-function 'defun) +(put 'define-category 'lisp-indent-function 'defun) +(put 'define-charset-internal 'lisp-indent-function 'defun) +(put 'define-fringe-bitmap 'lisp-indent-function 'defun) +(put 'prog1 'lisp-indent-function 1) +(put 'save-excursion 'lisp-indent-function 0) ;Elisp +(put 'save-restriction 'lisp-indent-function 0) ;Elisp +(put 'save-current-buffer 'lisp-indent-function 0) ;Elisp +(put 'let 'lisp-indent-function 1) +(put 'let* 'lisp-indent-function 1) +(put 'while 'lisp-indent-function 1) +(put 'if 'lisp-indent-function 2) +(put 'catch 'lisp-indent-function 1) +(put 'condition-case 'lisp-indent-function 2) +(put 'handler-case 'lisp-indent-function 1) ;CL +(put 'handler-bind 'lisp-indent-function 1) ;CL +(put 'unwind-protect 'lisp-indent-function 1) +(put 'with-output-to-temp-buffer 'lisp-indent-function 1) +(put 'closure 'lisp-indent-function 2) + +(defun indent-sexp (&optional endpos) + "Indent each line of the list starting just after point. +If optional arg ENDPOS is given, indent each line, stopping when +ENDPOS is encountered." + (interactive) + (let* ((parse-state (lisp-indent-initial-state))) + ;; We need a marker because we modify the buffer + ;; text preceding endpos. + (setq endpos (copy-marker + (if endpos endpos + ;; Get error now if we don't have a complete sexp + ;; after point. + (save-excursion + (forward-sexp 1) + (let ((eol (line-end-position))) + ;; We actually look for a sexp which ends + ;; after the current line so that we properly + ;; indent things like #s(...). This might not + ;; be needed if Bug#15998 is fixed. + (when (and (< (point) eol) + ;; Check if eol is within a sexp. + (> (nth 0 (save-excursion + (parse-partial-sexp + (point) eol))) + 0)) + (condition-case () + (while (< (point) eol) + (forward-sexp 1)) + ;; But don't signal an error for incomplete + ;; sexps following the first complete sexp + ;; after point. + (scan-error nil)))) + (point))))) + (save-excursion + (while (let ((indent (lisp-indent-calc-next parse-state)) + (ppss (lisp-indent-state-ppss parse-state))) + ;; If the line contains a comment indent it now with + ;; `indent-for-comment'. + (when (and (nth 4 ppss) (<= (nth 8 ppss) endpos)) + (save-excursion + (goto-char (lisp-indent-state-ppss-point parse-state)) + (indent-for-comment) + (setf (lisp-indent-state-ppss-point parse-state) + (line-end-position)))) + (when (< (point) endpos) + ;; Indent the next line, unless it's blank, or just a + ;; comment (we will `indent-for-comment' the latter). + (skip-chars-forward " \t") + (unless (or (eolp) (not indent) + (eq (char-syntax (char-after)) ?<)) + (indent-line-to indent)) + t)))) + (move-marker endpos nil))) + +(defun indent-pp-sexp (&optional arg) + "Indent each line of the list starting just after point, or prettyprint it. +A prefix argument specifies pretty-printing." + (interactive "P") + (if arg + (save-excursion + (save-restriction + (narrow-to-region (point) (progn (forward-sexp 1) (point))) + (pp-buffer) + (goto-char (point-max)) + (if (eq (char-before) ?\n) + (delete-char -1))))) + (indent-sexp)) + +;;;; Lisp paragraph filling commands. + +(defcustom emacs-lisp-docstring-fill-column 65 + "Value of `fill-column' to use when filling a docstring. +Any non-integer value means do not use a different value of +`fill-column' when filling docstrings." + :type '(choice (integer) + (const :tag "Use the current `fill-column'" t)) + :safe (lambda (x) (or (eq x t) (integerp x))) + :group 'lisp) + +(defun lisp-fill-paragraph (&optional justify) + "Like \\[fill-paragraph], but handle Emacs Lisp comments and docstrings. +If any of the current line is a comment, fill the comment or the +paragraph of it that point is in, preserving the comment's indentation +and initial semicolons." + (interactive "P") + (or (fill-comment-paragraph justify) + ;; Since fill-comment-paragraph returned nil, that means we're not in + ;; a comment: Point is on a program line; we are interested + ;; particularly in docstring lines. + ;; + ;; FIXME: The below bindings are probably mostly irrelevant + ;; since we're now narrowing to a region before filling. + ;; + ;; We bind `paragraph-start' and `paragraph-separate' temporarily. They + ;; are buffer-local, but we avoid changing them so that they can be set + ;; to make `forward-paragraph' and friends do something the user wants. + ;; + ;; `paragraph-start': The `(' in the character alternative and the + ;; left-singlequote plus `(' sequence after the \\| alternative prevent + ;; sexps and backquoted sexps that follow a docstring from being filled + ;; with the docstring. This setting has the consequence of inhibiting + ;; filling many program lines that are not docstrings, which is sensible, + ;; because the user probably asked to fill program lines by accident, or + ;; expecting indentation (perhaps we should try to do indenting in that + ;; case). The `;' and `:' stop the paragraph being filled at following + ;; comment lines and at keywords (e.g., in `defcustom'). Left parens are + ;; escaped to keep font-locking, filling, & paren matching in the source + ;; file happy. The `:' must be preceded by whitespace so that keywords + ;; inside of the docstring don't start new paragraphs (Bug#7751). + ;; + ;; `paragraph-separate': A clever regexp distinguishes the first line of + ;; a docstring and identifies it as a paragraph separator, so that it + ;; won't be filled. (Since the first line of documentation stands alone + ;; in some contexts, filling should not alter the contents the author has + ;; chosen.) Only the first line of a docstring begins with whitespace + ;; and a quotation mark and ends with a period or (rarely) a comma. + ;; + ;; The `fill-column' is temporarily bound to + ;; `emacs-lisp-docstring-fill-column' if that value is an integer. + (let ((paragraph-start + (concat paragraph-start + "\\|\\s-*\\([(;\"]\\|\\s-:\\|`(\\|#'(\\)")) + (paragraph-separate + (concat paragraph-separate "\\|\\s-*\".*[,\\.]$")) + (fill-column (if (and (integerp emacs-lisp-docstring-fill-column) + (derived-mode-p 'emacs-lisp-mode)) + emacs-lisp-docstring-fill-column + fill-column))) + (let ((ppss (syntax-ppss)) + (start (point)) + ;; Avoid recursion if we're being called directly with + ;; `M-x lisp-fill-paragraph' in an `emacs-lisp-mode' buffer. + (fill-paragraph-function t)) + (save-excursion + (save-restriction + ;; If we're not inside a string, then do very basic + ;; filling. This avoids corrupting embedded strings in + ;; code. + (if (not (ppss-comment-or-string-start ppss)) + (lisp--fill-line-simple) + ;; If we're in a string, then narrow (roughly) to that + ;; string before filling. This avoids filling Lisp + ;; statements that follow the string. + (when (ppss-string-terminator ppss) + (goto-char (ppss-comment-or-string-start ppss)) + ;; The string may be unterminated -- in that case, don't + ;; narrow. + (when (ignore-errors + (progn + (forward-sexp 1) + t)) + (narrow-to-region (1+ (ppss-comment-or-string-start ppss)) + (1- (point))))) + ;; Move back to where we were. + (goto-char start) + ;; We should fill the first line of a string + ;; separately (since it's usually a doc string). + (if (= (line-number-at-pos) 1) + (narrow-to-region (line-beginning-position) + (line-beginning-position 2)) + (save-excursion + (goto-char (point-min)) + (forward-line 1) + (narrow-to-region (point) (point-max)))) + (fill-paragraph justify))))))) + ;; Never return nil. + t) + +(defun lisp--fill-line-simple () + (narrow-to-region (line-beginning-position) (line-end-position)) + (goto-char (point-min)) + (while (and (not (eobp)) + (re-search-forward "\\_>" nil t)) + (when (> (current-column) fill-column) + (let ((start (point))) + (backward-sexp) + (if (looking-back "[[(]" (point-min)) + (goto-char start) + (skip-chars-backward " \t") + (insert "\n") + (forward-sexp)))) + (unless (eobp) + (forward-char 1)))) + +(defun indent-code-rigidly (start end arg &optional nochange-regexp) + "Indent all lines of code, starting in the region, sideways by ARG columns. +Does not affect lines starting inside comments or strings, assuming that +the start of the region is not inside them. + +Called from a program, takes args START, END, COLUMNS and NOCHANGE-REGEXP. +The last is a regexp which, if matched at the beginning of a line, +means don't indent that line." + (interactive "r\np") + (let (state) + (save-excursion + (goto-char end) + (setq end (point-marker)) + (goto-char start) + (or (bolp) + (setq state (parse-partial-sexp (point) + (progn + (forward-line 1) (point)) + nil nil state))) + (while (< (point) end) + (or (car (nthcdr 3 state)) + (and nochange-regexp + (looking-at nochange-regexp)) + ;; If line does not start in string, indent it + (let ((indent (current-indentation))) + (delete-region (point) (progn (skip-chars-forward " \t") (point))) + (or (eolp) + (indent-to (max 0 (+ indent arg)) 0)))) + (setq state (parse-partial-sexp (point) + (progn + (forward-line 1) (point)) + nil nil state)))))) + +(provide 'lisp-mode) + +;;; lisp-mode.el ends here |