Add tree-sitter-parser-embed-level and parent-node

Add parser properties embed-level and parent-node. They'll be
help us implement arbitrarily nested embeded parser, and
navigation across embedded and host parsers, respectively.

* src/treesit.c:
(Ftreesit_parser_embed_level):
(Ftreesit_parser_set_embed_level):
(Ftreesit_parser_parent_node):
(Ftreesit_parser_set_parent_node): New functions.
This commit is contained in:
Yuan Fu 2025-02-27 03:05:26 -08:00
parent 32da093e52
commit 30e1508ef2
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
2 changed files with 91 additions and 0 deletions

View file

@ -1367,6 +1367,8 @@ make_treesit_parser (Lisp_Object buffer, TSParser *parser,
lisp_parser->after_change_functions = Qnil;
lisp_parser->tag = tag;
lisp_parser->last_set_ranges = Qnil;
lisp_parser->embed_level = Qnil;
lisp_parser->parent_node = Qnil;
lisp_parser->buffer = buffer;
lisp_parser->parser = parser;
lisp_parser->tree = tree;
@ -1818,6 +1820,69 @@ DEFUN ("treesit-parser-tag",
return XTS_PARSER (parser)->tag;
}
DEFUN ("treesit-parser-embed-level",
Ftreesit_parser_embed_level, Streesit_parser_embed_level,
1, 1, 0,
doc: /* Return PARSER's embed level.
The embed level can be either nil or a non-negative integer. A value of
nil means the parser isn't part of the embedded parser tree. The
primary parser has embed level 0, from it, each layer of embedded parser
has +1 embed level. */)
(Lisp_Object parser)
{
treesit_check_parser (parser);
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,
doc: /* Set the embed level for PARSER to LEVEL. */)
(Lisp_Object parser, Lisp_Object level)
{
treesit_check_parser (parser);
if (!NILP (level))
{
CHECK_NUMBER (level);
if (XFIXNUM (level) < 0)
xsignal (Qargs_out_of_range, list1 (level));
}
XTS_PARSER (parser)->embed_level = level;
return level;
}
DEFUN ("treesit-parser-parent-node",
Ftreesit_parser_parent_node, Streesit_parser_parent_node,
1, 1, 0,
doc: /* Return PARSER's parent node, if one exists.
Only embeded local parser can have parent node. When Emacs uses a node
in the host parser to create this local parser, that node is considered
the parent node of the local parser. */)
(Lisp_Object parser)
{
treesit_check_parser (parser);
return XTS_PARSER (parser)->parent_node;
}
DEFUN ("treesit-parser-set-parent-node",
Ftreesit_parser_set_parent_node, Streesit_parser_set_parent_node,
2, 2, 0,
doc: /* Return PARSER's parent node to NODE. */)
(Lisp_Object parser, Lisp_Object node)
{
treesit_check_parser (parser);
if (!NILP (node))
CHECK_TS_NODE (node);
XTS_PARSER (parser)->parent_node = node;
return node;
}
/* Return true if PARSER is not deleted and its buffer is live. */
static bool
treesit_parser_live_p (Lisp_Object parser)
@ -4538,6 +4603,10 @@ applies to LANGUAGE-A will be redirected to LANGUAGE-B instead. */);
defsubr (&Streesit_parser_buffer);
defsubr (&Streesit_parser_language);
defsubr (&Streesit_parser_tag);
defsubr (&Streesit_parser_embed_level);
defsubr (&Streesit_parser_set_embed_level);
defsubr (&Streesit_parser_parent_node);
defsubr (&Streesit_parser_set_parent_node);
defsubr (&Streesit_parser_root_node);
defsubr (&Streesit_parse_string);

View file

@ -63,6 +63,28 @@ struct Lisp_TS_Parser
but rather return DEFAULT_RANGE. (A single range where start_byte
= 0, end_byte = UINT32_MAX). */
Lisp_Object last_set_ranges;
/* Parsers for embedded code blocks will have a non-zero embed level.
The primary parser has level 0, and each layer of embedded parser
gets +1 level. The embed level can be either a non-negative
integer or nil. Every parser created by treesit-parser-create
starts with a nil level. If the value is nil, that means the range
functions (treesit-update-ranges and friends) haven't touched this
parser yet, and this parser isn't part of the embed parser tree. */
Lisp_Object embed_level;
/* Some comments: Technically you could calculate embed_level by
following parent_node, but parent_node might be outdated so it's a
good idea to record embed_level separately. Embed_level and
parent_node could have been implemented as "parser properties" with
an obarray, but ultimately I think two explicit fields helps
documentation better and it's not clear to me that a property list
for a parser will be useful beyond this. And we can always convert
these to properties later, but not vice versa. */
/* When an embedded parser is created, it's usually based on a node in
the host parser. This field saves that node so it possible to
climb up and out of the embedded parser into the host parser. Note
that the range of the embedded parser doesn't have to match that of
the parent node. */
Lisp_Object parent_node;
/* The buffer associated with this parser. */
Lisp_Object buffer;
/* The pointer to the tree-sitter parser. Never NULL. */