1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-07 06:50:23 -08:00

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 <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.
This commit is contained in:
Yuan Fu 2024-06-02 21:52:48 -07:00
parent b44d511102
commit 760b54de08
No known key found for this signature in database
GPG key ID: 56E19BC57664A442

View file

@ -793,6 +793,18 @@ omitted, default END to BEG."
"Generic tree-sitter font-lock error" "Generic tree-sitter font-lock error"
'treesit-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 (defvar-local treesit-font-lock-settings nil
"A list of SETTINGs for treesit-based fontification. "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 during a single command loop, we use this variable to debounce
calls to `treesit--pre-redisplay'.") calls to `treesit--pre-redisplay'.")
(defun treesit--pre-redisplay (&rest _) (defun treesit--font-lock-mark-ranges-to-fontify (ranges _parser)
"Force a reparse on the primary parser and do some work. "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 After the parser reparses, we get the changed ranges, and
1) update non-primary parsers' ranges in the changed ranges 1) update non-primary parsers' ranges in the changed ranges
2) mark these ranges as to-be-fontified, 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, 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 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 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 fontify the slash, but also the whole block comment, which previously
wasn't fontified as comment due to incomplete parse tree." 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)) (unless (eq treesit--pre-redisplay-tick (buffer-chars-modified-tick))
(let ((primary-parser (when treesit-primary-parser
;; TODO: We need something less ugly than this for getting ;; Force a reparse on the primary parser, if everything is setup
;; the primary parser/language. ;; correctly, the parser should call
(if treesit-range-settings ;; `treesit--font-lock-mark-ranges-to-fontify' (which should be a
(let ((query (car (car treesit-range-settings)))) ;; notifier function of the primary parser).
(if (treesit-query-p query) (treesit-parser-root-node treesit-primary-parser))
(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))))))
(setq treesit--pre-redisplay-tick (buffer-chars-modified-tick)))) (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." START and END mark the current to-be-propertized region."
(treesit--pre-redisplay) (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)) (let ((new-start treesit--syntax-propertize-start))
(if (and new-start (< new-start start)) (if (and new-start (< new-start start))
(progn (progn
@ -2991,6 +3024,8 @@ enable tree-sitter navigation commands for them.
Make sure necessary parsers are created for the current buffer Make sure necessary parsers are created for the current buffer
before calling this function." before calling this function."
(unless treesit-primary-parser
(setq treesit-primary-parser (treesit--guess-primary-parser)))
;; Font-lock. ;; Font-lock.
(when treesit-font-lock-settings (when treesit-font-lock-settings
;; `font-lock-mode' wouldn't set up properly if ;; `font-lock-mode' wouldn't set up properly if
@ -3000,7 +3035,10 @@ before calling this function."
(font-lock-fontify-syntactically-function (font-lock-fontify-syntactically-function
. treesit-font-lock-fontify-region))) . treesit-font-lock-fontify-region)))
(treesit-font-lock-recompute-features) (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 ;; Syntax
(add-hook 'syntax-propertize-extend-region-functions (add-hook 'syntax-propertize-extend-region-functions
#'treesit--pre-syntax-ppss 0 t) #'treesit--pre-syntax-ppss 0 t)