]> git.eshelyaron.com Git - emacs.git/commitdiff
New function treesit-parser-changed-ranges
authorYuan Fu <casouri@gmail.com>
Wed, 17 Apr 2024 06:46:39 +0000 (23:46 -0700)
committerEshel Yaron <me@eshelyaron.com>
Sat, 20 Apr 2024 11:10:39 +0000 (14:10 +0300)
- Add a new field last_changed_ranges to tree-sitter parser object.
- Add a new function treesit-parser-changed-ranges

* doc/lispref/parsing.texi (Using Parser): Add the function in
tree-sitter manual.
* src/treesit.c (treesit_get_changed_ranges): New function, refactored
out of treesit_call_after_change_functions.
(treesit_call_after_change_functions): Pull out
treesit_get_changed_ranges.
(treesit_ensure_parsed): Save the changed ranges to the parser object.
(make_treesit_parser): Initialize the new parser field
last_changed_ranges.
(Ftreesit_parser_changed_ranges): New function.
(Qtreesit_unparsed_edits): New error.
* src/treesit.h (Lisp_TS_Parser): New field.

(cherry picked from commit 996b9576713f9d63ea7ff7e9630a15cb0a0214eb)

doc/lispref/parsing.texi
etc/NEWS
src/treesit.c
src/treesit.h

index 5fd1eaaa57ebae52ea9a4727e6ad7b83772aeb97..65672997bda463d2c1bdbee266b757fa6a393697 100644 (file)
@@ -539,6 +539,26 @@ symbol, rather than a lambda function.
 This function returns the list of @var{parser}'s notifier functions.
 @end defun
 
+Sometimes a user might want to synchronously get the changed ranges of
+the last reparse, and @code{treesit-parser-changed-ranges} is just for
+it.  This function basically returns the @var{ranges} that the notifier
+functions were passed.
+
+@defun treesit-parser-changed-ranges parser &optional quiet
+This function returns the ranges that has been changed since last
+reparse.  It returns a list of cons cells of the form
+@w{@code{(@var{start} . @var{end})}}, where @var{start} and @var{end}
+mark the start and the end positions of a range.
+
+This function should almost always be called immediately after
+reparsing.  If it's called when there are new buffer edits that hasn't
+been reparsed, Emacs signals @code{treesit-unparsed-edits}, unless
+@var{quiet} is non-nil.
+
+Calling this function multiple times consecutively doesn't change its
+return value; it always returns the ranges affected by the last reparse.
+@end defun
+
 @node Retrieving Nodes
 @section Retrieving Nodes
 @cindex retrieve node, tree-sitter
index b04d2f4be340da06d99399a1ffd2be14dd7b6cc4..549bbe019265be2d9e674da189b90dd521d50ec4 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2688,6 +2688,9 @@ only return parsers for that language.  If TAG is given, only return
 parsers with that tag.  Note that passing nil as tag doesn't mean return
 all parsers, but rather "all parsers with no tags".
 
++++
+*** New function 'treesit-parser-changed-ranges' which returns buffer regions that are affected by the last buffer edits
+
 \f
 * Changes in Emacs 30.1 on Non-Free Operating Systems
 
index d86ab5011873ab9e0505e18ab897ccdb2f0ee00e..763543612842e1395a27be13b097a5c00968944e 100644 (file)
@@ -1017,9 +1017,8 @@ treesit_check_buffer_size (struct buffer *buffer)
 
 static Lisp_Object treesit_make_ranges (const TSRange *, uint32_t, struct buffer *);
 
-static void
-treesit_call_after_change_functions (TSTree *old_tree, TSTree *new_tree,
-                                    Lisp_Object parser)
+static Lisp_Object
+treesit_get_changed_ranges (TSTree *old_tree, TSTree *new_tree, Lisp_Object parser)
 {
   /* If the old_tree is NULL, meaning this is the first parse, the
      changed range is the whole buffer.  */
@@ -1039,7 +1038,13 @@ treesit_call_after_change_functions (TSTree *old_tree, TSTree *new_tree,
       lisp_ranges = Fcons (Fcons (Fpoint_min (), Fpoint_max ()), Qnil);
       set_buffer_internal (oldbuf);
     }
+  return lisp_ranges;
+}
 
