Package: emacs;
Reported by: Juri Linkov <juri <at> linkov.net>
Date: Tue, 25 Mar 2025 18:44:02 UTC
Severity: normal
Fixed in version 31.0.50
Done: Juri Linkov <juri <at> linkov.net>
Bug is archived. No further changes may be made.
View this message in rfc822 format
From: Juri Linkov <juri <at> linkov.net> To: Yuan Fu <casouri <at> gmail.com> Cc: 77256 <at> debbugs.gnu.org, Vincenzo Pupillo <v.pupillo <at> gmail.com>, Stefan Monnier <monnier <at> iro.umontreal.ca> Subject: bug#77256: Treesit language-at-point Date: Wed, 09 Apr 2025 20:16:19 +0300
[Message part 1 (text/plain, inline)]
>>>>> Unfortunately, I discovered that it causes treesit-node-outdated errors. >>>>> >>>>> This is because overlays are updated by 'pre-redisplay-functions' >>>>> later than outline-minor-mode uses them by 'after-change-functions', >>>>> since 'after-change-functions' runs before 'pre-redisplay-functions'. >>>> >>>> This fixes the problem: >>>> >>>> (defun outline--fix-buttons-after-change (beg end _len) >>>> + (when (fboundp 'treesit-update-ranges) >>>> + (treesit-update-ranges beg end)) >>>> ;; Handle whole lines >>>> (save-excursion (goto-char beg) (setq beg (pos-bol))) >>>> (save-excursion (goto-char end) (setq end (pos-eol))) >>> >>> Using treesit-update-ranges is the right approach. But is there no >>> better place to put it? I assume there should be a function that >>> involves tree-sitter, in front of which you can put this call. >> >> 'treesit-outline-search' has no information about the positions of >> the beginning and end of the range of changed text. Only >> 'after-change-functions' has access to this information. >> >> So what we could do is this, and I've tested that this approach works: >> >> #+begin_src diff >> diff --git a/lisp/treesit.el b/lisp/treesit.el >> index 07861603244..c77b28c4647 100644 >> --- a/lisp/treesit.el >> +++ b/lisp/treesit.el >> @@ -4044,6 +4112,10 @@ treesit-outline-level >> >> level)) >> >> +(defun treesit--after-change (beg end _len) >> + "Force updating the ranges after each text change." >> + (treesit-update-ranges beg end)) >> + >> ;;; Hideshow mode >> >> (defun treesit-hs-block-end () >> @@ -4338,7 +4410,16 @@ treesit-major-mode-setup >> (setq treesit-outline-predicate >> #'treesit-outline-predicate--from-imenu)) >> (setq-local outline-search-function #'treesit-outline-search >> - outline-level #'treesit-outline-level)) >> + outline-level #'treesit-outline-level) >> + (add-hook 'outline-minor-mode-hook >> + (lambda () >> + (if (bound-and-true-p outline-minor-mode) >> + (add-hook 'after-change-functions >> + #'treesit--after-change >> + 0 t) >> + (remove-hook 'after-change-functions >> + #'treesit--after-change t))) >> + nil t)) >> >> ;; Remove existing local parsers. >> (dolist (ov (overlays-in (point-min) (point-max))) >> #+end_src > > This seems a bit convoluted to me. What do you think about adding > some hook or generic function to outline-minor-mode? Is this better?
[outline-after-change.patch (text/x-diff, inline)]
diff --git a/lisp/outline.el b/lisp/outline.el index 1209b1ce766..ff1322b1e93 100644 --- a/lisp/outline.el +++ b/lisp/outline.el @@ -1980,7 +1980,11 @@ outline--fix-up-all-buttons (outline--insert-button (if close-p 'close 'open)))) (or from (point-min)) (or to (point-max))))) -(defun outline--fix-buttons-after-change (beg end _len) +(defvar outline-after-change nil + "List of functions to call after each text change in outline-mode.") + +(defun outline--fix-buttons-after-change (beg end len) + (run-hook-with-args 'outline-after-change beg end len) ;; Handle whole lines (save-excursion (goto-char beg) (setq beg (pos-bol))) (save-excursion (goto-char end) (setq end (pos-eol))) diff --git a/lisp/treesit.el b/lisp/treesit.el index 8e57a6dae14..05523bc857c 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -4069,7 +4069,8 @@ treesit-outline-level level)) (defun treesit--after-change (beg end _len) - "Force updating the ranges after each text change." + "Force updating the ranges in BEG...END. +Expected to be called after each text change." (treesit-update-ranges beg end)) ;;; Hideshow mode @@ -4367,15 +4368,7 @@ treesit-major-mode-setup #'treesit-outline-predicate--from-imenu)) (setq-local outline-search-function #'treesit-outline-search outline-level #'treesit-outline-level) - (add-hook 'outline-minor-mode-hook - (lambda () - (if (bound-and-true-p outline-minor-mode) - (add-hook 'after-change-functions - #'treesit--after-change - 0 t) - (remove-hook 'after-change-functions - #'treesit--after-change t))) - nil t)) + (add-hook 'outline-after-change #'treesit--after-change nil t)) ;; Remove existing local parsers. (dolist (ov (overlays-in (point-min) (point-max)))
[Message part 3 (text/plain, inline)]
>>>> But can we do better? I see that 'treesit-major-mode-setup' >>>> adds the notifier >>>> >>>> (treesit-parser-add-notifier >>>> treesit-primary-parser #'treesit--font-lock-mark-ranges-to-fontify) >>>> >>>> to 'after_change_functions'. But why the treesit >>>> 'after_change_functions' notifier is called after the >>>> 'after-change-functions' hook? Can we change their order? >>> >>> Because we parse lazily. And I should’ve called them after-parse functions >>> in the docstring (good thing I named them notifiers, not >>> after-change-functions)—they get called after the parser re-parses, but the >>> parser doesn’t have to re-parse immediately after a buffer change. It only >>> re-parses when some Lisp asks for a node from the parse tree. >>> >>> In practice, that usually happens in pre-redisplay-function where we set >>> ranges for embedded parsers, which causes the primary parser to re-parse >>> and return a root node for querying. As a (intended) side-effect, the >>> notifiers get called and mark relevant regions to be re-fontified. >> >> Would it be possible on the first attempt of Lisp code to access a node >> to detect such a situation that there are some outdated nodes and >> to update their ranges automatically? > > Probably not, since you need to have correct ranges to know > which parser to use to get nodes in the first place.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.