Ditch the async range update in tree-sitter (bug#78402)

Right now in treesit-outline-search -> treesit-navigate-thing, a
freshly created tree-sitter node becomes outdated within the
function.  I'm not sure _exactly_ how it happend, but it might
look like this: we first get a node from, say, html parser, then
get another node from, say, liquid parser.  Creating the node
from liquid parser causes a reparse which updated the range of
the html parser, which rendered the html node outdated.

There're several problems with the current design, let's start
with the most obvious one: we add
treesit--font-lock-mark-ranges-to-fontify as a notifier of the
primar parser in treesit-major-mode-setup.  Now, if a ts major
mode inherits another major mode, treesit-major-mode-setup will
be called twice, once in the parent mode and once in the child
node, and two parsers will have the notifier.  But
treesit--font-lock-mark-ranges-to-fontify is designed to run
only once.

I believe this bug, together with some mysterious async
execution order, led to the problems we saw in the bug report.
My solution is to just make everything synchronous.

So I added treesit-parser-changed-regions, and modified
treesit--font-lock-mark-ranges-to-fontify to use it.  Now we
don't need to add the notifier to the primary parser anymore.

I also applied the tree-sitter-outline change we discussed in
the bug report.  (Change to treesit-outline-search, and remove
treesit--after-change.)

* lisp/treesit.el:
(treesit--font-lock-mark-ranges-to-fontify): Remove the unused
PARSER arg.
(treesit--pre-redisplay): Make use of
treesit-parser-changed-regions.
(treesit-outline-search): Call treesit--pre-redisplay in the
beginning.
(treesit--after-change): Remove function.
(treesit-major-mode-setup): Don't add notifier to primary parser.
This commit is contained in:
Yuan Fu 2025-07-08 23:15:58 -07:00
parent 564b947745
commit 159ddd27ee
No known key found for this signature in database
GPG key ID: 56E19BC57664A442

View file

@ -2020,7 +2020,7 @@ 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--font-lock-mark-ranges-to-fontify (ranges _parser)
(defun treesit--font-lock-mark-ranges-to-fontify (ranges)
"A notifier that marks ranges that needs refontification.
For RANGES and PARSER see `treesit-parser-add-notifier'.
@ -2074,17 +2074,15 @@ parser."
(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."
"Force a reparse on primary parser and mark regions to be fontified."
(unless (eq treesit--pre-redisplay-tick (buffer-chars-modified-tick))
(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))
;; Force a reparse on the primary parser and update embedded
;; parser ranges in the changed ranges.
(let ((affected-ranges (treesit-parser-changed-regions
treesit-primary-parser)))
(when affected-ranges
(treesit--font-lock-mark-ranges-to-fontify affected-ranges))))
(setq treesit--pre-redisplay-tick (buffer-chars-modified-tick))))
@ -4072,6 +4070,7 @@ this variable takes priority.")
"Search for the next outline heading in the syntax tree.
For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in
`outline-search-function'."
(treesit--pre-redisplay)
(if looking-at
(when (treesit-outline--at-point) (pos-bol))
@ -4158,11 +4157,6 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in
level))
(defun treesit--after-change (beg end _len)
"Force updating the ranges in BEG...END.
Expected to be called after each text change."
(treesit-update-ranges beg end))
;;; Hideshow mode
(defun treesit-hs-block-end ()
@ -4374,9 +4368,6 @@ before calling this function."
. treesit-font-lock-fontify-region)))
(treesit-font-lock-recompute-features)
(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))
(treesit-validate-font-lock-rules treesit-font-lock-settings))
;; Syntax
(add-hook 'syntax-propertize-extend-region-functions
@ -4462,8 +4453,7 @@ before calling this function."
(setq treesit-outline-predicate
#'treesit-outline-predicate--from-imenu))
(setq-local outline-search-function #'treesit-outline-search
outline-level #'treesit-outline-level)
(add-hook 'outline-after-change-functions #'treesit--after-change nil t))
outline-level #'treesit-outline-level))
;; Remove existing local parsers.
(dolist (ov (overlays-in (point-min) (point-max)))