emacs-31 6b670907fa9: Update tree-sitter manual for embed level

Yuan Fu <[email protected]> Fri, 24 Jul 2026 03:02:09 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: emacs-31
commit 6b670907fa9040b58f54d6958b1f551a7519585b
Author: Yuan Fu <[email protected]>
Commit: Yuan Fu <[email protected]>

    Update tree-sitter manual for embed level
    
    * doc/lispref/parsing.texi (Using Parser): Mention embed level.
    (Multiple Languages): Explain embed level, and local vs
    non-local parsers.
---
 doc/lispref/parsing.texi | 131 +++++++++++++++++++++++++++++++++--------------
 src/treesit.c            |   1 -
 2 files changed, 93 insertions(+), 39 deletions(-)

diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index 4da72b512b0..91680f89cd2 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -455,20 +455,19 @@ 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 a embed
+level that can be set by @code{treesit-parser-set-embed-level} and
+retrieved by @code{treesit-parser-embed-level} (for embed level,
+@pxref{parser-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 +1807,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 +1875,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 +2033,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 +2155,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 determines the range of embedded
+parsers. The capture name doesn'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} capturename. 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 +2194,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 create 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 are major
+mode created, 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 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} is 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/src/treesit.c b/src/treesit.c
index 55e6f8b94a1..7fb11048d18 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -2565,7 +2565,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,