From cf8b52f5053cc42fac0ddcefc1b2771c5838cb0d Mon Sep 17 00:00:00 2001
From: Yuan Fu <casouri@gmail.com>
Date: Sat, 3 Dec 2022 21:18:31 -0800
Subject: [PATCH 1/3] Refactor treesit_record_change

This is part of the multi-commit change to support indirect
buffers (bug#59693).

Besides moving the code to a new function, I also removed two lines:

-      CHECK_CONS (parser_list);
-      treesit_check_parser (lisp_parser);

because they could signal, and don't really make sense in a internal
function.  If we really want the checks, we could add easserts.

* src/treesit.c (treesit_record_change_1): New function.
(treesit_record_change): Move bulk of code to the new function.
---
 src/treesit.c | 139 ++++++++++++++++++++++++++------------------------
 1 file changed, 72 insertions(+), 67 deletions(-)

diff --git a/src/treesit.c b/src/treesit.c
index 4b150059fac..2ed9c2eafbe 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -690,6 +690,75 @@ treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte,
   ts_tree_edit (tree, &edit);
 }
 
+/* Update each parser in PARSER_LIST.  */
+static inline void
+treesit_record_change_1 (Lisp_Object parser_list, ptrdiff_t start_byte,
+			 ptrdiff_t old_end_byte, ptrdiff_t new_end_byte)
+{
+  FOR_EACH_TAIL_SAFE (parser_list)
+  {
+    Lisp_Object lisp_parser = XCAR (parser_list);
+    TSTree *tree = XTS_PARSER (lisp_parser)->tree;
+    /* See comment (ref:visible-beg-null) if you wonder why we don't
+       update visible_beg/end when tree is NULL.  */
+    if (tree != NULL)
+      {
+	eassert (start_byte <= old_end_byte);
+	eassert (start_byte <= new_end_byte);
+	/* Think the recorded change as a delete followed by an
+	   insert, and think of them as moving unchanged text back
+	   and forth.  After all, the whole point of updating the
+	   tree is to update the position of unchanged text.  */
+	ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg;
+	ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end;
+	eassert (visible_beg >= 0);
+	eassert (visible_beg <= visible_end);
+
+	/* AFFECTED_START/OLD_END/NEW_END are (0-based) offsets from
+	   VISIBLE_BEG.  min(visi_end, max(visi_beg, value)) clips
+	   value into [visi_beg, visi_end], and subtracting visi_beg
+	   gives the offset from visi_beg.  */
+	ptrdiff_t start_offset = (min (visible_end,
+				       max (visible_beg, start_byte))
+				  - visible_beg);
+	ptrdiff_t old_end_offset = (min (visible_end,
+					 max (visible_beg, old_end_byte))
+				    - visible_beg);
+	ptrdiff_t new_end_offset = (min (visible_end,
+					 max (visible_beg, new_end_byte))
+				    - visible_beg);
+	eassert (start_offset <= old_end_offset);
+	eassert (start_offset <= new_end_offset);
+
+	treesit_tree_edit_1 (tree, start_offset, old_end_offset,
+			     new_end_offset);
+	XTS_PARSER (lisp_parser)->need_reparse = true;
+	XTS_PARSER (lisp_parser)->timestamp++;
+
+	/* VISIBLE_BEG/END records tree-sitter's range of view in
+	   the buffer.  We need to adjust them when tree-sitter's
+	   view changes.  */
+	ptrdiff_t visi_beg_delta;
+	if (old_end_byte > new_end_byte)
+	  /* Move backward.  */
+	  visi_beg_delta = (min (visible_beg, new_end_byte)
+			    - min (visible_beg, old_end_byte));
+	else
+	  /* Move forward.  */
+	  visi_beg_delta = (old_end_byte < visible_beg
+			    ? new_end_byte - old_end_byte : 0);
+	XTS_PARSER (lisp_parser)->visible_beg = visible_beg + visi_beg_delta;
+	XTS_PARSER (lisp_parser)->visible_end = (visible_end
+						 + visi_beg_delta
+						 + (new_end_offset
+						    - old_end_offset));
+	eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0);
+	eassert (XTS_PARSER (lisp_parser)->visible_beg
+		 <= XTS_PARSER (lisp_parser)->visible_end);
+      }
+  }
+}
+
 /* Update each parser's tree after the user made an edit.  This
    function does not parse the buffer and only updates the tree, so it
    should be very fast.  */
