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.
Message #17 received at 77256 <at> debbugs.gnu.org (full text, mbox):
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> Subject: Re: bug#77256: Treesit language-at-point Date: Tue, 01 Apr 2025 19:53:37 +0300
[Message part 1 (text/plain, inline)]
>> @@ -185,15 +185,15 @@ treesit-language-at >> This function assumes that parser ranges are up-to-date. It >> returns the return value of `treesit-language-at-point-function' >> if it's non-nil, otherwise it returns the language of the first >> -parser in `treesit-parser-list', or nil if there is no parser. >> +parser from `treesit-parsers-at', or the primary parser. > > If we handle the fallback case in treesit-language-at directly, rather than > defining a separate default function, IMO we should describe the fallback > behavior as the default. So something like “Return the language at > POS. When there are multiple parsers that covers POS, determine the most > relevant parser (hence language) by their embed level. If > treesit-language-at-point-function is non-nil, return the return value of > that function instead.” (Not saying we should use this exact docstring but > to illustrate the point.) Now fixed in the next patch. >> +(defun treesit-parsers-at (&optional pos language with-host local-only) >> + "Return all the non-primary parsers at POS. > > I get why you used treesit-parsers-at rather than a more technically > correct name like treesit-non-primary-parsers-at, or > treesit-embeded-parsers-at. But this is confusing for not much benefit > IMO. I suggest either use treesit-parsers-at, and add an optional argument > exclude-primary; or use a more correct name and don’t include the primary > parser. Also, instead of using LOCAL-ONLY, we might be more future-prove to > use a ONLY parameter, and let user pass ‘local to mean local-only. This way > we can add the option of returning only non-local non-primary parsers in > the future, should the need arises (it already kinda does in > treesit-node-at and treesit-simple-imenu, technically global-parser should > be picked from non-local parsers). I can't find a better function signature than to add the argument ONLY with the following combinations of symbols for its list, wrt existing uses of treesit-parsers-at: nil treesit-language-at (nil returns all parsers) (local) treesit-local-parsers-at (primary global) treesit-node-at, treesit-simple-imenu (global local) treesit-outline-level, treesit-up-list > We should also update manual sections that mentions treesit-language-at. Will do when everything is ok with this patch:
[treesit-parsers-at.patch (text/x-diff, inline)]
diff --git a/lisp/treesit.el b/lisp/treesit.el index 54c29326df2..4ba46e5b7ce 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -182,18 +182,14 @@ treesit-language-at-point-function (defun treesit-language-at (position) "Return the language at POSITION. -This function assumes that parser ranges are up-to-date. It -returns the return value of `treesit-language-at-point-function' -if it's non-nil, otherwise it returns the language of the first -parser in `treesit-parser-list', or nil if there is no parser. - -In a multi-language buffer, make sure -`treesit-language-at-point-function' is implemented! Otherwise -`treesit-language-at' wouldn't return the correct result." +When there are multiple parsers that covers POSITION, determine +the most relevant parser (hence language) by their embed level. +If `treesit-language-at-point-function' is non-nil, return +the return value of that function instead." (if treesit-language-at-point-function (funcall treesit-language-at-point-function position) - (when-let* ((parser (car (treesit-parser-list)))) - (treesit-parser-language parser)))) + (treesit-parser-language + (car (treesit-parsers-at position))))) ;;; Node API supplement @@ -247,8 +243,9 @@ treesit-node-at (parser-or-lang (let* ((local-parser (car (treesit-local-parsers-at pos parser-or-lang))) - (global-parser (car (treesit-parser-list - nil parser-or-lang))) + (global-parser (car (treesit-parsers-at + pos parser-or-lang nil + '(primary global)))) (parser (or local-parser global-parser))) (when parser (treesit-parser-root-node parser)))) @@ -267,13 +264,10 @@ treesit-node-at (local-parser ;; Find the local parser with highest ;; embed-level at point. - (car (seq-sort-by #'treesit-parser-embed-level - (lambda (a b) - (> (or a 0) (or b 0))) - (treesit-local-parsers-at - pos lang)))) - (global-parser (car (treesit-parser-list - nil lang))) + (car (treesit-local-parsers-at pos lang))) + (global-parser (car (treesit-parsers-at + pos lang nil + '(primary global)))) (parser (or local-parser global-parser))) (when parser (treesit-parser-root-node parser)))))) @@ -851,6 +845,56 @@ treesit--clip-ranges if (<= start (car range) (cdr range) end) collect range)) +(defun treesit-parsers-at (&optional pos language with-host only) + "Return all parsers at POS. + +POS defaults to point. The returned parsers are sorted by +the decreasing embed level. + +If LANGUAGE is non-nil, only return parsers for LANGUAGE. + +If WITH-HOST is non-nil, return a list of (PARSER . HOST-PARSER) +instead. HOST-PARSER is the host parser which created the PARSER. + +If ONLY is nil, return all parsers including the primary parser. + +The argument ONLY can be a list of symbols that specify what +parsers to include in the return value. + +If ONLY contains the symbol `local', include local parsers. +Local parsers are those which only parse a limited region marked +by an overlay with non-nil `treesit-parser' property. + +If ONLY contains the symbol `global', include non-local parsers +excluding the primary parser. + +If ONLY contains the symbol `primary', include the primary parser." + (let ((res nil)) + ;; Refer to (ref:local-parser-overlay) for more explanation of local + ;; parser overlays. + (dolist (ov (overlays-at (or pos (point)))) + (when-let* ((parser (overlay-get ov 'treesit-parser)) + (host-parser (or (null with-host) + (overlay-get ov 'treesit-host-parser))) + (_ (or (null language) + (eq (treesit-parser-language parser) + language))) + (_ (or (null only) + (and (memq 'local only) (memq 'global only)) + (and (memq 'local only) + (overlay-get ov 'treesit-parser-local-p)) + (and (memq 'global only) + (not (overlay-get ov 'treesit-parser-local-p)))))) + (push (if with-host (cons parser host-parser) parser) res))) + (when (or (null only) (memq 'primary only)) + (setq res (cons treesit-primary-parser res))) + (seq-sort-by (lambda (p) + (treesit-parser-embed-level + (or (car-safe p) p))) + (lambda (a b) + (> (or a 0) (or b 0))) + res))) + (defun treesit-local-parsers-at (&optional pos language with-host) "Return all the local parsers at POS. @@ -862,19 +906,7 @@ treesit-local-parsers-at If WITH-HOST is non-nil, return a list of (PARSER . HOST-PARSER) instead. HOST-PARSER is the host parser which created the local PARSER." - (let ((res nil)) - ;; Refer to (ref:local-parser-overlay) for more explanation of local - ;; parser overlays. - (dolist (ov (overlays-at (or pos (point)))) - (let ((parser (overlay-get ov 'treesit-parser)) - (host-parser (overlay-get ov 'treesit-host-parser)) - (local-p (overlay-get ov 'treesit-parser-local-p))) - (when (and parser host-parser local-p - (or (null language) - (eq (treesit-parser-language parser) - language))) - (push (if with-host (cons parser host-parser) parser) res)))) - (nreverse res))) + (treesit-parsers-at pos language with-host '(local))) (defun treesit-local-parsers-on (&optional beg end language with-host) "Return the list of local parsers that cover the region between BEG and END. @@ -3135,9 +3173,7 @@ treesit-up-list (setq parent (treesit-parent-until parent pred))) (unless parent - (let ((parsers (seq-keep (lambda (o) - (overlay-get o 'treesit-host-parser)) - (overlays-at (point) t)))) + (let ((parsers (mapcar #'cdr (treesit-parsers-at (point) nil t '(global local))))) (while (and (not parent) parsers) (setq parent (treesit-parent-until (treesit-node-at (point) (car parsers)) pred) @@ -3887,9 +3943,8 @@ treesit-simple-imenu (lambda (entry) (let* ((lang (car entry)) (settings (cdr entry)) - (global-parser (car (treesit-parser-list nil lang))) - (local-parsers - (treesit-parser-list nil lang 'embedded))) + (global-parser (car (treesit-parsers-at nil lang nil '(primary global)))) + (local-parsers (treesit-local-parsers-at nil lang))) (cons (treesit-language-display-name lang) ;; No one says you can't have both global and local ;; parsers for the same language. E.g., Rust uses @@ -4029,9 +4087,7 @@ treesit-outline-level (setq level (1+ level))) ;; Continue counting the host nodes. - (dolist (parser (seq-keep (lambda (o) - (overlay-get o 'treesit-host-parser)) - (overlays-at (point) t))) + (dolist (parser (mapcar #'cdr (treesit-parsers-at (point) nil t '(global local)))) (let* ((node (treesit-node-at (point) parser)) (lang (treesit-parser-language parser)) (pred (alist-get lang treesit-aggregated-outline-predicate)))
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.