]> code.delx.au - gnu-emacs-elpa/blobdiff - context-coloring.el
Version 7.2.0.
[gnu-emacs-elpa] / context-coloring.el
index 5773b4094fdc052acb6fcc62612c485399c96c62..983315e1e9f38671fc478686ab1be2e67e6dfe1b 100644 (file)
@@ -3,9 +3,9 @@
 ;; Copyright (C) 2014-2015  Free Software Foundation, Inc.
 
 ;; Author: Jackson Ray Hamilton <jackson@jacksonrayhamilton.com>
-;; Version: 6.5.0
+;; Version: 7.2.0
 ;; Keywords: convenience faces tools
-;; Package-Requires: ((emacs "24.3") (js2-mode "20150126"))
+;; Package-Requires: ((emacs "24.3") (js2-mode "20150713"))
 ;; URL: https://github.com/jacksonrayhamilton/context-coloring
 
 ;; This file is part of GNU Emacs.
 
 ;;; Faces
 
-;; Create placeholder faces for users to populate.
-(dotimes (level 25)
+(defun context-coloring-defface (level light dark tty)
+  "Define a face for LEVEL with LIGHT, DARK and TTY colors."
   (let ((face (intern (format "context-coloring-level-%s-face" level)))
         (doc (format "Context coloring face, level %s." level)))
+    (custom-declare-face
+     face
+     `((((type tty)) (:foreground ,tty))
+       (((background light)) (:foreground ,light))
+       (((background dark)) (:foreground ,dark)))
+     doc
+     :group 'context-coloring)))
+
+;; Provide some default colors based off Emacs's defaults.
+(context-coloring-defface 0 "#000000" "#ffffff" nil)
+(context-coloring-defface 1 "#008b8b" "#00ffff" "yellow")
+(context-coloring-defface 2 "#0000ff" "#87cefa" "green")
+(context-coloring-defface 3 "#483d8b" "#b0c4de" "cyan")
+(context-coloring-defface 4 "#a020f0" "#eedd82" "blue")
+(context-coloring-defface 5 "#a0522d" "#98fb98" "magenta")
+(context-coloring-defface 6 "#228b22" "#7fffd4" "red")
+(context-coloring-defface 7 "#3f3f3f" "#cdcdcd" nil)
+
+(defconst context-coloring-default-maximum-face 7
+  "Maximum face when there are no custom faces.")
+
+;; Create placeholder faces for users and theme authors.
+(dotimes (level 18)
+  (let* ((level (+ level 8))
+         (face (intern (format "context-coloring-level-%s-face" level)))
+         (doc (format "Context coloring face, level %s." level)))
     (custom-declare-face face nil doc :group 'context-coloring)))
 
-(defvar context-coloring-maximum-face 24
-  "Index of the highest face available for coloring.")
+(defvar-local context-coloring-maximum-face nil
+  "Dynamic index of the highest face available for coloring.")
 
 (defsubst context-coloring-level-face (level)
-  "Return the symbol for a face with LEVEL."
+  "Return symbol for face with LEVEL."
   ;; `concat' is faster than `format' here.
   (intern-soft
    (concat "context-coloring-level-" (number-to-string level) "-face")))
 
 (defsubst context-coloring-bounded-level-face (level)
-  "Return the symbol for a face with LEVEL, bounded by
-`context-coloring-maximum-face'."
+  "Return symbol for face with LEVEL, bounded by the maximum."
   (context-coloring-level-face (min level context-coloring-maximum-face)))
 
+(defconst context-coloring-level-face-regexp
+  "context-coloring-level-\\([[:digit:]]+\\)-face"
+  "Extract a level from a face.")
+
+(defun context-coloring-theme-highest-level (theme)
+  "Return the highest coloring level for THEME, or -1."
+  (let* ((settings (get theme 'theme-settings))
+         (tail settings)
+         face-string
+         number
+         (found -1))
+    (while tail
+      (and (eq (nth 0 (car tail)) 'theme-face)
+           (setq face-string (symbol-name (nth 1 (car tail))))
+           (string-match
+            context-coloring-level-face-regexp
+            face-string)
+           (setq number (string-to-number
+                         (substring face-string
+                                    (match-beginning 1)
+                                    (match-end 1))))
+           (> number found)
+           (setq found number))
+      (setq tail (cdr tail)))
+    found))
+
+(defun context-coloring-update-maximum-face ()
+  "Save the highest possible face for the current theme."
+  (let ((themes (append custom-enabled-themes '(user)))
+        (continue t)
+        theme
+        highest-level)
+    (while continue
+      (setq theme (car themes))
+      (setq themes (cdr themes))
+      (setq highest-level (context-coloring-theme-highest-level theme))
+      (setq continue (and themes (= highest-level -1))))
+    (setq context-coloring-maximum-face
+          (cond
+           ((= highest-level -1)
+            context-coloring-default-maximum-face)
+           (t
+            highest-level)))))
+
 
 ;;; Change detection
 
@@ -115,6 +184,7 @@ START, END and LENGTH are recorded for later use."
 
 Increase this if your machine is high-performing.  Decrease it if
 it ain't."
+  :type 'float
   :group 'context-coloring)
 
 (make-obsolete-variable
@@ -165,8 +235,7 @@ it ain't."
 ;;; Colorization utilities
 
 (defsubst context-coloring-colorize-region (start end level)
-  "Color characters from the 1-indexed START point (inclusive) to
-the END point (exclusive) with the face corresponding to LEVEL."
+  "Color from START (inclusive) to END (exclusive) with LEVEL."
   (add-text-properties
    start
    end
@@ -180,27 +249,25 @@ the END point (exclusive) with the face corresponding to LEVEL."
 
 (defcustom context-coloring-syntactic-comments t
   "If non-nil, also color comments using `font-lock'."
+  :type 'boolean
   :group 'context-coloring)
 
 (defcustom context-coloring-syntactic-strings t
   "If non-nil, also color strings using `font-lock'."
+  :type 'boolean
   :group 'context-coloring)
 
 (defun context-coloring-font-lock-syntactic-comment-function (state)
-  "Tell `font-lock' to color a comment but not a string according
-to STATE."
+  "Color a comment according to STATE."
   (if (nth 3 state) nil font-lock-comment-face))
 
 (defun context-coloring-font-lock-syntactic-string-function (state)
-  "Tell `font-lock' to color a string but not a comment according
-to STATE."
+  "Color a string according to STATE."
   (if (nth 3 state) font-lock-string-face nil))
 
 (defsubst context-coloring-colorize-comments-and-strings (&optional min max)
-  "Color the current buffer's comments or strings if
-`context-coloring-syntactic-comments' or
-`context-coloring-syntactic-strings' are non-nil.  MIN defaults
-to the beginning of the buffer and MAX defaults to the end."
+  "Maybe color comments and strings in buffer from MIN to MAX.
+MIN defaults to beginning of buffer.  MAX defaults to end."
   (when (or context-coloring-syntactic-comments
             context-coloring-syntactic-strings)
     (let ((min (or min (point-min)))
@@ -221,8 +288,17 @@ to the beginning of the buffer and MAX defaults to the end."
         (when (eq major-mode 'emacs-lisp-mode)
           (font-lock-fontify-keywords-region min max))))))
 
+(defcustom context-coloring-initial-level 0
+  "Scope level at which to start coloring.
+
+If top-level variables and functions do not become global, but
+are scoped to a file (as in Node.js), set this to `1'."
+  :type 'integer
+  :safe #'integerp
+  :group 'context-coloring)
 
-;;; js2-mode colorization
+
+;;; JavaScript colorization
 
 (defvar-local context-coloring-js2-scope-level-hash-table nil
   "Associate `js2-scope' structures and with their scope
@@ -233,6 +309,8 @@ to the beginning of the buffer and MAX defaults to the end."
 
 The block-scoped `let' and `const' are introduced in ES6.  Enable
 this for ES6 code; disable it elsewhere."
+  :type 'boolean
+  :safe #'booleanp
   :group 'context-coloring)
 
 (make-obsolete-variable
@@ -240,11 +318,11 @@ this for ES6 code; disable it elsewhere."
  'context-coloring-javascript-block-scopes
  "7.0.0")
 
-(defsubst context-coloring-js2-scope-level (scope)
-  "Return the level of SCOPE."
+(defsubst context-coloring-js2-scope-level (scope initial)
+  "Return the level of SCOPE, starting from INITIAL."
   (cond ((gethash scope context-coloring-js2-scope-level-hash-table))
         (t
-         (let ((level 0)
+         (let ((level initial)
                (current-scope scope)
                enclosing-scope)
            (while (and current-scope
@@ -261,8 +339,7 @@ this for ES6 code; disable it elsewhere."
            (puthash scope level context-coloring-js2-scope-level-hash-table)))))
 
 (defsubst context-coloring-js2-local-name-node-p (node)
-  "Determine if NODE is a `js2-name-node' representing a local
-variable."
+  "Determine if NODE represents a local variable."
   (and (js2-name-node-p node)
        (let ((parent (js2-node-parent node)))
          (not (or (and (js2-object-prop-node-p parent)
@@ -288,9 +365,8 @@ variable."
       context-coloring-point-max)
      level)))
 
-(defun context-coloring-js2-colorize ()
-  "Color the current buffer using the abstract syntax tree
-generated by `js2-mode'."
+(defun context-coloring-js2-colorize-ast ()
+  "Color the buffer using the `js2-mode' abstract syntax tree."
   ;; Reset the hash table; the old one could be obsolete.
   (setq context-coloring-js2-scope-level-hash-table (make-hash-table :test #'eq))
   (setq context-coloring-point-max (point-max))
@@ -303,7 +379,7 @@ generated by `js2-mode'."
           ((js2-scope-p node)
            (context-coloring-js2-colorize-node
             node
-            (context-coloring-js2-scope-level node)))
+            (context-coloring-js2-scope-level node context-coloring-initial-level)))
           ((context-coloring-js2-local-name-node-p node)
            (let* ((enclosing-scope (js2-node-get-enclosing-scope node))
                   (defining-scope (js2-get-defining-scope
@@ -316,39 +392,155 @@ generated by `js2-mode'."
              (when (not (eq defining-scope enclosing-scope))
                (context-coloring-js2-colorize-node
                 node
-                (context-coloring-js2-scope-level defining-scope))))))
+                ;; Use `0' as an initial level so global variables are always at
+                ;; the highest level (even if `context-coloring-initial-level'
+                ;; specifies an initial level for the rest of the code).
+                (context-coloring-js2-scope-level defining-scope 0))))))
          ;; The `t' indicates to search children.
          t)))
     (context-coloring-colorize-comments-and-strings)))
 
+(defconst context-coloring-node-comment-regexp
+  (concat
+   ;; Ensure the "//" or "/*" comment starts with the directive.
+   "\\(//[[:space:]]*\\|/\\*[[:space:]]*\\)"
+   ;; Support multiple directive formats.
+   "\\("
+   ;; JSLint and JSHint support a JSON-like format.
+   "\\(jslint\\|jshint\\)[[:space:]].*?node:[[:space:]]*true"
+   "\\|"
+   ;; ESLint just specifies the option name.
+   "eslint-env[[:space:]].*?node"
+   "\\)")
+  "Match a comment body hinting at a Node.js program.")
+
+;; TODO: Add ES6 module detection.
+(defun context-coloring-js2-top-level-local-p ()
+  "Guess whether top-level variables are local.
+For instance, the current file could be a Node.js program."
+  (or
+   ;; A shebang is a pretty obvious giveaway.
+   (string-equal
+    "node"
+    (save-excursion
+      (goto-char (point-min))
+      (when (looking-at auto-mode-interpreter-regexp)
+        (match-string 2))))
+   ;; Otherwise, perform static analysis.
+   (progn
+     (setq context-coloring-js2-scope-level-hash-table (make-hash-table :test #'eq))
+     (catch 'node-program-p
+       (js2-visit-ast
+        js2-mode-ast
+        (lambda (node end-p)
+          (when (null end-p)
+            (when
+                (cond
+                 ;; Infer based on inline linter configuration.
+                 ((js2-comment-node-p node)
+                  (string-match-p
+                   context-coloring-node-comment-regexp
+                   (js2-node-string node)))
+                 ;; Infer based on the prescence of certain variables.
+                 ((and (js2-name-node-p node)
+                       (let ((parent (js2-node-parent node)))
+                         (not (and (js2-object-prop-node-p parent)
+                                   (eq node (js2-object-prop-node-left parent))))))
+                  (let ((name (js2-name-node-name node))
+                        (parent (js2-node-parent node)))
+                    (and
+                     (cond
+                      ;; Check whether this is "exports.something" or
+                      ;; "module.exports".
+                      ((js2-prop-get-node-p parent)
+                       (and
+                        (eq node (js2-prop-get-node-left parent))
+                        (or (string-equal name "exports")
+                            (let* ((property (js2-prop-get-node-right parent))
+                                   (property-name (js2-name-node-name property)))
+                              (and (string-equal name "module")
+                                   (string-equal property-name "exports"))))))
+                      ;; Check whether it's a "require('module')" call.
+                      ((js2-call-node-p parent)
+                       (or (string-equal name "require"))))
+                     (let* ((enclosing-scope (js2-node-get-enclosing-scope node))
+                            (defining-scope (js2-get-defining-scope
+                                             enclosing-scope name)))
+                       ;; The variable also must be global.
+                       (null defining-scope))))))
+              (throw 'node-program-p t))
+            ;; The `t' indicates to search children.
+            t)))
+       ;; Default to returning nil from the catch body.
+       nil))))
+
+(defcustom context-coloring-javascript-detect-top-level-scope t
+  "If non-nil, detect when to use file-level scope."
+  :type 'boolean
+  :group 'context-coloring)
+
+(defun context-coloring-js2-colorize ()
+  "Color the buffer using the `js2-mode'."
+  (cond
+   ;; Increase the initial level if we should.
+   ((and context-coloring-javascript-detect-top-level-scope
+         (context-coloring-js2-top-level-local-p))
+    (let ((context-coloring-initial-level 1))
+      (context-coloring-js2-colorize-ast)))
+   (t
+    (context-coloring-js2-colorize-ast))))
+
 
 ;;; Emacs Lisp colorization
 
+(defconst context-coloring-WORD-CODE 2)
+(defconst context-coloring-SYMBOL-CODE 3)
+(defconst context-coloring-OPEN-PARENTHESIS-CODE 4)
+(defconst context-coloring-CLOSE-PARENTHESIS-CODE 5)
+(defconst context-coloring-EXPRESSION-PREFIX-CODE 6)
+(defconst context-coloring-STRING-QUOTE-CODE 7)
+(defconst context-coloring-ESCAPE-CODE 9)
+(defconst context-coloring-COMMENT-START-CODE 11)
+(defconst context-coloring-COMMENT-END-CODE 12)
+
+(defconst context-coloring-OCTOTHORPE-CHAR (string-to-char "#"))
+(defconst context-coloring-APOSTROPHE-CHAR (string-to-char "'"))
+(defconst context-coloring-OPEN-PARENTHESIS-CHAR (string-to-char "("))
+(defconst context-coloring-COMMA-CHAR (string-to-char ","))
+(defconst context-coloring-AT-CHAR (string-to-char "@"))
+(defconst context-coloring-BACKTICK-CHAR (string-to-char "`"))
+
+(defsubst context-coloring-get-syntax-code ()
+  "Get the syntax code at point."
+  (syntax-class
+   ;; Faster version of `syntax-after':
+   (aref (syntax-table) (char-after (point)))))
+
 (defsubst context-coloring-forward-sws ()
   "Move forward through whitespace and comments."
   (while (forward-comment 1)))
 
 (defsubst context-coloring-elisp-forward-sws ()
-  "Move forward through whitespace and comments, colorizing
-comments along the way."
+  "Move through whitespace and comments, coloring comments."
   (let ((start (point)))
     (context-coloring-forward-sws)
     (context-coloring-colorize-comments-and-strings start (point))))
 
 (defsubst context-coloring-elisp-forward-sexp ()
-  "Like `forward-sexp', but colorize comments and strings along
-the way."
+  "Skip/ignore missing sexps, coloring comments and strings."
   (let ((start (point)))
-    (forward-sexp)
+    (when (= (context-coloring-get-syntax-code)
+             context-coloring-EXPRESSION-PREFIX-CODE)
+      ;; `forward-sexp' does not skip an unfinished expression (e.g. when the
+      ;; name of a symbol or the parentheses of a list do not follow a single
+      ;; quote).
+      (forward-char))
+    (condition-case nil
+        (forward-sexp)
+      (scan-error (context-coloring-forward-sws)))
     (context-coloring-elisp-colorize-comments-and-strings-in-region
      start (point))))
 
-(defsubst context-coloring-get-syntax-code ()
-  "Get the syntax code at point."
-  (syntax-class
-   ;; Faster version of `syntax-after':
-   (aref (syntax-table) (char-after (point)))))
-
 (defsubst context-coloring-exact-regexp (word)
   "Create a regexp matching exactly WORD."
   (concat "\\`" (regexp-quote word) "\\'"))
@@ -364,25 +556,7 @@ the way."
                                (context-coloring-exact-or-regexp
                                 '("t" "nil" "." "?")))
                          "\\|")
-  "Match words that might be considered symbols but can't be
-bound as variables.")
-
-(defconst context-coloring-WORD-CODE 2)
-(defconst context-coloring-SYMBOL-CODE 3)
-(defconst context-coloring-OPEN-PARENTHESIS-CODE 4)
-(defconst context-coloring-CLOSE-PARENTHESIS-CODE 5)
-(defconst context-coloring-EXPRESSION-PREFIX-CODE 6)
-(defconst context-coloring-STRING-QUOTE-CODE 7)
-(defconst context-coloring-ESCAPE-CODE 9)
-(defconst context-coloring-COMMENT-START-CODE 11)
-(defconst context-coloring-COMMENT-END-CODE 12)
-
-(defconst context-coloring-OCTOTHORPE-CHAR (string-to-char "#"))
-(defconst context-coloring-APOSTROPHE-CHAR (string-to-char "'"))
-(defconst context-coloring-OPEN-PARENTHESIS-CHAR (string-to-char "("))
-(defconst context-coloring-COMMA-CHAR (string-to-char ","))
-(defconst context-coloring-AT-CHAR (string-to-char "@"))
-(defconst context-coloring-BACKTICK-CHAR (string-to-char "`"))
+  "Match symbols that can't be bound as variables.")
 
 (defsubst context-coloring-elisp-identifier-p (syntax-code)
   "Check if SYNTAX-CODE is an elisp identifier constituent."
@@ -404,8 +578,7 @@ second.")
   "Current number of sexps leading up to the next pause.")
 
 (defsubst context-coloring-elisp-increment-sexp-count ()
-  "Maybe check if the current parse should be interrupted as a
-result of pending user input."
+  "Maybe check if the user interrupted the current parse."
   (setq context-coloring-elisp-sexp-count
         (1+ context-coloring-elisp-sexp-count))
   (when (and (zerop (% context-coloring-elisp-sexp-count
@@ -436,8 +609,7 @@ result of pending user input."
   (member variable (plist-get scope :variables)))
 
 (defsubst context-coloring-elisp-get-variable-level (variable)
-  "Search up the scope chain for the first instance of VARIABLE
-and return its level, or 0 (global) if it isn't found."
+  "Return the level of VARIABLE, or 0 if it isn't found."
   (let* ((scope-stack context-coloring-elisp-scope-stack)
          scope
          level)
@@ -476,8 +648,8 @@ and return its level, or 0 (global) if it isn't found."
    variable))
 
 (defsubst context-coloring-elisp-parse-bindable (callback)
-  "Parse the symbol at point, and if the symbol can be bound,
-invoke CALLBACK with it."
+  "Parse the symbol at point.
+If the symbol can be bound, invoke CALLBACK with it."
   (let* ((arg-string (buffer-substring-no-properties
                       (point)
                       (progn (context-coloring-elisp-forward-sexp)
@@ -488,14 +660,15 @@ invoke CALLBACK with it."
       (funcall callback arg-string))))
 
 (defun context-coloring-elisp-parse-let-varlist (type)
-  "Parse the list of variable initializers at point.  If TYPE is
-`let', all the variables are bound after all their initializers
-are parsed; if TYPE is `let*', each variable is bound immediately
-after its own initializer is parsed."
+  "Parse the list of variable initializers at point.
+If TYPE is `let', all the variables are bound after all their
+initializers are parsed; if TYPE is `let*', each variable is
+bound immediately after its own initializer is parsed."
   (let ((varlist '())
         syntax-code)
     ;; Enter.
     (forward-char)
+    (context-coloring-elisp-forward-sws)
     (while (/= (setq syntax-code (context-coloring-get-syntax-code))
                context-coloring-CLOSE-PARENTHESIS-CODE)
       (cond
@@ -535,6 +708,7 @@ after its own initializer is parsed."
   (let (syntax-code)
     ;; Enter.
     (forward-char)
+    (context-coloring-elisp-forward-sws)
     (while (/= (setq syntax-code (context-coloring-get-syntax-code))
                context-coloring-CLOSE-PARENTHESIS-CODE)
       (cond
@@ -559,8 +733,8 @@ after its own initializer is parsed."
   (context-coloring-elisp-forward-sws))
 
 (defun context-coloring-elisp-colorize-scope (callback)
-  "Color the whole scope at point with its one color.  Handle a
-header in CALLBACK."
+  "Color the whole scope at point with its one color.
+Handle a header in CALLBACK."
   (let ((start (point))
         (end (progn (forward-sexp)
                     (point))))
@@ -589,8 +763,8 @@ header in CALLBACK."
     (funcall callback)))
 
 (defun context-coloring-elisp-colorize-defun-like (callback)
-  "Color the defun-like function at point, parsing the header
-with CALLBACK."
+  "Color the defun-like function at point.
+Parse the header with CALLBACK."
   (context-coloring-elisp-colorize-scope
    (lambda ()
      (when (context-coloring-elisp-identifier-p (context-coloring-get-syntax-code))
@@ -615,6 +789,7 @@ with CALLBACK."
      (let (syntax-code)
        ;; Enter.
        (forward-char)
+       (context-coloring-elisp-forward-sws)
        (while (/= (setq syntax-code (context-coloring-get-syntax-code))
                   context-coloring-CLOSE-PARENTHESIS-CODE)
          (cond
@@ -626,8 +801,8 @@ with CALLBACK."
          (context-coloring-elisp-forward-sws))))))
 
 (defun context-coloring-elisp-colorize-lambda-like (callback)
-  "Color the lambda-like function at point, parsing the header
-with CALLBACK."
+  "Color the lambda-like function at point.
+Parsing the header with CALLBACK."
   (context-coloring-elisp-colorize-scope
    (lambda ()
      (context-coloring-elisp-parse-header callback))))
@@ -649,6 +824,29 @@ with CALLBACK."
    (lambda ()
      (context-coloring-elisp-parse-let-varlist 'let*))))
 
+(defun context-coloring-elisp-colorize-macroexp-let2 ()
+  "Color the `macroexp-let2' at point."
+  (let (syntax-code
+        variable)
+    (context-coloring-elisp-colorize-scope
+     (lambda ()
+       (and
+        (progn
+          (setq syntax-code (context-coloring-get-syntax-code))
+          (context-coloring-elisp-identifier-p syntax-code))
+        (progn
+          (context-coloring-elisp-colorize-sexp)
+          (context-coloring-elisp-forward-sws)
+          (setq syntax-code (context-coloring-get-syntax-code))
+          (context-coloring-elisp-identifier-p syntax-code))
+        (progn
+          (context-coloring-elisp-parse-bindable
+           (lambda (parsed-variable)
+             (setq variable parsed-variable)))
+          (context-coloring-elisp-forward-sws)
+          (when variable
+            (context-coloring-elisp-add-variable variable))))))))
+
 (defun context-coloring-elisp-colorize-cond ()
   "Color the `cond' at point."
   (let (syntax-code)
@@ -764,8 +962,10 @@ with CALLBACK."
       (puthash callee #'context-coloring-elisp-colorize-condition-case table))
     (dolist (callee '("dolist" "dotimes"))
       (puthash callee #'context-coloring-elisp-colorize-dolist table))
-    (puthash "let" #'context-coloring-elisp-colorize-let table)
+    (dolist (callee '("let" "gv-letplace"))
+      (puthash callee #'context-coloring-elisp-colorize-let table))
     (puthash "let*" #'context-coloring-elisp-colorize-let* table)
+    (puthash "macroexp-let2" #'context-coloring-elisp-colorize-macroexp-let2 table)
     (puthash "lambda" #'context-coloring-elisp-colorize-lambda table)
     (puthash "cond" #'context-coloring-elisp-colorize-cond table)
     (puthash "defadvice" #'context-coloring-elisp-colorize-defadvice table)
@@ -855,8 +1055,8 @@ with CALLBACK."
   (forward-char))
 
 (defun context-coloring-elisp-colorize-expression-prefix ()
-  "Color the expression prefix and the following expression at
-point.  It could be a quoted or backquoted expression."
+  "Color the expression prefix and expression at point.
+It could be a quoted or backquoted expression."
   (context-coloring-elisp-increment-sexp-count)
   (cond
    ((/= (char-after) context-coloring-BACKTICK-CHAR)
@@ -952,8 +1152,7 @@ point.  It could be a quoted or backquoted expression."
         (scan-error (progn))))))
 
 (defun context-coloring-elisp-colorize ()
-  "Color the current buffer, parsing elisp to determine its
-scopes and variables."
+  "Color the current Emacs Lisp buffer."
   (interactive)
   (context-coloring-elisp-colorize-guard
    (lambda ()
@@ -988,6 +1187,10 @@ scopes and variables."
 
 ;;; eval-expression colorization
 
+(defun context-coloring-eval-expression-match ()
+  "Determine expression start in `eval-expression'."
+  (string-match "\\`Eval: " (buffer-string)))
+
 (defun context-coloring-eval-expression-colorize ()
   "Color the `eval-expression' minibuffer prompt as elisp."
   (interactive)
@@ -995,7 +1198,7 @@ scopes and variables."
    (lambda ()
      (context-coloring-elisp-colorize-region-initially
       (progn
-        (string-match "\\`Eval: " (buffer-string))
+        (context-coloring-eval-expression-match)
         (1+ (match-end 0)))
       (point-max)))))
 
@@ -1003,8 +1206,7 @@ scopes and variables."
 ;;; Dispatch
 
 (defvar context-coloring-dispatch-hash-table (make-hash-table :test #'eq)
-  "Map dispatch strategy names to their corresponding property
-lists, which contain details about the strategies.")
+  "Map dispatch strategy names to their property lists.")
 
 (defvar context-coloring-mode-hash-table (make-hash-table :test #'eq)
   "Map major mode names to dispatch property lists.")
@@ -1067,8 +1269,7 @@ override `context-coloring-default-delay'.
                 properties)) context-coloring-dispatch-predicates))))
 
 (defun context-coloring-dispatch ()
-  "Determine the optimal track for scopification / coloring of
-the current buffer, then execute it."
+  "Determine how to color the current buffer, and color it."
   (let* ((dispatch (context-coloring-get-current-dispatch))
          (colorizer (plist-get dispatch :colorizer)))
     (catch 'interrupted
@@ -1080,6 +1281,7 @@ the current buffer, then execute it."
 (defun context-coloring-colorize ()
   "Color the current buffer by function context."
   (interactive)
+  (context-coloring-update-maximum-face)
   (context-coloring-dispatch))
 
 (defun context-coloring-colorize-with-buffer (buffer)
@@ -1116,7 +1318,8 @@ the current buffer, then execute it."
 ;; rely on this predicate instead.
 (defun context-coloring-eval-expression-predicate ()
   "Non-nil if the minibuffer is for `eval-expression'."
-  (eq this-command 'eval-expression))
+  ;; Kinda better than checking `this-command', because `this-command' changes.
+  (context-coloring-eval-expression-match))
 
 (context-coloring-define-dispatch
  'eval-expression
@@ -1126,6 +1329,22 @@ the current buffer, then execute it."
  :setup #'context-coloring-setup-idle-change-detection
  :teardown #'context-coloring-teardown-idle-change-detection)
 
+(defvar context-coloring-ignore-unavailable-predicates
+  (list
+   #'minibufferp)
+  "Cases when \"unavailable\" messages are silenced.
+Necessary in editing states where coloring is only sometimes
+permissible.")
+
+(defun context-coloring-ignore-unavailable-message-p ()
+  "Determine if the unavailable message should be silenced."
+  (let ((predicates context-coloring-ignore-unavailable-predicates)
+        (ignore-p nil))
+    (while (and predicates
+                (not ignore-p))
+      (setq ignore-p (funcall (pop predicates))))
+    ignore-p))
+
 
 ;;; Minor mode
 
@@ -1145,11 +1364,10 @@ comments and strings, is still colored with `font-lock'.
 The entire buffer is colored initially.  Changes to the buffer
 trigger recoloring.
 
-Certain custom themes have predefined colors from their palettes
-to use for coloring.  See `context-coloring-theme-hash-table' for
-the supported themes.  If the currently-enabled custom theme is
-not among these, you can define colors for it with
-`context-coloring-define-theme', which see.
+Define your own colors by customizing faces like
+`context-coloring-level-N-face', where N is a number starting
+from 0.  If no face is found on a custom theme nor the `user'
+theme, the defaults are used.
 
 New language / major mode support can be added with
 `context-coloring-define-dispatch', which see.
@@ -1174,8 +1392,8 @@ Feature inspired by Douglas Crockford."
           ;; Colorize once initially.
           (let ((context-coloring-parse-interruptable-p nil))
             (context-coloring-colorize))))
-       (t
-        (message "Context coloring is not available for this major mode")))))
+       ((not (context-coloring-ignore-unavailable-message-p))
+        (message "Context coloring is unavailable here")))))
    (t
     (let ((dispatch (context-coloring-get-current-dispatch)))
       (when dispatch