branch: master commit 9e142260e6bd7a4a700d3bba050e92e861a8027c Merge: dc8e99c 6f34a6a Author: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com> Commit: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com>
Merge branch 'feature/language-generalization' into develop --- README.md | 30 +++--- benchmark/scenarios.js | 2 +- context-coloring.el | 121 +++++++++++---------- {bin => languages/javascript/bin}/cli.js | 0 {bin => languages/javascript/bin}/scopifier | 0 {lib => languages/javascript/lib}/escope.js | 0 {lib => languages/javascript/lib}/esprima.js | 0 {lib => languages/javascript/lib}/estraverse.js | 0 scopifier.js => languages/javascript/scopifier.js | 0 screenshot.png | Bin 23970 -> 21829 bytes test/context-coloring-test.el | 18 +++- test/specs.js | 2 +- 12 files changed, 102 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 2c28844..cf1deff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ <img alt="Screenshot of JavaScript code highlighted by context." src="screenshot.png" title="Screenshot"> </p> -Highlights JavaScript code according to function context. +Highlights code according to function context. - Code in the global scope is one color. Code in functions within the global scope is a different color, and code within such functions is another color, @@ -12,19 +12,21 @@ Highlights JavaScript code according to function context. - Identifiers retain the color of the scope in which they were declared. - Comments are gray. -JavaScript programmers often leverage closures to bind nearby data to -functions. Lexical scope information at-a-glance can assist a programmer in -understanding the overall structure of a program. It can also help curb nasty -bugs like implicit globals and name shadowing. A rainbow can indicate excessive +Lexical scope information at-a-glance can assist a programmer in understanding +the overall structure of a program. It can also help curb nasty bugs like name +shadowing or unexpected assignment. A rainbow can indicate excessive complexity. A spot of contrast followed by an assignment expression could be a -side-effect... or, a specially-constructed object's private state could be -undergoing change. +side-effect... or, the state of a closure could be undergoing change. -This coloring scheme is probably more useful than conventional JavaScript -*syntax* highlighting. Highlighting keywords can help one to detect spelling -errors, and highlighting the content between quotation marks can alert one to -unclosed string literals. But a [linter][] can also spot these errors, along -with many others, and can be [seamlessly integrated via flycheck][integration]. +This coloring strategy is probably more useful than conventional *syntax* +highlighting. Highlighting keywords can help one to detect spelling errors, and +highlighting the content between quotation marks can alert one to unclosed +string literals. But a [linter][] could also spot those errors, and if +[integrated via flycheck][integration], an extra spot opens up in your editing +toolbelt. + +Give context coloring a try; you may find that it *changes the way you write +code*. ## Features @@ -37,7 +39,9 @@ with many others, and can be [seamlessly integrated via flycheck][integration]. ## Usage -Requires Emacs 24+ and [Node.js 0.10+][node]. +Requires Emacs 24+. + +JavaScript language support requires [Node.js 0.10+][node]. - Clone this repository. diff --git a/benchmark/scenarios.js b/benchmark/scenarios.js index 29e55c3..3e4fbeb 100644 --- a/benchmark/scenarios.js +++ b/benchmark/scenarios.js @@ -2,7 +2,7 @@ var fs = require('fs'), path = require('path'), - scopifier = require('../scopifier'), + scopifier = require('../languages/javascript/scopifier'), jqueryPath = path.join(__dirname, 'fixtures', 'jquery-2.1.1.js'), lodashPath = path.join(__dirname, 'fixtures', 'lodash-2.4.1.js'), diff --git a/context-coloring.el b/context-coloring.el index 4a022ba..b492288 100644 --- a/context-coloring.el +++ b/context-coloring.el @@ -1,9 +1,9 @@ -;;; context-coloring.el --- JavaScript syntax highlighting, except not for syntax. -*- lexical-binding: t; -*- +;;; context-coloring.el --- Syntax highlighting, except not for syntax. -*- lexical-binding: t; -*- ;; Copyright (C) 2014 Jackson Ray Hamilton ;; Author: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com> -;; Keywords: context coloring highlighting js javascript +;; Keywords: context coloring syntax highlighting ;; Version: 1.0.0 ;; Package-Requires: ((emacs "24")) @@ -22,15 +22,19 @@ ;;; Commentary: -;; Highlights JavaScript code according to function context. -;; -;; Usage: -;; -;; Install Node.js 0.10+. -;; In your ~/.emacs: -;; +;; Colors code by scope, rather than by syntax. + +;; A range of characters encompassing a scope is colored according to its level; +;; the global scope is white, scopes within the global scope are yellow, scopes +;; within scopes within the global scope are green, etc. Variables defined in a +;; parent scope which are referenced from child scopes retain the same color as +;; the scope in which they are defined; a variable defined in the global scope +;; will be the same color when referenced from nested scopes. + +;; To use, add the following to your ~/.emacs: + ;; (require 'context-coloring) -;; (add-hook 'js-mode-hook 'context-coloring-mode) +;; (add-hook 'js-mode-hook 'context-coloring-mode) ; Requires Node.js 0.10+. ;;; Code: @@ -91,6 +95,8 @@ "Context coloring face, level 6." :group 'context-coloring-faces) +;;; Additional 6 faces as placeholders for potential (insane) levels of nesting. + (defface context-coloring-level-7-face '((t (:inherit context-coloring-level-1-face))) "Context coloring face, level 7." @@ -146,18 +152,32 @@ For example: \"context-coloring-level-1-face\"." "-face"))) +;;; Constants + +(defconst context-coloring-path + (file-name-directory (or load-file-name buffer-file-name)) + "This file's directory.") + + ;;; Customizable variables +(let ((javascript-scopifier `(:type shell-command + :executable "node" + :command ,(expand-file-name + "./languages/javascript/bin/scopifier" + context-coloring-path)))) + (defcustom context-coloring-scopifier-plist + `(js-mode ,javascript-scopifier + js2-mode ,javascript-scopifier + js3-mode ,javascript-scopifier) + "Property list mapping major modes to scopification programs.")) + (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." :group 'context-coloring) -(defcustom context-coloring-benchmark-colorization nil - "If non-nil, display how long each colorization took." - :group 'context-coloring) - ;;; Local variables @@ -175,20 +195,9 @@ is a reference to that one process.") "Indication that the buffer has changed recently, which would imply that it should be colorized again.") -(defvar-local context-coloring-start-time nil - "Used to benchmark colorization time.") - ;;; Scopification -(defconst context-coloring-path - (file-name-directory (or load-file-name buffer-file-name)) - "This file's directory.") - -(defconst context-coloring-scopifier-path - (expand-file-name "./bin/scopifier" context-coloring-path) - "Path to the external scopifier executable.") - (defun context-coloring-apply-tokens (tokens) "Processes TOKENS to apply context-based coloring to the current buffer. Tokens are 3 integers: start, end, level. The @@ -217,11 +226,10 @@ buffer." "Specialized JSON parser for a flat array of numbers." (vconcat (mapcar 'string-to-number (split-string (substring input 1 -1) ",")))) -(defun context-coloring-scopify () - "Invokes the external 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'." +(defun context-coloring-scopify-shell-command (command) + "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'." ;; Prior running tokenization is implicitly obsolete if this function is ;; called. @@ -229,45 +237,52 @@ applying a parsed list of tokens to ;; Start the process. (setq context-coloring-scopifier-process - (start-process-shell-command "scopifier" nil context-coloring-scopifier-path)) + (start-process-shell-command "scopifier" nil command)) (let ((output "") - (buffer context-coloring-buffer) - (start-time context-coloring-start-time)) + (buffer context-coloring-buffer)) ;; The process may produce output in multiple chunks. This filter ;; accumulates the chunks into a message. - (set-process-filter context-coloring-scopifier-process - (lambda (process chunk) - (setq output (concat output chunk)))) + (set-process-filter + context-coloring-scopifier-process + (lambda (process chunk) + (setq output (concat output chunk)))) ;; When the process's message is complete, this sentinel parses it as JSON ;; and applies the tokens to the buffer. - (set-process-sentinel context-coloring-scopifier-process - (lambda (process event) - (when (equal "finished\n" event) - (let ((tokens (context-coloring-parse-array output))) - (with-current-buffer buffer - (context-coloring-apply-tokens tokens)) - (setq context-coloring-scopifier-process nil) - (when context-coloring-benchmark-colorization - (message "Colorized (after %f seconds)." (- (float-time) start-time)))))))) + (set-process-sentinel + context-coloring-scopifier-process + (lambda (process event) + (when (equal "finished\n" event) + (let ((tokens (context-coloring-parse-array output))) + (with-current-buffer buffer + (context-coloring-apply-tokens tokens)) + (setq context-coloring-scopifier-process nil)))))) ;; Give the process its input so it can begin. (process-send-region context-coloring-scopifier-process (point-min) (point-max)) (process-send-eof context-coloring-scopifier-process)) +(defun context-coloring-scopify () + "Determines the optimal track for scopification of the current +buffer, then scopifies the current buffer." + (let ((scopifier (plist-get context-coloring-scopifier-plist major-mode))) + (cond ((null scopifier) + (message "%s" "Context coloring is not available for this major mode")) + ((eq (plist-get scopifier :type) 'shell-command) + (let ((executable (plist-get scopifier :executable))) + (if (null (executable-find executable)) + (message "Context coloring executable \"%s\" not found" executable) + (context-coloring-scopify-shell-command (plist-get scopifier :command)))))))) + ;;; Colorization (defun context-coloring-colorize () "Colors the current buffer by function context." (interactive) - (when (executable-find "node") - (when context-coloring-benchmark-colorization - (setq context-coloring-start-time (float-time)) - (message "%s" "Colorizing...")) - (context-coloring-scopify))) + (context-coloring-scopify)) (defun context-coloring-change-function (start end length) "Registers a change so that a context-colored buffer can be @@ -291,7 +306,7 @@ colorizing would be redundant." ;;;###autoload (define-minor-mode context-coloring-mode - "Context-based code coloring for JavaScript, inspired by Douglas Crockford." + "Context-based code coloring, inspired by Douglas Crockford." nil " Context" nil (if (not context-coloring-mode) (progn @@ -305,10 +320,6 @@ colorizing would be redundant." ;; Remember this buffer. This value should not be dynamically-bound. (setq context-coloring-buffer (current-buffer)) - ;; Alert the user that the mode is not going to work. - (if (null (executable-find "node")) - (message "context-coloring-mode requires Node.js 0.10+ to be installed")) - ;; Colorize once initially. (context-coloring-colorize) diff --git a/bin/cli.js b/languages/javascript/bin/cli.js similarity index 100% rename from bin/cli.js rename to languages/javascript/bin/cli.js diff --git a/bin/scopifier b/languages/javascript/bin/scopifier similarity index 100% rename from bin/scopifier rename to languages/javascript/bin/scopifier diff --git a/lib/escope.js b/languages/javascript/lib/escope.js similarity index 100% rename from lib/escope.js rename to languages/javascript/lib/escope.js diff --git a/lib/esprima.js b/languages/javascript/lib/esprima.js similarity index 100% rename from lib/esprima.js rename to languages/javascript/lib/esprima.js diff --git a/lib/estraverse.js b/languages/javascript/lib/estraverse.js similarity index 100% rename from lib/estraverse.js rename to languages/javascript/lib/estraverse.js diff --git a/scopifier.js b/languages/javascript/scopifier.js similarity index 100% rename from scopifier.js rename to languages/javascript/scopifier.js diff --git a/screenshot.png b/screenshot.png index 14b94b6..fe45d9b 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/test/context-coloring-test.el b/test/context-coloring-test.el index d997d8b..0cc128c 100644 --- a/test/context-coloring-test.el +++ b/test/context-coloring-test.el @@ -17,7 +17,6 @@ FIXTURE." `(with-temp-buffer (insert (context-coloring-test-read-file ,fixture)) - (context-coloring-mode) ,@body)) (defun context-coloring-test-region-level-p (start end level) @@ -31,10 +30,27 @@ FIXTURE." "-face"))))) (setq i (+ i 1))))) +(defun context-coloring-test-message-should-be (expected) + (with-current-buffer "*Messages*" + (let ((messages (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))) + (let ((message (car (nthcdr (- (length messages) 2) messages)))) + (should (equal message expected)))))) + +(ert-deftest context-coloring-test-unsupported-mode () + (context-coloring-test-with-fixture + "./fixtures/function-scopes.js" + + (context-coloring-mode) + (context-coloring-test-message-should-be + "Context coloring is not available for this major mode"))) + (ert-deftest context-coloring-test-function-scopes () (context-coloring-test-with-fixture "./fixtures/function-scopes.js" + (js-mode) + (context-coloring-mode) + (sleep-for .25) ; Wait for asynchronous coloring to complete. (context-coloring-test-region-level-p 1 9 0) diff --git a/test/specs.js b/test/specs.js index 8eaa15c..d0ad425 100644 --- a/test/specs.js +++ b/test/specs.js @@ -5,7 +5,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var scopifier = require('../scopifier'); +var scopifier = require('../languages/javascript/scopifier'); var createEmacsBuffer = require('./createEmacsBuffer'); var threes = require('./threes');