]> code.delx.au - gnu-emacs-elpa/commitdiff
Merge commit '0c3a48f05d17f486a0c522f0f9ec316dce24d86d' from context-coloring
authorJackson Hamilton <jackson@jacksonrayhamilton.com>
Sun, 18 Oct 2015 11:20:32 +0000 (04:20 -0700)
committerJackson Hamilton <jackson@jacksonrayhamilton.com>
Sun, 18 Oct 2015 11:20:32 +0000 (04:20 -0700)
1  2 
packages/context-coloring/README.md
packages/context-coloring/context-coloring.el
packages/context-coloring/test/context-coloring-test.el
packages/context-coloring/test/fixtures/block-scopes.js
packages/context-coloring/test/fixtures/global.js
packages/context-coloring/test/fixtures/initial-level.js
packages/context-coloring/test/fixtures/let-star.el

index ff92dd962ec90d23d70400b169c07105433c3add,21f1eb5cf9d5d3b0ffa7a59d8e34d5cc2252e2b0..21f1eb5cf9d5d3b0ffa7a59d8e34d5cc2252e2b0
@@@ -16,6 -16,7 +16,7 @@@ By default, comments and strings are st
  - Light and dark customizable color schemes.
  - JavaScript support:
    - Script, function and block scopes (and even `catch` block scopes).
+   - Node.js "file-level" scope detection.
  - Emacs Lisp support:
    - `defun`, `lambda`, `let`, `let*`, `cond`, `condition-case`, `defadvice`,
      `dolist`, `quote`, `backquote` and backquote splicing.
@@@ -38,32 -39,18 +39,18 @@@ then add the following to your init fil
  (add-hook 'emacs-lisp-mode-hook #'context-coloring-mode)
  
  ;; eval-expression:
- (add-hook 'minibuffer-setup-hook #'context-coloring-mode)
+ (add-hook 'eval-expression-minibuffer-setup-hook #'context-coloring-mode) ; 24.4+
+ (add-hook 'minibuffer-setup-hook #'context-coloring-mode)                 ; 24.3
  ```
  
  ## Color Schemes
  
+ The [Zenburn](https://github.com/bbatsov/zenburn-emacs) theme, featured in the
+ screenshot above, now supports context coloring.
  You can define your own colors by customizing faces like
  `context-coloring-level-N-face`, where N is a number starting from 0.
  
- These are the colors used in the screenshot above:
- ```lisp
- (custom-theme-set-faces
-  'zenburn
-  '(context-coloring-level-0-face  ((t :foreground "#dcdccc")))
-  '(context-coloring-level-1-face  ((t :foreground "#93e0e3")))
-  '(context-coloring-level-2-face  ((t :foreground "#bfebbf")))
-  '(context-coloring-level-3-face  ((t :foreground "#f0dfaf")))
-  '(context-coloring-level-4-face  ((t :foreground "#dfaf8f")))
-  '(context-coloring-level-5-face  ((t :foreground "#cc9393")))
-  '(context-coloring-level-6-face  ((t :foreground "#dc8cc3")))
-  '(context-coloring-level-7-face  ((t :foreground "#94bff3")))
-  '(context-coloring-level-8-face  ((t :foreground "#9fc59f")))
-  '(context-coloring-level-9-face  ((t :foreground "#d0bf8f")))
-  '(context-coloring-level-10-face ((t :foreground "#dca3a3"))))
- ```
  [See here](https://gist.github.com/jacksonrayhamilton/6b89ca3b85182c490816) for
  some color schemes for popular custom themes.
  
@@@ -75,3 -62,5 +62,5 @@@
    strings using `font-lock`.
  - `context-coloring-javascript-block-scopes` (default: `nil`): If non-nil, also
    color block scopes in the scope hierarchy in JavaScript.
+ - `context-coloring-javascript-detect-top-level-scope` (default: `t`): If
+   non-nil, detect when to use file-level scope.
index 2b1d7afe4b1af793d68da89330db5e33d5dc48ea,8d6cacc0c116b8ebbc6430de3a47d803051d0e1f..8d6cacc0c116b8ebbc6430de3a47d803051d0e1f
@@@ -3,9 -3,9 +3,9 @@@
  ;; Copyright (C) 2014-2015  Free Software Foundation, Inc.
  
  ;; Author: Jackson Ray Hamilton <jackson@jacksonrayhamilton.com>
- ;; Version: 7.0.0
+ ;; Version: 7.1.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.
@@@ -184,6 -184,7 +184,7 @@@ START, END and LENGTH are recorded for 
  
  Increase this if your machine is high-performing.  Decrease it if
  it ain't."
+   :type 'float
    :group 'context-coloring)
  
  (make-obsolete-variable
  
  (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)
@@@ -285,8 -288,17 +288,17 @@@ MIN defaults to beginning of buffer.  M
          (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
  
  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
   '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
        context-coloring-point-max)
       level)))
  
- (defun context-coloring-js2-colorize ()
+ (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))
            ((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
               (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
  
index f5633b86e682cd750e797b6d573c2d7a35bb67ee,f643e914c508b5a9b917c235ac8345ef9f21d95d..f643e914c508b5a9b917c235ac8345ef9f21d95d
@@@ -365,21 -365,21 +365,21 @@@ signaled.
  
  ;;; Coloring tests
  
+ (defun context-coloring-test-face-to-level (face)
+   "Convert FACE symbol to its corresponding level, or nil."
+   (when face
+     (let* ((face-string (symbol-name face))
+            (matches (string-match
+                      context-coloring-level-face-regexp
+                      face-string)))
+       (when matches
+         (string-to-number (match-string 1 face-string))))))
  (defun context-coloring-test-assert-position-level (position level)
    "Assert that POSITION has LEVEL."
-   (let ((face (get-text-property position 'face))
-         actual-level)
-     (when (not (and face
-                     (let* ((face-string (symbol-name face))
-                            (matches (string-match
-                                      context-coloring-level-face-regexp
-                                      face-string)))
-                       (when matches
-                         (setq actual-level (string-to-number
-                                             (substring face-string
-                                                        (match-beginning 1)
-                                                        (match-end 1))))
-                         (= level actual-level)))))
+   (let* ((face (get-text-property position 'face))
+          (actual-level (context-coloring-test-face-to-level face)))
+     (when (not (= level actual-level))
        (ert-fail (format (concat "Expected level at position %s, "
                                  "which is \"%s\", to be %s; "
                                  "but it was %s")
@@@ -493,7 -493,7 +493,7 @@@ other non-letters are guaranteed to alw
    (lambda ()
      (context-coloring-test-assert-coloring "
  (xxxxxxxx () {
-     111 1 1 00000001xxx11
+     111 1 1 0000001xxx11
  }());")))
  
  (context-coloring-test-deftest-javascript block-scopes
      11 111 2
          222 12
          222 22
+         22222 12
+     2
+ }());
+ (xxxxxxxx () {
+     'xxx xxxxxx';
+     11 111 2
+         222 12
+         222 22
+         22222 22
      2
  }());"))
    :before (lambda ()
@@@ -592,6 -602,59 +602,59 @@@ ssssssssssss0")
    ;; As long as `add-text-properties' doesn't signal an error, this test passes.
    (lambda ()))
  
