]> git.eshelyaron.com Git - emacs.git/commitdiff
Rework tree-sitter font-lock
authorYuan Fu <casouri@gmail.com>
Thu, 13 Oct 2022 21:44:42 +0000 (14:44 -0700)
committerYuan Fu <casouri@gmail.com>
Thu, 13 Oct 2022 21:44:42 +0000 (14:44 -0700)
Remove :toggle and :level, add :feature.

* lisp/progmodes/js.el (js--treesit-settings): Add :feature.
(js--treesit-enable): Set treesit-font-lock-feature-list.
* lisp/progmodes/python.el: Replace :level with :feature.
(python-mode): Set treesit-font-lock-feature-list.
* lisp/treesit.el (treesit-font-lock-feature-list): New variable.
(treesit-font-lock-settings): Change format.
(treesit-font-lock-rules): Remove :toggle and :level, add :feature.
(treesit-font-lock-recompute-features): New function.
(treesit-font-lock-fontify-region): Change to work with the new
format.
(treesit-font-lock-enable): Add call to
treesit-font-lock-recompute-features.  And improve the font-lock-mode
workaround.

lisp/progmodes/js.el
lisp/progmodes/python.el
lisp/treesit.el

index 1b82e204b91ff45c18fbb7b5ef4922e71b980f64..18499a466af68139da82e88de1ec31f9abe2d84f 100644 (file)
@@ -3459,6 +3459,7 @@ indentation, which-function and movement functions."
 (defvar js--treesit-settings
   (treesit-font-lock-rules
    :language 'javascript
+   :feature 'basic
    :override t
    `(;; Everything overrides template string.
      (template_string) @font-lock-string-face
@@ -3637,8 +3638,9 @@ ARG is the same as in `end-of-defun."
   (setq-local beginning-of-defun-function #'js--treesit-beginning-of-defun)
   (setq-local end-of-defun-function #'js--treesit-end-of-defun)
 
-  (setq-local font-lock-defaults '(nil t))
+  (setq-local font-lock-keywords-only t)
   (setq-local treesit-font-lock-settings js--treesit-settings)
+  (setq-local treesit-font-lock-feature-list '((basic)))
 
   (add-hook 'which-func-functions #'js-treesit-current-defun nil t)
 
index a0466707e608853928374be3fd0a73a093f17e70..c48f9e95e98232541f3a0eef46441ba9d8442203 100644 (file)
@@ -1032,7 +1032,7 @@ Do not fontify the initial f for f-strings."
 (defvar python--treesit-settings
   (treesit-font-lock-rules
    :language 'python
-   :level 1
+   :feature 'basic
    '(;; Queries for def and class.
      (function_definition
       name: (identifier) @font-lock-function-name-face)
@@ -1048,7 +1048,7 @@ Do not fontify the initial f for f-strings."
       (:match "^\"\"\"" @font-lock-doc-face))
      (interpolation (identifier) @font-lock-variable-name-face))
    :language 'python
-   :level 2
+   :feature 'moderate
    :override t
    `(;; Keywords, builtins, and constants.
      [,@python--treesit-keywords] @font-lock-keyword-face
@@ -1066,7 +1066,7 @@ Do not fontify the initial f for f-strings."
 
      [(true) (false) (none)] @font-lock-constant-face)
    :language 'python
-   :level 3
+   :feature 'elaborate
    :override t
    `(;; Variable names.
      (assignment left: (identifier)
@@ -6401,7 +6401,9 @@ Add import for undefined name `%s' (empty to skip): "
   (if (and python-use-tree-sitter
            (treesit-can-enable-p))
       (progn
-        (setq-local font-lock-defaults '(nil t))
+        (setq-local font-lock-keywords-only t)
+        (setq-local treesit-font-lock-feature-list
+                    '((basic) (moderate) (elaborate)))
         (setq-local treesit-font-lock-settings
                     python--treesit-settings)
         (treesit-font-lock-enable))
index 1e911df1d96f459ebd11e0bd1b17b129f693e180..c8ba3e84e6212599104139905a2559e5d262bb83 100644 (file)
@@ -284,6 +284,28 @@ in-order.  START and END are passed to each range function."
               "Generic tree-sitter font-lock error"
               'treesit-error)
 
+(defvar-local treesit-font-lock-feature-list nil
+  "A list of lists of feature symbols.
+
+Each sublist represents a decoration level.
+`font-lock-maximum-decoration' controls which levels are
+activated.
+
+Inside each sublist are feature symbols, which corresponds to the
+:feature value of a query defined in `treesit-font-lock-rules'.
+Removing a feature symbol from this list disables the
+corresponding query during font-lock.
+
+Common feature names (for general programming language) include
+function-name, type, variable-name (LHS of assignments), builtin,
+constant, keyword, string-interpolation, comment, doc, string,
+operator, preprocessor, escape-sequence, key (in key-value
+pairs).  Major modes are free to subdivide or extend on these
+common features.
+
+For changes to this variable to take effect, run
+`treesit-font-lock-recompute-features'.")
+
 (defvar-local treesit-font-lock-settings nil
   "A list of SETTINGs for treesit-based fontification.
 
@@ -292,30 +314,21 @@ should always use `treesit-font-lock-rules' to set this variable.
 
 Each SETTING is of form
 
-    (LANGUAGE QUERY OVERRIDE TOGGLE LEVEL)
+    (QUERY ENABLE FEATURE OVERRIDE)
 
-Each SETTING controls one parser (often of different language).
-LANGUAGE is the language symbol.  See Info node `(elisp)Language
-Definitions'.
+QUERY must be a compiled query.  See Info node `(elisp)Pattern
+Matching' for how to write a query and compile it.
 
-QUERY is either a string query, a sexp query, or a compiled
-query.  See Info node `(elisp)Pattern Matching' for how to write
-a query in either string or s-expression form.  When using
-repeatedly, a compiled query is much faster than a string or sexp
-one, so it is recommend to compile your queries if it will be
-used over and over.
+For SETTING to be activated for font-lock, ENABLE must be t.  To
+disable this SETTING, set ENABLE to nil.
+
+FEATURE is the \"feature name\" of the query, users can control
+which features are enabled with `font-lock-maximum-decoration'
+and `treesit-font-lock-feature-list'.
 
 OVERRIDE is the override flag for this query.  Its value can be
 t, nil, append, prepend, keep.  See more in
-`treesit-font-lock-rules'.
-
-TOGGLE should be a variable (symbol) or nil.  The variable's
-value (nil/non-nil) controls whether to activate the query during
-fontification.  If TOGGLE is nil, the query is always activated.
-
-LEVEL is the decoration level of this query or nil.  Decoration
-level is controlled by `font-lock-maximum-decoration'.  If LEVEL
-is nil, the query is always activated.")
+`treesit-font-lock-rules'.")
 
 (defun treesit-font-lock-rules (&rest args)
   "Return a value suitable for `treesit-font-lock-settings'.
@@ -331,14 +344,18 @@ configure the query (and only that query).  For example,
     (treesit-font-lock-rules
      :language \\='javascript
      :override t
-     :enable 'html-fontify-script
+     :feature\\='constant
      \\='((true) @font-lock-constant-face
        (false) @font-lock-constant-face)
      :language \\='html
+     :feature \\='script
      \"(script_element) @font-lock-builtin-face\")
 
-For each QUERY, a :language keyword is required.  Other keywords
-include:
+For each QUERY, a :language keyword and a :feature keyword is
+required. Each query's :feature is a symbol summarizing what does
+the query fontify.  It is used to allow users to enable/disable
+certain features.  See `treesit-font-lock-kind-list' for more.
+Other keywords include:
 
   KEYWORD    VALUE    DESCRIPTION
   :override  nil      If the region already has a face,
@@ -347,19 +364,6 @@ include:
              append   Append the new face to existing ones
              prepend  Prepend the new face to existing ones
              keep     Fill-in regions without an existing face
-  :toggle    <symbol> If non-nil, the value should be a variable.
-                      The value of that variable (non-nil/nil)
-                      activates/deactivates the query during
-                      fontification.
-             nil      Always activate this query.
-  :level     <integer>If non-nil, the value is the decoration
-                      level of this query.
-                      (See `font-lock-maximum-decoration'.)
-             nil      Always activate this query.
-
-Note that a query is applied only when both :toggle and :level
-permit it.  :level is used for global, coarse-grained control,
-whereas :toggle is for local, fine-grained control.
 
 Capture names in QUERY should be face names like
 `font-lock-keyword-face'.  The captured node will be fontified
@@ -375,7 +379,7 @@ ignored.
   (let (;; Tracks the current :language/:override/:toggle/:level value
         ;; that following queries will apply to.
         current-language current-override
-        current-toggle current-level
+        current-feature
         ;; The list this function returns.
         (result nil))
     (while args
@@ -399,21 +403,14 @@ ignored.
                        `((or t nil append prepend keep)
                          ,flag)))
              (setq current-override flag)))
-          (:toggle
+          (:feature
            (let ((var (pop args)))
              (when (or (not (symbolp var))
                        (memq var '(t nil)))
                (signal 'treesit-font-lock-error
-                       `("Value of :toggle should be a variable name"
+                       `("Value of :feature should be a symbol"
                          ,var)))
-             (setq current-toggle var)))
-          (:level
-           (let ((level (pop args)))
-             (when (not (and (integerp level) (> level 0)))
-               (signal 'treesit-font-lock-error
-                       `("Value of :level should be a positive integer"
-                         ,level)))
-             (setq current-level level)))
+             (setq current-feature var)))
           ;; (2) Process query.
           ((pred treesit-query-p)
            (when (null current-language)
@@ -421,21 +418,40 @@ ignored.
                      `("Language unspecified, use :language keyword to specify a language for this query" ,token)))
            (if (treesit-compiled-query-p token)
                (push `(,current-language token) result)
-             (push `(,current-language
-                     ,(treesit-query-compile current-language token)
-                     ,current-override
-                     ,current-toggle
-                     ,current-level)
+             (push `(,(treesit-query-compile current-language token)
+                     t
+                     ,current-feature
+                     ,current-override)
                    result))
            ;; Clears any configurations set for this query.
            (setq current-language nil
                  current-override nil
-                 current-toggle nil
-                 current-level nil))
+                 current-feature nil))
           (_ (signal 'treesit-font-lock-error
-                     `("Unexpected value" token))))))
+                     `("Unexpected value" ,token))))))
     (nreverse result)))
 
+(defun treesit-font-lock-recompute-features ()
+  "Enable/disable font-lock settings according to decoration level.
+Sets the ENABLE flag for each setting in
+`treesit-font-lock-settings', according to
+`treesit-font-lock-feature-list' and
+`font-lock-maximum-decoration'."
+  (let* ((level (font-lock-value-in-major-mode
+                 font-lock-maximum-decoration))
+         (features (cl-loop
+                    for idx = 0 then (1+ idx)
+                    for features in treesit-font-lock-feature-list
+                    if (or (eq level t)
+                           (>= level (1+ idx)))
+                    append features)))
+    (cl-loop for idx = 0 then (1+ idx)
+             for setting in treesit-font-lock-settings
+             for feature = (nth 2 setting)
+             ;; Set the ENABLE flag for the setting.
+             do (setf (nth 1 (nth idx treesit-font-lock-settings))
+                      (if (memq feature features) t nil)))))
+
 (defun treesit-font-lock-fontify-region
     (start end &optional loudly)
   "Fontify the region between START and END.
@@ -443,27 +459,16 @@ If LOUDLY is non-nil, message some debugging information."
   (treesit-update-ranges start end)
   (font-lock-unfontify-region start end)
   (dolist (setting treesit-font-lock-settings)
-    (let* ((language (nth 0 setting))
-           (match-pattern (nth 1 setting))
-           (override (nth 2 setting))
-           (toggle (nth 3 setting))
-           (level (nth 4 setting))
-           (parser (treesit-parser-create language)))
-      (when-let ((node (treesit-node-on start end parser))
-                 ;; Only activate this query if both :toggle and
-                 ;; :level permit it.
-                 (activate
-                  (and (or (null toggle)
-                           (symbol-value toggle))
-                       (or (null level)
-                           (pcase (font-lock-value-in-major-mode
-                                   font-lock-maximum-decoration)
-                             ('t t)
-                             ('nil (eq level 1))
-                             (lvl (<= level lvl)))))))
+    (let* ((query (nth 0 setting))
+           (enable (nth 1 setting))
+           (override (nth 3 setting))
+           (language (treesit-query-language query)))
+      (when-let ((node (treesit-node-on start end language))
+                 ;; Only activate if ENABLE flag is t.
+                 (activate (eq t enable)))
         (ignore activate)
         (let ((captures (treesit-query-capture
-                         node match-pattern
+                         node query
                          ;; Specifying the range is important. More
                          ;; often than not, NODE will be the root
                          ;; node, and if we don't specify the range,
@@ -508,17 +513,15 @@ If LOUDLY is non-nil, message some debugging information."
 
 (defun treesit-font-lock-enable ()
   "Enable tree-sitter font-locking for the current buffer."
+  (treesit-font-lock-recompute-features)
   (setq-local font-lock-fontify-region-function
               #'treesit-font-lock-fontify-region)
   ;; If we don't set `font-lock-defaults' to some non-nil value,
-  ;; font-lock doesn't enable properly (the font-lock-mode-internal
-  ;; doesn't run).  See `font-lock-add-keywords'.
-  (when (and font-lock-mode
-             (null font-lock-keywords)
-             (null font-lock-defaults))
-    (font-lock-mode -1)
-    (setq-local font-lock-defaults '(nil t))
-    (font-lock-mode 1)))
+  ;; font-lock doesn't enable properly (`font-lock-mode-internal'
+  ;; doesn't run).  See `font-lock-specified-p'.
+  (when (null font-lock-defaults)
+    (setq font-lock-defaults '(nil)))
+  (font-lock-mode 1))
 
 ;;; Indent