]> git.eshelyaron.com Git - emacs.git/commitdiff
Use parser notifier to set parser ranges
authorYuan Fu <casouri@gmail.com>
Mon, 3 Jun 2024 04:52:48 +0000 (21:52 -0700)
committerEshel Yaron <me@eshelyaron.com>
Mon, 3 Jun 2024 09:52:09 +0000 (11:52 +0200)
This is a continuation from an earlier commit where I added
treesit-parser-changed-ranges and friends.  Dmitry raised an concern
about the edge case where the parser re-parses multiple times before
treesit--pre-redisplay has a chance to run and process changed ranges.

Instead of making treesit-parser-changed-ranges DTRT and become more
complicated, it's agreed that using parser notifier is a better
solution (and treesit-parser-changed-ranges can probably be removed).

So, I took out the code that does the work from treesit--pre-redisplay
and put them into treesit--font-lock-mark-ranges-to-fontify.  This
function will be called on each parser re-parse.  And in
treesit--pre-redisplay, to ensure that treesit--f-l-m-r-to-f gets
called, we force the primary parser to re-parse.

I also added a new variable that major modes need to set,
treesit-primary-parser.  I also added code that makes Emacs guess the
primary parser if that variable isn't set.

Documentation fot treesit-primary-parser will come later.

For futher reference, the message id for the message that prompted
this change is <dc94733b-df75-446c-980e-1c8ea65826cf@gutov.dev>

* lisp/treesit.el (treesit-primary-parser): New variable.
(treesit--font-lock-mark-ranges-to-fontify): New function.
(treesit--guess-primary-parser): New function.
(treesit--pre-redisplay): Extract out.
(treesit--pre-syntax-ppss): Add comments.
(treesit-major-mode-setup): Guess and set treesit-primary-parser; add
treesit--font-lock-mark-ranges-to-fontify as a notifier to the primary
parser.

(cherry picked from commit 760b54de080c238ea9f7b16055e820862d3e8896)

lisp/treesit.el

index 0475227c726822ed1a15fb9e6305ace4bf69234a..aa3beb8617438dbab2add9ce939dc3279f197548 100644 (file)
@@ -793,6 +793,18 @@ omitted, default END to BEG."
               "Generic tree-sitter font-lock error"
               'treesit-error)
 
+;; The primary parser will be access frequently (after each re-parse,
+;; before redisplay, etc, see
+;; `treesit--font-lock-mark-ranges-to-fontify'), so we don't want to
+;; allow it to be a callback function which returns the primary parser
+;; (it might be slow).  It's not something that needs to be dynamic
+;; anyway.
+(defvar-local treesit-primary-parser nil
+  "The primary parser for this buffer.
+
+The primary parser should be a parser that parses the entire buffer, as
+opposed to embedded parsers which parses only part of the buffer.")
+
 (defvar-local treesit-font-lock-settings nil
   "A list of SETTINGs for treesit-based fontification.
 
@@ -1391,13 +1403,15 @@ Because `pre-redisplay-functions' could be called multiple times
 during a single command loop, we use this variable to debounce
 calls to `treesit--pre-redisplay'.")
 
-(defun treesit--pre-redisplay (&rest _)
-  "Force a reparse on the primary parser and do some work.
+(defun treesit--font-lock-mark-ranges-to-fontify (ranges _parser)
+  "A notifier that marks ranges that needs refontification.
+
+For RANGES and PARSER see `treesit-parser-add-notifier'.
 
 After the parser reparses, we get the changed ranges, and
 1) update non-primary parsers' ranges in the changed ranges
 2) mark these ranges as to-be-fontified,
-3) tell syntax-ppss to start reparsing from the min point of the ranges
+3) tell syntax-ppss to start reparsing from the min point of the ranges.
 
 We need to mark to-be-fontified ranges before redisplay starts working,
 because sometimes the range edited by the user is not the only range
@@ -1405,33 +1419,48 @@ that needs to be refontified.  For example, when the user types the
 final slash of a C block comment /* xxx */, not only do we need to
 fontify the slash, but also the whole block comment, which previously
 wasn't fontified as comment due to incomplete parse tree."
