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