@@ -697,74 +766,10 @@ treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte,
 treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
 		       ptrdiff_t new_end_byte)
 {
-  Lisp_Object parser_list;
+  Lisp_Object parser_list = BVAR (current_buffer, ts_parser_list);
+  treesit_record_change_1 (parser_list, start_byte,
+			   old_end_byte, new_end_byte);
 
-  parser_list = BVAR (current_buffer, ts_parser_list);
-
-  FOR_EACH_TAIL_SAFE (parser_list)
-    {
-      CHECK_CONS (parser_list);
-      Lisp_Object lisp_parser = XCAR (parser_list);
-      treesit_check_parser (lisp_parser);
-      TSTree *tree = XTS_PARSER (lisp_parser)->tree;
-      /* See comment (ref:visible-beg-null) if you wonder why we don't
-      update visible_beg/end when tree is NULL.  */
-      if (tree != NULL)
-	{
-	  eassert (start_byte <= old_end_byte);
-	  eassert (start_byte <= new_end_byte);
-	  /* Think the recorded change as a delete followed by an
-	     insert, and think of them as moving unchanged text back
-	     and forth.  After all, the whole point of updating the
-	     tree is to update the position of unchanged text.  */
-	  ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg;
-	  ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end;
-	  eassert (visible_beg >= 0);
-	  eassert (visible_beg <= visible_end);
-
-	  /* AFFECTED_START/OLD_END/NEW_END are (0-based) offsets from
-	     VISIBLE_BEG.  min(visi_end, max(visi_beg, value)) clips
-	     value into [visi_beg, visi_end], and subtracting visi_beg
-	     gives the offset from visi_beg.  */
-	  ptrdiff_t start_offset = (min (visible_end,
-					 max (visible_beg, start_byte))
-				    - visible_beg);
-	  ptrdiff_t old_end_offset = (min (visible_end,
-					   max (visible_beg, old_end_byte))
-				      - visible_beg);
-	  ptrdiff_t new_end_offset = (min (visible_end,
-					   max (visible_beg, new_end_byte))
-				      - visible_beg);
-	  eassert (start_offset <= old_end_offset);
-	  eassert (start_offset <= new_end_offset);
-
-	  treesit_tree_edit_1 (tree, start_offset, old_end_offset,
-			       new_end_offset);
-	  XTS_PARSER (lisp_parser)->need_reparse = true;
-	  XTS_PARSER (lisp_parser)->timestamp++;
-
-	  /* VISIBLE_BEG/END records tree-sitter's range of view in
-	     the buffer.  We need to adjust them when tree-sitter's
-	     view changes.  */
-	  ptrdiff_t visi_beg_delta;
-	  if (old_end_byte > new_end_byte)
-	    /* Move backward.  */
-	    visi_beg_delta = (min (visible_beg, new_end_byte)
-			      - min (visible_beg, old_end_byte));
-	  else
-	    /* Move forward.  */
-	    visi_beg_delta = (old_end_byte < visible_beg
-			      ? new_end_byte - old_end_byte : 0);
-	  XTS_PARSER (lisp_parser)->visible_beg = visible_beg + visi_beg_delta;
-	  XTS_PARSER (lisp_parser)->visible_end = (visible_end
-						   + visi_beg_delta
-						   + (new_end_offset
-						      - old_end_offset));
-	  eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0);
-	  eassert (XTS_PARSER (lisp_parser)->visible_beg
-		   <= XTS_PARSER (lisp_parser)->visible_end);
-	}
-    }
 }
 
 /* Comment (ref:visible-beg-null) The purpose of visible_beg/end is to
-- 
2.33.1


From 98623c2a5fa6776d63d341c9975f066d6849fff0 Mon Sep 17 00:00:00 2001
From: Yuan Fu <casouri@gmail.com>
Date: Sat, 3 Dec 2022 21:28:08 -0800
Subject: [PATCH 2/3] Rename ts_parser_list and add treesit_indirect_list

This is part of the multi-commit change to support indirect buffers in
tree-sitter (bug#59693).

ts_parser_list dodged the prefix rename from ts_ to treesit_, so I
took the opportunity to bring it to justice.

* src/buffer.c (bset_ts_parser_list): Rename.
(bset_treesit_indirect_list): New function.
(reset_buffer, init_buffer_once): Initialize treesit_indirect_list.
Rename ts_parser_list.

* src/buffer.h (struct buffer): Add treesit_indirect_list.  Rename
ts_parser_list.

* src/treesit.c (treesit_record_change_1)
(Ftreesit_parser_create)
(Ftreesit_parser_delete)
(Ftreesit_parser_list): Rename ts_parser_list.
---
 src/buffer.c  | 18 +++++++++++++-----
 src/buffer.h  |  7 ++++++-
 src/treesit.c | 13 +++++++------
 3 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/buffer.c b/src/buffer.c
index 71be7ed9e13..d7dd2c3a2d8 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -233,9 +233,14 @@ bset_extra_line_spacing (struct buffer *b, Lisp_Object val)
 }
 #ifdef HAVE_TREE_SITTER
 static void
-bset_ts_parser_list (struct buffer *b, Lisp_Object val)
+bset_treesit_parser_list (struct buffer *b, Lisp_Object val)
 {
-  b->ts_parser_list_ = val;
+  b->treesit_parser_list_ = val;
+}
+static void
+bset_treesit_indirect_list (struct buffer *b, Lisp_Object val)
+{
+  b->treesit_indirect_list_ = val;
 }
 #endif
 static void
@@ -1066,7 +1071,8 @@ reset_buffer (register struct buffer *b)
   bset_cursor_type (b, BVAR (&buffer_defaults, cursor_type));
   bset_extra_line_spacing (b, BVAR (&buffer_defaults, extra_line_spacing));
 #ifdef HAVE_TREE_SITTER
-  bset_ts_parser_list (b, Qnil);
+  bset_treesit_parser_list (b, Qnil);
+  bset_treesit_indirect_list (b, Qnil);
 #endif
 
   b->display_error_modiff = 0;
@@ -4692,7 +4698,8 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_type), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, extra_line_spacing), idx); ++idx;
 #ifdef HAVE_TREE_SITTER
-  XSETFASTINT (BVAR (&buffer_local_flags, ts_parser_list), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, treesit_parser_list), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, treesit_indirect_list), idx); ++idx;
 #endif
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), idx); ++idx;
 
@@ -4763,7 +4770,8 @@ init_buffer_once (void)
   bset_cursor_type (&buffer_defaults, Qt);
   bset_extra_line_spacing (&buffer_defaults, Qnil);
 #ifdef HAVE_TREE_SITTER
-  bset_ts_parser_list (&buffer_defaults, Qnil);
+  bset_treesit_parser_list (&buffer_defaults, Qnil);
+  bset_treesit_indirect_list (&buffer_defaults, Qnil);
 #endif
   bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);
 
diff --git a/src/buffer.h b/src/buffer.h
index dded0cd98c1..984c5ee4437 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -575,7 +575,12 @@ #define BVAR(buf, field) ((buf)->field ## _)
 
 #ifdef HAVE_TREE_SITTER
   /* A list of tree-sitter parsers for this buffer.  */
