1 ;;; context-coloring-emacs-lisp.el --- Emacs Lisp support -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2014-2016 Free Software Foundation, Inc.
5 ;; This file is part of GNU Emacs.
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;; Add Emacs Lisp context coloring support.
26 (require 'context-coloring)
29 ;;; Emacs Lisp colorization
31 (defconst context-coloring-WORD-CODE 2)
32 (defconst context-coloring-SYMBOL-CODE 3)
33 (defconst context-coloring-OPEN-PARENTHESIS-CODE 4)
34 (defconst context-coloring-CLOSE-PARENTHESIS-CODE 5)
35 (defconst context-coloring-EXPRESSION-PREFIX-CODE 6)
36 (defconst context-coloring-STRING-QUOTE-CODE 7)
37 (defconst context-coloring-ESCAPE-CODE 9)
38 (defconst context-coloring-COMMENT-START-CODE 11)
39 (defconst context-coloring-COMMENT-END-CODE 12)
41 (defconst context-coloring-OCTOTHORPE-CHAR (string-to-char "#"))
42 (defconst context-coloring-APOSTROPHE-CHAR (string-to-char "'"))
43 (defconst context-coloring-OPEN-PARENTHESIS-CHAR (string-to-char "("))
44 (defconst context-coloring-COMMA-CHAR (string-to-char ","))
45 (defconst context-coloring-AT-CHAR (string-to-char "@"))
46 (defconst context-coloring-BACKTICK-CHAR (string-to-char "`"))
48 (defsubst context-coloring-get-syntax-code ()
49 "Get the syntax code at point."
51 ;; Faster version of `syntax-after':
52 (aref (syntax-table) (char-after (point)))))
54 (defsubst context-coloring-forward-sws ()
55 "Move forward through whitespace and comments."
56 (while (forward-comment 1)))
58 (defsubst context-coloring-elisp-forward-sws ()
59 "Move through whitespace and comments, coloring comments."
60 (let ((start (point)))
61 (context-coloring-forward-sws)
62 (context-coloring-colorize-comments-and-strings start (point))))
64 (defsubst context-coloring-elisp-forward-sexp ()
65 "Skip/ignore missing sexps, coloring comments and strings."
66 (let ((start (point)))
67 (when (= (context-coloring-get-syntax-code)
68 context-coloring-EXPRESSION-PREFIX-CODE)
69 ;; `forward-sexp' does not skip an unfinished expression (e.g. when the
70 ;; name of a symbol or the parentheses of a list do not follow a single
75 (scan-error (context-coloring-forward-sws)))
76 (context-coloring-elisp-colorize-comments-and-strings-in-region
79 (defsubst context-coloring-exact-regexp (word)
80 "Create a regexp matching exactly WORD."
81 (concat "\\`" (regexp-quote word) "\\'"))
83 (defsubst context-coloring-exact-or-regexp (words)
84 "Create a regexp matching any exact word in WORDS."
85 (context-coloring-join
86 (mapcar #'context-coloring-exact-regexp words) "\\|"))
88 (defconst context-coloring-elisp-ignored-word-regexp
89 (context-coloring-join (list "\\`[-+]?[0-9]"
91 (context-coloring-exact-or-regexp
92 '("t" "nil" "." "?")))
94 "Match symbols that can't be bound as variables.")
96 (defsubst context-coloring-elisp-identifier-p (syntax-code)
97 "Check if SYNTAX-CODE is an elisp identifier constituent."
98 (or (= syntax-code context-coloring-WORD-CODE)
99 (= syntax-code context-coloring-SYMBOL-CODE)))
101 (defconst context-coloring-elisp-sexps-per-pause 350
102 "Pause after this many iterations to check for user input.
103 If user input is pending, stop the parse. This makes for a
104 smoother user experience for large files.
106 This number should trigger pausing at about 60 frames per
109 (defvar context-coloring-elisp-sexp-count 0
110 "Current number of sexps leading up to the next pause.")
112 (defsubst context-coloring-elisp-increment-sexp-count ()
113 "Maybe check if the user interrupted the current parse."
114 (setq context-coloring-elisp-sexp-count
115 (1+ context-coloring-elisp-sexp-count))
116 (when (and (zerop (% context-coloring-elisp-sexp-count
117 context-coloring-elisp-sexps-per-pause))
118 context-coloring-parse-interruptable-p
120 (throw 'interrupted t)))
122 (defvar context-coloring-elisp-scope-stack '()
123 "List of scopes in the current parse.")
125 (defsubst context-coloring-elisp-make-scope (level)
126 "Make a scope object for LEVEL."
131 (defsubst context-coloring-elisp-scope-get-level (scope)
132 "Get the level of SCOPE object."
133 (plist-get scope :level))
135 (defsubst context-coloring-elisp-scope-add-variable (scope variable)
136 "Add to SCOPE a VARIABLE."
137 (plist-put scope :variables (cons variable (plist-get scope :variables))))
139 (defsubst context-coloring-elisp-scope-has-variable (scope variable)
140 "Check if SCOPE has VARIABLE."
141 (member variable (plist-get scope :variables)))
143 (defsubst context-coloring-elisp-get-variable-level (variable)
144 "Return the level of VARIABLE, or 0 if it isn't found."
145 (let* ((scope-stack context-coloring-elisp-scope-stack)
148 (while (and scope-stack (not level))
149 (setq scope (car scope-stack))
151 ((context-coloring-elisp-scope-has-variable scope variable)
152 (setq level (context-coloring-elisp-scope-get-level scope)))
154 (setq scope-stack (cdr scope-stack)))))
155 ;; Assume a global variable.
158 (defsubst context-coloring-elisp-get-current-scope-level ()
159 "Get the nesting level of the current scope."
161 ((car context-coloring-elisp-scope-stack)
162 (context-coloring-elisp-scope-get-level (car context-coloring-elisp-scope-stack)))
166 (defsubst context-coloring-elisp-push-scope ()
167 "Add a new scope to the bottom of the scope chain."
168 (push (context-coloring-elisp-make-scope
169 (1+ (context-coloring-elisp-get-current-scope-level)))
170 context-coloring-elisp-scope-stack))
172 (defsubst context-coloring-elisp-pop-scope ()
173 "Remove the scope on the bottom of the scope chain."
174 (pop context-coloring-elisp-scope-stack))
176 (defsubst context-coloring-elisp-add-variable (variable)
177 "Add VARIABLE to the current scope."
178 (context-coloring-elisp-scope-add-variable
179 (car context-coloring-elisp-scope-stack)
182 (defsubst context-coloring-elisp-parse-bindable (callback)
183 "Parse the symbol at point.
184 If the symbol can be bound, invoke CALLBACK with it."
185 (let* ((arg-string (buffer-substring-no-properties
187 (progn (context-coloring-elisp-forward-sexp)
189 (when (not (string-match-p
190 context-coloring-elisp-ignored-word-regexp
192 (funcall callback arg-string))))
194 (defun context-coloring-elisp-parse-let-varlist (type)
195 "Parse the list of variable initializers at point.
196 If TYPE is `let', all the variables are bound after all their
197 initializers are parsed; if TYPE is `let*', each variable is
198 bound immediately after its own initializer is parsed."
203 (context-coloring-elisp-forward-sws)
204 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
205 context-coloring-CLOSE-PARENTHESIS-CODE)
207 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
209 (context-coloring-elisp-forward-sws)
210 (setq syntax-code (context-coloring-get-syntax-code))
211 (when (context-coloring-elisp-identifier-p syntax-code)
212 (context-coloring-elisp-parse-bindable
215 (context-coloring-elisp-forward-sws)
216 (setq syntax-code (context-coloring-get-syntax-code))
217 (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
218 (context-coloring-elisp-colorize-sexp)))
219 (context-coloring-elisp-forward-sws)
220 ;; Skip past the closing parenthesis.
222 ((context-coloring-elisp-identifier-p syntax-code)
223 (context-coloring-elisp-parse-bindable
225 (push var varlist))))
228 (context-coloring-elisp-forward-sexp)))
229 (when (eq type 'let*)
230 (context-coloring-elisp-add-variable (pop varlist)))
231 (context-coloring-elisp-forward-sws))
234 (context-coloring-elisp-add-variable (pop varlist))))
238 (defun context-coloring-elisp-parse-arglist ()
239 "Parse the list of function arguments at point."
243 (context-coloring-elisp-forward-sws)
244 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
245 context-coloring-CLOSE-PARENTHESIS-CODE)
247 ((context-coloring-elisp-identifier-p syntax-code)
248 (context-coloring-elisp-parse-bindable
250 (context-coloring-elisp-add-variable arg))))
253 (context-coloring-elisp-forward-sexp)))
254 (context-coloring-elisp-forward-sws))
258 (defun context-coloring-elisp-skip-callee-name ()
259 "Skip past the opening parenthesis and name of a function."
262 (context-coloring-elisp-forward-sws)
263 ;; Skip past the function name.
265 (context-coloring-elisp-forward-sws))
267 (defun context-coloring-elisp-colorize-scope (callback)
268 "Color the whole scope at point with its one color.
269 Handle a header in CALLBACK."
270 (let ((start (point))
271 (end (progn (forward-sexp)
273 (context-coloring-elisp-push-scope)
274 ;; Splash the whole thing in one color.
275 (context-coloring-colorize-region
278 (context-coloring-elisp-get-current-scope-level))
279 ;; Even if the parse is interrupted, this region should still be colored
281 (context-coloring-elisp-colorize-comments-and-strings-in-region
285 (context-coloring-elisp-skip-callee-name)
287 (context-coloring-elisp-colorize-region (point) (1- end))
290 (context-coloring-elisp-pop-scope)))
292 (defun context-coloring-elisp-parse-header (callback)
293 "Parse a function header at point with CALLBACK."
294 (when (= (context-coloring-get-syntax-code) context-coloring-OPEN-PARENTHESIS-CODE)
297 (defun context-coloring-elisp-colorize-defun-like (callback)
298 "Color the defun-like function at point.
299 Parse the header with CALLBACK."
300 (context-coloring-elisp-colorize-scope
302 (when (context-coloring-elisp-identifier-p (context-coloring-get-syntax-code))
303 ;; Color the defun's name with the top-level color.
304 (context-coloring-colorize-region
306 (progn (forward-sexp)
309 (context-coloring-elisp-forward-sws)
310 (context-coloring-elisp-parse-header callback)))))
312 (defun context-coloring-elisp-colorize-defun ()
313 "Color the `defun' at point."
314 (context-coloring-elisp-colorize-defun-like
315 'context-coloring-elisp-parse-arglist))
317 (defun context-coloring-elisp-colorize-defadvice ()
318 "Color the `defadvice' at point."
319 (context-coloring-elisp-colorize-defun-like
324 (context-coloring-elisp-forward-sws)
325 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
326 context-coloring-CLOSE-PARENTHESIS-CODE)
328 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
329 (context-coloring-elisp-parse-arglist))
332 (context-coloring-elisp-forward-sexp)))
333 (context-coloring-elisp-forward-sws))))))
335 (defun context-coloring-elisp-colorize-lambda-like (callback)
336 "Color the lambda-like function at point.
337 Parsing the header with CALLBACK."
338 (context-coloring-elisp-colorize-scope
340 (context-coloring-elisp-parse-header callback))))
342 (defun context-coloring-elisp-colorize-lambda ()
343 "Color the `lambda' at point."
344 (context-coloring-elisp-colorize-lambda-like
345 'context-coloring-elisp-parse-arglist))
347 (defun context-coloring-elisp-colorize-let ()
348 "Color the `let' at point."
349 (context-coloring-elisp-colorize-lambda-like
351 (context-coloring-elisp-parse-let-varlist 'let))))
353 (defun context-coloring-elisp-colorize-let* ()
354 "Color the `let*' at point."
355 (context-coloring-elisp-colorize-lambda-like
357 (context-coloring-elisp-parse-let-varlist 'let*))))
359 (defun context-coloring-elisp-colorize-macroexp-let2 ()
360 "Color the `macroexp-let2' at point."
363 (context-coloring-elisp-colorize-scope
367 (setq syntax-code (context-coloring-get-syntax-code))
368 (context-coloring-elisp-identifier-p syntax-code))
370 (context-coloring-elisp-colorize-sexp)
371 (context-coloring-elisp-forward-sws)
372 (setq syntax-code (context-coloring-get-syntax-code))
373 (context-coloring-elisp-identifier-p syntax-code))
375 (context-coloring-elisp-parse-bindable
376 (lambda (parsed-variable)
377 (setq variable parsed-variable)))
378 (context-coloring-elisp-forward-sws)
380 (context-coloring-elisp-add-variable variable))))))))
382 (defun context-coloring-elisp-colorize-cond ()
383 "Color the `cond' at point."
385 (context-coloring-elisp-skip-callee-name)
386 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
387 context-coloring-CLOSE-PARENTHESIS-CODE)
389 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
390 ;; Colorize inside the parens.
391 (let ((start (point)))
393 (context-coloring-elisp-colorize-region
394 (1+ start) (1- (point)))
399 (context-coloring-elisp-forward-sexp)))
400 (context-coloring-elisp-forward-sws))
404 (defun context-coloring-elisp-colorize-condition-case ()
405 "Color the `condition-case' at point."
410 (context-coloring-elisp-colorize-scope
412 (setq syntax-code (context-coloring-get-syntax-code))
413 ;; Gracefully ignore missing variables.
414 (when (context-coloring-elisp-identifier-p syntax-code)
415 (context-coloring-elisp-parse-bindable
416 (lambda (parsed-variable)
417 (setq variable parsed-variable)))
418 (context-coloring-elisp-forward-sws))
419 (context-coloring-elisp-colorize-sexp)
420 (context-coloring-elisp-forward-sws)
421 ;; Parse the handlers with the error variable in scope.
423 (context-coloring-elisp-add-variable variable))
424 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
425 context-coloring-CLOSE-PARENTHESIS-CODE)
427 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
428 (setq case-pos (point))
429 (context-coloring-elisp-forward-sexp)
430 (setq case-end (point))
434 (context-coloring-elisp-forward-sws)
435 (setq syntax-code (context-coloring-get-syntax-code))
436 (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
437 ;; Skip the condition name(s).
438 (context-coloring-elisp-forward-sexp)
439 ;; Color the remaining portion of the handler.
440 (context-coloring-elisp-colorize-region
447 (context-coloring-elisp-forward-sexp)))
448 (context-coloring-elisp-forward-sws))))))
450 (defun context-coloring-elisp-colorize-dolist ()
451 "Color the `dolist' at point."
454 (context-coloring-elisp-colorize-scope
456 (setq syntax-code (context-coloring-get-syntax-code))
457 (when (= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
459 (context-coloring-elisp-forward-sws)
460 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
461 context-coloring-CLOSE-PARENTHESIS-CODE)
464 (or (= index 0) (= index 2))
465 (context-coloring-elisp-identifier-p syntax-code))
466 ;; Add the first or third name to the scope.
467 (context-coloring-elisp-parse-bindable
469 (context-coloring-elisp-add-variable variable))))
472 (context-coloring-elisp-colorize-sexp)))
473 (context-coloring-elisp-forward-sws)
474 (setq index (1+ index)))
478 (defun context-coloring-elisp-colorize-quote ()
479 "Color the `quote' at point."
480 (let* ((start (point))
481 (end (progn (forward-sexp)
483 (context-coloring-colorize-region
486 (context-coloring-elisp-get-current-scope-level))
487 (context-coloring-elisp-colorize-comments-and-strings-in-region start end)))
489 (defvar context-coloring-elisp-callee-dispatch-hash-table
490 (let ((table (make-hash-table :test 'equal)))
491 (dolist (callee '("defun" "defun*" "defsubst" "defmacro" "cl-defun" "cl-defsubst" "cl-defmacro"))
492 (puthash callee #'context-coloring-elisp-colorize-defun table))
493 (dolist (callee '("condition-case" "condition-case-unless-debug"))
494 (puthash callee #'context-coloring-elisp-colorize-condition-case table))
495 (dolist (callee '("dolist" "dotimes"))
496 (puthash callee #'context-coloring-elisp-colorize-dolist table))
497 (dolist (callee '("let" "gv-letplace"))
498 (puthash callee #'context-coloring-elisp-colorize-let table))
499 (puthash "let*" #'context-coloring-elisp-colorize-let* table)
500 (puthash "macroexp-let2" #'context-coloring-elisp-colorize-macroexp-let2 table)
501 (puthash "lambda" #'context-coloring-elisp-colorize-lambda table)
502 (puthash "cond" #'context-coloring-elisp-colorize-cond table)
503 (puthash "defadvice" #'context-coloring-elisp-colorize-defadvice table)
504 (puthash "quote" #'context-coloring-elisp-colorize-quote table)
505 (puthash "backquote" #'context-coloring-elisp-colorize-backquote table)
507 "Map function names to their coloring functions.")
509 (defun context-coloring-elisp-colorize-parenthesized-sexp ()
510 "Color the sexp enclosed by parenthesis at point."
511 (context-coloring-elisp-increment-sexp-count)
512 (let* ((start (point))
513 (end (progn (forward-sexp)
515 (syntax-code (progn (goto-char start)
517 ;; Coloring is unnecessary here, it'll happen
519 (context-coloring-forward-sws)
520 (context-coloring-get-syntax-code)))
522 ;; Figure out if the sexp is a special form.
524 ((and (context-coloring-elisp-identifier-p syntax-code)
525 (setq dispatch-function (gethash
526 (buffer-substring-no-properties
528 (progn (forward-sexp)
530 context-coloring-elisp-callee-dispatch-hash-table)))
532 (funcall dispatch-function))
533 ;; Not a special form; just colorize the remaining region.
535 (context-coloring-colorize-region
538 (context-coloring-elisp-get-current-scope-level))
539 (context-coloring-elisp-colorize-region (point) (1- end))
542 (defun context-coloring-elisp-colorize-symbol ()
543 "Color the symbol at point."
544 (context-coloring-elisp-increment-sexp-count)
545 (let* ((symbol-pos (point))
546 (symbol-end (progn (forward-sexp)
548 (symbol-string (buffer-substring-no-properties
552 ((string-match-p context-coloring-elisp-ignored-word-regexp symbol-string))
554 (context-coloring-colorize-region
557 (context-coloring-elisp-get-variable-level
560 (defun context-coloring-elisp-colorize-backquote-form ()
561 "Color the backquote form at point."
562 (let ((start (point))
563 (end (progn (forward-sexp)
567 (while (> end (progn (forward-char)
569 (setq char (char-after))
570 (when (= char context-coloring-COMMA-CHAR)
572 (when (= (char-after) context-coloring-AT-CHAR)
573 ;; If we don't do this "@" could be interpreted as a symbol.
575 (context-coloring-elisp-forward-sws)
576 (context-coloring-elisp-colorize-sexp)))
577 ;; We could probably do this as part of the above loop but it'd be
579 (context-coloring-elisp-colorize-comments-and-strings-in-region
582 (defun context-coloring-elisp-colorize-backquote ()
583 "Color the `backquote' at point."
584 (context-coloring-elisp-skip-callee-name)
585 (context-coloring-elisp-colorize-backquote-form)
589 (defun context-coloring-elisp-colorize-expression-prefix ()
590 "Color the expression prefix and expression at point.
591 It could be a quoted or backquoted expression."
592 (context-coloring-elisp-increment-sexp-count)
594 ((/= (char-after) context-coloring-BACKTICK-CHAR)
595 (context-coloring-elisp-forward-sexp))
597 (context-coloring-elisp-colorize-backquote-form))))
599 (defun context-coloring-elisp-colorize-comment ()
600 "Color the comment at point."
601 (context-coloring-elisp-increment-sexp-count)
602 (context-coloring-elisp-forward-sws))
604 (defun context-coloring-elisp-colorize-string ()
605 "Color the string at point."
606 (context-coloring-elisp-increment-sexp-count)
607 (let ((start (point)))
609 (context-coloring-colorize-comments-and-strings start (point))))
611 ;; Elisp has whitespace, words, symbols, open/close parenthesis, expression
612 ;; prefix, string quote, comment starters/enders and escape syntax classes only.
614 (defun context-coloring-elisp-colorize-sexp ()
615 "Color the sexp at point."
616 (let ((syntax-code (context-coloring-get-syntax-code)))
618 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
619 (context-coloring-elisp-colorize-parenthesized-sexp))
620 ((context-coloring-elisp-identifier-p syntax-code)
621 (context-coloring-elisp-colorize-symbol))
622 ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
623 (context-coloring-elisp-colorize-expression-prefix))
624 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
625 (context-coloring-elisp-colorize-string))
626 ((= syntax-code context-coloring-ESCAPE-CODE)
629 (defun context-coloring-elisp-colorize-comments-and-strings-in-region (start end)
630 "Color comments and strings between START and END."
633 (while (> end (progn (skip-syntax-forward "^\"<\\" end)
635 (setq syntax-code (context-coloring-get-syntax-code))
637 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
638 (context-coloring-elisp-colorize-string))
639 ((= syntax-code context-coloring-COMMENT-START-CODE)
640 (context-coloring-elisp-colorize-comment))
641 ((= syntax-code context-coloring-ESCAPE-CODE)
642 (forward-char 2))))))
644 (defun context-coloring-elisp-colorize-region (start end)
645 "Color everything between START and END."
648 (while (> end (progn (skip-syntax-forward "^w_('\"<\\" end)
650 (setq syntax-code (context-coloring-get-syntax-code))
652 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
653 (context-coloring-elisp-colorize-parenthesized-sexp))
654 ((context-coloring-elisp-identifier-p syntax-code)
655 (context-coloring-elisp-colorize-symbol))
656 ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
657 (context-coloring-elisp-colorize-expression-prefix))
658 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
659 (context-coloring-elisp-colorize-string))
660 ((= syntax-code context-coloring-COMMENT-START-CODE)
661 (context-coloring-elisp-colorize-comment))
662 ((= syntax-code context-coloring-ESCAPE-CODE)
663 (forward-char 2))))))
665 (defun context-coloring-elisp-colorize-region-initially (start end)
666 "Begin coloring everything between START and END."
667 (setq context-coloring-elisp-sexp-count 0)
668 (setq context-coloring-elisp-scope-stack '())
669 (let ((inhibit-point-motion-hooks t)
670 (case-fold-search nil)
671 ;; This is a recursive-descent parser, so give it a big stack.
672 (max-lisp-eval-depth (max max-lisp-eval-depth 3000))
673 (max-specpdl-size (max max-specpdl-size 3000)))
674 (context-coloring-elisp-colorize-region start end)))
676 (defun context-coloring-elisp-colorize-guard (callback)
677 "Silently color in CALLBACK."
678 (with-silent-modifications
682 ;; Scan errors can happen virtually anywhere if parenthesis are
683 ;; unbalanced. Just swallow them. (`progn' for test coverage.)
684 (scan-error (progn))))))
687 (defun context-coloring-elisp-colorize ()
688 "Color the current Emacs Lisp buffer."
690 (context-coloring-elisp-colorize-guard
693 ;; Just colorize the changed region.
694 (context-coloring-changed-p
695 (let* ( ;; Prevent `beginning-of-defun' from making poor assumptions.
696 (open-paren-in-column-0-is-defun-start nil)
697 ;; Seek the beginning and end of the previous and next
698 ;; offscreen defuns, so just enough is colored.
699 (start (progn (goto-char context-coloring-changed-start)
700 (while (and (< (point-min) (point))
701 (pos-visible-in-window-p))
705 (end (progn (goto-char context-coloring-changed-end)
706 (while (and (> (point-max) (point))
707 (pos-visible-in-window-p))
711 (context-coloring-elisp-colorize-region-initially start end)
712 ;; Fast coloring is nice, but if the code is not well-formed
713 ;; (e.g. an unclosed string literal is parsed at any time) then
714 ;; there could be leftover incorrectly-colored code offscreen. So
715 ;; do a clean sweep as soon as appropriate.
716 (context-coloring-schedule-coloring context-coloring-default-delay)))
718 (context-coloring-elisp-colorize-region-initially (point-min) (point-max)))))))
720 (context-coloring-define-dispatch
722 :modes '(emacs-lisp-mode lisp-interaction-mode)
723 :colorizer #'context-coloring-elisp-colorize
724 :delay 0.016 ;; Thanks to lazy colorization this can be 60 frames per second.
725 :setup #'context-coloring-setup-idle-change-detection
726 :teardown #'context-coloring-teardown-idle-change-detection)
729 ;;; eval-expression colorization
731 (defun context-coloring-eval-expression-match ()
732 "Determine expression start in `eval-expression'."
733 (string-match "\\`Eval: " (buffer-string)))
736 (defun context-coloring-eval-expression-colorize ()
737 "Color the `eval-expression' minibuffer prompt as elisp."
739 (context-coloring-elisp-colorize-guard
741 (context-coloring-elisp-colorize-region-initially
743 (context-coloring-eval-expression-match)
747 ;; `eval-expression-minibuffer-setup-hook' is not available in Emacs 24.3, so
748 ;; the backwards-compatible recommendation is to use `minibuffer-setup-hook' and
749 ;; rely on this predicate instead.
750 (defun context-coloring-eval-expression-predicate ()
751 "Non-nil if the minibuffer is for `eval-expression'."
752 ;; Kinda better than checking `this-command', because `this-command' changes.
753 (context-coloring-eval-expression-match))
755 (context-coloring-define-dispatch
757 :predicate #'context-coloring-eval-expression-predicate
758 :colorizer #'context-coloring-eval-expression-colorize
760 :setup #'context-coloring-setup-idle-change-detection
761 :teardown #'context-coloring-teardown-idle-change-detection)
763 (provide 'context-coloring-emacs-lisp)
765 ;;; context-coloring-emacs-lisp.el ends here