branch: master commit f52a5e58fccffa3071fa3d0183d6b281f8a148c2 Merge: 3b749e8 f4e1cbc Author: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com> Commit: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com>
Merge commit 'f4e1cbc5386fbf197ff8bcb6a9275f17c9cbe8e4' from context-coloring --- packages/context-coloring/Makefile | 11 +- .../benchmark/context-coloring-benchmark.el | 24 +- packages/context-coloring/context-coloring.el | 378 ++++++++++---------- .../context-coloring/test/context-coloring-test.el | 147 ++++---- 4 files changed, 287 insertions(+), 273 deletions(-) diff --git a/packages/context-coloring/Makefile b/packages/context-coloring/Makefile index 2d37cd6..c265382 100644 --- a/packages/context-coloring/Makefile +++ b/packages/context-coloring/Makefile @@ -1,7 +1,7 @@ EMACS = emacs DEPENDENCIES = libraries/ert-async.el libraries/js2-mode.el -all: clean compile test +all: uncompile compile test bench: ${DEPENDENCIES} ${EMACS} -Q \ @@ -17,8 +17,11 @@ compile: ${DEPENDENCIES} -L libraries \ -f batch-byte-compile *.el libraries/*.el -clean: - rm -f *.elc libraries/*.elc ${DEPENDENCIES} +uncompile: + rm -f *.elc libraries/*.elc + +clean: uncompile + rm -f ${DEPENDENCIES} ${DEPENDENCIES}: ${EMACS} -Q -batch \ @@ -34,4 +37,4 @@ test: ${DEPENDENCIES} -l test/context-coloring-test.el \ -f ert-run-tests-batch-and-exit -.PHONY: all bench compile clean test +.PHONY: all bench compile uncompile clean test diff --git a/packages/context-coloring/benchmark/context-coloring-benchmark.el b/packages/context-coloring/benchmark/context-coloring-benchmark.el index 004b66f..3da8d79 100644 --- a/packages/context-coloring/benchmark/context-coloring-benchmark.el +++ b/packages/context-coloring/benchmark/context-coloring-benchmark.el @@ -17,8 +17,19 @@ ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <http://www.gnu.org/licenses/>. +;;; Commentary: + +;; Benchmarks for context-coloring. + +;; `ert' instruments and benchmarks the package's functions, and the results are +;; logged to `benchmark/logs'. + +;; To run, execute `make bench' from the project root. + ;;; Code: +(require 'js2-mode) + (defconst context-coloring-benchmark-path (file-name-directory (or load-file-name buffer-file-name)) "This file's directory.") @@ -28,7 +39,7 @@ (expand-file-name path context-coloring-benchmark-path)) (defun context-coloring-benchmark-log-results (result-file fixture) - "Log benchmarking results for FIXTURE to RESULT-FILE." + "Log benchmarking results to RESULT-FILE for fixture FIXTURE." (elp-results) (let ((results-buffer (current-buffer))) (with-temp-buffer @@ -49,7 +60,8 @@ asynchrony." "Run the next test in LIST by calling CONTINUE. When LIST is exhausted, call STOP instead." (if (null list) - (context-coloring-benchmark-next-tick stop) + (progn + (context-coloring-benchmark-next-tick stop)) (context-coloring-benchmark-next-tick (lambda () (funcall @@ -59,8 +71,9 @@ exhausted, call STOP instead." (context-coloring-benchmark-next (cdr list) continue stop))))))) (defun context-coloring-benchmark-async (title setup teardown fixtures callback) - "Measure the performance of all FIXTURES, calling CALLBACK when -all are done." + "Execute a benchmark titled TITLE with SETUP and TEARDOWN +callbacks. Measure the performance of all FIXTURES, calling +CALLBACK when all are done." (funcall setup) (let ((result-file (context-coloring-benchmark-resolve-path (format "./logs/results-%s-%s.log" @@ -92,7 +105,7 @@ all are done." (find-file fixture))) (lambda () (funcall teardown) - (if callback (funcall callback)))))) + (when callback (funcall callback)))))) (defconst context-coloring-benchmark-js-fixtures '("./fixtures/jquery-2.1.1.js" @@ -121,7 +134,6 @@ all are done." (defun context-coloring-benchmark-js2-mode-setup () "Preparation logic for `js2-mode'." - (require 'js2-mode) (setq js2-mode-show-parse-errors nil) (setq js2-mode-show-strict-warnings nil) (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode)) diff --git a/packages/context-coloring/context-coloring.el b/packages/context-coloring/context-coloring.el index 6b6ffe9..849d392 100644 --- a/packages/context-coloring/context-coloring.el +++ b/packages/context-coloring/context-coloring.el @@ -5,7 +5,7 @@ ;; Author: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com> ;; URL: https://github.com/jacksonrayhamilton/context-coloring ;; Keywords: context coloring syntax highlighting -;; Version: 5.0.0 +;; Version: 6.0.0 ;; Package-Requires: ((emacs "24") (js2-mode "20150126")) ;; This file is part of GNU Emacs. @@ -34,8 +34,8 @@ ;; Lexical scope information at-a-glance can assist a programmer in ;; understanding the overall structure of a program. It can help to curb nasty -;; bugs like name shadowing. A rainbow can indicate excessive complexity. State -;; change within a closure is easily monitored. +;; bugs like name shadowing. A rainbow can indicate excessive complexity. +;; State change within a closure is easily monitored. ;; By default, Context Coloring still highlights comments and strings ;; syntactically. It is still easy to differentiate code from non-code, and @@ -56,135 +56,88 @@ (require 'js2-mode) -;;; Customizable options - -(defcustom context-coloring-delay 0.25 - "Delay between a buffer update and colorization. - -Increase this if your machine is high-performing. Decrease it if -it ain't. - -Supported modes: `js-mode', `js3-mode'" - :group 'context-coloring) - -(defcustom context-coloring-comments-and-strings t - "If non-nil, also color comments and strings using `font-lock'." - :group 'context-coloring) - -(defcustom context-coloring-js-block-scopes nil - "If non-nil, also color block scopes in the scope hierarchy in JavaScript. - -The block-scoped `let' and `const' are introduced in ES6. If you -are writing ES6 code, enable this; otherwise, don't. - -Supported modes: `js2-mode'" - :group 'context-coloring) - -(defcustom context-coloring-benchmark-colorization nil - "If non-nil, track how long colorization takes and print -messages with the colorization duration." - :group 'context-coloring) - - ;;; Local variables (defvar-local context-coloring-buffer nil "Reference to this buffer (for timers).") -(defvar-local context-coloring-scopifier-process nil - "Reference to the single scopifier process that can be - running.") - -(defvar-local context-coloring-colorize-idle-timer nil - "Reference to the currently-running idle timer.") - -(defvar-local context-coloring-changed nil - "Indication that the buffer has changed recently, which would -imply that it should be colorized again by -`context-coloring-colorize-idle-timer' if that timer is being -used.") - ;;; Faces (defun context-coloring-defface (level tty light dark) - "Dynamically define a face for LEVEL with colors for TTY, LIGHT -and DARK backgrounds." + "Define a face for LEVEL with colors for TTY, LIGHT and DARK +backgrounds." (let ((face (intern (format "context-coloring-level-%s-face" level))) (doc (format "Context coloring face, level %s." level))) - (eval - (macroexpand - `(defface ,face - '((((type tty)) (:foreground ,tty)) - (((background light)) (:foreground ,light)) - (((background dark)) (:foreground ,dark))) - ,doc - :group 'context-coloring))))) - -(defvar context-coloring-face-count nil - "Number of faces available for coloring.") - -(defun context-coloring-defface-default (level) + (custom-declare-face + face + `((((type tty)) (:foreground ,tty)) + (((background light)) (:foreground ,light)) + (((background dark)) (:foreground ,dark))) + doc + :group 'context-coloring))) + +(defun context-coloring-defface-neutral (level) "Define a face for LEVEL with the default neutral colors." (context-coloring-defface level nil "#3f3f3f" "#cdcdcd")) -(defun context-coloring-set-colors-default () - (context-coloring-defface 0 nil "#000000" "#ffffff") - (context-coloring-defface 1 "yellow" "#007f80" "#ffff80") - (context-coloring-defface 2 "green" "#001580" "#cdfacd") - (context-coloring-defface 3 "cyan" "#550080" "#d8d8ff") - (context-coloring-defface 4 "blue" "#802b00" "#e7c7ff") - (context-coloring-defface 5 "magenta" "#6a8000" "#ffcdcd") - (context-coloring-defface 6 "red" "#008000" "#ffe390") - (context-coloring-defface-default 7) - (setq context-coloring-face-count 8)) - -(context-coloring-set-colors-default) - -;; Color theme authors can have up to 26 levels: 1 (0th) for globals, 24 -;; (1st-24th) for in-betweens, and 1 (25th) for infinity. +(context-coloring-defface 0 nil "#000000" "#ffffff") +(context-coloring-defface 1 "yellow" "#007f80" "#ffff80") +(context-coloring-defface 2 "green" "#001580" "#cdfacd") +(context-coloring-defface 3 "cyan" "#550080" "#d8d8ff") +(context-coloring-defface 4 "blue" "#802b00" "#e7c7ff") +(context-coloring-defface 5 "magenta" "#6a8000" "#ffcdcd") +(context-coloring-defface 6 "red" "#008000" "#ffe390") +(context-coloring-defface-neutral 7) + +(defvar context-coloring-maximum-face nil + "Index of the highest face available for coloring.") + +(defvar context-coloring-original-maximum-face nil + "Fallback value for `context-coloring-maximum-face' when all + themes have been disabled.") + +(setq context-coloring-maximum-face 7) + +(setq context-coloring-original-maximum-face + context-coloring-maximum-face) + +;; Theme authors can have up to 26 levels: 1 (0th) for globals, 24 (1st-24th) +;; for nested levels, and 1 (25th) for infinity. (dotimes (number 18) - (context-coloring-defface-default (+ number context-coloring-face-count))) + (context-coloring-defface-neutral (+ number context-coloring-maximum-face 1))) ;;; Face functions -(defsubst context-coloring-face-symbol (level) - "Returns a symbol for a face with LEVEL." +(defsubst context-coloring-level-face (level) + "Return the symbol for a face with LEVEL." ;; `concat' is faster than `format' here. - (intern-soft (concat "context-coloring-level-" - (number-to-string level) - "-face"))) - -(defun context-coloring-set-colors (&rest colors) - "Set context coloring's levels' coloring to COLORS, where the -Nth element of COLORS is level N's color." - (setq context-coloring-face-count (length colors)) - (let ((level 0)) - (dolist (color colors) - ;; Ensure there are available faces to contain new colors. - (when (not (context-coloring-face-symbol level)) - (context-coloring-defface-default level)) - (set-face-foreground (context-coloring-face-symbol level) color) - (setq level (+ level 1))))) + (intern-soft + (concat "context-coloring-level-" (number-to-string level) "-face"))) -(defsubst context-coloring-level-face (level) - "Returns the face name for LEVEL." - (context-coloring-face-symbol (min level context-coloring-face-count))) +(defsubst context-coloring-bounded-level-face (level) + "Return the symbol for a face with LEVEL, bounded by +`context-coloring-maximum-face'." + (context-coloring-level-face (min level context-coloring-maximum-face))) ;;; Colorization utilities (defsubst context-coloring-colorize-region (start end level) - "Colorizes characters from the 1-indexed START (inclusive) to -END (exclusive) with the face corresponding to LEVEL." + "Color characters from the 1-indexed START point (inclusive) to +the END point (exclusive) with the face corresponding to LEVEL." (add-text-properties start end - `(face ,(context-coloring-level-face level)))) + `(face ,(context-coloring-bounded-level-face level)))) + +(defcustom context-coloring-comments-and-strings t + "If non-nil, also color comments and strings using `font-lock'." + :group 'context-coloring) (defsubst context-coloring-maybe-colorize-comments-and-strings () - "Colorizes the current buffer's comments and strings if + "Color the current buffer's comments and strings if `context-coloring-comments-and-strings' is non-nil." (when context-coloring-comments-and-strings (save-excursion @@ -194,11 +147,20 @@ END (exclusive) with the face corresponding to LEVEL." ;;; js2-mode colorization (defvar-local context-coloring-js2-scope-level-hash-table nil - "Associates `js2-scope' structures and with their scope + "Associate `js2-scope' structures and with their scope levels.") +(defcustom context-coloring-js-block-scopes nil + "If non-nil, also color block scopes in the scope hierarchy in JavaScript. + +The block-scoped `let' and `const' are introduced in ES6. Enable +this for ES6 code; disable it elsewhere. + +Supported modes: `js2-mode'" + :group 'context-coloring) + (defsubst context-coloring-js2-scope-level (scope) - "Gets the level of SCOPE." + "Return the level of SCOPE." (cond ((gethash scope context-coloring-js2-scope-level-hash-table)) (t (let ((level 0) @@ -218,7 +180,7 @@ END (exclusive) with the face corresponding to LEVEL." (puthash scope level context-coloring-js2-scope-level-hash-table))))) (defsubst context-coloring-js2-local-name-node-p (node) - "Determines if NODE is a js2-name-node representing a local + "Determine if NODE is a `js2-name-node' representing a local variable." (and (js2-name-node-p node) (let ((parent (js2-node-parent node))) @@ -230,7 +192,7 @@ variable." (eq node (js2-prop-get-node-right parent)))))))) (defsubst context-coloring-js2-colorize-node (node level) - "Colors NODE with the color for LEVEL." + "Color NODE with the color for LEVEL." (let ((start (js2-node-abs-pos node))) (context-coloring-colorize-region start @@ -238,8 +200,8 @@ variable." level))) (defun context-coloring-js2-colorize () - "Colorizes the current buffer using the abstract syntax tree -generated by js2-mode." + "Color the current buffer using the abstract syntax tree +generated by `js2-mode'." ;; Reset the hash table; the old one could be obsolete. (setq context-coloring-js2-scope-level-hash-table (make-hash-table :test 'eq)) (with-silent-modifications @@ -273,8 +235,8 @@ generated by js2-mode." ;;; Shell command scopification / colorization (defun context-coloring-apply-tokens (tokens) - "Processes a vector of TOKENS to apply context-based coloring -to the current buffer. Tokens are 3 integers: start, end, level. + "Process a vector of TOKENS to apply context-based coloring to +the current buffer. Tokens are 3 integers: start, end, level. The vector is flat, with a new token occurring after every 3rd element." (with-silent-modifications @@ -288,24 +250,26 @@ element." (setq i (+ i 3)))) (context-coloring-maybe-colorize-comments-and-strings))) -(defun context-coloring-parse-array (input) - "Specialized JSON parser for a flat array of numbers." +(defun context-coloring-parse-array (array) + "Parse ARRAY as a flat JSON array of numbers." (vconcat - (mapcar 'string-to-number (split-string (substring input 1 -1) ",")))) + (mapcar 'string-to-number (split-string (substring array 1 -1) ",")))) + +(defvar-local context-coloring-scopifier-process nil + "The single scopifier process that can be running.") (defun context-coloring-kill-scopifier () - "Kills the currently-running scopifier process for this -buffer." + "Kill the currently-running scopifier process." (when (not (null context-coloring-scopifier-process)) (delete-process context-coloring-scopifier-process) (setq context-coloring-scopifier-process nil))) (defun context-coloring-scopify-shell-command (command &optional callback) - "Invokes a scopifier with the current buffer's contents, -reading the scopifier's response asynchronously and applying a -parsed list of tokens to `context-coloring-apply-tokens'. + "Invoke a scopifier via COMMAND with the current buffer's contents, +read the scopifier's response asynchronously and apply a parsed +list of tokens to `context-coloring-apply-tokens'. -Invokes CALLBACK when complete." +Invoke CALLBACK when complete." ;; Prior running tokenization is implicitly obsolete if this function is ;; called. @@ -335,7 +299,7 @@ Invokes CALLBACK when complete." (with-current-buffer buffer (context-coloring-apply-tokens tokens)) (setq context-coloring-scopifier-process nil) - (if callback (funcall callback))))))) + (when callback (funcall callback))))))) ;; Give the process its input so it can begin. (process-send-region @@ -348,11 +312,11 @@ Invokes CALLBACK when complete." ;;; Dispatch (defvar context-coloring-dispatch-hash-table (make-hash-table :test 'eq) - "Mapping of dispatch strategy names to their corresponding - property lists, which contain details about the strategies.") + "Map dispatch strategy names to their corresponding property + lists, which contain details about the strategies.") (defvar context-coloring-mode-hash-table (make-hash-table :test 'eq) - "Mapping of major mode names to dispatch property lists.") + "Map major mode names to dispatch property lists.") (defun context-coloring-select-dispatch (mode dispatch) "Use DISPATCH for MODE." @@ -390,7 +354,13 @@ buffer a returns a flat vector of start, end and level data. `:command' - Shell command to execute with the current buffer sent via stdin, and with a flat JSON array of start, end and -level data returned via stdout." +level data returned via stdout. + +`:setup' - Arbitrary code to set up this dispatch when +`context-coloring-mode' is enabled. + +`:teardown' - Arbitrary code to tear down this dispatch when +`context-coloring-mode' is disabled." (let ((modes (plist-get properties :modes)) (colorizer (plist-get properties :colorizer)) (scopifier (plist-get properties :scopifier)) @@ -415,17 +385,23 @@ level data returned via stdout." (context-coloring-define-dispatch 'javascript-js2 :modes '(js2-mode) - :colorizer 'context-coloring-js2-colorize) + :colorizer 'context-coloring-js2-colorize + :setup + (lambda () + (add-hook 'js2-post-parse-callbacks 'context-coloring-colorize nil t)) + :teardown + (lambda () + (remove-hook 'js2-post-parse-callbacks 'context-coloring-colorize t))) (defun context-coloring-dispatch (&optional callback) - "Determines the optimal track for scopification / colorization -of the current buffer, then executes it. + "Determine the optimal track for scopification / coloring of +the current buffer, then execute it. -Invokes CALLBACK when complete. It is invoked synchronously for +Invoke CALLBACK when complete. It is invoked synchronously for elisp tracks, and asynchronously for shell command tracks." (let ((dispatch (gethash major-mode context-coloring-mode-hash-table))) - (if (null dispatch) - (message "%s" "Context coloring is not available for this major mode")) + (when (null dispatch) + (message "%s" "Context coloring is not available for this major mode")) (let (colorizer scopifier command @@ -433,43 +409,44 @@ elisp tracks, and asynchronously for shell command tracks." (cond ((setq colorizer (plist-get dispatch :colorizer)) (funcall colorizer) - (if callback (funcall callback))) + (when callback (funcall callback))) ((setq scopifier (plist-get dispatch :scopifier)) (context-coloring-apply-tokens (funcall scopifier)) - (if callback (funcall callback))) + (when callback (funcall callback))) ((setq command (plist-get dispatch :command)) (setq executable (plist-get dispatch :executable)) (if (and executable (null (executable-find executable))) - (message "Executable \"%s\" not found" executable) + (progn + (message "Executable \"%s\" not found" executable)) (context-coloring-scopify-shell-command command callback))))))) ;;; Colorization (defun context-coloring-colorize (&optional callback) - "Colors the current buffer by function context. + "Color the current buffer by function context. -Invokes CALLBACK when complete; see `context-coloring-dispatch'." +Invoke CALLBACK when complete; see `context-coloring-dispatch'." (interactive) - (let ((start-time (float-time))) - (context-coloring-dispatch - (lambda () - (when context-coloring-benchmark-colorization - (message "Colorization took %.3f seconds" (- (float-time) start-time))) - (if callback (funcall callback)))))) + (context-coloring-dispatch + (lambda () + (when callback (funcall callback))))) + +(defvar-local context-coloring-changed nil + "Indication that the buffer has changed recently, which implies +that it should be colored again by +`context-coloring-colorize-idle-timer' if that timer is being +used.") (defun context-coloring-change-function (_start _end _length) - "Registers a change so that a buffer can be colorized soon." + "Register a change so that a buffer can be colorized soon." ;; Tokenization is obsolete if there was a change. (context-coloring-kill-scopifier) (setq context-coloring-changed t)) (defun context-coloring-maybe-colorize () - "Colorize unders certain conditions. This will run as an idle -timer, so firstly the buffer must not be some other buffer. -Additionally, the buffer must have changed, otherwise colorizing -would be redundant." + "Colorize the current buffer if it has changed." (when (and (eq context-coloring-buffer (window-buffer (selected-window))) context-coloring-changed) (setq context-coloring-changed nil) @@ -479,7 +456,7 @@ would be redundant." ;;; Themes (defvar context-coloring-theme-hash-table (make-hash-table :test 'eq) - "Mapping of theme names to theme properties.") + "Map theme names to theme properties.") (defun context-coloring-theme-p (theme) "Return t if THEME is defined, nil otherwise." @@ -487,11 +464,11 @@ would be redundant." (defconst context-coloring-level-face-regexp "context-coloring-level-\\([[:digit:]]+\\)-face" - "Regular expression for extracting a level from a face.") + "Extract a level from a face.") (defvar context-coloring-originally-set-theme-hash-table (make-hash-table :test 'eq) - "Cache of custom themes who originally set their own + "Cache custom themes who originally set their own `context-coloring-level-N-face' faces.") (defun context-coloring-theme-originally-set-p (theme) @@ -520,7 +497,7 @@ originally set for THEME, nil otherwise." found))))) (defun context-coloring-cache-originally-set (theme originally-set) - "Remember if THEME had colors originally set for it; if + "Remember if THEME had colors originally set for it. If ORIGINALLY-SET is non-nil, it did, otherwise it didn't." ;; Caching whether a theme was originally set is kind of dirty, but we have to ;; do it to remember the past state of the theme. There are probably some @@ -531,14 +508,14 @@ ORIGINALLY-SET is non-nil, it did, otherwise it didn't." context-coloring-originally-set-theme-hash-table)) (defun context-coloring-warn-theme-originally-set (theme) - "Warns the user that the colors for a theme are already -originally set." + "Warn the user that the colors for THEME are already originally +set." (warn "Context coloring colors for theme `%s' are already defined" theme)) (defun context-coloring-theme-highest-level (theme) "Return the highest level N of a face like -`context-coloring-level-N-face' set for THEME, or -1 if there is -none." +`context-coloring-level-N-face' set for THEME, or `-1' if there +is none." (let* ((settings (get theme 'theme-settings)) (tail settings) face-string @@ -560,19 +537,19 @@ none." found)) (defun context-coloring-apply-theme (theme) - "Applies THEME's properties to its respective custom theme, + "Apply THEME's properties to its respective custom theme, which must already exist and which *should* already be enabled." (let* ((properties (gethash theme context-coloring-theme-hash-table)) (colors (plist-get properties :colors)) (level -1)) - (setq context-coloring-face-count (length colors)) + (setq context-coloring-maximum-face (- (length colors) 1)) (apply 'custom-theme-set-faces theme (mapcar (lambda (color) (setq level (+ level 1)) - `(,(context-coloring-face-symbol level) ((t (:foreground ,color))))) + `(,(context-coloring-level-face level) ((t (:foreground ,color))))) colors)))) (defun context-coloring-define-theme (theme &rest properties) @@ -628,9 +605,8 @@ precedence, i.e. the car of `custom-enabled-themes'." (context-coloring-apply-theme name))))))) (defun context-coloring-enable-theme (theme) - "Applies THEME if its colors are not already set, else just -sets `context-coloring-face-count' to the correct value for -THEME." + "Apply THEME if its colors are not already set, else just set +`context-coloring-maximum-face' to the correct value for THEME." (let* ((properties (gethash theme context-coloring-theme-hash-table)) (recede (plist-get properties :recede)) (override (plist-get properties :override))) @@ -641,7 +617,7 @@ THEME." ;; This can be true whether originally set by a custom theme or by a ;; context theme. ((> highest-level -1) - (setq context-coloring-face-count (+ highest-level 1))) + (setq context-coloring-maximum-face highest-level)) ;; It is possible that the corresponding custom theme did not exist at ;; the time of defining this context theme, and in that case the above ;; condition proves the custom theme did not originally set any faces, @@ -660,21 +636,27 @@ THEME." (context-coloring-apply-theme theme)))))) (defadvice enable-theme (after context-coloring-enable-theme (theme) activate) - "Enable colors for context themes just-in-time. We can't set -faces for custom themes that might not exist yet." + "Enable colors for context themes just-in-time." (when (and (not (eq theme 'user)) ; Called internally by `enable-theme'. (custom-theme-p theme) ; Guard against non-existent themes. (context-coloring-theme-p theme)) + (when (= (length custom-enabled-themes) 0) + ;; Cache because we can't reliably figure it out in reverse. + (setq context-coloring-original-maximum-face + context-coloring-maximum-face)) (context-coloring-enable-theme theme))) (defadvice disable-theme (after context-coloring-disable-theme (theme) activate) - "Colors are disabled normally, but -`context-coloring-face-count' isn't. Update it here." + "Update `context-coloring-maximum-face'." (when (custom-theme-p theme) ; Guard against non-existent themes. (let ((enabled-theme (car custom-enabled-themes))) (if (context-coloring-theme-p enabled-theme) - (context-coloring-enable-theme enabled-theme) - (context-coloring-set-colors-default))))) + (progn + (context-coloring-enable-theme enabled-theme)) + ;; Assume we are back to no theme; act as if nothing ever happened. + ;; This is still prone to intervention, but rather extraordinarily. + (setq context-coloring-maximum-face + context-coloring-original-maximum-face))))) (context-coloring-define-theme 'ample @@ -696,7 +678,7 @@ faces for custom themes that might not exist yet." "#401440" "#0f2050" "#205070" - "#336c6c" + "#437c7c" "#23733c" "#6b400c" "#603a60" @@ -803,7 +785,7 @@ faces for custom themes that might not exist yet." "#BFEBBF" "#F0DFAF" "#DFAF8F" - "#CC9393" + "#BC8383" "#DC8CC3" "#94BFF3" "#9FC59F" @@ -813,6 +795,18 @@ faces for custom themes that might not exist yet." ;;; Minor mode +(defvar-local context-coloring-colorize-idle-timer nil + "The currently-running idle timer.") + +(defcustom context-coloring-delay 0.25 + "Delay between a buffer update and colorization. + +Increase this if your machine is high-performing. Decrease it if +it ain't. + +Supported modes: `js-mode', `js3-mode'" + :group 'context-coloring) + ;;;###autoload (define-minor-mode context-coloring-mode "Context-based code coloring, inspired by Douglas Crockford." @@ -822,10 +816,15 @@ faces for custom themes that might not exist yet." (context-coloring-kill-scopifier) (when context-coloring-colorize-idle-timer (cancel-timer context-coloring-colorize-idle-timer)) - (remove-hook - 'js2-post-parse-callbacks 'context-coloring-colorize t) - (remove-hook - 'after-change-functions 'context-coloring-change-function t) + (let ((dispatch (gethash major-mode context-coloring-mode-hash-table))) + (when dispatch + (let ((command (plist-get dispatch :command)) + (teardown (plist-get dispatch :teardown))) + (when command + (remove-hook + 'after-change-functions 'context-coloring-change-function t)) + (when teardown + (funcall teardown))))) (font-lock-mode) (jit-lock-mode t)) @@ -836,26 +835,25 @@ faces for custom themes that might not exist yet." (font-lock-mode 0) (jit-lock-mode nil) - ;; Colorize once initially. - (context-coloring-colorize) + (let ((dispatch (gethash major-mode context-coloring-mode-hash-table))) + (when dispatch + (let ((command (plist-get dispatch :command)) + (setup (plist-get dispatch :setup))) + (when command + ;; Shell commands recolor on change, idly. + (add-hook + 'after-change-functions 'context-coloring-change-function nil t) + (setq context-coloring-colorize-idle-timer + (run-with-idle-timer + context-coloring-delay + t + 'context-coloring-maybe-colorize))) + (when setup + (funcall setup))))) - (cond - ((equal major-mode 'js2-mode) - ;; Only recolor on reparse. - (add-hook 'js2-post-parse-callbacks 'context-coloring-colorize nil t)) - (t - ;; Only recolor on change, idly. - (add-hook 'after-change-functions 'context-coloring-change-function nil t) - (setq context-coloring-colorize-idle-timer - (run-with-idle-timer - context-coloring-delay - t - 'context-coloring-maybe-colorize)))))) + ;; Colorize once initially. + (context-coloring-colorize))) (provide 'context-coloring) -;; Local Variables: -;; eval: (when (fboundp 'rainbow-mode) (rainbow-mode 1)) -;; End: - ;;; context-coloring.el ends here diff --git a/packages/context-coloring/test/context-coloring-test.el b/packages/context-coloring/test/context-coloring-test.el index fdb0d83..88a7158 100644 --- a/packages/context-coloring/test/context-coloring-test.el +++ b/packages/context-coloring/test/context-coloring-test.el @@ -17,6 +17,15 @@ ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <http://www.gnu.org/licenses/>. +;;; Commentary: + +;; Tests for context-coloring. + +;; Tests for both synchronous (elisp) and asynchronous (shell command) coloring +;; are available. Basic plugin functionality is also tested. + +;; To run, execute `make test' from the project root. + ;;; Code: (require 'ert-async) @@ -29,25 +38,24 @@ "This file's directory.") (defun context-coloring-test-read-file (path) - "Read a file's contents into a string." + "Read a file's contents from PATH into a string." (with-temp-buffer (insert-file-contents (expand-file-name path context-coloring-test-path)) (buffer-string))) (defun context-coloring-test-setup () - "Preparation code to run before all tests." + "Prepare before all tests." (setq context-coloring-comments-and-strings nil)) (defun context-coloring-test-cleanup () - "Cleanup code to run after all tests." + "Cleanup after all tests." (setq context-coloring-comments-and-strings t) (setq context-coloring-after-colorize-hook nil) - (setq context-coloring-js-block-scopes nil) - (context-coloring-set-colors-default)) + (setq context-coloring-js-block-scopes nil)) (defmacro context-coloring-test-with-fixture (fixture &rest body) - "Evaluate BODY in a temporary buffer with the relative -FIXTURE." + "With the relative FIXTURE, evaluate BODY in a temporary +buffer." `(with-temp-buffer (unwind-protect (progn @@ -73,14 +81,14 @@ is done." (defun context-coloring-test-with-fixture-async (fixture callback &optional setup) - "Evaluate CALLBACK in a temporary buffer with the relative -FIXTURE. A teardown callback is passed to CALLBACK for it to -invoke when it is done. An optional SETUP callback can be passed -to run arbitrary code before the mode is invoked." + "With the relative FIXTURE, evaluate CALLBACK in a temporary +buffer. A teardown callback is passed to CALLBACK for it to +invoke when it is done. An optional SETUP callback can run +arbitrary code before the mode is invoked." (context-coloring-test-with-temp-buffer-async (lambda (done-with-temp-buffer) (context-coloring-test-setup) - (if setup (funcall setup)) + (when setup (funcall setup)) (insert (context-coloring-test-read-file fixture)) (funcall callback @@ -117,8 +125,8 @@ instantiated in SETUP." ,@body)) (defmacro context-coloring-test-deftest-js-mode (name) - "Define an asynchronous test for `js-mode' in the typical -format." + "Define an asynchronous test for `js-mode' with the name NAME +in the typical format." (let ((test-name (intern (format "context-coloring-test-js-mode-%s" name))) (fixture (format "./fixtures/%s.js" name)) (function-name (intern-soft @@ -133,7 +141,8 @@ format." (funcall done)))))) (defmacro context-coloring-test-deftest-js2-mode (name) - "Define a test for `js2-mode' in the typical format." + "Define a test for `js2-mode' with the name NAME in the typical +format." (let ((test-name (intern (format "context-coloring-test-js2-mode-%s" name))) (fixture (format "./fixtures/%s.js" name)) (function-name (intern-soft @@ -147,9 +156,9 @@ format." ;;; Assertion functions (defmacro context-coloring-test-assert-region (&rest body) - "Skeleton for asserting something about the face of points in a -region. Provides the free variables `i', `length', `point', -`face' and `actual-level'." + "Assert something about the face of points in a region. +Provides the free variables `i', `length', `point', `face' and +`actual-level' to the code in BODY." `(let ((i 0) (length (- end start))) (while (< i length) @@ -200,19 +209,19 @@ EXPECTED-FACE." start end 'font-lock-comment-delimiter-face)) (defun context-coloring-test-assert-region-comment (start end) - "Assert that all points in the range [START, END) have + "Assert that all points in the range [START, END) have `font-lock-comment-face'." (context-coloring-test-assert-region-face start end 'font-lock-comment-face)) (defun context-coloring-test-assert-region-string (start end) - "Assert that all points in the range [START, END) have + "Assert that all points in the range [START, END) have `font-lock-string-face'." (context-coloring-test-assert-region-face start end 'font-lock-string-face)) (defun context-coloring-test-assert-message (expected buffer) - "Assert that BUFFER has message EXPECTED." + "Assert that message EXPECTED exists in BUFFER." (when (null (get-buffer buffer)) (ert-fail (format @@ -247,12 +256,12 @@ EXPECTED-FACE." (defun context-coloring-test-kill-buffer (buffer) "Kill BUFFER if it exists." - (if (get-buffer buffer) (kill-buffer buffer))) + (when (get-buffer buffer) (kill-buffer buffer))) (defun context-coloring-test-assert-face (level foreground &optional negate) "Assert that a face for LEVEL exists and that its `:foreground' -is FOREGROUND." - (let* ((face (context-coloring-face-symbol level)) +is FOREGROUND, or the inverse if NEGATE is non-nil." + (let* ((face (context-coloring-level-face level)) actual-foreground) (when (not (or negate face)) @@ -271,7 +280,8 @@ is FOREGROUND." (defun context-coloring-test-assert-not-face (&rest arguments) "Assert that LEVEL does not have a face with `:foreground' -FOREGROUND." +FOREGROUND. Apply ARGUMENTS to +`context-coloring-test-assert-face', see that function." (apply 'context-coloring-test-assert-face (append arguments '(t)))) @@ -286,32 +296,6 @@ FOREGROUND." "Context coloring is not available for this major mode" "*Messages*"))) -(ert-deftest context-coloring-test-set-colors () - ;; This test has an irreversible side-effect in that it defines faces beyond - ;; 7. Faces 0 through 7 are reset to their default states, so it might not - ;; matter, but be aware anyway. - (context-coloring-set-colors - "#000000" - "#111111" - "#222222" - "#333333" - "#444444" - "#555555" - "#666666" - "#777777" - "#888888" - "#999999") - (context-coloring-test-assert-face 0 "#000000") - (context-coloring-test-assert-face 1 "#111111") - (context-coloring-test-assert-face 2 "#222222") - (context-coloring-test-assert-face 3 "#333333") - (context-coloring-test-assert-face 4 "#444444") - (context-coloring-test-assert-face 5 "#555555") - (context-coloring-test-assert-face 6 "#666666") - (context-coloring-test-assert-face 7 "#777777") - (context-coloring-test-assert-face 8 "#888888") - (context-coloring-test-assert-face 9 "#999999")) - (defvar context-coloring-test-theme-index 0 "Unique index for unique theme names.") @@ -326,8 +310,8 @@ FOREGROUND." (defun context-coloring-test-assert-theme-originally-set-p (settings &optional negate) "Assert that `context-coloring-theme-originally-set-p' returns -t for a theme with SETTINGS (or the inverse if NEGATE is -non-nil)." +t for a theme with SETTINGS, or the inverse if NEGATE is +non-nil." (let ((theme (context-coloring-test-get-next-theme))) (put theme 'theme-settings settings) (when (funcall (if negate 'identity 'not) @@ -341,7 +325,9 @@ non-nil)." (defun context-coloring-test-assert-not-theme-originally-set-p (&rest arguments) "Assert that `context-coloring-theme-originally-set-p' does not -return t for a theme with SETTINGS." +return t for a theme with SETTINGS. Apply ARGUMENTS to +`context-coloring-test-assert-theme-originally-set-p', see that +function." (apply 'context-coloring-test-assert-theme-originally-set-p (append arguments '(t)))) @@ -368,7 +354,8 @@ EXPECTED-LEVEL." (defun context-coloring-test-assert-theme-highest-level (theme expected-level &optional negate) - "Assert that THEME has the highest level EXPECTED-LEVEL." + "Assert that THEME has the highest level EXPECTED-LEVEL, or the +inverse if NEGATE is non-nil." (let ((highest-level (context-coloring-theme-highest-level theme))) (when (funcall (if negate 'identity 'not) (eq highest-level expected-level)) (ert-fail (format (concat "Expected theme with settings `%s' " @@ -379,7 +366,10 @@ EXPECTED-LEVEL." (if negate "did" (format "was %s" highest-level))))))) (defun context-coloring-test-assert-theme-not-highest-level (&rest arguments) - "Assert that THEME's highest level is not EXPECTED-LEVEL." + "Assert that THEME's highest level is not EXPECTED-LEVEL. +Apply ARGUMENTS to +`context-coloring-test-assert-theme-highest-level', see that +function." (apply 'context-coloring-test-assert-theme-highest-level (append arguments '(t)))) @@ -404,9 +394,10 @@ EXPECTED-LEVEL." ) (defmacro context-coloring-test-deftest-define-theme (name &rest body) - "Define a test with an automatically-generated theme symbol -available as a free variable `theme'. Side-effects from enabling -themes are reversed after the test completes." + "Define a test with name NAME and an automatically-generated +theme symbol available as a free variable `theme'. Side-effects +from enabling themes are reversed after BODY is executed and the +test completes." (declare (indent defun)) (let ((deftest-name (intern (format "context-coloring-test-define-theme-%s" name)))) @@ -417,8 +408,7 @@ themes are reversed after the test completes." (progn ,@body) ;; Always cleanup. - (disable-theme theme) - (context-coloring-set-colors-default)))))) + (disable-theme theme)))))) (defun context-coloring-test-deftheme (theme) "Dynamically define theme THEME." @@ -563,21 +553,24 @@ theme THEME is signaled." (context-coloring-test-assert-face 0 "#aaaaaa") (context-coloring-test-assert-face 1 "#bbbbbb")) -(defun context-coloring-test-assert-face-count (count &optional negate) - "Assert that `context-coloring-face-count' is COUNT." +(defun context-coloring-test-assert-maximum-face (maximum &optional negate) + "Assert that `context-coloring-maximum-face' is MAXIMUM, or the +inverse if NEGATE is non-nil." (when (funcall (if negate 'identity 'not) - (eq context-coloring-face-count count)) - (ert-fail (format (concat "Expected `context-coloring-face-count' " + (eq context-coloring-maximum-face maximum)) + (ert-fail (format (concat "Expected `context-coloring-maximum-face' " "%sto be `%s', " "but it %s.") - (if negate "not " "") count + (if negate "not " "") maximum (if negate "was" - (format "was `%s'" context-coloring-face-count)))))) + (format "was `%s'" context-coloring-maximum-face)))))) -(defun context-coloring-test-assert-not-face-count (&rest arguments) - "Assert that `context-coloring-face-count' is not COUNT." - (apply 'context-coloring-test-assert-face-count +(defun context-coloring-test-assert-not-maximum-face (&rest arguments) + "Assert that `context-coloring-maximum-face' is not MAXIMUM. +Apply ARGUMENTS to `context-coloring-test-assert-maximum-face', +see that function." + (apply 'context-coloring-test-assert-maximum-face (append arguments '(t)))) (context-coloring-test-deftest-define-theme disable-cascade @@ -608,17 +601,18 @@ theme THEME is signaled." (context-coloring-test-assert-face 0 "#cccccc") (context-coloring-test-assert-face 1 "#dddddd") (context-coloring-test-assert-face 2 "#eeeeee") - (context-coloring-test-assert-face-count 3)) + (context-coloring-test-assert-maximum-face 2)) (disable-theme second-theme) (context-coloring-test-assert-face 0 "#aaaaaa") (context-coloring-test-assert-face 1 "#bbbbbb") - (context-coloring-test-assert-face-count 2)) + (context-coloring-test-assert-maximum-face 1)) (disable-theme theme) (context-coloring-test-assert-not-face 0 "#aaaaaa") (context-coloring-test-assert-not-face 1 "#bbbbbb") - (context-coloring-test-assert-not-face-count 2)) + (context-coloring-test-assert-not-maximum-face 1)) (defun context-coloring-test-js-function-scopes () + "Test fixtures/functions-scopes.js." (context-coloring-test-assert-region-level 1 9 0) (context-coloring-test-assert-region-level 9 23 1) (context-coloring-test-assert-region-level 23 25 0) @@ -636,6 +630,7 @@ theme THEME is signaled." (context-coloring-test-deftest-js2-mode function-scopes) (defun context-coloring-test-js-global () + "Test fixtures/global.js." (context-coloring-test-assert-region-level 20 28 1) (context-coloring-test-assert-region-level 28 35 0) (context-coloring-test-assert-region-level 35 41 1)) @@ -644,6 +639,7 @@ theme THEME is signaled." (context-coloring-test-deftest-js2-mode global) (defun context-coloring-test-js-block-scopes () + "Test fixtures/block-scopes.js." (context-coloring-test-assert-region-level 20 64 1) (setq context-coloring-js-block-scopes t) (context-coloring-colorize) @@ -655,6 +651,7 @@ theme THEME is signaled." (context-coloring-test-deftest-js2-mode block-scopes) (defun context-coloring-test-js-catch () + "Test fixtures/js-catch.js." (context-coloring-test-assert-region-level 20 27 1) (context-coloring-test-assert-region-level 27 51 2) (context-coloring-test-assert-region-level 51 52 1) @@ -668,12 +665,14 @@ theme THEME is signaled." (context-coloring-test-deftest-js2-mode catch) (defun context-coloring-test-js-key-names () + "Test fixtures/key-names.js." (context-coloring-test-assert-region-level 20 63 1)) (context-coloring-test-deftest-js-mode key-names) (context-coloring-test-deftest-js2-mode key-names) (defun context-coloring-test-js-property-lookup () + "Test fixtures/property-lookup.js." (context-coloring-test-assert-region-level 20 26 0) (context-coloring-test-assert-region-level 26 38 1) (context-coloring-test-assert-region-level 38 44 0) @@ -685,12 +684,14 @@ theme THEME is signaled." (context-coloring-test-deftest-js2-mode property-lookup) (defun context-coloring-test-js-key-values () + "Test fixtures/key-values.js." (context-coloring-test-assert-region-level 78 79 1)) (context-coloring-test-deftest-js-mode key-values) (context-coloring-test-deftest-js2-mode key-values) (defun context-coloring-test-js-comments-and-strings () + "Test fixtures/comments-and-strings.js." (context-coloring-test-assert-region-comment-delimiter 1 4) (context-coloring-test-assert-region-comment 4 8) (context-coloring-test-assert-region-comment-delimiter 9 12)