-  Lisp_Object ts_parser_list_;
+  Lisp_Object treesit_parser_list_;
+  /* A list of indirect buffers of this buffer whose tree-sitter
+     parsers wants to be updated with buffer content changes.  This
+     list does not necessarily include all indirect buffers of this
+     buffer, and buffers in this list are not necessarily alive.  */
+  Lisp_Object treesit_indirect_list_;
 #endif
   /* Cursor type to display in non-selected windows.
      t means to use hollow box cursor.
diff --git a/src/treesit.c b/src/treesit.c
index 2ed9c2eafbe..9a076be67c5 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -766,7 +766,7 @@ treesit_record_change_1 (Lisp_Object parser_list, ptrdiff_t start_byte,
 treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
 		       ptrdiff_t new_end_byte)
 {
-  Lisp_Object parser_list = BVAR (current_buffer, ts_parser_list);
+  Lisp_Object parser_list = BVAR (current_buffer, treesit_parser_list);
   treesit_record_change_1 (parser_list, start_byte,
 			   old_end_byte, new_end_byte);
 
@@ -1279,7 +1279,7 @@ DEFUN ("treesit-parser-create",
   treesit_check_buffer_size (buf);
 
   /* See if we can reuse a parser.  */
-  for (Lisp_Object tail = BVAR (buf, ts_parser_list);
+  for (Lisp_Object tail = BVAR (buf, treesit_parser_list);
        NILP (no_reuse) && !NILP (tail);
        tail = XCDR (tail))
     {
@@ -1306,7 +1306,8 @@ DEFUN ("treesit-parser-create",
 						 language);
 
   /* Update parser-list.  */
-  BVAR (buf, ts_parser_list) = Fcons (lisp_parser, BVAR (buf, ts_parser_list));
+  Lisp_Object buffer_list = BVAR (buf, treesit_parser_list);
+  BVAR (buf, treesit_parser_list) = Fcons (lisp_parser, buffer_list);
 
   return lisp_parser;
 }
@@ -1323,8 +1324,8 @@ DEFUN ("treesit-parser-delete",
   Lisp_Object buffer = XTS_PARSER (parser)->buffer;
   struct buffer *buf = XBUFFER (buffer);
 
-  BVAR (buf, ts_parser_list)
-    = Fdelete (parser, BVAR (buf, ts_parser_list));
+  BVAR (buf, treesit_parser_list)
+    = Fdelete (parser, BVAR (buf, treesit_parser_list));
 
   XTS_PARSER (parser)->deleted = true;
   return Qnil;
@@ -1350,7 +1351,7 @@ DEFUN ("treesit-parser-list",
   Lisp_Object return_list = Qnil;
   Lisp_Object tail;
 
-  tail = BVAR (buf, ts_parser_list);
+  tail = BVAR (buf, treesit_parser_list);
 
   FOR_EACH_TAIL (tail)
     return_list = Fcons (XCAR (tail), return_list);
-- 
2.33.1


From 6f338f347f737531af8b29776f3745e653710f12 Mon Sep 17 00:00:00 2001
From: Yuan Fu <casouri@gmail.com>
Date: Sat, 3 Dec 2022 22:55:22 -0800
Subject: [PATCH 3/3] Update tree-sitter parser on both base and indirect
 buffers

This is part of the multi-commit change to support indirect buffers in
tree-sitter (bug#59693).

In this commit we finally share the buffer change across all base and
indirect buffers.  I also fixed a bug in Ftreesit_parser_create where
we unconditionally use the current buffer when creating the parser,
regardless of the value of the BUFFER parameter.

* src/treesit.c (treesit_reap_indirect_buffers): New function.
(treesit_record_change): Update all parsers in both the base buffer
and its indirect buffers.
(Ftreesit_parser_create): Add this buffer to its base buffer's
treesit_indirect_list.
(Ftreesit__indirect_buffer_list): New function.

* test/src/treesit-tests.el (treesit-indirect-buffers): New test.
---
 src/treesit.c             | 90 +++++++++++++++++++++++++++++++++++++--
 test/src/treesit-tests.el | 63 +++++++++++++++++++++++++++
 2 files changed, 149 insertions(+), 4 deletions(-)

diff --git a/src/treesit.c b/src/treesit.c
index 9a076be67c5..c6ee0c3322f 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -690,6 +690,27 @@ treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte,
   ts_tree_edit (tree, &edit);
 }
 
+/* Reap deleted buffers in the treesit_parser_list of BASE_BUFFER.  */
+static inline void
+treesit_reap_indirect_buffers (struct buffer *base_buffer)
+{
+  eassert (base_buffer->base_buffer == 0);
+  Lisp_Object buffer_list = BVAR (base_buffer, treesit_indirect_list);
+  Lisp_Object new_buffer_list = Qnil;
+
+  /* Go over the current buffer list and build a new list with only
+     live buffers, and replace the old list with the new list.  */
+  FOR_EACH_TAIL_SAFE (buffer_list)
+  {
+    Lisp_Object the_buffer = XCAR (buffer_list);
+    eassert (BUFFERP (the_buffer));
+    if (BUFFER_LIVE_P (XBUFFER (the_buffer)))
+      new_buffer_list = Fcons (the_buffer, new_buffer_list);
+  }
+  new_buffer_list = Fnreverse (new_buffer_list);
+  BVAR (base_buffer, treesit_indirect_list) = new_buffer_list;
+}
+
 /* Update each parser in PARSER_LIST.  */
 static inline void
 treesit_record_change_1 (Lisp_Object parser_list, ptrdiff_t start_byte,
@@ -766,10 +787,37 @@ treesit_record_change_1 (Lisp_Object parser_list, ptrdiff_t start_byte,
 treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
 		       ptrdiff_t new_end_byte)
 {
-  Lisp_Object parser_list = BVAR (current_buffer, treesit_parser_list);
+  /* We always act on the base buffer.  If this buffer is an indirect
+     buffer, switch to its base buffer.  */
+  struct buffer *base_buffer = current_buffer;
+  if (current_buffer->base_buffer)
+    base_buffer = current_buffer->base_buffer;
+
+  /* First update base buffer's parsers.  */
+  Lisp_Object parser_list = BVAR (base_buffer, treesit_parser_list);
   treesit_record_change_1 (parser_list, start_byte,
 			   old_end_byte, new_end_byte);
 
+  /* Then update parsers of indirect buffers.  */
+  Lisp_Object buffer_list = BVAR (base_buffer, treesit_indirect_list);
+  bool need_reap = false;
+  FOR_EACH_TAIL_SAFE (buffer_list)
+  {
+    struct buffer *buffer = XBUFFER (XCAR (buffer_list));
+    if (BUFFER_LIVE_P (buffer))
+      {
+	Lisp_Object parser_list = BVAR (buffer, treesit_parser_list);
+	treesit_record_change_1 (parser_list, start_byte,
+				 old_end_byte, new_end_byte);
+      }
+    else
+      need_reap = true;
+  }
+
+  /* If we encountered deleted indirect buffer above, remove it from
+     the indirect list.  */
+  if (need_reap)
+    treesit_reap_indirect_buffers (base_buffer);
 }
 
 /* Comment (ref:visible-beg-null) The purpose of visible_beg/end is to
@@ -1270,7 +1318,10 @@ DEFUN ("treesit-parser-create",
   CHECK_SYMBOL (language);
   struct buffer *buf;
   if (NILP (buffer))
-    buf = current_buffer;
+    {
+      buf = current_buffer;
+      XSETBUFFER (buffer, current_buffer);
+    }
   else
     {
       CHECK_BUFFER (buffer);
@@ -1301,14 +1352,26 @@ DEFUN ("treesit-parser-create",
   ts_parser_set_language (parser, lang);
 
   /* Create parser.  */
-  Lisp_Object lisp_parser = make_treesit_parser (Fcurrent_buffer (),
-						 parser, NULL,
+  Lisp_Object lisp_parser = make_treesit_parser (buffer, parser, NULL,
 						 language);
 
   /* Update parser-list.  */
   Lisp_Object buffer_list = BVAR (buf, treesit_parser_list);
   BVAR (buf, treesit_parser_list) = Fcons (lisp_parser, buffer_list);
 
+  /* If this buffer is an indirect buffer, add this buffer to its base
+     buffer's treesit_indirect_list.  */
+  if (buf->base_buffer)
+    {
+      struct buffer *base_buffer = buf->base_buffer;
+      Lisp_Object indirect_list = BVAR (base_buffer, treesit_indirect_list);
+      if (NILP (Fmemq (buffer, indirect_list)))
+	{
+	  indirect_list = Fcons (buffer, indirect_list);
+	  BVAR (base_buffer, treesit_indirect_list) = indirect_list;
+	}
+    }
+
   return lisp_parser;
 }
 
@@ -1382,6 +1445,24 @@ DEFUN ("treesit-parser-language",
   return XTS_PARSER (parser)->language_symbol;
 }
 
+DEFUN ("treesit--indirect-buffer-list",
+       Ftreesit__indirect_buffer_list, Streesit__indirect_buffer_list,
+       0, 1, 0,
+       doc: /* This INTERNAL function is used for testing only.
+It does NOT return all the indirect buffers of BUFFER.  */)
+  (Lisp_Object buffer)
+{
+  CHECK_BUFFER (buffer);
+  struct buffer *buf = XBUFFER (buffer);
+  Lisp_Object return_list = Qnil;
+  Lisp_Object tail = BVAR (buf, treesit_indirect_list);
+
+  FOR_EACH_TAIL (tail)
+    return_list = Fcons (XCAR (tail), return_list);
+
+  return Freverse (return_list);
+}
+
 /*** Parser API */
 
 DEFUN ("treesit-parser-root-node",
@@ -3105,6 +3186,7 @@ syms_of_treesit (void)
   defsubr (&Streesit_parser_list);
   defsubr (&Streesit_parser_buffer);
   defsubr (&Streesit_parser_language);
+  defsubr (&Streesit__indirect_buffer_list);
 
   defsubr (&Streesit_parser_root_node);
   /* defsubr (&Streesit_parse_string); */
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el
index 80fde408cd3..bc8502531dc 100644
--- a/test/src/treesit-tests.el
+++ b/test/src/treesit-tests.el
@@ -31,6 +31,7 @@
 (declare-function treesit-parser-create "treesit.c")
 (declare-function treesit-parser-delete "treesit.c")
 (declare-function treesit-parser-list "treesit.c")
+(declare-function treesit--indirect-buffer-list "treesit.c")
 (declare-function treesit-parser-buffer "treesit.c")
 (declare-function treesit-parser-language "treesit.c")
 
@@ -156,6 +157,68 @@ treesit-node-api
       (should (treesit-node-eq root-node root-node))
       (should (not (treesit-node-eq root-node doc-node))))))
 
+(ert-deftest treesit-indirect-buffers ()
+  "Test basic parsing routines."
+  (skip-unless (treesit-language-available-p 'json))
+  (let* ((base-buffer (get-buffer-create "*treesit test*"))
+         (indirect-1 (make-indirect-buffer base-buffer "*treesit test 1*"))
+         (indirect-2 (make-indirect-buffer base-buffer "*treesit test 2*")))
+    (unwind-protect
+        (progn
+          (treesit-parser-create 'json base-buffer)
+          (treesit-parser-create 'json indirect-1)
+          (treesit-parser-create 'json indirect-2)
+          ;; 1. Creating parsers in indirect buffers should add them
+          ;; to the base buffer's indirect list.
+          (should (equal (treesit--indirect-buffer-list base-buffer)
+                         (list indirect-2 indirect-1)))
+          ;; 2. Creating additional parsers in the indirect buffer
+          ;; should not add duplicate indirect buffers to the base
+          ;; buffer's indirect list.
+          (treesit-parser-create 'json indirect-2 t)
+          (should (equal (treesit--indirect-buffer-list base-buffer)
+                         (list indirect-2 indirect-1)))
+          ;; 3. Edit the base buffer...
+          (with-current-buffer base-buffer
+            (insert "[1,2,3]"))
+          ;; ...and parsers in the indirect buffer should be updated too.
+          (with-current-buffer indirect-1
+            (should (eq (treesit-node-end (treesit-buffer-root-node))
+                        8))
+            ;; 4. Edit the indirect buffer...
+            (goto-char 2)
+            (insert "0,"))
+          ;; ...and parsers in the base and other indirect buffers should be
+          ;; updated too.
+          (with-current-buffer base-buffer
+            (should (eq (treesit-node-end (treesit-buffer-root-node))
+                        10)))
+          (with-current-buffer indirect-2
+            (should (eq (treesit-node-end (treesit-buffer-root-node))
+                        10)))
+          ;; 5. Kill an indirect buffer, editing the base buffer and the
+          ;; other indirect buffer should be fine.
+          (kill-buffer indirect-1)
+          (with-current-buffer base-buffer
+            (delete-region (point-min) (point-max)))
+          (with-current-buffer indirect-2
+            (insert "[1,2,3]"))
+          (with-current-buffer base-buffer
+            (should (eq (treesit-node-end (treesit-buffer-root-node))
+                        8)))
+          (with-current-buffer indirect-2
+            (should (eq (treesit-node-end (treesit-buffer-root-node))
+                        8)))
+          ;; 6. The killed indirect buffer should be removed from the
+          ;; base buffer's indirect list.
+          (should (equal (treesit--indirect-buffer-list base-buffer)
+                         (list indirect-2)))
+          (kill-buffer indirect-2)
+          (kill-buffer base-buffer))
+      (kill-buffer base-buffer)
+      (kill-buffer base-buffer)
+      (kill-buffer base-buffer))))
+
 (ert-deftest treesit-query-api ()
   "Tests for query API."
   (skip-unless (treesit-language-available-p 'json))
-- 
2.33.1