+  (dolist (range ranges)
+    ;; 1. Update ranges.
+    (treesit-update-ranges (car range) (cdr range))
+    ;; 2. Mark the changed ranges to be fontified.
+    (when treesit--font-lock-verbose
+      (message "Notifier received range: %s-%s"
+               (car range) (cdr range)))
+    (with-silent-modifications
+      (put-text-property (car range) (cdr range) 'fontified nil))
+    ;; 3. Set `treesit--syntax-propertize-start'.
+    (if (null treesit--syntax-propertize-start)
+        (setq treesit--syntax-propertize-start (car range))
+      (setq treesit--syntax-propertize-start
+            (min treesit--syntax-propertize-start (car range))))))
+
+(defun treesit--guess-primary-parser ()
+  "Guess the primary parser of the current buffer and return it.
+
+Normally in a tree-sitter major mode, there is a primary parser that
+parses the entire buffer (as opposed to embedded parsers which only
+parses part of the buffer).  This function tries to find and return that
+parser."
+  (if treesit-range-settings
+      (let ((query (car (car treesit-range-settings))))
+        (if (treesit-query-p query)
+            (treesit-parser-create
+             (treesit-query-language query))
+          (car (treesit-parser-list))))
+    (car (treesit-parser-list))))
+
+(defun treesit--pre-redisplay (&rest _)
+  "Force a reparse on the primary parser and mark regions to be fontified.
+
+The actual work is carried out by
+`treesit--font-lock-mark-ranges-to-fontify', which see."
   (unless (eq treesit--pre-redisplay-tick (buffer-chars-modified-tick))
-    (let ((primary-parser
-           ;; TODO: We need something less ugly than this for getting
-           ;; the primary parser/language.
-           (if treesit-range-settings
-               (let ((query (car (car treesit-range-settings))))
-                 (if (treesit-query-p query)
-                     (treesit-parser-create
-                      (treesit-query-language query))
-                   (car (treesit-parser-list))))
-             (car (treesit-parser-list)))))
-      ;; Force a reparse on the primary parser.
-      (treesit-parser-root-node primary-parser)
-      (dolist (range (treesit-parser-changed-ranges primary-parser))
-        ;; 1. Update ranges.
-        (treesit-update-ranges (car range) (cdr range))
-        ;; 2. Mark the changed ranges to be fontified.
-        (when treesit--font-lock-verbose
-          (message "Notifier received range: %s-%s"
-                   (car range) (cdr range)))
-        (with-silent-modifications
-          (put-text-property (car range) (cdr range) 'fontified nil))
-        ;; 3. Set `treesit--syntax-propertize-start'.
-        (if (null treesit--syntax-propertize-start)
-            (setq treesit--syntax-propertize-start (car range))
-          (setq treesit--syntax-propertize-start
-                (min treesit--syntax-propertize-start (car range))))))
+    (when treesit-primary-parser
+      ;; Force a reparse on the primary parser, if everything is setup
+      ;; correctly, the parser should call
+      ;; `treesit--font-lock-mark-ranges-to-fontify' (which should be a
+      ;; notifier function of the primary parser).
+      (treesit-parser-root-node treesit-primary-parser))
 
     (setq treesit--pre-redisplay-tick (buffer-chars-modified-tick))))
 
@@ -1445,6 +1474,10 @@ whole region affected by the last reparse.
 
 START and END mark the current to-be-propertized region."
   (treesit--pre-redisplay)
+  ;; `treesit--syntax-propertize-start' is set by
+  ;; `treesit--font-lock-mark-ranges-to-fontify', which is called after
+  ;; each re-parser on the primary parser and in
+  ;; `treesit--pre-redisplay'.
   (let ((new-start treesit--syntax-propertize-start))
     (if (and new-start (< new-start start))
         (progn
@@ -2991,6 +3024,8 @@ enable tree-sitter navigation commands for them.
 
 Make sure necessary parsers are created for the current buffer
 before calling this function."
+  (unless treesit-primary-parser
+    (setq treesit-primary-parser (treesit--guess-primary-parser)))
   ;; Font-lock.
   (when treesit-font-lock-settings
     ;; `font-lock-mode' wouldn't set up properly if
@@ -3000,7 +3035,10 @@ before calling this function."
                    (font-lock-fontify-syntactically-function
                     . treesit-font-lock-fontify-region)))
     (treesit-font-lock-recompute-features)
-    (add-hook 'pre-redisplay-functions #'treesit--pre-redisplay 0 t))
+    (add-hook 'pre-redisplay-functions #'treesit--pre-redisplay 0 t)
+    (when treesit-primary-parser
+      (treesit-parser-add-notifier
+       treesit-primary-parser #'treesit--font-lock-mark-ranges-to-fontify)))
   ;; Syntax
   (add-hook 'syntax-propertize-extend-region-functions
             #'treesit--pre-syntax-ppss 0 t)