Package: emacs;
Reported by: Juri Linkov <juri <at> linkov.net>
Date: Tue, 13 May 2025 06:34:02 UTC
Severity: normal
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
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.