+ (context-coloring-colorize-comments-and-strings)))
+
+
+;;; Emacs Lisp colorization
+
+(defsubst context-coloring-forward-sws ()
+ "Move forward through whitespace and comments."
+ (while (forward-comment 1)))
+
+(defsubst context-coloring-elisp-forward-sws ()
+ "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', coloring skipped comments and strings."
+ (let ((start (point)))
+ (forward-sexp)
+ (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) "\\'"))
+
+(defsubst context-coloring-exact-or-regexp (words)
+ "Create a regexp matching any exact word in WORDS."
+ (context-coloring-join
+ (mapcar #'context-coloring-exact-regexp words) "\\|"))
+
+(defconst context-coloring-elisp-ignored-word-regexp
+ (context-coloring-join (list "\\`[-+]?[0-9]"
+ "\\`[&:].+"
+ (context-coloring-exact-or-regexp
+ '("t" "nil" "." "?")))
+ "\\|")
+ "Match symbols that 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 "`"))
+
+(defsubst context-coloring-elisp-identifier-p (syntax-code)
+ "Check if SYNTAX-CODE is an elisp identifier constituent."
+ (or (= syntax-code context-coloring-WORD-CODE)
+ (= syntax-code context-coloring-SYMBOL-CODE)))
+
+(defvar context-coloring-parse-interruptable-p t
+ "Set this to nil to force parse to continue until finished.")
+
+(defconst context-coloring-elisp-sexps-per-pause 350
+ "Pause after this many iterations to check for user input.
+If user input is pending, stop the parse. This makes for a
+smoother user experience for large files.
+
+This number should trigger pausing at about 60 frames per
+second.")
+
+(defvar context-coloring-elisp-sexp-count 0
+ "Current number of sexps leading up to the next pause.")
+
+(defsubst context-coloring-elisp-increment-sexp-count ()
+ "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
+ context-coloring-elisp-sexps-per-pause))
+ context-coloring-parse-interruptable-p
+ (input-pending-p))
+ (throw 'interrupted t)))
+
+(defvar context-coloring-elisp-scope-stack '()
+ "List of scopes in the current parse.")
+
+(defsubst context-coloring-elisp-make-scope (level)
+ "Make a scope object for LEVEL."
+ (list
+ :level level
+ :variables '()))
+
+(defsubst context-coloring-elisp-scope-get-level (scope)
+ "Get the level of SCOPE object."
+ (plist-get scope :level))
+
+(defsubst context-coloring-elisp-scope-add-variable (scope variable)
+ "Add to SCOPE a VARIABLE."
+ (plist-put scope :variables (cons variable (plist-get scope :variables))))
+
+(defsubst context-coloring-elisp-scope-has-variable (scope variable)
+ "Check if SCOPE has VARIABLE."
+ (member variable (plist-get scope :variables)))
+
+(defsubst context-coloring-elisp-get-variable-level (variable)
+ "Return the level of VARIABLE, or 0 if it isn't found."
+ (let* ((scope-stack context-coloring-elisp-scope-stack)
+ scope
+ level)
+ (while (and scope-stack (not level))
+ (setq scope (car scope-stack))
+ (cond
+ ((context-coloring-elisp-scope-has-variable scope variable)
+ (setq level (context-coloring-elisp-scope-get-level scope)))
+ (t
+ (setq scope-stack (cdr scope-stack)))))
+ ;; Assume a global variable.
+ (or level 0)))
+
+(defsubst context-coloring-elisp-get-current-scope-level ()
+ "Get the nesting level of the current scope."
+ (cond
+ ((car context-coloring-elisp-scope-stack)
+ (context-coloring-elisp-scope-get-level (car context-coloring-elisp-scope-stack)))
+ (t
+ 0)))
+
+(defsubst context-coloring-elisp-push-scope ()
+ "Add a new scope to the bottom of the scope chain."
+ (push (context-coloring-elisp-make-scope
+ (1+ (context-coloring-elisp-get-current-scope-level)))
+ context-coloring-elisp-scope-stack))
+
+(defsubst context-coloring-elisp-pop-scope ()
+ "Remove the scope on the bottom of the scope chain."
+ (pop context-coloring-elisp-scope-stack))
+
+(defsubst context-coloring-elisp-add-variable (variable)
+ "Add VARIABLE to the current scope."
+ (context-coloring-elisp-scope-add-variable
+ (car context-coloring-elisp-scope-stack)
+ variable))
+
+(defsubst context-coloring-elisp-parse-bindable (callback)
+ "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)
+ (point)))))
+ (when (not (string-match-p
+ context-coloring-elisp-ignored-word-regexp
+ arg-string))
+ (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."
+ (let ((varlist '())
+ syntax-code)
+ ;; Enter.
+ (forward-char)
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (forward-char)
+ (context-coloring-elisp-forward-sws)
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (when (context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-parse-bindable
+ (lambda (var)
+ (push var varlist)))
+ (context-coloring-elisp-forward-sws)
+ (setq syntax-code (context-coloring-get-syntax-code))
+ (when (/= syntax-code context-coloring-CLOSE-PARENTHESIS-CODE)
+ (context-coloring-elisp-colorize-sexp)))
+ (context-coloring-elisp-forward-sws)
+ ;; Skip past the closing parenthesis.
+ (forward-char))
+ ((context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-parse-bindable
+ (lambda (var)
+ (push var varlist))))
+ (t
+ ;; Ignore artifacts.
+ (context-coloring-elisp-forward-sexp)))
+ (when (eq type 'let*)
+ (context-coloring-elisp-add-variable (pop varlist)))
+ (context-coloring-elisp-forward-sws))
+ (when (eq type 'let)
+ (while varlist
+ (context-coloring-elisp-add-variable (pop varlist))))
+ ;; Exit.
+ (forward-char)))
+
+(defun context-coloring-elisp-parse-arglist ()
+ "Parse the list of function arguments at point."
+ (let (syntax-code)
+ ;; Enter.
+ (forward-char)
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((context-coloring-elisp-identifier-p syntax-code)
+ (context-coloring-elisp-parse-bindable
+ (lambda (arg)
+ (context-coloring-elisp-add-variable arg))))
+ (t
+ ;; Ignore artifacts.
+ (context-coloring-elisp-forward-sexp)))
+ (context-coloring-elisp-forward-sws))
+ ;; Exit.
+ (forward-char)))
+
+(defun context-coloring-elisp-skip-callee-name ()
+ "Skip past the opening parenthesis and name of a function."
+ ;; Enter.
+ (forward-char)
+ (context-coloring-elisp-forward-sws)
+ ;; Skip past the function name.
+ (forward-sexp)
+ (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."
+ (let ((start (point))
+ (end (progn (forward-sexp)
+ (point))))
+ (context-coloring-elisp-push-scope)
+ ;; Splash the whole thing in one color.
+ (context-coloring-colorize-region
+ start
+ end
+ (context-coloring-elisp-get-current-scope-level))
+ ;; Even if the parse is interrupted, this region should still be colored
+ ;; syntactically.
+ (context-coloring-elisp-colorize-comments-and-strings-in-region
+ start
+ end)
+ (goto-char start)
+ (context-coloring-elisp-skip-callee-name)
+ (funcall callback)
+ (context-coloring-elisp-colorize-region (point) (1- end))
+ ;; Exit.
+ (forward-char)
+ (context-coloring-elisp-pop-scope)))
+
+(defun context-coloring-elisp-parse-header (callback)
+ "Parse a function header at point with CALLBACK."
+ (when (= (context-coloring-get-syntax-code) context-coloring-OPEN-PARENTHESIS-CODE)
+ (funcall callback)))
+
+(defun context-coloring-elisp-colorize-defun-like (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))
+ ;; Color the defun's name with the top-level color.
+ (context-coloring-colorize-region
+ (point)
+ (progn (forward-sexp)
+ (point))
+ 0)
+ (context-coloring-elisp-forward-sws)
+ (context-coloring-elisp-parse-header callback)))))
+
+(defun context-coloring-elisp-colorize-defun ()
+ "Color the `defun' at point."
+ (context-coloring-elisp-colorize-defun-like
+ 'context-coloring-elisp-parse-arglist))
+
+(defun context-coloring-elisp-colorize-defadvice ()
+ "Color the `defadvice' at point."
+ (context-coloring-elisp-colorize-defun-like
+ (lambda ()
+ (let (syntax-code)
+ ;; Enter.
+ (forward-char)
+ (while (/= (setq syntax-code (context-coloring-get-syntax-code))
+ context-coloring-CLOSE-PARENTHESIS-CODE)
+ (cond
+ ((= syntax-code context-coloring-OPEN-PARENTHESIS-CODE)
+ (context-coloring-elisp-parse-arglist))
+ (t
+ ;; Ignore artifacts.
+ (context-coloring-elisp-forward-sexp)))
+ (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."
+ (context-coloring-elisp-colorize-scope
+ (lambda ()
+ (context-coloring-elisp-parse-header callback))))