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