]> code.delx.au - gnu-emacs-elpa/blobdiff - context-coloring.el
Don't color function calls as level 0.
[gnu-emacs-elpa] / context-coloring.el
index d7071c9d60767587d4569ab3e1caaa98b4c1787b..0f65b571fe32e5be1272ce9b791033dc997a54b7 100644 (file)
@@ -3,10 +3,10 @@
 ;; Copyright (C) 2014-2015  Free Software Foundation, Inc.
 
 ;; Author: Jackson Ray Hamilton <jackson@jacksonrayhamilton.com>
-;; Version: 6.2.0
+;; Version: 6.2.1
 ;; Keywords: convenience faces tools
-;; Homepage: https://github.com/jacksonrayhamilton/context-coloring
 ;; Package-Requires: ((emacs "24") (js2-mode "20150126"))
+;; URL: https://github.com/jacksonrayhamilton/context-coloring
 
 ;; This file is part of GNU Emacs.
 
@@ -244,47 +244,357 @@ variable."
                        ;; `js2-prop-get-node', so this always works.
                        (eq node (js2-prop-get-node-right parent))))))))
 
+(defvar-local context-coloring-point-max nil
+  "Cached value of `point-max'.")
+
 (defsubst context-coloring-js2-colorize-node (node level)
   "Color NODE with the color for LEVEL."
   (let ((start (js2-node-abs-pos node)))
     (context-coloring-colorize-region
      start
-     (+ start (js2-node-len node)) ; End
+     (min
+      ;; End
+      (+ start (js2-node-len node))
+      ;; Somes nodes (like the ast when there is an unterminated multiline
+      ;; comment) will stretch to the value of `point-max'.
+      context-coloring-point-max)
      level)))
 
 (defun context-coloring-js2-colorize ()
   "Color the current buffer using the abstract syntax tree
 generated by `js2-mode'."
-  ;; Don't bother trying to color a mangled tree.
-  (when (= 0 (length js2-parsed-errors))
-    ;; 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
-      (js2-visit-ast
-       js2-mode-ast
-       (lambda (node end-p)
-         (when (null end-p)
-           (cond
-            ((js2-scope-p node)
-             (context-coloring-js2-colorize-node
-              node
-              (context-coloring-js2-scope-level node)))
-            ((context-coloring-js2-local-name-node-p node)
-             (let* ((enclosing-scope (js2-node-get-enclosing-scope node))
-                    (defining-scope (js2-get-defining-scope
-                                     enclosing-scope
-                                     (js2-name-node-name node))))
-               ;; The tree seems to be walked lexically, so an entire scope will
-               ;; be colored, including its name nodes, before they are reached.
-               ;; Coloring the nodes defined in that scope would be redundant, so
-               ;; don't do it.
-               (when (not (eq defining-scope enclosing-scope))
-                 (context-coloring-js2-colorize-node
-                  node
-                  (context-coloring-js2-scope-level defining-scope))))))
-           ;; The `t' indicates to search children.
-           t)))
-      (context-coloring-maybe-colorize-comments-and-strings))))
+  ;; Reset the hash table; the old one could be obsolete.
+  (setq context-coloring-js2-scope-level-hash-table (make-hash-table :test 'eq))
+  (setq context-coloring-point-max (point-max))
+  (with-silent-modifications
+    (js2-visit-ast
+     js2-mode-ast
+     (lambda (node end-p)
+       (when (null end-p)
+         (cond
+          ((js2-scope-p node)
+           (context-coloring-js2-colorize-node
+            node
+            (context-coloring-js2-scope-level node)))
+          ((context-coloring-js2-local-name-node-p node)
+           (let* ((enclosing-scope (js2-node-get-enclosing-scope node))
+                  (defining-scope (js2-get-defining-scope
+                                   enclosing-scope
+                                   (js2-name-node-name node))))
+             ;; The tree seems to be walked lexically, so an entire scope will
+             ;; be colored, including its name nodes, before they are reached.
+             ;; Coloring the nodes defined in that scope would be redundant, so
+             ;; don't do it.
+             (when (not (eq defining-scope enclosing-scope))
+               (context-coloring-js2-colorize-node
+                node
+                (context-coloring-js2-scope-level defining-scope))))))
+         ;; The `t' indicates to search children.
+         t)))
+    (context-coloring-maybe-colorize-comments-and-strings)))
+
+
+;;; Emacs Lisp colorization
+
+(defun context-coloring-make-scope (depth level)
+  (list
+   :depth depth
+   :level level
+   :variables (make-hash-table)))
+
+(defun context-coloring-scope-get-depth (scope)
+  (plist-get scope :depth))
+
+(defun context-coloring-scope-get-level (scope)
+  (plist-get scope :level))
+
+(defun context-coloring-scope-add-variable (scope variable)
+  (puthash variable t (plist-get scope :variables)))
+
+(defun context-coloring-scope-get-variable (scope variable)
+  (gethash variable (plist-get scope :variables)))
+
+(defun context-coloring-get-variable-level (scope-stack variable)
+  (let* (scope
+         level)
+    (while (and scope-stack (not level))
+      (setq scope (car scope-stack))
+      (cond
+       ((context-coloring-scope-get-variable scope variable)
+        (setq level (context-coloring-scope-get-level scope)))
+       (t
+        (setq scope-stack (cdr scope-stack)))))
+    ;; Assume global
+    (or level 0)))
+
+(defun context-coloring-make-backtick (end enabled)
+  (list
+   :end end
+   :enabled enabled))
+
+(defun context-coloring-backtick-get-end (backtick)
+  (plist-get backtick :end))
+
+(defun context-coloring-backtick-get-enabled (backtick)
+  (plist-get backtick :enabled))
+
+(defun context-coloring-backtick-enabled-p (backtick-stack)
+  (context-coloring-backtick-get-enabled (car backtick-stack)))
+
+(defun context-coloring-make-let-value (end)
+  (list
+   :end end))
+
+(defun context-coloring-let-value-get-end (let-value)
+  (plist-get let-value :end))
+
+(defun context-coloring-emacs-lisp-identifier-syntax-p (syntax-code)
+  (or (= 2 syntax-code)
+      (= 3 syntax-code)))
+
+(defun context-coloring-forward-sws ()
+  "Move forward through whitespace and comments."
+  (while (forward-comment 1)))
+
+(defun context-coloring-at-open-parenthesis ()
+  (= 4 (logand #xFFFF (car (syntax-after (point))))))
+
+(defun context-coloring-emacs-lisp-colorize ()
+  "Color the current buffer by parsing emacs lisp sexps."
+  (with-silent-modifications
+    (save-excursion
+      ;; TODO: Can probably make this lazy to the nearest defun
+      (goto-char (point-min))
+      (let* ((inhibit-point-motion-hooks t)
+             (end (point-max))
+             (last-ppss-pos (point))
+             (ppss (syntax-ppss))
+             (scope-stack `(,(context-coloring-make-scope -1 0))) ; -1 never matches a depth
+             (backtick-stack `(,(context-coloring-make-backtick -1 nil)))
+             (let-value-stack `(,(context-coloring-make-let-value -1)))
+             one-word-found-p
+             in-defun-p
+             in-lambda-p
+             in-let*-p
+             defun-arglist
+             defun-arg
+             let-varlist
+             let-var
+             variable
+             variable-end
+             variable-string
+             variable-scope-level
+             token-pos
+             token-syntax
+             token-syntax-code
+             token-char
+             child-0-pos
+             child-0-end
+             child-0-syntax
+             child-0-syntax-code
+             child-0-string
+             child-1-pos
+             child-1-end
+             child-1-syntax
+             child-1-syntax-code
+             child-2-end)
+        (while (> end (progn (skip-syntax-forward "^()w_'" end)
+                             (point)))
+          (setq token-pos (point))
+          (setq token-syntax (syntax-after token-pos))
+          (setq ppss (parse-partial-sexp last-ppss-pos token-pos nil nil ppss))
+          (setq last-ppss-pos token-pos)
+          ;; `skip-syntax-forward' leaves the point at the delimiter, move past
+          ;; it.
+          (setq token-syntax-code (logand #xFFFF (car token-syntax)))
+          (setq token-char (string-to-char (buffer-substring-no-properties
+                                            token-pos
+                                            (1+ token-pos))))
+          (cond
+
+           ;; Resolve invalid state
+           ((cond
+             ;; Inside string?
+             ((nth 3 ppss)
+              (skip-syntax-forward "^\"" end)
+              (forward-char)
+              t)
+             ;; Inside comment?
+             ((nth 4 ppss)
+              (skip-syntax-forward "^>" end) ; comment ender
+              t)))
+
+           ;; Expression prefix
+           ;; Has to come first in case of commas
+           ((= 6 token-syntax-code)
+            (forward-char)
+            (cond
+             ;; Just outright skip top-level symbols
+             ((not (or (cadr backtick-stack)
+                       (= token-char 96))) ; 96 = '`'
+              (goto-char (scan-sexps (point) 1)))
+             ((or (= token-char 96)  ; 96 = '`'
+                  (= token-char 44)) ; 44 = ','
+              ;; Have to manage backticks
+              (setq backtick-stack (cons (context-coloring-make-backtick
+                                          (scan-sexps (point) 1) ; End of the backtick
+                                          (= token-char 96)) ; 96 = '`'
+                                         backtick-stack)))))
+
+           ;; End backtick
+           ((and (cadr backtick-stack)
+                 (>= (point) (context-coloring-backtick-get-end (car backtick-stack))))
+            (setq backtick-stack (cdr backtick-stack)))
+
+           ;; Restricted by backtick
+           ((and (cadr backtick-stack)
+                 (context-coloring-backtick-enabled-p backtick-stack))
+            (forward-char))
+
+           ;; Opening delimiter
+           ((= 4 token-syntax-code)
+            (forward-char)
+            ;; Lookahead for scopes / function calls
+            (context-coloring-forward-sws)
+            (setq child-0-pos (point))
+            (setq child-0-syntax (syntax-after child-0-pos))
+            (setq child-0-syntax-code (logand #xFFFF (car child-0-syntax)))
+            (cond
+             ;; Word
+             ((context-coloring-emacs-lisp-identifier-syntax-p child-0-syntax-code)
+              (setq one-word-found-p t)
+              (setq child-0-end (scan-sexps child-0-pos 1))
+              (setq child-0-string (buffer-substring-no-properties child-0-pos child-0-end))
+              (cond
+               ((string-match-p "\\`defun\\'\\|\\`defmacro\\'" child-0-string)
+                (setq in-defun-p t))
+               ((string-match-p "\\`lambda\\'" child-0-string)
+                (setq in-lambda-p t))
+               ((string-match-p "\\`let\\*\\'" child-0-string)
+                (setq in-let*-p t)))))
+            (when (or in-defun-p
+                      in-lambda-p
+                      in-let*-p)
+              (setq scope-stack (cons (context-coloring-make-scope
+                                       (nth 0 ppss)
+                                       (1+ (context-coloring-scope-get-level
+                                            (car scope-stack))))
+                                      scope-stack)))
+            ;; TODO: Probably redundant and wasteful
+            (context-coloring-colorize-region token-pos
+                                              (scan-sexps token-pos 1)
+                                              (context-coloring-scope-get-level
+                                               (car scope-stack)))
+            (cond
+             ((or in-defun-p
+                  in-lambda-p)
+              (goto-char child-0-end)
+              (when in-defun-p
+                ;; Lookahead for defun name
+                (context-coloring-forward-sws)
+                (setq child-1-pos (point))
+                (setq child-1-syntax (syntax-after child-1-pos))
+                (setq child-1-syntax-code (logand #xFFFF (car child-1-syntax)))
+                (cond
+                 ;; Word
+                 ((context-coloring-emacs-lisp-identifier-syntax-p child-1-syntax-code)
+                  (setq child-1-end (scan-sexps child-1-pos 1))
+                  ;; defuns are global so use level 0
+                  (context-coloring-colorize-region child-1-pos child-1-end 0)
+                  (goto-char child-1-end))))
+              ;; Lookahead for parameters
+              (context-coloring-forward-sws)
+              (when (context-coloring-at-open-parenthesis)
+                ;; Actually it should be `child-1-end' for `lambda'.
+                (setq child-2-end (scan-sexps (point) 1))
+                (setq defun-arglist (read (buffer-substring-no-properties
+                                           (point)
+                                           child-2-end)))
+                (while defun-arglist
+                  (setq defun-arg (car defun-arglist))
+                  (when (and (symbolp defun-arg)
+                             (string-match-p "\\`[^&:]" (symbol-name defun-arg)))
+                    (context-coloring-scope-add-variable
+                     (car scope-stack)
+                     defun-arg))
+                  (setq defun-arglist (cdr defun-arglist)))
+                (goto-char child-2-end))
+              ;; Cleanup
+              (setq in-defun-p nil)
+              (setq in-lambda-p nil))
+             (in-let*-p
+              (goto-char child-0-end)
+              ;; Lookahead for bindings
+              (context-coloring-forward-sws)
+              (setq child-1-pos (point))
+              (setq child-1-syntax (syntax-after child-1-pos))
+              (setq child-1-syntax-code (logand #xFFFF (car child-1-syntax)))
+              (when (= 4 child-1-syntax-code)
+                (setq child-1-end (scan-sexps (point) 1))
+                (setq let-varlist (read (buffer-substring-no-properties
+                                         (point)
+                                         child-1-end)))
+                (while let-varlist
+                  (setq let-var (car let-varlist))
+                  (cond
+                   ((symbolp let-var)
+                    (context-coloring-scope-add-variable
+                     (car scope-stack)
+                     let-var))
+                   ((listp let-var)
+                    (context-coloring-scope-add-variable
+                     (car scope-stack)
+                     (car let-var))
+                    ;; TODO: Recurse or use stack to eval var value
+                    ))
+                  (setq let-varlist (cdr let-varlist)))
+                (goto-char child-1-end))
+              ;; Cleanup
+              (setq in-let*-p nil))
+             (t
+              (goto-char (cond
+                          ;; If there was a word, continue parsing after it.
+                          (one-word-found-p
+                           (1+ child-0-end))
+                          (t
+                           (1+ token-pos))))))
+            ;; Cleanup
+            (setq one-word-found-p nil))
+
+           ;; Word (variable)
+           ((context-coloring-emacs-lisp-identifier-syntax-p token-syntax-code)
+            (setq variable-end (scan-sexps (point) 1))
+            (setq variable-string (buffer-substring-no-properties
+                                   token-pos
+                                   variable-end))
+            (cond
+             ;; Ignore constants such as numbers, keywords, t, nil. These can't
+             ;; be rebound, so they should be treated like syntax.
+             ((string-match-p "\\`[-+]?[0-9]\\|\\`t\\'\\|\\`nil\\'" variable-string))
+             ((keywordp (read variable-string)))
+             (t
+              (setq variable (intern variable-string))
+              (setq variable-scope-level
+                    (context-coloring-get-variable-level scope-stack variable))
+              (when (/= variable-scope-level (context-coloring-scope-get-level
+                                              (car scope-stack)))
+                (context-coloring-colorize-region
+                 token-pos
+                 variable-end
+                 variable-scope-level))))
+            (goto-char variable-end))
+
+           ;; Closing delimiter
+           ((= 5 token-syntax-code)
+            (forward-char)
+            ;; End scope
+            (setq ppss (parse-partial-sexp last-ppss-pos (point) nil nil ppss))
+            (setq last-ppss-pos (point))
+            (when (= (nth 0 ppss) (context-coloring-scope-get-depth (car scope-stack)))
+              (setq scope-stack (cdr scope-stack))))
+
+           ))))
+    (context-coloring-maybe-colorize-comments-and-strings)))
 
 
 ;;; Shell command scopification / colorization
@@ -461,6 +771,11 @@ should be numeric, e.g. \"2\", \"19700101\", \"1.2.3\",
  (lambda ()
    (remove-hook 'js2-post-parse-callbacks 'context-coloring-colorize t)))
 
+(context-coloring-define-dispatch
+ 'emacs-lisp
+ :modes '(emacs-lisp-mode)
+ :colorizer 'context-coloring-emacs-lisp-colorize)
+
 (defun context-coloring-dispatch (&optional callback)
   "Determine the optimal track for scopification / coloring of
 the current buffer, then execute it.
@@ -786,7 +1101,7 @@ precedence, i.e. the car of `custom-enabled-themes'."
            "#5180b3"
            "#ab75c3"
            "#cd7542"
-           "#dF9522"
+           "#df9522"
            "#454545"))
 
 (context-coloring-define-theme
@@ -820,27 +1135,27 @@ precedence, i.e. the car of `custom-enabled-themes'."
  'leuven
  :recede t
  :colors '("#333333"
-           "#0000FF"
-           "#6434A3"
-           "#BA36A5"
-           "#D0372D"
-           "#036A07"
+           "#0000ff"
+           "#6434a3"
+           "#ba36a5"
+           "#d0372d"
+           "#036a07"
            "#006699"
-           "#006FE0"
+           "#006fe0"
            "#808080"))
 
 (context-coloring-define-theme
  'monokai
  :recede t
- :colors '("#F8F8F2"
-           "#66D9EF"
-           "#A1EFE4"
-           "#A6E22E"
-           "#E6DB74"
-           "#FD971F"
-           "#F92672"
-           "#FD5FF0"
-           "#AE81FF"))
+ :colors '("#f8f8f2"
+           "#66d9ef"
+           "#a1efe4"
+           "#a6e22e"
+           "#e6db74"
+           "#fd971f"
+           "#f92672"
+           "#fd5ff0"
+           "#ae81ff"))
 
 (context-coloring-define-theme
  'solarized
@@ -858,26 +1173,26 @@ precedence, i.e. the car of `custom-enabled-themes'."
            "#dc322f"
            "#d33682"
            "#6c71c4"
-           "#69B7F0"
-           "#69CABF"
-           "#B4C342"
-           "#DEB542"
-           "#F2804F"
-           "#FF6E64"
-           "#F771AC"
-           "#9EA0E5"))
+           "#69b7f0"
+           "#69cabf"
+           "#b4c342"
+           "#deb542"
+           "#f2804f"
+           "#ff6e64"
+           "#f771ac"
+           "#9ea0e5"))
 
 (context-coloring-define-theme
  'spacegray
  :recede t
  :colors '("#ffffff"
-           "#89AAEB"
-           "#C189EB"
+           "#89aaeb"
+           "#c189eb"
            "#bf616a"
-           "#DCA432"
+           "#dca432"
            "#ebcb8b"
-           "#B4EB89"
-           "#89EBCA"))
+           "#b4eb89"
+           "#89ebca"))
 
 (context-coloring-define-theme
  'tango
@@ -899,17 +1214,17 @@ precedence, i.e. the car of `custom-enabled-themes'."
 (context-coloring-define-theme
  'zenburn
  :recede t
- :colors '("#DCDCCC"
-           "#93E0E3"
-           "#BFEBBF"
-           "#F0DFAF"
-           "#DFAF8F"
-           "#CC9393"
-           "#DC8CC3"
-           "#94BFF3"
-           "#9FC59F"
-           "#D0BF8F"
-           "#DCA3A3"))
+ :colors '("#dcdccc"
+           "#93e0e3"
+           "#bfebbf"
+           "#f0dfaf"
+           "#dfaf8f"
+           "#cc9393"
+           "#dc8cc3"
+           "#94bff3"
+           "#9fc59f"
+           "#d0bf8f"
+           "#dca3a3"))
 
 
 ;;; Minor mode