From 252ba2a2cdfe948723ea9ccd8088b7b580618227 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Sun, 2 Jun 2024 21:52:48 -0700 Subject: [PATCH] Use parser notifier to set parser ranges 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 * 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 | 98 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/lisp/treesit.el b/lisp/treesit.el index 0475227c726..aa3beb86174 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -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) -- 2.39.2