]> code.delx.au - gnu-emacs-elpa/blob - context-coloring.el
Add defcustom types.
[gnu-emacs-elpa] / context-coloring.el
1 ;;; context-coloring.el --- Highlight by scope -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
4
5 ;; Author: Jackson Ray Hamilton <jackson@jacksonrayhamilton.com>
6 ;; Version: 7.0.0
7 ;; Keywords: convenience faces tools
8 ;; Package-Requires: ((emacs "24.3") (js2-mode "20150713"))
9 ;; URL: https://github.com/jacksonrayhamilton/context-coloring
10
11 ;; This file is part of GNU Emacs.
12
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
17
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27
28 ;; Highlights code by scope. Top-level scopes are one color, second-level
29 ;; scopes are another color, and so on. Variables retain the color of the scope
30 ;; in which they are defined. A variable defined in an outer scope referenced
31 ;; by an inner scope is colored the same as the outer scope.
32
33 ;; By default, comments and strings are still highlighted syntactically.
34
35 ;;; Code:
36
37 (require 'js2-mode)
38
39
40 ;;; Utilities
41
42 (defun context-coloring-join (strings delimiter)
43 "Join a list of STRINGS with the string DELIMITER."
44 (mapconcat #'identity strings delimiter))
45
46
47 ;;; Faces
48
49 (defun context-coloring-defface (level light dark tty)
50 "Define a face for LEVEL with LIGHT, DARK and TTY colors."
51 (let ((face (intern (format "context-coloring-level-%s-face" level)))
52 (doc (format "Context coloring face, level %s." level)))
53 (custom-declare-face
54 face
55 `((((type tty)) (:foreground ,tty))
56 (((background light)) (:foreground ,light))
57 (((background dark)) (:foreground ,dark)))
58 doc
59 :group 'context-coloring)))
60
61 ;; Provide some default colors based off Emacs's defaults.
62 (context-coloring-defface 0 "#000000" "#ffffff" nil)
63 (context-coloring-defface 1 "#008b8b" "#00ffff" "yellow")
64 (context-coloring-defface 2 "#0000ff" "#87cefa" "green")
65 (context-coloring-defface 3 "#483d8b" "#b0c4de" "cyan")
66 (context-coloring-defface 4 "#a020f0" "#eedd82" "blue")
67 (context-coloring-defface 5 "#a0522d" "#98fb98" "magenta")
68 (context-coloring-defface 6 "#228b22" "#7fffd4" "red")
69 (context-coloring-defface 7 "#3f3f3f" "#cdcdcd" nil)
70
71 (defconst context-coloring-default-maximum-face 7
72 "Maximum face when there are no custom faces.")
73
74 ;; Create placeholder faces for users and theme authors.
75 (dotimes (level 18)
76 (let* ((level (+ level 8))
77 (face (intern (format "context-coloring-level-%s-face" level)))
78 (doc (format "Context coloring face, level %s." level)))
79 (custom-declare-face face nil doc :group 'context-coloring)))
80
81 (defvar-local context-coloring-maximum-face nil
82 "Dynamic index of the highest face available for coloring.")
83
84 (defsubst context-coloring-level-face (level)
85 "Return symbol for face with LEVEL."
86 ;; `concat' is faster than `format' here.
87 (intern-soft
88 (concat "context-coloring-level-" (number-to-string level) "-face")))
89
90 (defsubst context-coloring-bounded-level-face (level)
91 "Return symbol for face with LEVEL, bounded by the maximum."
92 (context-coloring-level-face (min level context-coloring-maximum-face)))
93
94 (defconst context-coloring-level-face-regexp
95 "context-coloring-level-\\([[:digit:]]+\\)-face"
96 "Extract a level from a face.")
97
98 (defun context-coloring-theme-highest-level (theme)
99 "Return the highest coloring level for THEME, or -1."
100 (let* ((settings (get theme 'theme-settings))
101 (tail settings)
102 face-string
103 number
104 (found -1))
105 (while tail
106 (and (eq (nth 0 (car tail)) 'theme-face)
107 (setq face-string (symbol-name (nth 1 (car tail))))
108 (string-match
109 context-coloring-level-face-regexp
110 face-string)
111 (setq number (string-to-number
112 (substring face-string
113 (match-beginning 1)
114 (match-end 1))))
115 (> number found)
116 (setq found number))
117 (setq tail (cdr tail)))
118 found))
119
120 (defun context-coloring-update-maximum-face ()
121 "Save the highest possible face for the current theme."
122 (let ((themes (append custom-enabled-themes '(user)))
123 (continue t)
124 theme
125 highest-level)
126 (while continue
127 (setq theme (car themes))
128 (setq themes (cdr themes))
129 (setq highest-level (context-coloring-theme-highest-level theme))
130 (setq continue (and themes (= highest-level -1))))
131 (setq context-coloring-maximum-face
132 (cond
133 ((= highest-level -1)
134 context-coloring-default-maximum-face)
135 (t
136 highest-level)))))
137
138
139 ;;; Change detection
140
141 (defvar-local context-coloring-changed-p nil
142 "Indication that the buffer has changed recently, which implies
143 that it should be colored again by
144 `context-coloring-maybe-colorize-idle-timer' if that timer is
145 being used.")
146
147 (defvar-local context-coloring-changed-start nil
148 "Beginning of last text that changed.")
149
150 (defvar-local context-coloring-changed-end nil
151 "End of last text that changed.")
152
153 (defvar-local context-coloring-changed-length nil
154 "Length of last text that changed.")
155
156 (defun context-coloring-change-function (start end length)
157 "Register a change so that a buffer can be colorized soon.
158
159 START, END and LENGTH are recorded for later use."
160 ;; Tokenization is obsolete if there was a change.
161 (setq context-coloring-changed-start start)
162 (setq context-coloring-changed-end end)
163 (setq context-coloring-changed-length length)
164 (setq context-coloring-changed-p t))
165
166 (defun context-coloring-maybe-colorize-with-buffer (buffer)
167 "Color BUFFER and if it has changed."
168 (when (and (eq buffer (current-buffer))
169 context-coloring-changed-p)
170 (context-coloring-colorize-with-buffer buffer)
171 (setq context-coloring-changed-p nil)
172 (setq context-coloring-changed-start nil)
173 (setq context-coloring-changed-end nil)
174 (setq context-coloring-changed-length nil)))
175
176 (defvar-local context-coloring-maybe-colorize-idle-timer nil
177 "The currently-running idle timer for conditional coloring.")
178
179 (defvar-local context-coloring-colorize-idle-timer nil
180 "The currently-running idle timer for unconditional coloring.")
181
182 (defcustom context-coloring-default-delay 0.25
183 "Default delay between a buffer update and colorization.
184
185 Increase this if your machine is high-performing. Decrease it if
186 it ain't."
187 :type 'float
188 :group 'context-coloring)
189
190 (make-obsolete-variable
191 'context-coloring-delay
192 'context-coloring-default-delay
193 "6.4.0")
194
195 (defun context-coloring-cancel-timer (timer)
196 "Cancel TIMER."
197 (when timer
198 (cancel-timer timer)))
199
200 (defun context-coloring-schedule-coloring (time)
201 "Schedule coloring to occur once after Emacs is idle for TIME."
202 (context-coloring-cancel-timer context-coloring-colorize-idle-timer)
203 (setq context-coloring-colorize-idle-timer
204 (run-with-idle-timer
205 time
206 nil
207 #'context-coloring-colorize-with-buffer
208 (current-buffer))))
209
210 (defun context-coloring-setup-idle-change-detection ()
211 "Setup idle change detection."
212 (let ((dispatch (context-coloring-get-current-dispatch)))
213 (add-hook
214 'after-change-functions #'context-coloring-change-function nil t)
215 (add-hook
216 'kill-buffer-hook #'context-coloring-teardown-idle-change-detection nil t)
217 (setq context-coloring-maybe-colorize-idle-timer
218 (run-with-idle-timer
219 (or (plist-get dispatch :delay) context-coloring-default-delay)
220 t
221 #'context-coloring-maybe-colorize-with-buffer
222 (current-buffer)))))
223
224 (defun context-coloring-teardown-idle-change-detection ()
225 "Teardown idle change detection."
226 (dolist (timer (list context-coloring-colorize-idle-timer
227 context-coloring-maybe-colorize-idle-timer))
228 (context-coloring-cancel-timer timer))
229 (remove-hook
230 'kill-buffer-hook #'context-coloring-teardown-idle-change-detection t)
231 (remove-hook
232 'after-change-functions #'context-coloring-change-function t))
233
234
235 ;;; Colorization utilities
236
237 (defsubst context-coloring-colorize-region (start end level)
238 "Color from START (inclusive) to END (exclusive) with LEVEL."
239 (add-text-properties
240 start
241 end
242 `(face ,(context-coloring-bounded-level-face level))))
243
244 (make-obsolete-variable
245 'context-coloring-comments-and-strings
246 "use `context-coloring-syntactic-comments' and
247 `context-coloring-syntactic-strings' instead."
248 "6.1.0")
249
250 (defcustom context-coloring-syntactic-comments t
251 "If non-nil, also color comments using `font-lock'."
252 :type 'boolean
253 :group 'context-coloring)
254
255 (defcustom context-coloring-syntactic-strings t
256 "If non-nil, also color strings using `font-lock'."
257 :type 'boolean
258 :group 'context-coloring)
259
260 (defun context-coloring-font-lock-syntactic-comment-function (state)
261 "Color a comment according to STATE."
262 (if (nth 3 state) nil font-lock-comment-face))
263
264 (defun context-coloring-font-lock-syntactic-string-function (state)
265 "Color a string according to STATE."
266 (if (nth 3 state) font-lock-string-face nil))
267
268 (defsubst context-coloring-colorize-comments-and-strings (&optional min max)
269 "Maybe color comments and strings in buffer from MIN to MAX.
270 MIN defaults to beginning of buffer. MAX defaults to end."
271 (when (or context-coloring-syntactic-comments
272 context-coloring-syntactic-strings)
273 (let ((min (or min (point-min)))
274 (max (or max (point-max)))
275 (font-lock-syntactic-face-function
276 (cond
277 ((and context-coloring-syntactic-comments
278 (not context-coloring-syntactic-strings))
279 #'context-coloring-font-lock-syntactic-comment-function)
280 ((and context-coloring-syntactic-strings
281 (not context-coloring-syntactic-comments))
282 #'context-coloring-font-lock-syntactic-string-function)
283 (t
284 font-lock-syntactic-face-function))))
285 (save-excursion
286 (font-lock-fontify-syntactically-region min max)
287 ;; TODO: Make configurable at the dispatch level.
288 (when (eq major-mode 'emacs-lisp-mode)
289 (font-lock-fontify-keywords-region min max))))))
290
291 (defcustom context-coloring-initial-level 0
292 "Scope level at which to start coloring.
293
294 If top-level variables and functions do not become global, but
295 are scoped to a file (as in Node.js), set this to `1'."
296 :type 'integer
297 :safe #'integerp
298 :group 'context-coloring)
299
300 (make-variable-buffer-local 'context-coloring-initial-level)
301
302
303 ;;; js2-mode colorization
304
305 (defvar-local context-coloring-js2-scope-level-hash-table nil
306 "Associate `js2-scope' structures and with their scope
307 levels.")
308
309 (defcustom context-coloring-javascript-block-scopes nil
310 "If non-nil, also color block scopes in the scope hierarchy in JavaScript.
311
312 The block-scoped `let' and `const' are introduced in ES6. Enable
313 this for ES6 code; disable it elsewhere."
314 :type 'boolean
315 :safe #'booleanp
316 :group 'context-coloring)
317
318 (make-obsolete-variable
319 'context-coloring-js-block-scopes
320 'context-coloring-javascript-block-scopes
321 "7.0.0")
322
323 (defsubst context-coloring-js2-scope-level (scope initial)
324 "Return the level of SCOPE, starting from INITIAL."
325 (cond ((gethash scope context-coloring-js2-scope-level-hash-table))
326 (t
327 (let ((level initial)
328 (current-scope scope)
329 enclosing-scope)
330 (while (and current-scope
331 (js2-node-parent current-scope)
332 (setq enclosing-scope
333 (js2-node-get-enclosing-scope current-scope)))
334 (when (or context-coloring-javascript-block-scopes
335 (let ((type (js2-scope-type current-scope)))
336 (or (= type js2-SCRIPT)
337 (= type js2-FUNCTION)
338 (= type js2-CATCH))))
339 (setq level (+ level 1)))
340 (setq current-scope enclosing-scope))
341 (puthash scope level context-coloring-js2-scope-level-hash-table)))))
342
343 (defsubst context-coloring-js2-local-name-node-p (node)
344 "Determine if NODE represents a local variable."
345 (and (js2-name-node-p node)
346 (let ((parent (js2-node-parent node)))
347 (not (or (and (js2-object-prop-node-p parent)
348 (eq node (js2-object-prop-node-left parent)))
349 (and (js2-prop-get-node-p parent)
350 ;; For nested property lookup, the node on the left is a
351 ;; `js2-prop-get-node', so this always works.
352 (eq node (js2-prop-get-node-right parent))))))))
353
354 (defvar-local context-coloring-point-max nil
355 "Cached value of `point-max'.")
356
357 (defsubst context-coloring-js2-colorize-node (node level)
358 "Color NODE with the color for LEVEL."
359 (let ((start (js2-node-abs-pos node)))
360 (context-coloring-colorize-region
361 start
362 (min
363 ;; End
364 (+ start (js2-node-len node))
365 ;; Somes nodes (like the ast when there is an unterminated multiline
366 ;; comment) will stretch to the value of `point-max'.
367 context-coloring-point-max)
368 level)))
369
370 (defun context-coloring-js2-colorize ()
371 "Color the buffer using the `js2-mode' abstract syntax tree."
372 ;; Reset the hash table; the old one could be obsolete.
373 (setq context-coloring-js2-scope-level-hash-table (make-hash-table :test #'eq))
374 (setq context-coloring-point-max (point-max))
375 (with-silent-modifications
376 (js2-visit-ast
377 js2-mode-ast
378 (lambda (node end-p)
379 (when (null end-p)
380 (cond
381 ((js2-scope-p node)
382 (context-coloring-js2-colorize-node
383 node
384 (context-coloring-js2-scope-level node context-coloring-initial-level)))
385 ((context-coloring-js2-local-name-node-p node)
386 (let* ((enclosing-scope (js2-node-get-enclosing-scope node))
387 (defining-scope (js2-get-defining-scope
388 enclosing-scope
389 (js2-name-node-name node))))
390 ;; The tree seems to be walked lexically, so an entire scope will
391 ;; be colored, including its name nodes, before they are reached.
392 ;; Coloring the nodes defined in that scope would be redundant, so
393 ;; don't do it.
394 (when (not (eq defining-scope enclosing-scope))
395 (context-coloring-js2-colorize-node
396 node
397 ;; Use `0' as an initial level so global variables are always at
398 ;; the highest level (even if `context-coloring-initial-level'
399 ;; specifies an initial level for the rest of the code).
400 (context-coloring-js2-scope-level defining-scope 0))))))
401 ;; The `t' indicates to search children.
402 t)))
403 (context-coloring-colorize-comments-and-strings)))
404
405
406 ;;; Emacs Lisp colorization
407
408 (defsubst context-coloring-forward-sws ()
409 "Move forward through whitespace and comments."
410 (while (forward-comment 1)))
411
412 (defsubst context-coloring-elisp-forward-sws ()
413 "Move through whitespace and comments, coloring comments."
414 (let ((start (point)))
415 (context-coloring-forward-sws)
416 (context-coloring-colorize-comments-and-strings start (point))))
417
418 (defsubst context-coloring-elisp-forward-sexp ()
419 "Like `forward-sexp', coloring skipped comments and strings."
420 (let ((start (point)))
421 (forward-sexp)
422 (context-coloring-elisp-colorize-comments-and-strings-in-region
423 start (point))))
424
425 (defsubst context-coloring-get-syntax-code ()
426 "Get the syntax code at point."
427 (syntax-class
428 ;; Faster version of `syntax-after':
429 (aref (syntax-table) (char-after (point)))))
430
431 (defsubst context-coloring-exact-regexp (word)
432 "Create a regexp matching exactly WORD."
433 (concat "\\`" (regexp-quote word) "\\'"))
434
435 (defsubst context-coloring-exact-or-regexp (words)
436 "Create a regexp matching any exact word in WORDS."
437 (context-coloring-join
438 (mapcar #'context-coloring-exact-regexp words) "\\|"))
439
440 (defconst context-coloring-elisp-ignored-word-regexp
441 (context-coloring-join (list "\\`[-+]?[0-9]"
442 "\\`[&:].+"
443 (context-coloring-exact-or-regexp
444 '("t" "nil" "." "?")))
445 "\\|")
446 "Match symbols that can't be bound as variables.")
447
448 (defconst context-coloring-WORD-CODE 2)
449 (defconst context-coloring-SYMBOL-CODE 3)
450 (defconst context-coloring-OPEN-PARENTHESIS-CODE 4)
451 (defconst context-coloring-CLOSE-PARENTHESIS-CODE 5)
452 (defconst context-coloring-EXPRESSION-PREFIX-CODE 6)
453 (defconst context-coloring-STRING-QUOTE-CODE 7)
454 (defconst context-coloring-ESCAPE-CODE 9)
455 (defconst context-coloring-COMMENT-START-CODE 11)
456 (defconst context-coloring-COMMENT-END-CODE 12)
457
458 (defconst context-coloring-OCTOTHORPE-CHAR (string-to-char "#"))
459 (defconst context-coloring-APOSTROPHE-CHAR (string-to-char "'"))
460 (defconst context-coloring-OPEN-PARENTHESIS-CHAR (string-to-char "("))
461 (defconst context-coloring-COMMA-CHAR (string-to-char ","))
462 (defconst context-coloring-AT-CHAR (string-to-char "@"))
463 (defconst context-coloring-BACKTICK-CHAR (string-to-char "`"))
464
465 (defsubst context-coloring-elisp-identifier-p (syntax-code)
466 "Check if SYNTAX-CODE is an elisp identifier constituent."
467 (or (= syntax-code context-coloring-WORD-CODE)
468 (= syntax-code context-coloring-SYMBOL-CODE)))
469
470 (defvar context-coloring-parse-interruptable-p t
471 "Set this to nil to force parse to continue until finished.")
472
473 (defconst context-coloring-elisp-sexps-per-pause 350
474 "Pause after this many iterations to check for user input.
475 If user input is pending, stop the parse. This makes for a
476 smoother user experience for large files.
477
478 This number should trigger pausing at about 60 frames per
479 second.")
480
481 (defvar context-coloring-elisp-sexp-count 0
482 "Current number of sexps leading up to the next pause.")
483
484 (defsubst context-coloring-elisp-increment-sexp-count ()
485 "Maybe check if the user interrupted the current parse."
486 (setq context-coloring-elisp-sexp-count
487 (1+ context-coloring-elisp-sexp-count))
488 (when (and (zerop (% context-coloring-elisp-sexp-count
489 context-coloring-elisp-sexps-per-pause))
490 context-coloring-parse-interruptable-p
491 (input-pending-p))
492 (throw 'interrupted t)))
493
494 (defvar context-coloring-elisp-scope-stack '()
495 "List of scopes in the current parse.")
496
497 (defsubst context-coloring-elisp-make-scope (level)
498 "Make a scope object for LEVEL."
499 (list
500 :level level
501 :variables '()))
502
503 (defsubst context-coloring-elisp-scope-get-level (scope)
504 "Get the level of SCOPE object."
505 (plist-get scope :level))
506
507 (defsubst context-coloring-elisp-scope-add-variable (scope variable)
508 "Add to SCOPE a VARIABLE."
509 (plist-put scope :variables (cons variable (plist-get scope :variables))))
510
511 (defsubst context-coloring-elisp-scope-has-variable (scope variable)
512 "Check if SCOPE has VARIABLE."
513 (member variable (plist-get scope :variables)))
514
515 (defsubst context-coloring-elisp-get-variable-level (variable)
516 "Return the level of VARIABLE, or 0 if it isn't found."
517 (let* ((scope-stack context-coloring-elisp-scope-stack)
518 scope
519 level)
520 (while (and scope-stack (not level))
521 (setq scope (car scope-stack))
522 (cond
523 ((context-coloring-elisp-scope-has-variable scope variable)
524 (setq level (context-coloring-elisp-scope-get-level scope)))
525 (t
526 (setq scope-stack (cdr scope-stack)))))
527 ;; Assume a global variable.
528 (or level 0)))
529
530 (defsubst context-coloring-elisp-get-current-scope-level ()
531 "Get the nesting level of the current scope."
532 (cond
533 ((car context-coloring-elisp-scope-stack)
534 (context-coloring-elisp-scope-get-level (car context-coloring-elisp-scope-stack)))
535 (t
536 0)))
537
538 (defsubst context-coloring-elisp-push-scope ()
539 "Add a new scope to the bottom of the scope chain."
540 (push (context-coloring-elisp-make-scope
541 (1+ (context-coloring-elisp-get-current-scope-level)))
542 context-coloring-elisp-scope-stack))
543
544 (defsubst context-coloring-elisp-pop-scope ()
545 "Remove the scope on the bottom of the scope chain."
546 (pop context-coloring-elisp-scope-stack))
547
548 (defsubst context-coloring-elisp-add-variable (variable)
549 "Add VARIABLE to the current scope."
550 (context-coloring-elisp-scope-add-variable
551 (car context-coloring-elisp-scope-stack)
552 variable))
553
554 (defsubst context-coloring-elisp-parse-bindable (callback)
555 "Parse the symbol at point.
556 If the symbol can be bound, invoke CALLBACK with it."
557 (let* ((arg-string (buffer-substring-no-properties
558 (point)
559 (progn (context-coloring-elisp-forward-sexp)
560 (point)))))
561 (when (not (string-match-p
562 context-coloring-elisp-ignored-word-regexp
563 arg-string))
564 (funcall callback arg-string))))
565
566 (defun context-coloring-elisp-parse-let-varlist (type)
567 "Parse the list of variable initializers at point.
568 If TYPE is `let', all the variables are bound after all their
569 initializers are parsed; if TYPE is `let*', each variable is
570 bound immediately after its own initializer is parsed."
571 (let ((varlist '())
572 syntax-code)
573 ;; Enter.
574 (forward-char)
575 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
576 context-coloring-CLOSE-PARENTHESIS-CODE)
577 (cond
578 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
579 (forward-char)
580 (context-coloring-elisp-forward-sws)
581 (setq syntax-code (context-coloring-get-syntax-code))
582 (when (context-coloring-elisp-identifier-p syntax-code)
583 (context-coloring-elisp-parse-bindable
584 (lambda (var)
585 (push var varlist)))
586 (context-coloring-elisp-forward-sws)
587 (setq syntax-code (context-coloring-get-syntax-code))
588 (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
589 (context-coloring-elisp-colorize-sexp)))
590 (context-coloring-elisp-forward-sws)
591 ;; Skip past the closing parenthesis.
592 (forward-char))
593 ((context-coloring-elisp-identifier-p syntax-code)
594 (context-coloring-elisp-parse-bindable
595 (lambda (var)
596 (push var varlist))))
597 (t
598 ;; Ignore artifacts.
599 (context-coloring-elisp-forward-sexp)))
600 (when (eq type 'let*)
601 (context-coloring-elisp-add-variable (pop varlist)))
602 (context-coloring-elisp-forward-sws))
603 (when (eq type 'let)
604 (while varlist
605 (context-coloring-elisp-add-variable (pop varlist))))
606 ;; Exit.
607 (forward-char)))
608
609 (defun context-coloring-elisp-parse-arglist ()
610 "Parse the list of function arguments at point."
611 (let (syntax-code)
612 ;; Enter.
613 (forward-char)
614 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
615 context-coloring-CLOSE-PARENTHESIS-CODE)
616 (cond
617 ((context-coloring-elisp-identifier-p syntax-code)
618 (context-coloring-elisp-parse-bindable
619 (lambda (arg)
620 (context-coloring-elisp-add-variable arg))))
621 (t
622 ;; Ignore artifacts.
623 (context-coloring-elisp-forward-sexp)))
624 (context-coloring-elisp-forward-sws))
625 ;; Exit.
626 (forward-char)))
627
628 (defun context-coloring-elisp-skip-callee-name ()
629 "Skip past the opening parenthesis and name of a function."
630 ;; Enter.
631 (forward-char)
632 (context-coloring-elisp-forward-sws)
633 ;; Skip past the function name.
634 (forward-sexp)
635 (context-coloring-elisp-forward-sws))
636
637 (defun context-coloring-elisp-colorize-scope (callback)
638 "Color the whole scope at point with its one color.
639 Handle a header in CALLBACK."
640 (let ((start (point))
641 (end (progn (forward-sexp)
642 (point))))
643 (context-coloring-elisp-push-scope)
644 ;; Splash the whole thing in one color.
645 (context-coloring-colorize-region
646 start
647 end
648 (context-coloring-elisp-get-current-scope-level))
649 ;; Even if the parse is interrupted, this region should still be colored
650 ;; syntactically.
651 (context-coloring-elisp-colorize-comments-and-strings-in-region
652 start
653 end)
654 (goto-char start)
655 (context-coloring-elisp-skip-callee-name)
656 (funcall callback)
657 (context-coloring-elisp-colorize-region (point) (1- end))
658 ;; Exit.
659 (forward-char)
660 (context-coloring-elisp-pop-scope)))
661
662 (defun context-coloring-elisp-parse-header (callback)
663 "Parse a function header at point with CALLBACK."
664 (when (= (context-coloring-get-syntax-code) context-coloring-OPEN-PARENTHESIS-CODE)
665 (funcall callback)))
666
667 (defun context-coloring-elisp-colorize-defun-like (callback)
668 "Color the defun-like function at point.
669 Parse the header with CALLBACK."
670 (context-coloring-elisp-colorize-scope
671 (lambda ()
672 (when (context-coloring-elisp-identifier-p (context-coloring-get-syntax-code))
673 ;; Color the defun's name with the top-level color.
674 (context-coloring-colorize-region
675 (point)
676 (progn (forward-sexp)
677 (point))
678 0)
679 (context-coloring-elisp-forward-sws)
680 (context-coloring-elisp-parse-header callback)))))
681
682 (defun context-coloring-elisp-colorize-defun ()
683 "Color the `defun' at point."
684 (context-coloring-elisp-colorize-defun-like
685 'context-coloring-elisp-parse-arglist))
686
687 (defun context-coloring-elisp-colorize-defadvice ()
688 "Color the `defadvice' at point."
689 (context-coloring-elisp-colorize-defun-like
690 (lambda ()
691 (let (syntax-code)
692 ;; Enter.
693 (forward-char)
694 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
695 context-coloring-CLOSE-PARENTHESIS-CODE)
696 (cond
697 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
698 (context-coloring-elisp-parse-arglist))
699 (t
700 ;; Ignore artifacts.
701 (context-coloring-elisp-forward-sexp)))
702 (context-coloring-elisp-forward-sws))))))
703
704 (defun context-coloring-elisp-colorize-lambda-like (callback)
705 "Color the lambda-like function at point.
706 Parsing the header with CALLBACK."
707 (context-coloring-elisp-colorize-scope
708 (lambda ()
709 (context-coloring-elisp-parse-header callback))))
710
711 (defun context-coloring-elisp-colorize-lambda ()
712 "Color the `lambda' at point."
713 (context-coloring-elisp-colorize-lambda-like
714 'context-coloring-elisp-parse-arglist))
715
716 (defun context-coloring-elisp-colorize-let ()
717 "Color the `let' at point."
718 (context-coloring-elisp-colorize-lambda-like
719 (lambda ()
720 (context-coloring-elisp-parse-let-varlist 'let))))
721
722 (defun context-coloring-elisp-colorize-let* ()
723 "Color the `let*' at point."
724 (context-coloring-elisp-colorize-lambda-like
725 (lambda ()
726 (context-coloring-elisp-parse-let-varlist 'let*))))
727
728 (defun context-coloring-elisp-colorize-cond ()
729 "Color the `cond' at point."
730 (let (syntax-code)
731 (context-coloring-elisp-skip-callee-name)
732 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
733 context-coloring-CLOSE-PARENTHESIS-CODE)
734 (cond
735 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
736 ;; Colorize inside the parens.
737 (let ((start (point)))
738 (forward-sexp)
739 (context-coloring-elisp-colorize-region
740 (1+ start) (1- (point)))
741 ;; Exit.
742 (forward-char)))
743 (t
744 ;; Ignore artifacts.
745 (context-coloring-elisp-forward-sexp)))
746 (context-coloring-elisp-forward-sws))
747 ;; Exit.
748 (forward-char)))
749
750 (defun context-coloring-elisp-colorize-condition-case ()
751 "Color the `condition-case' at point."
752 (let (syntax-code
753 variable
754 case-pos
755 case-end)
756 (context-coloring-elisp-colorize-scope
757 (lambda ()
758 (setq syntax-code (context-coloring-get-syntax-code))
759 ;; Gracefully ignore missing variables.
760 (when (context-coloring-elisp-identifier-p syntax-code)
761 (context-coloring-elisp-parse-bindable
762 (lambda (parsed-variable)
763 (setq variable parsed-variable)))
764 (context-coloring-elisp-forward-sws))
765 (context-coloring-elisp-colorize-sexp)
766 (context-coloring-elisp-forward-sws)
767 ;; Parse the handlers with the error variable in scope.
768 (when variable
769 (context-coloring-elisp-add-variable variable))
770 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
771 context-coloring-CLOSE-PARENTHESIS-CODE)
772 (cond
773 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
774 (setq case-pos (point))
775 (context-coloring-elisp-forward-sexp)
776 (setq case-end (point))
777 (goto-char case-pos)
778 ;; Enter.
779 (forward-char)
780 (context-coloring-elisp-forward-sws)
781 (setq syntax-code (context-coloring-get-syntax-code))
782 (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
783 ;; Skip the condition name(s).
784 (context-coloring-elisp-forward-sexp)
785 ;; Color the remaining portion of the handler.
786 (context-coloring-elisp-colorize-region
787 (point)
788 (1- case-end)))
789 ;; Exit.
790 (forward-char))
791 (t
792 ;; Ignore artifacts.
793 (context-coloring-elisp-forward-sexp)))
794 (context-coloring-elisp-forward-sws))))))
795
796 (defun context-coloring-elisp-colorize-dolist ()
797 "Color the `dolist' at point."
798 (let (syntax-code
799 (index 0))
800 (context-coloring-elisp-colorize-scope
801 (lambda ()
802 (setq syntax-code (context-coloring-get-syntax-code))
803 (when (= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
804 (forward-char)
805 (context-coloring-elisp-forward-sws)
806 (while (/= (setq syntax-code (context-coloring-get-syntax-code))
807 context-coloring-CLOSE-PARENTHESIS-CODE)
808 (cond
809 ((and
810 (or (= index 0) (= index 2))
811 (context-coloring-elisp-identifier-p syntax-code))
812 ;; Add the first or third name to the scope.
813 (context-coloring-elisp-parse-bindable
814 (lambda (variable)
815 (context-coloring-elisp-add-variable variable))))
816 (t
817 ;; Color artifacts.
818 (context-coloring-elisp-colorize-sexp)))
819 (context-coloring-elisp-forward-sws)
820 (setq index (1+ index)))
821 ;; Exit.
822 (forward-char))))))
823
824 (defun context-coloring-elisp-colorize-quote ()
825 "Color the `quote' at point."
826 (let* ((start (point))
827 (end (progn (forward-sexp)
828 (point))))
829 (context-coloring-colorize-region
830 start
831 end
832 (context-coloring-elisp-get-current-scope-level))
833 (context-coloring-elisp-colorize-comments-and-strings-in-region start end)))
834
835 (defvar context-coloring-elisp-callee-dispatch-hash-table
836 (let ((table (make-hash-table :test 'equal)))
837 (dolist (callee '("defun" "defun*" "defsubst" "defmacro" "cl-defun" "cl-defsubst" "cl-defmacro"))
838 (puthash callee #'context-coloring-elisp-colorize-defun table))
839 (dolist (callee '("condition-case" "condition-case-unless-debug"))
840 (puthash callee #'context-coloring-elisp-colorize-condition-case table))
841 (dolist (callee '("dolist" "dotimes"))
842 (puthash callee #'context-coloring-elisp-colorize-dolist table))
843 (puthash "let" #'context-coloring-elisp-colorize-let table)
844 (puthash "let*" #'context-coloring-elisp-colorize-let* table)
845 (puthash "lambda" #'context-coloring-elisp-colorize-lambda table)
846 (puthash "cond" #'context-coloring-elisp-colorize-cond table)
847 (puthash "defadvice" #'context-coloring-elisp-colorize-defadvice table)
848 (puthash "quote" #'context-coloring-elisp-colorize-quote table)
849 (puthash "backquote" #'context-coloring-elisp-colorize-backquote table)
850 table)
851 "Map function names to their coloring functions.")
852
853 (defun context-coloring-elisp-colorize-parenthesized-sexp ()
854 "Color the sexp enclosed by parenthesis at point."
855 (context-coloring-elisp-increment-sexp-count)
856 (let* ((start (point))
857 (end (progn (forward-sexp)
858 (point)))
859 (syntax-code (progn (goto-char start)
860 (forward-char)
861 ;; Coloring is unnecessary here, it'll happen
862 ;; presently.
863 (context-coloring-forward-sws)
864 (context-coloring-get-syntax-code)))
865 dispatch-function)
866 ;; Figure out if the sexp is a special form.
867 (cond
868 ((and (context-coloring-elisp-identifier-p syntax-code)
869 (setq dispatch-function (gethash
870 (buffer-substring-no-properties
871 (point)
872 (progn (forward-sexp)
873 (point)))
874 context-coloring-elisp-callee-dispatch-hash-table)))
875 (goto-char start)
876 (funcall dispatch-function))
877 ;; Not a special form; just colorize the remaining region.
878 (t
879 (context-coloring-colorize-region
880 start
881 end
882 (context-coloring-elisp-get-current-scope-level))
883 (context-coloring-elisp-colorize-region (point) (1- end))
884 (forward-char)))))
885
886 (defun context-coloring-elisp-colorize-symbol ()
887 "Color the symbol at point."
888 (context-coloring-elisp-increment-sexp-count)
889 (let* ((symbol-pos (point))
890 (symbol-end (progn (forward-sexp)
891 (point)))
892 (symbol-string (buffer-substring-no-properties
893 symbol-pos
894 symbol-end)))
895 (cond
896 ((string-match-p context-coloring-elisp-ignored-word-regexp symbol-string))
897 (t
898 (context-coloring-colorize-region
899 symbol-pos
900 symbol-end
901 (context-coloring-elisp-get-variable-level
902 symbol-string))))))
903
904 (defun context-coloring-elisp-colorize-backquote-form ()
905 "Color the backquote form at point."
906 (let ((start (point))
907 (end (progn (forward-sexp)
908 (point)))
909 char)
910 (goto-char start)
911 (while (> end (progn (forward-char)
912 (point)))
913 (setq char (char-after))
914 (when (= char context-coloring-COMMA-CHAR)
915 (forward-char)
916 (when (= (char-after) context-coloring-AT-CHAR)
917 ;; If we don't do this "@" could be interpreted as a symbol.
918 (forward-char))
919 (context-coloring-elisp-forward-sws)
920 (context-coloring-elisp-colorize-sexp)))
921 ;; We could probably do this as part of the above loop but it'd be
922 ;; repetitive.
923 (context-coloring-elisp-colorize-comments-and-strings-in-region
924 start end)))
925
926 (defun context-coloring-elisp-colorize-backquote ()
927 "Color the `backquote' at point."
928 (context-coloring-elisp-skip-callee-name)
929 (context-coloring-elisp-colorize-backquote-form)
930 ;; Exit.
931 (forward-char))
932
933 (defun context-coloring-elisp-colorize-expression-prefix ()
934 "Color the expression prefix and expression at point.
935 It could be a quoted or backquoted expression."
936 (context-coloring-elisp-increment-sexp-count)
937 (cond
938 ((/= (char-after) context-coloring-BACKTICK-CHAR)
939 (context-coloring-elisp-forward-sexp))
940 (t
941 (context-coloring-elisp-colorize-backquote-form))))
942
943 (defun context-coloring-elisp-colorize-comment ()
944 "Color the comment at point."
945 (context-coloring-elisp-increment-sexp-count)
946 (context-coloring-elisp-forward-sws))
947
948 (defun context-coloring-elisp-colorize-string ()
949 "Color the string at point."
950 (context-coloring-elisp-increment-sexp-count)
951 (let ((start (point)))
952 (forward-sexp)
953 (context-coloring-colorize-comments-and-strings start (point))))
954
955 ;; Elisp has whitespace, words, symbols, open/close parenthesis, expression
956 ;; prefix, string quote, comment starters/enders and escape syntax classes only.
957
958 (defun context-coloring-elisp-colorize-sexp ()
959 "Color the sexp at point."
960 (let ((syntax-code (context-coloring-get-syntax-code)))
961 (cond
962 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
963 (context-coloring-elisp-colorize-parenthesized-sexp))
964 ((context-coloring-elisp-identifier-p syntax-code)
965 (context-coloring-elisp-colorize-symbol))
966 ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
967 (context-coloring-elisp-colorize-expression-prefix))
968 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
969 (context-coloring-elisp-colorize-string))
970 ((= syntax-code context-coloring-ESCAPE-CODE)
971 (forward-char 2)))))
972
973 (defun context-coloring-elisp-colorize-comments-and-strings-in-region (start end)
974 "Color comments and strings between START and END."
975 (let (syntax-code)
976 (goto-char start)
977 (while (> end (progn (skip-syntax-forward "^\"<\\" end)
978 (point)))
979 (setq syntax-code (context-coloring-get-syntax-code))
980 (cond
981 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
982 (context-coloring-elisp-colorize-string))
983 ((= syntax-code context-coloring-COMMENT-START-CODE)
984 (context-coloring-elisp-colorize-comment))
985 ((= syntax-code context-coloring-ESCAPE-CODE)
986 (forward-char 2))))))
987
988 (defun context-coloring-elisp-colorize-region (start end)
989 "Color everything between START and END."
990 (let (syntax-code)
991 (goto-char start)
992 (while (> end (progn (skip-syntax-forward "^w_('\"<\\" end)
993 (point)))
994 (setq syntax-code (context-coloring-get-syntax-code))
995 (cond
996 ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
997 (context-coloring-elisp-colorize-parenthesized-sexp))
998 ((context-coloring-elisp-identifier-p syntax-code)
999 (context-coloring-elisp-colorize-symbol))
1000 ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
1001 (context-coloring-elisp-colorize-expression-prefix))
1002 ((= syntax-code context-coloring-STRING-QUOTE-CODE)
1003 (context-coloring-elisp-colorize-string))
1004 ((= syntax-code context-coloring-COMMENT-START-CODE)
1005 (context-coloring-elisp-colorize-comment))
1006 ((= syntax-code context-coloring-ESCAPE-CODE)
1007 (forward-char 2))))))
1008
1009 (defun context-coloring-elisp-colorize-region-initially (start end)
1010 "Begin coloring everything between START and END."
1011 (setq context-coloring-elisp-sexp-count 0)
1012 (setq context-coloring-elisp-scope-stack '())
1013 (let ((inhibit-point-motion-hooks t)
1014 (case-fold-search nil)
1015 ;; This is a recursive-descent parser, so give it a big stack.
1016 (max-lisp-eval-depth (max max-lisp-eval-depth 3000))
1017 (max-specpdl-size (max max-specpdl-size 3000)))
1018 (context-coloring-elisp-colorize-region start end)))
1019
1020 (defun context-coloring-elisp-colorize-guard (callback)
1021 "Silently color in CALLBACK."
1022 (with-silent-modifications
1023 (save-excursion
1024 (condition-case nil
1025 (funcall callback)
1026 ;; Scan errors can happen virtually anywhere if parenthesis are
1027 ;; unbalanced. Just swallow them. (`progn' for test coverage.)
1028 (scan-error (progn))))))
1029
1030 (defun context-coloring-elisp-colorize ()
1031 "Color the current Emacs Lisp buffer."
1032 (interactive)
1033 (context-coloring-elisp-colorize-guard
1034 (lambda ()
1035 (cond
1036 ;; Just colorize the changed region.
1037 (context-coloring-changed-p
1038 (let* ( ;; Prevent `beginning-of-defun' from making poor assumptions.
1039 (open-paren-in-column-0-is-defun-start nil)
1040 ;; Seek the beginning and end of the previous and next
1041 ;; offscreen defuns, so just enough is colored.
1042 (start (progn (goto-char context-coloring-changed-start)
1043 (while (and (< (point-min) (point))
1044 (pos-visible-in-window-p))
1045 (end-of-line 0))
1046 (beginning-of-defun)
1047 (point)))
1048 (end (progn (goto-char context-coloring-changed-end)
1049 (while (and (> (point-max) (point))
1050 (pos-visible-in-window-p))
1051 (forward-line 1))
1052 (end-of-defun)
1053 (point))))
1054 (context-coloring-elisp-colorize-region-initially start end)
1055 ;; Fast coloring is nice, but if the code is not well-formed
1056 ;; (e.g. an unclosed string literal is parsed at any time) then
1057 ;; there could be leftover incorrectly-colored code offscreen. So
1058 ;; do a clean sweep as soon as appropriate.
1059 (context-coloring-schedule-coloring context-coloring-default-delay)))
1060 (t
1061 (context-coloring-elisp-colorize-region-initially (point-min) (point-max)))))))
1062
1063
1064 ;;; eval-expression colorization
1065
1066 (defun context-coloring-eval-expression-match ()
1067 "Determine expression start in `eval-expression'."
1068 (string-match "\\`Eval: " (buffer-string)))
1069
1070 (defun context-coloring-eval-expression-colorize ()
1071 "Color the `eval-expression' minibuffer prompt as elisp."
1072 (interactive)
1073 (context-coloring-elisp-colorize-guard
1074 (lambda ()
1075 (context-coloring-elisp-colorize-region-initially
1076 (progn
1077 (context-coloring-eval-expression-match)
1078 (1+ (match-end 0)))
1079 (point-max)))))
1080
1081
1082 ;;; Dispatch
1083
1084 (defvar context-coloring-dispatch-hash-table (make-hash-table :test #'eq)
1085 "Map dispatch strategy names to their property lists.")
1086
1087 (defvar context-coloring-mode-hash-table (make-hash-table :test #'eq)
1088 "Map major mode names to dispatch property lists.")
1089
1090 (defvar context-coloring-dispatch-predicates '()
1091 "Functions which may return a dispatch.")
1092
1093 (defun context-coloring-get-current-dispatch ()
1094 "Return the first dispatch appropriate for the current state."
1095 (let ((predicates context-coloring-dispatch-predicates)
1096 (parent major-mode)
1097 dispatch)
1098 ;; Maybe a predicate will be satisfied and return a dispatch.
1099 (while (and predicates
1100 (not (setq dispatch (funcall (pop predicates))))))
1101 ;; If not, maybe a major mode (or a derivative) will define a dispatch.
1102 (when (not dispatch)
1103 (while (and parent
1104 (not (setq dispatch (gethash parent context-coloring-mode-hash-table)))
1105 (setq parent (get parent 'derived-mode-parent)))))
1106 dispatch))
1107
1108 (defun context-coloring-define-dispatch (symbol &rest properties)
1109 "Define a new dispatch named SYMBOL with PROPERTIES.
1110
1111 A \"dispatch\" is a property list describing a strategy for
1112 coloring a buffer.
1113
1114 PROPERTIES must include one of `:modes' or `:predicate', and a
1115 `:colorizer'.
1116
1117 `:modes' - List of major modes this dispatch is valid for.
1118
1119 `:predicate' - Function that determines if the dispatch is valid
1120 for any given state.
1121
1122 `:colorizer' - Function that parses and colors the buffer.
1123
1124 `:delay' - Delay between buffer update and colorization, to
1125 override `context-coloring-default-delay'.
1126
1127 `:setup' - Arbitrary code to set up this dispatch when
1128 `context-coloring-mode' is enabled.
1129
1130 `:teardown' - Arbitrary code to tear down this dispatch when
1131 `context-coloring-mode' is disabled."
1132 (let ((modes (plist-get properties :modes))
1133 (predicate (plist-get properties :predicate))
1134 (colorizer (plist-get properties :colorizer)))
1135 (when (null (or modes predicate))
1136 (error "No mode or predicate defined for dispatch"))
1137 (when (not colorizer)
1138 (error "No colorizer defined for dispatch"))
1139 (puthash symbol properties context-coloring-dispatch-hash-table)
1140 (dolist (mode modes)
1141 (puthash mode properties context-coloring-mode-hash-table))
1142 (when predicate
1143 (push (lambda ()
1144 (when (funcall predicate)
1145 properties)) context-coloring-dispatch-predicates))))
1146
1147 (defun context-coloring-dispatch ()
1148 "Determine how to color the current buffer, and color it."
1149 (let* ((dispatch (context-coloring-get-current-dispatch))
1150 (colorizer (plist-get dispatch :colorizer)))
1151 (catch 'interrupted
1152 (funcall colorizer))))
1153
1154
1155 ;;; Colorization
1156
1157 (defun context-coloring-colorize ()
1158 "Color the current buffer by function context."
1159 (interactive)
1160 (context-coloring-update-maximum-face)
1161 (context-coloring-dispatch))
1162
1163 (defun context-coloring-colorize-with-buffer (buffer)
1164 "Color BUFFER."
1165 ;; Don't select deleted buffers.
1166 (when (get-buffer buffer)
1167 (with-current-buffer buffer
1168 (context-coloring-colorize))))
1169
1170
1171 ;;; Built-in dispatches
1172
1173 (context-coloring-define-dispatch
1174 'javascript
1175 :modes '(js2-mode)
1176 :colorizer #'context-coloring-js2-colorize
1177 :setup
1178 (lambda ()
1179 (add-hook 'js2-post-parse-callbacks #'context-coloring-colorize nil t))
1180 :teardown
1181 (lambda ()
1182 (remove-hook 'js2-post-parse-callbacks #'context-coloring-colorize t)))
1183
1184 (context-coloring-define-dispatch
1185 'emacs-lisp
1186 :modes '(emacs-lisp-mode)
1187 :colorizer #'context-coloring-elisp-colorize
1188 :delay 0.016 ;; Thanks to lazy colorization this can be 60 frames per second.
1189 :setup #'context-coloring-setup-idle-change-detection
1190 :teardown #'context-coloring-teardown-idle-change-detection)
1191
1192 ;; `eval-expression-minibuffer-setup-hook' is not available in Emacs 24.3, so
1193 ;; the backwards-compatible recommendation is to use `minibuffer-setup-hook' and
1194 ;; rely on this predicate instead.
1195 (defun context-coloring-eval-expression-predicate ()
1196 "Non-nil if the minibuffer is for `eval-expression'."
1197 ;; Kinda better than checking `this-command', because `this-command' changes.
1198 (context-coloring-eval-expression-match))
1199
1200 (context-coloring-define-dispatch
1201 'eval-expression
1202 :predicate #'context-coloring-eval-expression-predicate
1203 :colorizer #'context-coloring-eval-expression-colorize
1204 :delay 0.016
1205 :setup #'context-coloring-setup-idle-change-detection
1206 :teardown #'context-coloring-teardown-idle-change-detection)
1207
1208 (defvar context-coloring-ignore-unavailable-predicates
1209 (list
1210 #'minibufferp)
1211 "Cases when \"unavailable\" messages are silenced.
1212 Necessary in editing states where coloring is only sometimes
1213 permissible.")
1214
1215 (defun context-coloring-ignore-unavailable-message-p ()
1216 "Determine if the unavailable message should be silenced."
1217 (let ((predicates context-coloring-ignore-unavailable-predicates)
1218 (ignore-p nil))
1219 (while (and predicates
1220 (not ignore-p))
1221 (setq ignore-p (funcall (pop predicates))))
1222 ignore-p))
1223
1224
1225 ;;; Minor mode
1226
1227 ;;;###autoload
1228 (define-minor-mode context-coloring-mode
1229 "Toggle contextual code coloring.
1230 With a prefix argument ARG, enable Context Coloring mode if ARG
1231 is positive, and disable it otherwise. If called from Lisp,
1232 enable the mode if ARG is omitted or nil.
1233
1234 Context Coloring mode is a buffer-local minor mode. When
1235 enabled, code is colored by scope. Scopes are colored
1236 hierarchically. Variables referenced from nested scopes retain
1237 the color of their defining scopes. Certain syntax, like
1238 comments and strings, is still colored with `font-lock'.
1239
1240 The entire buffer is colored initially. Changes to the buffer
1241 trigger recoloring.
1242
1243 Define your own colors by customizing faces like
1244 `context-coloring-level-N-face', where N is a number starting
1245 from 0. If no face is found on a custom theme nor the `user'
1246 theme, the defaults are used.
1247
1248 New language / major mode support can be added with
1249 `context-coloring-define-dispatch', which see.
1250
1251 Feature inspired by Douglas Crockford."
1252 nil " Context" nil
1253 (cond
1254 (context-coloring-mode
1255 ;; Font lock is incompatible with this mode; the converse is also true.
1256 (font-lock-mode 0)
1257 (jit-lock-mode nil)
1258 ;; ...but we do use font-lock functions here.
1259 (font-lock-set-defaults)
1260 ;; Safely change the value of this function as necessary.
1261 (make-local-variable 'font-lock-syntactic-face-function)
1262 (let ((dispatch (context-coloring-get-current-dispatch)))
1263 (cond
1264 (dispatch
1265 (let ((setup (plist-get dispatch :setup)))
1266 (when setup
1267 (funcall setup))
1268 ;; Colorize once initially.
1269 (let ((context-coloring-parse-interruptable-p nil))
1270 (context-coloring-colorize))))
1271 ((not (context-coloring-ignore-unavailable-message-p))
1272 (message "Context coloring is unavailable here")))))
1273 (t
1274 (let ((dispatch (context-coloring-get-current-dispatch)))
1275 (when dispatch
1276 (let ((teardown (plist-get dispatch :teardown)))
1277 (when teardown
1278 (funcall teardown)))))
1279 (font-lock-mode)
1280 (jit-lock-mode t))))
1281
1282 (provide 'context-coloring)
1283
1284 ;;; context-coloring.el ends here