master 9c2fab94ec1 3/5: Merge from origin/emacs-31

Eli Zaretskii <[email protected]> Sat, 25 Jul 2026 04:52:16 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 9c2fab94ec11fda068046bddcb44a89a619fbd19
Merge: 31c0007f583 2771f99c722
Author: Eli Zaretskii <[email protected]>
Commit: Eli Zaretskii <[email protected]>

    Merge from origin/emacs-31
    
    2771f99c722 ; * admin/MAINTAINERS: Mark Amin as maintaining some more...
    19eeda16a4a ; Fix recent changes in treesit documentation
    29bbd02347d Fix treesit_cursor_helper_1 for zero-width nodes (bug#81436)
    6b670907fa9 Update tree-sitter manual for embed level
    0f6d977a88f ; * test/src/xdisp-tests.el (xdisp-test-format-mode-line)...
---
 admin/MAINTAINERS        |   3 ++
 doc/lispref/parsing.texi | 132 +++++++++++++++++++++++++++++++++--------------
 lisp/treesit.el          |   2 +-
 src/treesit.c            |  11 ++--
 test/src/xdisp-tests.el  |   8 +--
 5 files changed, 110 insertions(+), 46 deletions(-)

diff --git a/admin/MAINTAINERS b/admin/MAINTAINERS
index 3ffa21c0d45..a2e80bf2dc8 100644
--- a/admin/MAINTAINERS
+++ b/admin/MAINTAINERS
@@ -123,6 +123,9 @@ Amin Bandali
 	ERC
 	    lisp/erc/*
 	    doc/misc/erc.texi
+	lisp/net/browse-url.el
+	lisp/net/eww.el
+	lisp/url/*
 
 Andrea Corallo
 	Lisp native compiler
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index 4da72b512b0..dac6574a969 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -455,20 +455,20 @@ is non-@code{nil}, this function always creates a new parser.
 @code{nil}.  Different parsers can have the same tag.
 @end defun
 
-Given a parser, we can query information about it.
-
-@defun treesit-parser-buffer parser
-This function returns the buffer associated with @var{parser}.
-@end defun
-
-@defun treesit-parser-language parser
-This function returns the language used by @var{parser}.
-@end defun
-
-@defun treesit-parser-p object
-This function checks if @var{object} is a tree-sitter parser, and
-returns non-@code{nil} if it is, and @code{nil} otherwise.
-@end defun
+@findex treesit-parser-buffer
+@findex treesit-parser-language
+@findex treesit-parser-embed-level
+@findex treesit-parser-p
+Given a parser, we can query information about it:
+@code{treesit-parser-buffer} returns the buffer associated with a
+parser, and @code{treesit-parser-language} returns the language used by
+the parser.  A parser's tag can be retrieved by
+@code{treesit-parser-tag}.  Each parser also optionally carries an embed
+level that can be set by @code{treesit-parser-set-embed-level} and
+retrieved by @code{treesit-parser-embed-level} (see
+@ref{parser-embed-level}, for more about embed level).  Finally,
+@code{treesit-parser-p} tells you whether an object is a tree-sitter
+parser.
 
 There is no need to explicitly parse a buffer, because parsing is done
 automatically and lazily.  A parser only parses when a Lisp program
@@ -1808,10 +1808,11 @@ Again, @var{thing} can be either a symbol or a predicate.
 
 If @var{parser} is non-@code{nil}, only use that parser's parse tree.
 Otherwise try each parser covering point, from the most specific
-(deepest-embedded) to the least specific.  If there are multiple parsers with
-the same embed level at @var{position}, which parser is tried first is
-undefined.  If @var{parser} is a language symbol, the function limits
-the parsers it tries to the ones for that language.
+(deepest-embedded) to the least specific.  If there are multiple parsers
+with the same embed level (@pxref{parser-embed-level}) at
+@var{position}, which parser is tried first is undefined.  If
+@var{parser} is a language symbol, the function limits the parsers it
+tries to the ones for that language.
 @end defun
 
 @defun treesit-thing-next position thing &optional parser
@@ -1875,8 +1876,9 @@ position must be strictly greater than @var{position}.
 If @var{parser} is non-nil, only use that parser's parse tree.
 Otherwise try each parser covering point, from the most specific
 (deepest-embedded) to the least specific.  If there are multiple parsers
-with the same embed level at @var{position}, which parser is tried first is
-undefined.  @var{parser} can also be a language symbol.
+with the same embed level (@pxref{parser-embed-level}) at
+@var{position}, which parser is tried first is undefined.  @var{parser}
+can also be a language symbol.
 @end defun
 
 @findex treesit-beginning-of-thing
@@ -2032,11 +2034,12 @@ for nodes in a region.
 @defun treesit-language-at pos
 This function returns the language of the text at buffer position
 @var{pos}.  Under the hood it calls
-@code{treesit-language-at-point-function} and returns its return
-value.  If @code{treesit-language-at-point-function} is @code{nil},
-this function returns the language of the deepest parser by embed level
-among parsers returned by @code{treesit-parsers-at}.  If there is no
-parser at that buffer position, it returns @code{nil}.
+@code{treesit-language-at-point-function} and returns its return value.
+If @code{treesit-language-at-point-function} is @code{nil}, this
+function returns the language of the deepest parser by embed level
+(@pxref{parser-embed-level}) among parsers returned by
+@code{treesit-parsers-at}.  If there is no parser at that buffer
+position, it returns @code{nil}.
 @end defun
 
 @heading Supporting multiple languages in major modes
@@ -2153,10 +2156,38 @@ a @var{query} preceded by zero or more @var{keyword}/@var{value}
 pairs.  Each @var{query} is a tree-sitter query in either the string,
 s-expression, or compiled form, or a function.
 
+@code{treesit-update-ranges} uses @var{query} to figure out how to set
+the ranges for parsers for the embedded language.  It queries
+@var{query} in a host language parser, computes the ranges which the
+captured nodes span, and applies these ranges to embedded language
+parsers.
+
 If @var{query} is a tree-sitter query, it should be preceded by two
 @var{keyword}/@var{value} pairs, where the @code{:embed} keyword
-specifies the embedded language, and the @code{:host} keyword
-specifies the host language.
+specifies the embedded language, and the @code{:host} keyword specifies
+the host language.  The captured nodes determine the range of embedded
+parsers.  The capture names don't matter, but names that starts with an
+underscore are ignored, and the @code{@@language} capture name is
+reserved.
+
+If the embedded language is dynamic, then @code{:embed} can specify a
+function rathar than a static language.  The function takes the node
+captured with @code{@@language} capture name.  Care have to be taken that
+the @code{@@language} capture and the range capture are in the same
+match group.  The function should return either a language symbol, or nil
+if no valid language can be found.
+
+If the query is given a @code{:offset} keyword whose value is a pair
+of numbers, each captured range is offset by those numbers.  For
+example, an offset of @code{(1 . -1)} will update a captured range of
+@code{(2 . 8)} to be @code{(3 . 7)}.  This can be used to exclude things
+like surrounding delimiters from being included in the range covered by
+an embedded parser.
+
+By default, a parser sees its ranges as a continuum, rather than
+treating them as separate independent segments.  Therefore, if the
+embedded ranges are semantically independent segments, they should be
+processed by local parsers, described below.
 
 @cindex local parser
 If the query is given the @code{:local} keyword whose value is
@@ -2164,19 +2195,44 @@ If the query is given the @code{:local} keyword whose value is
 otherwise the range shares a parser with other ranges for the same
 language.
 
-By default, a parser sees its ranges as a continuum, rather than
-treating them as separate independent segments.  Therefore, if the
-embedded ranges are semantically independent segments, they should be
-processed by local parsers, described below.
-
-Local parser set to a range can be retrieved by
+Local parsers set to a range can be retrieved by
 @code{treesit-local-parsers-at} and @code{treesit-local-parsers-on}.
 
-@code{treesit-update-ranges} uses @var{query} to figure out how to set
-the ranges for parsers for the embedded language.  It queries
-@var{query} in a host language parser, computes the ranges which the
-captured nodes span, and applies these ranges to embedded language
-parsers.
+Emacs treats local embedded parsers and non-local embedded parsers quite
+differently:
+
+@itemize @bullet
+@item
+Local parsers are managed by Emacs.  Emacs creates new local parsers when
+they are needed and deletes them when they are not.  Non-local parsers
+must be created by major modes, and Emacs only make use of them if one
+exists.
+
+@item
+Because local parsers can be freely created by Emacs, they are suitable
+for recursively nested embedded parsers, where the embed level can be
+arbitrarily deep.  On the other hand, since non-local parsers created by
+major modes, they can only handle a fix number of embed levels.
+@end itemize
+
+@anchor{parser-embed-level}
+@cindex embed level, tree-sitter
+The embed level of the top-level parsers is 0 or omitted, the first
+level of embedded parsers get embed level 1, and so on.  For local
+parsers, each embed level creates a new parser; for non-local parsers,
+since Emacs cannot create non-local parsers freely, it tries to find a
+usable parser at the right embed level; if none exists, Emacs just gives
+up.
+
+If the query is given a @code{:range-fn} keyword whose value is a
+function, Emacs uses that function to compute the ranges to use for the
+embedded parser.  The function takes as arguments the captured node and
+the offset given by the @code{:offset} keyword, and returns a list of
+ranges, where each range is a cons of the start and end position.  For
+local parsers, each @var{query} creates one local parser, and the ranges
+set by the @code{:range-fn} are applied to that parser; for non-local
+parsers, all the ranges (including ranges created by @code{:range-fn})
+in that embed level are joined together and applied to the same parser.
 
 If @var{query} is a function, it doesn't need any @var{keyword} and
 @var{value} pair.  It should be a function that takes 2 arguments,
diff --git a/lisp/treesit.el b/lisp/treesit.el
index f318e08a565..8a1fb1ca86c 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -3832,7 +3832,7 @@ the current line if the beginning of the defun is indented."
 Return the first non-nil evaluation of BODY.
 
 \(fn (SYM VAL) &rest BODY)"
-  (declare (indent 1))
+  (declare (indent 1) (debug ((symbolp form) body)))
   (let ((result-sym (gensym))
         (val-sym (gensym))
         (sym (car sym-val))
diff --git a/src/treesit.c b/src/treesit.c
index 2b4718ec002..73094bfbbdf 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -2559,7 +2559,6 @@ embedding increments the embed level by 1.  */)
   return XTS_PARSER (parser)->embed_level;
 }
 