+static void
+treesit_call_after_change_functions (Lisp_Object lisp_ranges,
+                                    Lisp_Object parser)
+{
   specpdl_ref count = SPECPDL_INDEX ();
 
   /* let's trust the after change functions and not clone a new ranges
@@ -1091,13 +1096,17 @@ treesit_ensure_parsed (Lisp_Object parser)
   XTS_PARSER (parser)->tree = new_tree;
   XTS_PARSER (parser)->need_reparse = false;
 
+  Lisp_Object changed_ranges;
+  changed_ranges = treesit_get_changed_ranges (tree, new_tree, parser);
+  XTS_PARSER (parser)->last_changed_ranges = changed_ranges;
+
   /* After-change functions should run at the very end, most crucially
      after need_reparse is set to false, this way if the function
      calls some tree-sitter function which invokes
      treesit_ensure_parsed again, it returns early and do not
      recursively call the after change functions again.
      (ref:notifier-inside-ensure-parsed)  */
-  treesit_call_after_change_functions (tree, new_tree, parser);
+  treesit_call_after_change_functions (changed_ranges, parser);
   ts_tree_delete (tree);
 }
 
@@ -1171,6 +1180,7 @@ 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->last_changed_ranges = Qnil;
   lisp_parser->buffer = buffer;
   lisp_parser->parser = parser;
   lisp_parser->tree = tree;
@@ -1818,6 +1828,32 @@ positions.  PARSER is the parser issuing the notification.   */)
   return Qnil;
 }
 
+DEFUN ("treesit-parser-changed-ranges", Ftreesit_parser_changed_ranges,
+       Streesit_parser_changed_ranges,
+       1, 2, 0,
+       doc: /* Return the buffer regions affected by the last reparse of PARSER.
+
+Returns a list of cons (BEG . END), where each cons represents a region
+in which the buffer content was affected by the last reparse.
+
+This function should almost always be called immediately after
+reparsing.  If it's called when there are new buffer edits that hasn't
+been reparsed, Emacs signals `treesit-unparsed-edits', unless QUIET is
+non-nil.
+
+Calling this function multiple times consecutively doesn't change its
+return value; it always returns the ranges affected by the last
+reparse.  */)
+  (Lisp_Object parser, Lisp_Object quiet)
+{
+  treesit_check_parser (parser);
+
+  if (XTS_PARSER (parser)->need_reparse && NILP (quiet))
+    xsignal1 (Qtreesit_unparsed_edits, parser);
+
+  return XTS_PARSER (parser)->last_changed_ranges;
+}
+
 \f
 /*** Node API  */
 
@@ -4010,6 +4046,7 @@ syms_of_treesit (void)
   DEFSYM (Qtreesit_query_error, "treesit-query-error");
   DEFSYM (Qtreesit_parse_error, "treesit-parse-error");
   DEFSYM (Qtreesit_range_invalid, "treesit-range-invalid");
+  DEFSYM (Qtreesit_unparsed_edits, "treesit-unparsed_edits");
   DEFSYM (Qtreesit_buffer_too_large,
          "treesit-buffer-too-large");
   DEFSYM (Qtreesit_load_language_error,
@@ -4038,6 +4075,8 @@ syms_of_treesit (void)
   define_error (Qtreesit_range_invalid,
                "RANGES are invalid: they have to be ordered and should not overlap",
                Qtreesit_error);
+  define_error (Qtreesit_unparsed_edits, "There are unparsed edits in the buffer",
+               Qtreesit_error);
   define_error (Qtreesit_buffer_too_large, "Buffer too large (> 4GiB)",
                Qtreesit_error);
   define_error (Qtreesit_load_language_error,
@@ -4178,6 +4217,8 @@ the symbol of that THING.  For example, (or sexp sentence).  */);
   defsubr (&Streesit_parser_add_notifier);
   defsubr (&Streesit_parser_remove_notifier);
 
+  defsubr (&Streesit_parser_changed_ranges);
+
   defsubr (&Streesit_node_type);
   defsubr (&Streesit_node_start);
   defsubr (&Streesit_node_end);
index bb81bf0e2b3854daaa5abf18edcaf078d14faaec..aa71933fe8da960987b6d3342b88e979baae7470 100644 (file)
@@ -49,6 +49,9 @@ struct Lisp_TS_Parser
      ranges the users wants to set, and avoid reparse if the new
      ranges is the same as the last set one.  */
   Lisp_Object last_set_ranges;
+  /* The range of buffer content that was affected by the last
+     re-parse.  */
+  Lisp_Object last_changed_ranges;
   /* The buffer associated with this parser.  */
   Lisp_Object buffer;
   /* The pointer to the tree-sitter parser.  Never NULL.  */