+(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)
+ (context-coloring-elisp-skip-callee-name)
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ ;; Colorize inside the parens.
+ (let ((start (point)))
+ (forward-sexp)
+ (context-coloring-elisp-colorize-region
+ (1+ start) (1- (point)))
+ ;; Exit.
+ (forward-char)))
+ (t
+ ;; Ignore artifacts.
+ (context-coloring-elisp-forward-sexp)))
+ (context-coloring-elisp-forward-sws))
+ ;; Exit.
+ (forward-char)))
+
+(defun context-coloring-elisp-colorize-condition-case ()
+ "Color the `condition-case' at point."
+ (let (syntax-code
+ variable
+ case-pos
+ case-end)
+ (context-coloring-elisp-colorize-scope
+ (lambda ()
+ (setq syntax-code (context-coloring-get-syntax-code))
+ ;; Gracefully ignore missing variables.
+ (when (context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-parse-bindable
+ (lambda (parsed-variable)
+ (setq variable parsed-variable)))
+ (context-coloring-elisp-forward-sws))
+ (context-coloring-elisp-colorize-sexp)
+ (context-coloring-elisp-forward-sws)
+ ;; Parse the handlers with the error variable in scope.
+ (when variable
+ (context-coloring-elisp-add-variable variable))
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (setq case-pos (point))
+ (context-coloring-elisp-forward-sexp)
+ (setq case-end (point))
+ (goto-char case-pos)
+ ;; Enter.
+ (forward-char)
+ (context-coloring-elisp-forward-sws)
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
+ ;; Skip the condition name(s).
+ (context-coloring-elisp-forward-sexp)
+ ;; Color the remaining portion of the handler.
+ (context-coloring-elisp-colorize-region
+ (point)
+ (1- case-end)))
+ ;; Exit.
+ (forward-char))
+ (t
+ ;; Ignore artifacts.
+ (context-coloring-elisp-forward-sexp)))
+ (context-coloring-elisp-forward-sws))))))
+
+(defun context-coloring-elisp-colorize-dolist ()
+ "Color the `dolist' at point."
+ (let (syntax-code
+ (index 0))
+ (context-coloring-elisp-colorize-scope
+ (lambda ()
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (when (= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (forward-char)
+ (context-coloring-elisp-forward-sws)
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((and
+ (or (= index 0) (= index 2))
+ (context-coloring-elisp-identifier-p syntax-code))
+ ;; Add the first or third name to the scope.
+ (context-coloring-elisp-parse-bindable
+ (lambda (variable)
+ (context-coloring-elisp-add-variable variable))))
+ (t
+ ;; Color artifacts.
+ (context-coloring-elisp-colorize-sexp)))
+ (context-coloring-elisp-forward-sws)
+ (setq index (1+ index)))
+ ;; Exit.
+ (forward-char))))))
+
+(defun context-coloring-elisp-colorize-quote ()
+ "Color the `quote' at point."
+ (let* ((start (point))
+ (end (progn (forward-sexp)
+ (point))))
+ (context-coloring-colorize-region
+ start
+ end
+ (context-coloring-elisp-get-current-scope-level))
+ (context-coloring-elisp-colorize-comments-and-strings-in-region start end)))
+
+(defvar context-coloring-elisp-callee-dispatch-hash-table
+ (let ((table (make-hash-table :test 'equal)))
+ (dolist (callee '("defun" "defun*" "defsubst" "defmacro" "cl-defun" "cl-defsubst" "cl-defmacro"))
+ (puthash callee #'context-coloring-elisp-colorize-defun table))
+ (dolist (callee '("condition-case" "condition-case-unless-debug"))
+ (puthash callee #'context-coloring-elisp-colorize-condition-case table))
+ (dolist (callee '("dolist" "dotimes"))
+ (puthash callee #'context-coloring-elisp-colorize-dolist 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)
+ (puthash "quote" #'context-coloring-elisp-colorize-quote table)
+ (puthash "backquote" #'context-coloring-elisp-colorize-backquote table)
+ table)
+ "Map function names to their coloring functions.")
+
+(defun context-coloring-elisp-colorize-parenthesized-sexp ()
+ "Color the sexp enclosed by parenthesis at point."
+ (context-coloring-elisp-increment-sexp-count)
+ (let* ((start (point))
+ (end (progn (forward-sexp)
+ (point)))
+ (syntax-code (progn (goto-char start)
+ (forward-char)
+ ;; Coloring is unnecessary here, it'll happen
+ ;; presently.
+ (context-coloring-forward-sws)
+ (context-coloring-get-syntax-code)))
+ dispatch-function)
+ ;; Figure out if the sexp is a special form.
+ (cond
+ ((and (context-coloring-elisp-identifier-p syntax-code)
+ (setq dispatch-function (gethash
+ (buffer-substring-no-properties
+ (point)
+ (progn (forward-sexp)
+ (point)))
+ context-coloring-elisp-callee-dispatch-hash-table)))
+ (goto-char start)
+ (funcall dispatch-function))
+ ;; Not a special form; just colorize the remaining region.
+ (t
+ (context-coloring-colorize-region
+ start
+ end
+ (context-coloring-elisp-get-current-scope-level))
+ (context-coloring-elisp-colorize-region (point) (1- end))
+ (forward-char)))))
+
+(defun context-coloring-elisp-colorize-symbol ()
+ "Color the symbol at point."
+ (context-coloring-elisp-increment-sexp-count)
+ (let* ((symbol-pos (point))
+ (symbol-end (progn (forward-sexp)
+ (point)))
+ (symbol-string (buffer-substring-no-properties
+ symbol-pos
+ symbol-end)))
+ (cond
+ ((string-match-p context-coloring-elisp-ignored-word-regexp symbol-string))
+ (t
+ (context-coloring-colorize-region
+ symbol-pos
+ symbol-end
+ (context-coloring-elisp-get-variable-level
+ symbol-string))))))
+
+(defun context-coloring-elisp-colorize-backquote-form ()
+ "Color the backquote form at point."
+ (let ((start (point))
+ (end (progn (forward-sexp)
+ (point)))
+ char)
+ (goto-char start)
+ (while (> end (progn (forward-char)
+ (point)))
+ (setq char (char-after))
+ (when (= char context-coloring-COMMA-CHAR)
+ (forward-char)
+ (when (= (char-after) context-coloring-AT-CHAR)
+ ;; If we don't do this "@" could be interpreted as a symbol.
+ (forward-char))
+ (context-coloring-elisp-forward-sws)
+ (context-coloring-elisp-colorize-sexp)))
+ ;; We could probably do this as part of the above loop but it'd be
+ ;; repetitive.
+ (context-coloring-elisp-colorize-comments-and-strings-in-region
+ start end)))
+
+(defun context-coloring-elisp-colorize-backquote ()
+ "Color the `backquote' at point."
+ (context-coloring-elisp-skip-callee-name)
+ (context-coloring-elisp-colorize-backquote-form)
+ ;; Exit.
+ (forward-char))
+
+(defun context-coloring-elisp-colorize-expression-prefix ()
+ "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)
+ (context-coloring-elisp-forward-sexp))
+ (t
+ (context-coloring-elisp-colorize-backquote-form))))
+
+(defun context-coloring-elisp-colorize-comment ()
+ "Color the comment at point."
+ (context-coloring-elisp-increment-sexp-count)
+ (context-coloring-elisp-forward-sws))
+
+(defun context-coloring-elisp-colorize-string ()
+ "Color the string at point."
+ (context-coloring-elisp-increment-sexp-count)
+ (let ((start (point)))
+ (forward-sexp)
+ (context-coloring-colorize-comments-and-strings start (point))))
+
+;; Elisp has whitespace, words, symbols, open/close parenthesis, expression
+;; prefix, string quote, comment starters/enders and escape syntax classes only.
+
+(defun context-coloring-elisp-colorize-sexp ()
+ "Color the sexp at point."
+ (let ((syntax-code (context-coloring-get-syntax-code)))
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (context-coloring-elisp-colorize-parenthesized-sexp))
+ ((context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-colorize-symbol))
+ ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
+ (context-coloring-elisp-colorize-expression-prefix))
+ ((= syntax-code context-coloring-STRING-QUOTE-CODE)
+ (context-coloring-elisp-colorize-string))
+ ((= syntax-code context-coloring-ESCAPE-CODE)
+ (forward-char 2)))))
+
+(defun context-coloring-elisp-colorize-comments-and-strings-in-region (start end)
+ "Color comments and strings between START and END."
+ (let (syntax-code)
+ (goto-char start)
+ (while (> end (progn (skip-syntax-forward "^\"<\\" end)
+ (point)))
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (cond
+ ((= syntax-code context-coloring-STRING-QUOTE-CODE)
+ (context-coloring-elisp-colorize-string))
+ ((= syntax-code context-coloring-COMMENT-START-CODE)
+ (context-coloring-elisp-colorize-comment))
+ ((= syntax-code context-coloring-ESCAPE-CODE)
+ (forward-char 2))))))
+
+(defun context-coloring-elisp-colorize-region (start end)
+ "Color everything between START and END."
+ (let (syntax-code)
+ (goto-char start)
+ (while (> end (progn (skip-syntax-forward "^w_('\"<\\" end)
+ (point)))
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (context-coloring-elisp-colorize-parenthesized-sexp))
+ ((context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-colorize-symbol))
+ ((= syntax-code context-coloring-EXPRESSION-PREFIX-CODE)
+ (context-coloring-elisp-colorize-expression-prefix))
+ ((= syntax-code context-coloring-STRING-QUOTE-CODE)
+ (context-coloring-elisp-colorize-string))
+ ((= syntax-code context-coloring-COMMENT-START-CODE)
+ (context-coloring-elisp-colorize-comment))
+ ((= syntax-code context-coloring-ESCAPE-CODE)
+ (forward-char 2))))))
+
+(defun context-coloring-elisp-colorize-region-initially (start end)
+ "Begin coloring everything between START and END."
+ (setq context-coloring-elisp-sexp-count 0)
+ (setq context-coloring-elisp-scope-stack '())
+ (let ((inhibit-point-motion-hooks t)
+ (case-fold-search nil)
+ ;; This is a recursive-descent parser, so give it a big stack.
+ (max-lisp-eval-depth (max max-lisp-eval-depth 3000))
+ (max-specpdl-size (max max-specpdl-size 3000)))
+ (context-coloring-elisp-colorize-region start end)))
+
+(defun context-coloring-elisp-colorize-guard (callback)
+ "Silently color in CALLBACK."
+ (with-silent-modifications
+ (save-excursion
+ (condition-case nil
+ (funcall callback)
+ ;; Scan errors can happen virtually anywhere if parenthesis are
+ ;; unbalanced. Just swallow them. (`progn' for test coverage.)
+ (scan-error (progn))))))
+
+(defun context-coloring-elisp-colorize ()
+ "Color the current Emacs Lisp buffer."
+ (interactive)
+ (context-coloring-elisp-colorize-guard
+ (lambda ()
+ (cond
+ ;; Just colorize the changed region.
+ (context-coloring-changed-p
+ (let* ( ;; Prevent `beginning-of-defun' from making poor assumptions.
+ (open-paren-in-column-0-is-defun-start nil)
+ ;; Seek the beginning and end of the previous and next
+ ;; offscreen defuns, so just enough is colored.
+ (start (progn (goto-char context-coloring-changed-start)
+ (while (and (< (point-min) (point))
+ (pos-visible-in-window-p))
+ (end-of-line 0))
+ (beginning-of-defun)
+ (point)))
+ (end (progn (goto-char context-coloring-changed-end)
+ (while (and (> (point-max) (point))
+ (pos-visible-in-window-p))
+ (forward-line 1))
+ (end-of-defun)
+ (point))))
+ (context-coloring-elisp-colorize-region-initially start end)
+ ;; Fast coloring is nice, but if the code is not well-formed
+ ;; (e.g. an unclosed string literal is parsed at any time) then
+ ;; there could be leftover incorrectly-colored code offscreen. So
+ ;; do a clean sweep as soon as appropriate.
+ (context-coloring-schedule-coloring context-coloring-default-delay)))
+ (t
+ (context-coloring-elisp-colorize-region-initially (point-min) (point-max)))))))
+
+
+;;; 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)
+ (context-coloring-elisp-colorize-guard
+ (lambda ()
+ (context-coloring-elisp-colorize-region-initially
+ (progn
+ (context-coloring-eval-expression-match)
+ (1+ (match-end 0)))
+ (point-max)))))