+ (defun context-coloring-test-assert-javascript-elevated-level ()
+   "Assert that the \"initial-level.js\" file has elevated scope."
+   (context-coloring-test-assert-coloring "
+ 111 1 1 0000001xxx11"))
+ (defun context-coloring-test-assert-javascript-global-level ()
+   "Assert that the \"initial-level.js\" file has global scope."
+   (context-coloring-test-assert-coloring "
+ 000 0 0 0000000xxx00"))
+ (context-coloring-test-deftest-javascript initial-level
+   (lambda ()
+     (context-coloring-test-assert-javascript-elevated-level))
+   :fixture "initial-level.js"
+   :before (lambda ()
+             (setq context-coloring-initial-level 1))
+   :after (lambda ()
+            (setq context-coloring-initial-level 0)))
+ (defun context-coloring-test-setup-top-level-scope (string)
+   "Make STRING the first line and colorize again."
+   (goto-char (point-min))
+   (kill-whole-line 0)
+   (insert string)
+   ;; Reparsing triggers recoloring.
+   (js2-reparse))
+ (context-coloring-test-deftest-javascript top-level-scope
+   (lambda ()
+     (let ((positive-indicators
+            (list "#!/usr/bin/env node"
+                  "/*jslint node: true */"
+                  "// jshint node: true"
+                  "/*eslint-env node */"
+                  "module.exports"
+                  "module.exports.a"
+                  "exports.a"
+                  "require('a')"))
+           (negative-indicators
+            (list "// Blah blah jshint blah."
+                  "module"
+                  "exports"
+                  "var require; require('a')")))
+       (dolist (indicator positive-indicators)
+         (context-coloring-test-setup-top-level-scope indicator)
+         (context-coloring-test-assert-javascript-elevated-level))
+       (dolist (indicator negative-indicators)
+         (context-coloring-test-setup-top-level-scope indicator)
+         (context-coloring-test-assert-javascript-global-level))))
+   :fixture "initial-level.js")
  (context-coloring-test-deftest-emacs-lisp defun
    (lambda ()
      (context-coloring-test-assert-coloring "
           22 02
           22 222
      2222 1 1 2 2 2 000022
-   1111 1 1 1 0 0 000011")))
+   1111 1 1 1 0 0 000011"))
+   :fixture "let-star.el")
  
  (context-coloring-test-deftest-emacs-lisp cond
    (lambda ()
index 735ca6f110b4c68b0f9ac678934c0d6767d19c81,86e4a1368a4ee50d46cd6ad3cdbfb3efebcdc047..86e4a1368a4ee50d46cd6ad3cdbfb3efebcdc047
@@@ -2,5 -2,15 +2,15 @@@
      if (1) {
          var a;
          let b;
+         const c;
+     }
+ }());
+ (function () {
+     'use strict';
+     if (1) {
+         var a;
+         let b;
+         const c;
      }
  }());
index a35619d4a9e84d47e3745c1867d0e16b4364f1d9,3de21470d7f47c663754c6418c775feb58996a04..3de21470d7f47c663754c6418c775feb58996a04
@@@ -1,3 -1,3 +1,3 @@@
  (function () {
-     var a = require('a');
+     var a = global('a');
  }());
index 0000000000000000000000000000000000000000,24a4b717346aa8d1c37d261d5c5125fd7e87c00c..24a4b717346aa8d1c37d261d5c5125fd7e87c00c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,2 +1,2 @@@
+ var a = global('a');