GNU bug report logs - #78402
treesit after-change-functions

Previous Next

Package: emacs;

Reported by: Juri Linkov <juri <at> linkov.net>

Date: Tue, 13 May 2025 06:34:02 UTC

Severity: normal

Full log


Message #50 received at 78402 <at> debbugs.gnu.org (full text, mbox):

From: Yuan Fu <casouri <at> gmail.com>
To: Juri Linkov <juri <at> linkov.net>
Cc: Stefan Monnier <monnier <at> iro.umontreal.ca>, 78402 <at> debbugs.gnu.org
Subject: Re: bug#78402: treesit after-change-functions
Date: Fri, 6 Jun 2025 00:55:04 -0700

> On Jun 4, 2025, at 10:16 AM, Juri Linkov <juri <at> linkov.net> wrote:
> 
>> Hmm, not sure what I’m doing wrong but I still can’t trigger the
>> error. I’m using emacs -q, with Stefan’s patch, enabled liquid mode
>> and outline-minor-mode, and deleted the <script> tag on line 19.
>> Any ideas?
> 
> Sorry, I missed essential non-default settings:
> 
>  (setq outline-minor-mode-cycle t)
>  (setq outline-minor-mode-use-buttons t)
> 
> Also (setq debug-on-error t) is nice to have.
> So here is a complete test case:
> 
> 0. emacs -Q
> 1. M-: (setq outline-minor-mode-cycle t)
> 2. M-: (setq outline-minor-mode-use-buttons t)
> 3. M-: (setq debug-on-error t)
> 4. C-x p f test/manual/indent/html-multi.html
> 5. M-x load-library RET treesit-x
> 6. M-x liquid-generic-ts-mode
> 7. M-x outline-minor-mode
> 8. move point to the first 's' in 'script' on line 19
> 9. type 'M-d' ('kill-word')

Aha, I reproduced it. It didn’t happen when I kill-word, but happened when I undo. I’m still trying to understand why the node-outdated error can happen. Each parser has a tick, which is incremented whenever it re-parses. When we create a node from the parser, the node inherits the current tick number of the parser. We only consider a node outdated when its tick is less than its parser’s tick, meaning the parser re-parsed after the node is created. For a parser to re-parse, it has to a) receive a buffer edit or b) change it’s ranges; and then someone needs to request a node from it.

That means in the following backtrace, the parser received a buffer edit or changed its ranges after the `prev` node is created. And that’s baffling to me.

  treesit-node-start(#<treesit-node-outdated>)
  (if (consp smaller) (car smaller) (treesit-node-start smaller))
  (let ((larger-start (if (consp larger) (car larger) (treesit-node-start larger))) (larger-end (if (consp larger) (cdr larger) (treesit-node-end larger))) (smaller-start (if (consp smaller) (car smaller) (treesit-node-start smaller))) (smaller-end (if (consp smaller) (cdr smaller) (treesit-node-end smaller)))) (cond ((eq strict 't) (let nil (and (< larger-start smaller-start) (< smaller-end larger-end)))) ((eq strict 'partial) (let nil (and (or (not (eq larger-start smaller-start)) (not (eq larger-end smaller-end))) (<= larger-start smaller-start smaller-end larger-end)))) (t (let nil (<= larger-start smaller-start smaller-end larger-end)))))
  treesit-node-enclosed-p(#<treesit-node-outdated> #<treesit-node element in 26-379>)
  (not (treesit-node-enclosed-p prev parent))
  (and parent prev (not (treesit-node-enclosed-p prev parent)))
  (if (and parent prev (not (treesit-node-enclosed-p prev parent))) (progn (setq prev nil)))
  (let ((prev (treesit-thing-prev pos thing)) (next (treesit-thing-next pos thing)) (parent (treesit-thing-at pos thing t))) (if (and parent prev (not (treesit-node-enclosed-p prev parent))) (progn (setq prev nil))) (if (and parent next (not (treesit-node-enclosed-p next parent))) (progn (setq next nil))) (if (and (eq tactic 'top-level) parent) (progn (progn (setq parent (treesit-node-top-level parent thing t)) (setq prev nil) (setq next nil)))) (if (eq tactic 'restricted) (setq pos (funcall advance (cond ((and (null next) (null prev)) parent) ((> arg 0) next) (t prev)))) (if (> arg 0) (if (and (eq side 'beg) (cond (next (and ... ...)) (parent t))) (setq pos (or (treesit-navigate-thing (treesit-node-end ...) 1 'beg thing tactic t) (throw 'term nil))) (setq pos (funcall advance (or next parent)))) (if (and (eq side 'end) (cond (prev (and ... ...)) (parent t))) (setq pos (or (treesit-navigate-thing (treesit-node-start ...) -1 'end thing tactic t) (throw 'term nil))) (setq pos (funcall advance (or prev parent)))))) (setq counter (- counter 1)))



> 
>>>> BTW, I noticed that when tree-sitter-provided outline is in effect,
>>>> pressing tab on the lines determined as outline titles—like first line
>>>> of a defun—toggles folding instead of indenting the code. That feels
>>>> intrusive IMHO. It’s not a problem for “traditional” kind of outline
>>>> headers in comments, but code is another story.
>>>> 
>>>> Take this code as example:
>>>> 
>>>> function MyBigFunc(param1
>>>>                 param2) {
>>>> const abc = 'def';
>>>> function embed() {
>>>>   return true;
>>>> }
>>>> }
>>>> 
>>>> Right now I can’t indent the embed function, because pressing TAB
>>>> folds it. Another common practice is to select the whole MyBigFunc
>>>> function and press TAB to indent everything in the region, that
>>>> wouldn’t work if point happens to be on the first line, which is
>>>> common.
>>> 
>>> Indeed, there is a clash between TAB keybindings.
>>> So to address this ambiguity we created a special option
>>> 'outline-minor-mode-cycle-filter' that defines in what context
>>> TAB should fold outlines.  For example, you can customize:
>>> 
>>> (setopt outline-minor-mode-cycle-filter (lambda () (bolp)))
>>> 
>>> Then TAB will fold only when pressed at the beginning of the line.
>>> Anywhere else TAB will indent the line.
>> 
>> Thanks. Digging further, I’m only hitting this problem because I have
>> outline-minor-mode-cycle set to t. So technically with default
>> settings, one wouldn’t have this problem.
>> 
>> But I can’t be the only one that sets outline-minor-mode-cycle to t,
>> expecting to only cycle outline sections in comments. Can we do something
>> to prevent people tripping on this? Maybe change the default value of
>> outline-minor-mode-cycle-filter to exclude code?
> 
> Do you use the default 'outline-regexp'?  For example in emacs-lisp-mode
> it matches not only comments, but also opening parens at the beginning
> of function definitions in code.  So I don't understand how it would be
> useful to fold only comments, but not code.

Ah, I guess never pressed TAB on the first line of a defun in elisp mode. I still think it’ll be problematic on languages that has nested defun (eg, javascript). But maybe I’m being old man yell at cloud. I guess we can see if there’s someone that actually complains about it.

Yuan



This bug report was last modified 11 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.