all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Yuan Fu <casouri@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 59693@debbugs.gnu.org, miha@kamnitnik.top
Subject: bug#59693: 29.0.50; treesitter in base buffer doesn't respond to modifications in indirect buffer correctly
Date: Sat, 3 Dec 2022 23:20:59 -0800	[thread overview]
Message-ID: <FAC09E08-DB2F-4FA3-BB83-3209960A0D31@gmail.com> (raw)
In-Reply-To: <5AEFFFF0-9788-4006-87A6-89AF741A33C4@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 2007 bytes --]



> On Dec 2, 2022, at 5:01 PM, Yuan Fu <casouri@gmail.com> wrote:
> 
> 
> 
>> On Dec 2, 2022, at 12:33 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> 
>>> From: Yuan Fu <casouri@gmail.com>
>>> Date: Thu, 1 Dec 2022 21:05:43 -0800
>>> Cc: miha@kamnitnik.top,
>>> 59693@debbugs.gnu.org
>>> 
>>>> In the insdel.c hooks where you record changes to buffer text, you should
>>>> see if the buffer has a base_buffer, and if so, update any parsers of the
>>>> base buffer as well.
>>> 
>>> Actually there’s a little bit of problem. When we edit the base buffer, we would want to update the parsers in all of its indirect buffers as well, and AFAICT there is no pointer from base buffer to the indirect buffer, only the other way around. 
>> 
>> That's not the problem presented by the OP, though.
> 
> Yeah, but they are the same problem in spirit, ie, parser not updated when base/indirect buffer receive changes.
> 
>> 
>>> We don’t want indirect buffer and base buffers to share parsers, since they can have different narrowing, and semantically indirect buffers should share anything but the text with the base buffer.
>> 
>> Yes, the parsers should not be shared.
>> 
>>> How about this: we change current_buffer->parser_list from a plain list of parsers to a cons (PARSER-LIST . INDIRECT-PARSER-LIST), where PARSER-LIST is as before. But for base buffers, INDIRECT-PARSER-LIST includes all the parsers of its indirect buffers; and for indirect buffers, INDIRECT-PARSER-LIST is nil.
>> 
>> You can maybe have the indirect buffers in the list, not their parsers.
>> That could make it easier to access other treesit-related information of the
>> indirect buffers, if needed.
> 
> Good idea, it’s easier to know when to remove the reference with buffers, aka when buffer is killed.

I now have a patch that fixes this problem. WDYT? I added a new buffer field since it’s cleaner than turning ts_parser_list into a cons, hopefully that’s not frowned upon. 


Yuan


[-- Attachment #2: indirect.patch --]
[-- Type: application/octet-stream, Size: 23043 bytes --]

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


  reply	other threads:[~2022-12-04  7:20 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-29 20:21 bug#59693: 29.0.50; treesitter in base buffer doesn't respond to modifications in indirect buffer correctly miha--- via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-11-30 10:17 ` Yuan Fu
2022-11-30 14:05   ` Eli Zaretskii
2022-12-02  5:05     ` Yuan Fu
2022-12-02  8:33       ` Eli Zaretskii
2022-12-03  1:01         ` Yuan Fu
2022-12-04  7:20           ` Yuan Fu [this message]
2022-12-04  7:46             ` Eli Zaretskii
2022-12-04 23:21               ` Yuan Fu
2022-12-05  3:49       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-05  8:19         ` Eli Zaretskii
2022-12-05 15:29           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-05 15:44             ` Eli Zaretskii
2022-12-05 20:14               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-06  2:15               ` Yuan Fu
2022-12-06  3:57                 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-12-06 12:17                 ` Eli Zaretskii
2022-12-07 23:13                   ` Yuan Fu
2022-12-08  6:47                     ` Eli Zaretskii
2022-12-10  1:41 ` Yuan Fu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=FAC09E08-DB2F-4FA3-BB83-3209960A0D31@gmail.com \
    --to=casouri@gmail.com \
    --cc=59693@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    --cc=miha@kamnitnik.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.