GNU bug report logs -
#78402
treesit after-change-functions
Previous Next
To reply to this bug, email your comments to 78402 AT debbugs.gnu.org.
Toggle the display of automated, internal messages from the tracker.
Report forwarded
to
monnier <at> iro.umontreal.ca, casouri <at> gmail.com, bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Tue, 13 May 2025 06:34:02 GMT)
Full text and
rfc822 format available.
Acknowledgement sent
to
Juri Linkov <juri <at> linkov.net>
:
New bug report received and forwarded. Copy sent to
monnier <at> iro.umontreal.ca, casouri <at> gmail.com, bug-gnu-emacs <at> gnu.org
.
(Tue, 13 May 2025 06:34:02 GMT)
Full text and
rfc822 format available.
Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
This new request is the continuation from bug#77256 and
https://lists.gnu.org/archive/html/emacs-devel/2025-05/msg00300.html
about updating treesit ranges for after-change-functions,
particularly for outline--fix-buttons-after-change that uses ranges.
Hopefully Yuan could verify how implementable is this:
> BTW the fix should be fairly simple, AFAICT: the `treesit.c` code is
> told about the buffer change before `after-change-functions` runs, so it
> can record "ranges need to be updated" somewhere, and then lazily update
> the ranges next time some code uses treesit functions that depend on the
> ranges (e.g. outline's after-change function). If the fully automatic
> lazy update is too eager, we can delay it to an explicit call to
> `treesit-ensure-ranges-are-uptodate` which outline's after-change
> function would have to call manually (same as all other current calls
> to `treesit-update-ranges`)
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Thu, 15 May 2025 07:12:02 GMT)
Full text and
rfc822 format available.
Message #8 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On May 12, 2025, at 11:27 PM, Juri Linkov <juri <at> linkov.net> wrote:
>
> This new request is the continuation from bug#77256 and
> https://lists.gnu.org/archive/html/emacs-devel/2025-05/msg00300.html
> about updating treesit ranges for after-change-functions,
> particularly for outline--fix-buttons-after-change that uses ranges.
>
> Hopefully Yuan could verify how implementable is this:
>
>> BTW the fix should be fairly simple, AFAICT: the `treesit.c` code is
>> told about the buffer change before `after-change-functions` runs, so it
>> can record "ranges need to be updated" somewhere, and then lazily update
>> the ranges next time some code uses treesit functions that depend on the
>> ranges (e.g. outline's after-change function). If the fully automatic
>> lazy update is too eager, we can delay it to an explicit call to
>> `treesit-ensure-ranges-are-uptodate` which outline's after-change
>> function would have to call manually (same as all other current calls
>> to `treesit-update-ranges`)
First of all, the affected range could be larger than the changed range (think inserting “/*” into a C buffer, everything behind the /* is affected). And you don’t really need to remember the changed range, we already does that in the form of updating the existing tree-sitter parse-tree with buffer edit information. When we finally re-parse the buffer, the parser knows the affected range. And we already rely on that information to selectively update ranges. Specifically, we get the affected regions of the primary parser (which should be the superset of all the affected ranges of all the embedded parsers), and update embedded parser ranges in those regions.
It seems that we only need to invoke treesit--pre-redisplay whenever the root node is accessed (you have to get the root node to accessing any node in the parse tree). treesit--pre-redisplay, if haven’t called since last buffer-chars-modified-tick, will force a re-parse on the primary parser, get the affected region, and update ranges for embedded parsers on the region.
Let me ponder on this a bit more to make sure it’s actually a good idea.
Btw, what’s the issue we’re trying to fix here? If some lisp want to have up-to-date ranges, it can just call treesit--pre-redisplay or treesit-update-ranges. Why do we get the node-outdated error?
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Thu, 15 May 2025 17:09:01 GMT)
Full text and
rfc822 format available.
Message #11 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> Btw, what’s the issue we’re trying to fix here? If some lisp want to
> have up-to-date ranges, it can just call treesit--pre-redisplay or
> treesit-update-ranges. Why do we get the node-outdated error?
One example where this is needed is updating the outline buttons
based on the changed text in a ts-mode buffer.
Either after-change-functions or jit-lock-function calls
outline--fix-up-all-buttons that uses treesit-outline-search
that requires the updated ranges.
I see that treesit-indent and treesit-indent-region
explicitly calls treesit-update-ranges at the beginning.
Is it how treesit command are intended to update ranges
for themselves? It would be inefficient to call
treesit-update-ranges in treesit-outline-search.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Sat, 17 May 2025 21:13:01 GMT)
Full text and
rfc822 format available.
Message #14 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
> up-to-date ranges, it can just call treesit--pre-redisplay or
> treesit-update-ranges. Why do we get the node-outdated error?
Hmm... so what you're saying is that we should do something like the
patch below?
Stefan
diff --git a/lisp/treesit.el b/lisp/treesit.el
index a353bc942d3..0c368de6a03 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -4050,6 +4050,9 @@ treesit-outline-search
"Search for the next outline heading in the syntax tree.
For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in
`outline-search-function'."
+ (if backward
+ (treesit-update-ranges bound (point))
+ (treesit-update-ranges (point) bound))
(if looking-at
(when (treesit-outline--at-point) (pos-bol))
@@ -4136,11 +4139,6 @@ treesit-outline-level
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 ()
@@ -4438,8 +4436,7 @@ 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)
- (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)))
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Mon, 19 May 2025 06:50:02 GMT)
Full text and
rfc822 format available.
Message #17 received at 78402 <at> debbugs.gnu.org (full text, mbox):
>> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
>> up-to-date ranges, it can just call treesit--pre-redisplay or
>> treesit-update-ranges. Why do we get the node-outdated error?
>
> Hmm... so what you're saying is that we should do something like the
> patch below?
Maybe Yuan could confirm if `treesit-update-ranges`
is already optimized for frequent calls on overlapping ranges
that don't need updating and where 'beg' and 'end' fall back
to (point-min) and (point-max).
> + (if backward
> + (treesit-update-ranges bound (point))
> + (treesit-update-ranges (point) bound))
This also requires turning markers into integers before:
(when (markerp bound) (setq bound (marker-position bound)))
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Wed, 21 May 2025 06:39:02 GMT)
Full text and
rfc822 format available.
Message #20 received at 78402 <at> debbugs.gnu.org (full text, mbox):
>>> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
>>> up-to-date ranges, it can just call treesit--pre-redisplay or
>>> treesit-update-ranges. Why do we get the node-outdated error?
>>
>> Hmm... so what you're saying is that we should do something like the
>> patch below?
>
> Maybe Yuan could confirm if `treesit-update-ranges`
> is already optimized for frequent calls on overlapping ranges
> that don't need updating and where 'beg' and 'end' fall back
> to (point-min) and (point-max).
It seems not optimized since in my tests outline-minor-mode
is much slower with this change.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Wed, 21 May 2025 07:23:01 GMT)
Full text and
rfc822 format available.
Message #23 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On May 20, 2025, at 11:35 PM, Juri Linkov <juri <at> linkov.net> wrote:
>
>>>> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
>>>> up-to-date ranges, it can just call treesit--pre-redisplay or
>>>> treesit-update-ranges. Why do we get the node-outdated error?
>>>
>>> Hmm... so what you're saying is that we should do something like the
>>> patch below?
>>
>> Maybe Yuan could confirm if `treesit-update-ranges`
>> is already optimized for frequent calls on overlapping ranges
>> that don't need updating and where 'beg' and 'end' fall back
>> to (point-min) and (point-max).
>
> It seems not optimized since in my tests outline-minor-mode
> is much slower with this change.
Hey, sorry for the delay. I've been busy with real life. As you said, `treesit-update-ranges` doesn’t have any optimization for repeated calls. OTOH, `treesit--pre-redisplay` has optimization for repeated calls while buffer content doesn’t change. Maybe the patch can use that instead?
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Wed, 21 May 2025 17:48:02 GMT)
Full text and
rfc822 format available.
Message #26 received at 78402 <at> debbugs.gnu.org (full text, mbox):
Yuan Fu [2025-05-21 00:21:39] wrote:
>> On May 20, 2025, at 11:35 PM, Juri Linkov <juri <at> linkov.net> wrote:
>>>>> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
>>>>> up-to-date ranges, it can just call treesit--pre-redisplay or
>>>>> treesit-update-ranges. Why do we get the node-outdated error?
>>>>
>>>> Hmm... so what you're saying is that we should do something like the
>>>> patch below?
>>>
>>> Maybe Yuan could confirm if `treesit-update-ranges`
>>> is already optimized for frequent calls on overlapping ranges
>>> that don't need updating and where 'beg' and 'end' fall back
>>> to (point-min) and (point-max).
>>
>> It seems not optimized since in my tests outline-minor-mode
>> is much slower with this change.
>
> Hey, sorry for the delay. I've been busy with real life. As you said,
> `treesit-update-ranges` doesn’t have any optimization for repeated
> calls.
That seems like a potential problem for existing uses of that function
(we're just "lucky" that they're expected to be used once-per-command).
> OTOH, `treesit--pre-redisplay` has optimization for repeated calls
> while buffer content doesn’t change. Maybe the patch can use that instead?
Like in the patch below?
That sounds OK, but then `treesit--pre-redisplay` needs to be renamed to
something like `treesit--update` or `treesit--sync`, and maybe
`treesit-font-lock-fontify-region`, `treesit-indent`, and
`treesit-indent-region` should use that function instead of
`treesit-update-ranges`?
Stefan
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 5df8eb70cbf..89edc700f75 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -2615,6 +2615,7 @@ treesit--indent-1
(defun treesit-indent ()
"Indent according to the result of `treesit-indent-function'."
+ ;; FIXME: Use `prog-indent-line'?
(treesit-update-ranges (line-beginning-position)
(line-end-position))
;; We don't return 'noindent even if no rules match, because
@@ -4064,6 +4065,7 @@ treesit-outline-search
"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))
@@ -4150,11 +4152,6 @@ treesit-outline-level
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 ()
@@ -4452,8 +4449,7 @@ 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)
- (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)))
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Thu, 22 May 2025 05:37:01 GMT)
Full text and
rfc822 format available.
Message #29 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On May 21, 2025, at 10:47 AM, Stefan Monnier <monnier <at> iro.umontreal.ca> wrote:
>
> Yuan Fu [2025-05-21 00:21:39] wrote:
>>> On May 20, 2025, at 11:35 PM, Juri Linkov <juri <at> linkov.net> wrote:
>>>>>> Btw, what’s the issue we’re trying to fix here? If some lisp want to have
>>>>>> up-to-date ranges, it can just call treesit--pre-redisplay or
>>>>>> treesit-update-ranges. Why do we get the node-outdated error?
>>>>>
>>>>> Hmm... so what you're saying is that we should do something like the
>>>>> patch below?
>>>>
>>>> Maybe Yuan could confirm if `treesit-update-ranges`
>>>> is already optimized for frequent calls on overlapping ranges
>>>> that don't need updating and where 'beg' and 'end' fall back
>>>> to (point-min) and (point-max).
>>>
>>> It seems not optimized since in my tests outline-minor-mode
>>> is much slower with this change.
>>
>> Hey, sorry for the delay. I've been busy with real life. As you said,
>> `treesit-update-ranges` doesn’t have any optimization for repeated
>> calls.
>
> That seems like a potential problem for existing uses of that function
> (we're just "lucky" that they're expected to be used once-per-command).
Kind of, yeah. Most of the time fontification updates the ranges early enough that other lisp commands/functions never see the state when the ranges are not up to date.
>
>> OTOH, `treesit--pre-redisplay` has optimization for repeated calls
>> while buffer content doesn’t change. Maybe the patch can use that instead?
>
> Like in the patch below?
>
> That sounds OK, but then `treesit--pre-redisplay` needs to be renamed to
> something like `treesit--update` or `treesit--sync`, and maybe
> `treesit-font-lock-fontify-region`, `treesit-indent`, and
> `treesit-indent-region` should use that function instead of
> `treesit-update-ranges`?
>
Yeah. On top of that, we can try calling `treesit--pre-redisplay` in `treesit-node-at`, which should ensure range is always up-to-date. The downside is the potential cost of updating range for the whole buffer, especially when an edit changes actually does affect the whole buffer (like inserting /* in C) and we have to add/remove hundred of local parsers (eg, for doxygen).
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Thu, 22 May 2025 17:18:02 GMT)
Full text and
rfc822 format available.
Message #32 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> @@ -4064,6 +4065,7 @@ treesit-outline-search
> "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))
Alas, the same error:
Error: treesit-node-outdated (#<treesit-node-outdated>)
treesit-node-start(#<treesit-node-outdated>)
treesit-node-enclosed-p(#<treesit-node-outdated> #<treesit-node element in 26-373>)
treesit-navigate-thing(252 1 beg html-ts-mode--outline-predicate)
treesit-outline-search(#<marker at 274 in html-multi.html>)
outline-map-region(#<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_48> 253 274)
outline--fix-up-all-buttons(253 274)
outline--fix-buttons(253 257)
#f(compiled-function (fun) #<bytecode 0x1bafbf8e90b212bf>)(outline--fix-buttons)
run-hook-wrapped(#f(compiled-function (fun) #<bytecode 0x1bafbf8e90b212bf>) outline--fix-buttons)
jit-lock--run-functions(253 257)
jit-lock-fontify-now(253 401)
jit-lock-function(253)
redisplay_internal\ \(C\ function\)()
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Mon, 26 May 2025 03:56:02 GMT)
Full text and
rfc822 format available.
Message #35 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On May 22, 2025, at 10:06 AM, Juri Linkov <juri <at> linkov.net> wrote:
>
>> @@ -4064,6 +4065,7 @@ treesit-outline-search
>> "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))
>
> Alas, the same error:
Gah! Sorry, I don’t have time to properly look into this right now. But I’ll try to find some time as soon as possible (pinky promise).
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Sun, 01 Jun 2025 22:32:02 GMT)
Full text and
rfc822 format available.
Message #38 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On May 25, 2025, at 8:55 PM, Yuan Fu <casouri <at> gmail.com> wrote:
>
>
>
>> On May 22, 2025, at 10:06 AM, Juri Linkov <juri <at> linkov.net> wrote:
>>
>>> @@ -4064,6 +4065,7 @@ treesit-outline-search
>>> "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))
>>
>> Alas, the same error:
>
> Gah! Sorry, I don’t have time to properly look into this right now. But I’ll try to find some time as soon as possible (pinky promise).
Hi Juri, how did you produce this error? I tried to use the liquid-generic-ts-mode example in bug#77256 but that didn’t trigger the error.
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.
Maybe I should create a separate bug report to discuss/address this?
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Tue, 03 Jun 2025 16:43:02 GMT)
Full text and
rfc822 format available.
Message #41 received at 78402 <at> debbugs.gnu.org (full text, mbox):
>>>> @@ -4064,6 +4065,7 @@ treesit-outline-search
>>>> "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))
>>>
>>> Alas, the same error:
>>
>> Gah! Sorry, I don’t have time to properly look into this right now. But I’ll try to find some time as soon as possible (pinky promise).
>
> Hi Juri, how did you produce this error? I tried to use the
> liquid-generic-ts-mode example in bug#77256 but that didn’t trigger
> the error.
You can produce this error by visiting test/manual/indent/html-multi.html
with enabled liquid-generic-ts-mode, then delete the word "script"
(outside of comments).
> 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.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Wed, 04 Jun 2025 07:32:01 GMT)
Full text and
rfc822 format available.
Message #44 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> On Jun 3, 2025, at 8:29 AM, Juri Linkov <juri <at> linkov.net> wrote:
>
>>>>> @@ -4064,6 +4065,7 @@ treesit-outline-search
>>>>> "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))
>>>>
>>>> Alas, the same error:
>>>
>>> Gah! Sorry, I don’t have time to properly look into this right now. But I’ll try to find some time as soon as possible (pinky promise).
>>
>> Hi Juri, how did you produce this error? I tried to use the
>> liquid-generic-ts-mode example in bug#77256 but that didn’t trigger
>> the error.
>
> You can produce this error by visiting test/manual/indent/html-multi.html
> with enabled liquid-generic-ts-mode, then delete the word "script"
> (outside of comments).
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?
>
>> 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?
Yuan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Wed, 04 Jun 2025 17:18:02 GMT)
Full text and
rfc822 format available.
Message #47 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> 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')
>>> 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.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Fri, 06 Jun 2025 07:56:01 GMT)
Full text and
rfc822 format available.
Message #50 received at 78402 <at> debbugs.gnu.org (full text, mbox):
> 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
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#78402
; Package
emacs
.
(Fri, 06 Jun 2025 15:51:01 GMT)
Full text and
rfc822 format available.
Message #53 received at 78402 <at> debbugs.gnu.org (full text, mbox):
>> 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.
This is what I observe too: when it doesn't fail on edit,
then in any case it fails on 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.
Maybe calling this from after-change-functions affects the order of
calling parser functions.
This bug report was last modified today.
Previous Next
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.