-/* TODO: Mention in manual, once the API stabilizes.  */
 DEFUN ("treesit-parser-set-embed-level",
        Ftreesit_parser_set_embed_level, Streesit_parser_set_embed_level,
        2, 2, 0,
@@ -4258,9 +4257,13 @@ treesit_cursor_helper_1 (TSTreeCursor *cursor, TSNode *target,
     return true;
 
   /* ts_tree_cursor_goto_first_child_for_byte is significantly faster,
-     so despite it having problems (see bug#60127), we try it
-     first.  */
-  if (ts_tree_cursor_goto_first_child_for_byte (cursor, start_pos) == -1
+     so despite it having problems (see bug#60127), we try it first.
+     Also, ts_tree_cursor_goto_first_child_for_byte can't find
+     zero-width nodes (which exists and are legit, e.g., markdown's
+     block_continuation), because a zero-width node can't contain a pos
+     (end > pos).  */
+  if (start_pos != end_pos
+      && ts_tree_cursor_goto_first_child_for_byte (cursor, start_pos) == -1
       && !ts_tree_cursor_goto_first_child (cursor))
     return false;
 
diff --git a/test/src/xdisp-tests.el b/test/src/xdisp-tests.el
index f3df858e218..8eb57253972 100644
--- a/test/src/xdisp-tests.el
+++ b/test/src/xdisp-tests.el
@@ -205,10 +205,12 @@ int main () {
       (font-lock-mode 0)
       (insert "\n")
       (insert m1)
-      (setq s1 (window-text-pixel-size nil (line-beginning-position) (point)))
+      (setq s1 (buffer-text-pixel-size))
+      (erase-buffer)
       (insert "\n")
       (insert m2)
-      (setq s2 (window-text-pixel-size nil (line-beginning-position) (point)))
-      (should (equal m1 m2)))))
+      (setq s2 (buffer-text-pixel-size))
+      (should (equal m1 m2))
+      (should (equal s1 s2)))))
 
 ;;; xdisp-tests.el ends here