aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2025-03-18 17:26:26 -0700
committerYuan Fu2025-03-19 11:59:42 -0700
commit289e1bdae28d7dbb27ae8f323b6f50f5c430a961 (patch)
treeeca2b73e0fba0d5c58e832fa80d08ce5525d1ae6
parent72f6cf8e77f3eac8fe2e5fc6ad29c2348314b84b (diff)
downloademacs-289e1bdae28d7dbb27ae8f323b6f50f5c430a961.tar.gz
emacs-289e1bdae28d7dbb27ae8f323b6f50f5c430a961.zip
-rw-r--r--src/buffer.c21
-rw-r--r--src/buffer.h30
-rw-r--r--src/casefiddle.c13
-rw-r--r--src/editfns.c44
-rw-r--r--src/insdel.c55
-rw-r--r--src/treesit.c433
-rw-r--r--src/treesit.h23
-rw-r--r--test/src/treesit-tests.el45
8 files changed, 617 insertions, 47 deletions
diff --git a/src/buffer.c b/src/buffer.c
index a408b799ff4..e1f138d6f42 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -641,6 +641,13 @@ even if it is dead. The return value is never nil. */)
641 bset_width_table (b, Qnil); 641 bset_width_table (b, Qnil);
642 b->prevent_redisplay_optimizations_p = 1; 642 b->prevent_redisplay_optimizations_p = 1;
643 643
644#ifdef HAVE_TREE_SITTER
645 b->ts_linecol_cache.pos = 1;
646 b->ts_linecol_cache.byte_pos = 1;
647 b->ts_linecol_cache.line = 1;
648 b->ts_linecol_cache.col = 0;
649#endif
650
644 /* An ordinary buffer normally doesn't need markers 651 /* An ordinary buffer normally doesn't need markers
645 to handle BEGV and ZV. */ 652 to handle BEGV and ZV. */
646 bset_pt_marker (b, Qnil); 653 bset_pt_marker (b, Qnil);
@@ -867,6 +874,13 @@ Interactively, CLONE and INHIBIT-BUFFER-HOOKS are nil. */)
867 b->bidi_paragraph_cache = 0; 874 b->bidi_paragraph_cache = 0;
868 bset_width_table (b, Qnil); 875 bset_width_table (b, Qnil);
869 876
877#ifdef HAVE_TREE_SITTER
878 b->ts_linecol_cache.pos = 1;
879 b->ts_linecol_cache.byte_pos = 1;
880 b->ts_linecol_cache.line = 1;
881 b->ts_linecol_cache.col = 0;
882#endif
883
870 name = Fcopy_sequence (name); 884 name = Fcopy_sequence (name);
871 set_string_intervals (name, NULL); 885 set_string_intervals (name, NULL);
872 bset_name (b, name); 886 bset_name (b, name);
@@ -2618,6 +2632,11 @@ results, see Info node `(elisp)Swapping Text'. */)
2618 bset_point_before_scroll (current_buffer, Qnil); 2632 bset_point_before_scroll (current_buffer, Qnil);
2619 bset_point_before_scroll (other_buffer, Qnil); 2633 bset_point_before_scroll (other_buffer, Qnil);
2620 2634
2635#ifdef HAVE_TREE_SITTER
2636 swapfield_ (ts_parser_list, Lisp_Object);
2637 swapfield (ts_linecol_cache, struct ts_linecol);
2638#endif
2639
2621 modiff_incr (&current_buffer->text->modiff, 1); 2640 modiff_incr (&current_buffer->text->modiff, 1);
2622 modiff_incr (&other_buffer->text->modiff, 1); 2641 modiff_incr (&other_buffer->text->modiff, 1);
2623 modiff_incr (&current_buffer->text->chars_modiff, 1); 2642 modiff_incr (&current_buffer->text->chars_modiff, 1);
@@ -5019,8 +5038,6 @@ DEFUN ("overlay-tree", Foverlay_tree, Soverlay_tree, 0, 1, 0,
5019} 5038}
5020#endif 5039#endif
5021 5040
5022
5023
5024/* Initialize the buffer routines. */ 5041/* Initialize the buffer routines. */
5025void 5042void
5026syms_of_buffer (void) 5043syms_of_buffer (void)
diff --git a/src/buffer.h b/src/buffer.h
index 5c0a6ab3118..9479b141424 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -220,6 +220,23 @@ extern ptrdiff_t advance_to_char_boundary (ptrdiff_t byte_pos);
220 220
221/* Define the actual buffer data structures. */ 221/* Define the actual buffer data structures. */
222 222
223/* This data structure stores the cache of a position and its line and
224 column number. The column number is counted in bytes. The line
225 number and column number don't consider narrowing. */
226struct ts_linecol
227{
228 /* A position in the buffer. */
229 ptrdiff_t pos;
230 /* The byte position of POS. */
231 ptrdiff_t byte_pos;
232 /* The line number of this position. */
233 ptrdiff_t line;
234 /* The column number (in bytes) of this position. Unlike Emacs'
235 convention, this is 0-based (because tree-sitter column is
236 0-based). Simply the byte offset from BOL (or BOB). */
237 ptrdiff_t col;
238};
239
223/* This data structure describes the actual text contents of a buffer. 240/* This data structure describes the actual text contents of a buffer.
224 It is shared between indirect buffers and their base buffer. */ 241 It is shared between indirect buffers and their base buffer. */
225 242
@@ -700,6 +717,19 @@ struct buffer
700 /* The interval tree containing this buffer's overlays. */ 717 /* The interval tree containing this buffer's overlays. */
701 struct itree_tree *overlays; 718 struct itree_tree *overlays;
702 719
720 /* Right now only tree-sitter makes use of this, so I don't want
721 non-tree-sitter build to pay for it. If something else can make
722 use of this, we can remove the gate. */
723#ifdef HAVE_TREE_SITTER
724 /* Cache of line and column number of a position. The position cached
725 is usually near point. Tree-sitter uses this cache to calculate
726 line and column of the beginning and end of buffer edits. This
727 cache is refreshed in buffer edit functions, so it's always
728 up-to-date. Usually, the newly calculated position and line/column
729 are saved to this field. Initialized to position 1. */
730 struct ts_linecol ts_linecol_cache;
731#endif
732
703 /* Changes in the buffer are recorded here for undo, and t means 733 /* Changes in the buffer are recorded here for undo, and t means
704 don't record anything. This information belongs to the base 734 don't record anything. This information belongs to the base
705 buffer of an indirect buffer. But we can't store it in the 735 buffer of an indirect buffer. But we can't store it in the
diff --git a/src/casefiddle.c b/src/casefiddle.c
index faeb16fb8f2..dba483460ce 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -543,6 +543,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
543#ifdef HAVE_TREE_SITTER 543#ifdef HAVE_TREE_SITTER
544 ptrdiff_t start_byte = CHAR_TO_BYTE (start); 544 ptrdiff_t start_byte = CHAR_TO_BYTE (start);
545 ptrdiff_t old_end_byte = CHAR_TO_BYTE (end); 545 ptrdiff_t old_end_byte = CHAR_TO_BYTE (end);
546 struct ts_linecol start_linecol
547 = treesit_linecol_maybe (start, start_byte,
548 current_buffer->ts_linecol_cache);
549 struct ts_linecol old_end_linecol
550 = treesit_linecol_maybe (end, old_end_byte,
551 current_buffer->ts_linecol_cache);
546#endif 552#endif
547 553
548 ptrdiff_t orig_end = end; 554 ptrdiff_t orig_end = end;
@@ -565,8 +571,11 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
565 update_compositions (start, end, CHECK_ALL); 571 update_compositions (start, end, CHECK_ALL);
566 } 572 }
567#ifdef HAVE_TREE_SITTER 573#ifdef HAVE_TREE_SITTER
568 treesit_record_change (start_byte, old_end_byte, 574 ptrdiff_t new_end = orig_end + added;
569 CHAR_TO_BYTE (orig_end + added)); 575 ptrdiff_t new_end_byte = CHAR_TO_BYTE (new_end);
576
577 treesit_record_change (start_byte, old_end_byte, new_end_byte,
578 start_linecol, old_end_linecol, new_end);
570#endif 579#endif
571 580
572 return orig_end + added; 581 return orig_end + added;
diff --git a/src/editfns.c b/src/editfns.c
index f4e9a022fe2..5d5f4b30015 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -2234,6 +2234,19 @@ Both characters must have the same length of multi-byte form. */)
2234 = !NILP (BVAR (current_buffer, enable_multibyte_characters)); 2234 = !NILP (BVAR (current_buffer, enable_multibyte_characters));
2235 int fromc, toc; 2235 int fromc, toc;
2236 2236
2237#ifdef HAVE_TREE_SITTER
2238 ptrdiff_t start_char = fix_position (start);
2239 ptrdiff_t old_end_char = fix_position (end);
2240 ptrdiff_t start_byte = CHAR_TO_BYTE (start_char);
2241 ptrdiff_t old_end_byte = CHAR_TO_BYTE (old_end_char);
2242 struct ts_linecol start_linecol
2243 = treesit_linecol_maybe (start_char, start_byte,
2244 current_buffer->ts_linecol_cache);
2245 struct ts_linecol old_end_linecol
2246 = treesit_linecol_maybe (old_end_char, old_end_byte,
2247 current_buffer->ts_linecol_cache);
2248#endif
2249
2237 restart: 2250 restart:
2238 2251
2239 validate_region (&start, &end); 2252 validate_region (&start, &end);
@@ -2334,7 +2347,8 @@ Both characters must have the same length of multi-byte form. */)
2334 if (changed > 0) 2347 if (changed > 0)
2335 { 2348 {
2336#ifdef HAVE_TREE_SITTER 2349#ifdef HAVE_TREE_SITTER
2337 treesit_record_change (changed, last_changed, last_changed); 2350 treesit_record_change (start_byte, old_end_byte, old_end_byte,
2351 start_linecol, old_end_linecol, old_end_char);
2338#endif 2352#endif
2339 signal_after_change (changed, 2353 signal_after_change (changed,
2340 last_changed - changed, last_changed - changed); 2354 last_changed - changed, last_changed - changed);
@@ -2521,6 +2535,14 @@ It returns the number of characters changed. */)
2521 } 2535 }
2522 else 2536 else
2523 { 2537 {
2538#ifdef HAVE_TREE_SITTER
2539 struct ts_linecol start_linecol
2540 = treesit_linecol_maybe (pos, pos_byte,
2541 current_buffer->ts_linecol_cache);
2542 struct ts_linecol old_end_linecol
2543 = treesit_linecol_maybe (pos + 1, pos_byte + len,
2544 start_linecol);
2545#endif
2524 record_change (pos, 1); 2546 record_change (pos, 1);
2525 while (str_len-- > 0) 2547 while (str_len-- > 0)
2526 *p++ = *str++; 2548 *p++ = *str++;
@@ -2528,12 +2550,13 @@ It returns the number of characters changed. */)
2528 update_compositions (pos, pos + 1, CHECK_BORDER); 2550 update_compositions (pos, pos + 1, CHECK_BORDER);
2529 2551
2530#ifdef HAVE_TREE_SITTER 2552#ifdef HAVE_TREE_SITTER
2531 /* In the previous branch, replace_range() notifies 2553 /* In the previous branch, replace_range() notifies
2532 changes to tree-sitter, but in this branch, we 2554 changes to tree-sitter, but in this branch, we
2533 modified buffer content manually, so we need to 2555 modified buffer content manually, so we need to
2534 notify tree-sitter manually. */ 2556 notify tree-sitter manually. */
2535 treesit_record_change (pos_byte, pos_byte + len, 2557 treesit_record_change (pos_byte, pos_byte + len,
2536 pos_byte + len); 2558 pos_byte + len, start_linecol,
2559 old_end_linecol, pos + 1);
2537#endif 2560#endif
2538 } 2561 }
2539 characters_changed++; 2562 characters_changed++;
@@ -4481,6 +4504,15 @@ ring. */)
4481 start1_byte = CHAR_TO_BYTE (start1); 4504 start1_byte = CHAR_TO_BYTE (start1);
4482 end2_byte = CHAR_TO_BYTE (end2); 4505 end2_byte = CHAR_TO_BYTE (end2);
4483 4506
4507#ifdef HAVE_TREE_SITTER
4508 struct ts_linecol start_linecol
4509 = treesit_linecol_maybe (start1, start1_byte,
4510 current_buffer->ts_linecol_cache);
4511 struct ts_linecol old_end_linecol
4512 = treesit_linecol_maybe (end2, end2_byte,
4513 current_buffer->ts_linecol_cache);
4514#endif
4515
4484 /* Make sure the gap won't interfere, by moving it out of the text 4516 /* Make sure the gap won't interfere, by moving it out of the text
4485 we will operate on. */ 4517 we will operate on. */
4486 if (start1 < gap && gap < end2) 4518 if (start1 < gap && gap < end2)
@@ -4620,10 +4652,8 @@ ring. */)
4620 } 4652 }
4621 4653
4622#ifdef HAVE_TREE_SITTER 4654#ifdef HAVE_TREE_SITTER
4623 /* I don't think it's common to transpose two far-apart regions, so 4655 treesit_record_change (start1_byte, end2_byte, end2_byte,
4624 amalgamating the edit into one should be fine. This is what the 4656 start_linecol, old_end_linecol, end2);
4625 signal_after_change below does, too. */
4626 treesit_record_change (start1_byte, end2_byte, end2_byte);
4627#endif 4657#endif
4628 4658
4629 signal_after_change (start1, end2 - start1, end2 - start1); 4659 signal_after_change (start1, end2 - start1, end2 - start1);
diff --git a/src/insdel.c b/src/insdel.c
index 9b770725971..dd5be1d9ca7 100644
--- a/src/insdel.c
+++ b/src/insdel.c
@@ -891,6 +891,11 @@ insert_1_both (const char *string,
891 if (NILP (BVAR (current_buffer, enable_multibyte_characters))) 891 if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
892 nchars = nbytes; 892 nchars = nbytes;
893 893
894#ifdef HAVE_TREE_SITTER
895 struct ts_linecol start_linecol
896 = treesit_linecol_maybe (PT, PT_BYTE, current_buffer->ts_linecol_cache);
897#endif
898
894 if (prepare) 899 if (prepare)
895 /* Do this before moving and increasing the gap, 900 /* Do this before moving and increasing the gap,
896 because the before-change hooks might move the gap 901 because the before-change hooks might move the gap
@@ -945,7 +950,9 @@ insert_1_both (const char *string,
945#ifdef HAVE_TREE_SITTER 950#ifdef HAVE_TREE_SITTER
946 eassert (nbytes >= 0); 951 eassert (nbytes >= 0);
947 eassert (PT_BYTE >= 0); 952 eassert (PT_BYTE >= 0);
948 treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes); 953
954 treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes,
955 start_linecol, start_linecol, PT + nchars);
949#endif 956#endif
950 957
951 adjust_point (nchars, nbytes); 958 adjust_point (nchars, nbytes);
@@ -1017,6 +1024,11 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
1017 = count_size_as_multibyte (SDATA (string) + pos_byte, 1024 = count_size_as_multibyte (SDATA (string) + pos_byte,
1018 nbytes); 1025 nbytes);
1019 1026
1027#ifdef HAVE_TREE_SITTER
1028 struct ts_linecol start_linecol
1029 = treesit_linecol_maybe (PT, PT_BYTE, current_buffer->ts_linecol_cache);
1030#endif
1031
1020 /* Do this before moving and increasing the gap, 1032 /* Do this before moving and increasing the gap,
1021 because the before-change hooks might move the gap 1033 because the before-change hooks might move the gap
1022 or make it smaller. */ 1034 or make it smaller. */
@@ -1081,7 +1093,9 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
1081#ifdef HAVE_TREE_SITTER 1093#ifdef HAVE_TREE_SITTER
1082 eassert (nbytes >= 0); 1094 eassert (nbytes >= 0);
1083 eassert (PT_BYTE >= 0); 1095 eassert (PT_BYTE >= 0);
1084 treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes); 1096
1097 treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes,
1098 start_linecol, start_linecol, PT + nchars);
1085#endif 1099#endif
1086 1100
1087 adjust_point (nchars, outgoing_nbytes); 1101 adjust_point (nchars, outgoing_nbytes);
@@ -1094,7 +1108,8 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
1094 GPT_ADDR (if not text_at_gap_tail). 1108 GPT_ADDR (if not text_at_gap_tail).
1095 Contrary to insert_from_gap, this does not invalidate any cache, 1109 Contrary to insert_from_gap, this does not invalidate any cache,
1096 nor update any markers, nor record any buffer modification information 1110 nor update any markers, nor record any buffer modification information
1097 of any sort, with the single exception of notifying tree-sitter. */ 1111 of any sort, with the single exception of notifying tree-sitter and
1112 updating tree-sitter linecol cache. */
1098void 1113void
1099insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail) 1114insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
1100{ 1115{
@@ -1103,6 +1118,8 @@ insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
1103 1118
1104#ifdef HAVE_TREE_SITTER 1119#ifdef HAVE_TREE_SITTER
1105 ptrdiff_t ins_bytepos = GPT_BYTE; 1120 ptrdiff_t ins_bytepos = GPT_BYTE;
1121 struct ts_linecol start_linecol
1122 = treesit_linecol_maybe (GPT, GPT_BYTE, current_buffer->ts_linecol_cache);
1106#endif 1123#endif
1107 1124
1108 GAP_SIZE -= nbytes; 1125 GAP_SIZE -= nbytes;
@@ -1123,7 +1140,9 @@ insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
1123#ifdef HAVE_TREE_SITTER 1140#ifdef HAVE_TREE_SITTER
1124 eassert (nbytes >= 0); 1141 eassert (nbytes >= 0);
1125 eassert (ins_bytepos >= 0); 1142 eassert (ins_bytepos >= 0);
1126 treesit_record_change (ins_bytepos, ins_bytepos, ins_bytepos + nbytes); 1143
1144 treesit_record_change (ins_bytepos, ins_bytepos, ins_bytepos + nbytes,
1145 start_linecol, start_linecol, ins_bytepos + nbytes);
1127#endif 1146#endif
1128} 1147}
1129 1148
@@ -1186,6 +1205,8 @@ insert_from_buffer (struct buffer *buf,
1186 1205
1187#ifdef HAVE_TREE_SITTER 1206#ifdef HAVE_TREE_SITTER
1188 ptrdiff_t obyte = PT_BYTE; 1207 ptrdiff_t obyte = PT_BYTE;
1208 struct ts_linecol start_linecol
1209 = treesit_linecol_maybe (opoint, obyte, current_buffer->ts_linecol_cache);
1189#endif 1210#endif
1190 1211
1191 insert_from_buffer_1 (buf, charpos, nchars, inherit); 1212 insert_from_buffer_1 (buf, charpos, nchars, inherit);
@@ -1196,7 +1217,9 @@ insert_from_buffer (struct buffer *buf,
1196 eassert (PT_BYTE >= BEG_BYTE); 1217 eassert (PT_BYTE >= BEG_BYTE);
1197 eassert (obyte >= BEG_BYTE); 1218 eassert (obyte >= BEG_BYTE);
1198 eassert (PT_BYTE >= obyte); 1219 eassert (PT_BYTE >= obyte);
1199 treesit_record_change (obyte, obyte, PT_BYTE); 1220
1221 treesit_record_change (obyte, obyte, PT_BYTE,
1222 start_linecol, start_linecol, PT);
1200#endif 1223#endif
1201} 1224}
1202 1225
@@ -1481,6 +1504,14 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
1481 if (nbytes_del <= 0 && inschars == 0) 1504 if (nbytes_del <= 0 && inschars == 0)
1482 return; 1505 return;
1483 1506
1507#ifdef HAVE_TREE_SITTER
1508 struct ts_linecol start_linecol
1509 = treesit_linecol_maybe (from, from_byte, current_buffer->ts_linecol_cache);
1510 struct ts_linecol old_end_linecol
1511 = treesit_linecol_maybe (to, to_byte, current_buffer->ts_linecol_cache);
1512#endif
1513
1514
1484 ptrdiff_t insbeg_bytes, insend_bytes; 1515 ptrdiff_t insbeg_bytes, insend_bytes;
1485 ptrdiff_t insbytes; 1516 ptrdiff_t insbytes;
1486 unsigned char *insbeg_ptr; 1517 unsigned char *insbeg_ptr;
@@ -1623,7 +1654,9 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
1623 eassert (to_byte >= from_byte); 1654 eassert (to_byte >= from_byte);
1624 eassert (outgoing_insbytes >= 0); 1655 eassert (outgoing_insbytes >= 0);
1625 eassert (from_byte >= 0); 1656 eassert (from_byte >= 0);
1626 treesit_record_change (from_byte, to_byte, from_byte + outgoing_insbytes); 1657
1658 treesit_record_change (from_byte, to_byte, from_byte + outgoing_insbytes,
1659 start_linecol, old_end_linecol, from + inschars);
1627#endif 1660#endif
1628 1661
1629 /* Relocate point as if it were a marker. */ 1662 /* Relocate point as if it were a marker. */
@@ -1948,6 +1981,13 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
1948 nchars_del = to - from; 1981 nchars_del = to - from;
1949 nbytes_del = to_byte - from_byte; 1982 nbytes_del = to_byte - from_byte;
1950 1983
1984#ifdef HAVE_TREE_SITTER
1985 struct ts_linecol start_linecol
1986 = treesit_linecol_maybe (from, from_byte, current_buffer->ts_linecol_cache);
1987 struct ts_linecol old_end_linecol
1988 = treesit_linecol_maybe (to, to_byte, current_buffer->ts_linecol_cache);
1989#endif
1990
1951 /* Make sure the gap is somewhere in or next to what we are deleting. */ 1991 /* Make sure the gap is somewhere in or next to what we are deleting. */
1952 if (from > GPT) 1992 if (from > GPT)
1953 gap_right (from, from_byte); 1993 gap_right (from, from_byte);
@@ -2007,7 +2047,8 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
2007#ifdef HAVE_TREE_SITTER 2047#ifdef HAVE_TREE_SITTER
2008 eassert (from_byte <= to_byte); 2048 eassert (from_byte <= to_byte);
2009 eassert (from_byte >= 0); 2049 eassert (from_byte >= 0);
2010 treesit_record_change (from_byte, to_byte, from_byte); 2050 treesit_record_change (from_byte, to_byte, from_byte,
2051 start_linecol, old_end_linecol, from);
2011#endif 2052#endif
2012 2053
2013 return deletion; 2054 return deletion;
diff --git a/src/treesit.c b/src/treesit.c
index b0979397d35..583a01b7f38 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -864,6 +864,176 @@ loaded or the file name couldn't be determined, return nil. */)
864} 864}
865 865
866 866
867/*** Linecol functions */
868
869#define TREE_SITTER_DEBUG_LINECOL true
870
871static void
872treesit_print_linecol (struct ts_linecol linecol)
873{
874 printf ("{ line=%ld col=%ld pos=%ld byte_pos=%ld }\n", linecol.line, linecol.col, linecol.pos, linecol.byte_pos);
875}
876
877static void
878treesit_validate_linecol (const char *name, struct ts_linecol linecol)
879{
880 eassert (linecol.pos <= Z);
881
882 if (linecol.pos > Z)
883 {
884 printf ("OUT OF RANGE\n");
885 treesit_print_linecol (linecol);
886 printf ("Z: %ld\n", Z);
887 }
888
889 ptrdiff_t true_line_count = count_lines (BEG, linecol.byte_pos) + 1;
890 eassert (true_line_count == linecol.line);
891
892 if (true_line_count != linecol.line)
893 {
894 printf ("MISMATCH\n");
895 printf ("%s: ", name);
896 treesit_print_linecol (linecol);
897 printf ("true: %ld\n", true_line_count);
898 }
899}
900
901/* Calculate and return the line and column number of BYTE_POS by
902 scanning newlines from CACHE. CACHE must be valid. */
903static struct ts_linecol
904treesit_linecol_of_pos (ptrdiff_t target_pos, ptrdiff_t target_byte_pos,
905 struct ts_linecol cache)
906{
907 eassert (target_pos == target_byte_pos);
908
909 if (TREE_SITTER_DEBUG_LINECOL)
910 {
911 /* eassert (true_line_count == cache.line); */
912 treesit_validate_linecol ("cache", cache);
913 }
914
915 /* When we finished searching for newlines between CACHE and
916 TARGET_POS, BYTE_POS_2 is at TARGET_POS, and BYTE_POS_1 is at the
917 previous newline. If TARGET_POS happends to be on a newline,
918 BYTE_POS_1 will be on that position. BYTE_POS_1 is used for
919 calculating the column. (If CACHE and TARGET_POS are in the same
920 line, BYTE_POS_1 is unset and we don't use it.) */
921 ptrdiff_t byte_pos_1 = 0;
922 ptrdiff_t pos_2 = 0;
923 ptrdiff_t byte_pos_2 = 0;
924 /* Number of lines between CACHE and TARGET_POS. */
925 ptrdiff_t line_delta = 0;
926
927 if (target_byte_pos == cache.byte_pos)
928 return cache;
929
930 /* Search forward. */
931 if (cache.byte_pos < target_byte_pos)
932 {
933 pos_2 = cache.pos;
934 byte_pos_2 = cache.byte_pos;
935 while (byte_pos_2 < target_byte_pos)
936 {
937 ptrdiff_t counted = 0;
938 pos_2 = find_newline (pos_2, byte_pos_2, target_pos, target_byte_pos,
939 1, &counted, &byte_pos_2, false);
940
941 if (counted > 0) byte_pos_1 = byte_pos_2;
942 line_delta += counted;
943 /* printf ("byte_pos_2=%ld counted=%ld line_delta=%ld\n", byte_pos_2, counted, line_delta); */
944 }
945 eassert (byte_pos_2 == target_byte_pos);
946 /* At this point, byte_pos_2 is at target_pos, and byte_pos_1 is
947 at the previous newline if we went across any. */
948
949 struct ts_linecol target_linecol;
950 target_linecol.pos = target_pos;
951 target_linecol.byte_pos = target_byte_pos;
952 target_linecol.line = cache.line + line_delta;
953 /* If we moved across any newline, use the previous newline to
954 calculate the column; if we stayed at the same line, use the
955 cached column to calculate the new column. */
956 target_linecol.col = line_delta > 0
957 ? target_byte_pos - byte_pos_1
958 : target_byte_pos - cache.byte_pos + cache.col;
959
960 if (TREE_SITTER_DEBUG_LINECOL)
961 {
962 /* eassert (true_line_count == target_linecol.line); */
963 treesit_validate_linecol ("target", target_linecol);
964 }
965
966 return target_linecol;
967 }
968
969 /* Search backward. */
970 printf ("BACK\n");
971 pos_2 = cache.pos + 1;
972 /* The "+1" Cancels out with the "-1" in the first iteration. */
973 byte_pos_2 = cache.byte_pos + 1;
974 while (byte_pos_2 > target_byte_pos)
975 {
976 ptrdiff_t counted = 0;
977 /* pos_2 - 1 won't underflow because of the loop condition. */
978 pos_2 = find_newline (pos_2 - 1, byte_pos_2 - 1,
979 target_pos, target_byte_pos,
980 -1, &counted, &byte_pos_2, false);
981 line_delta += counted;
982 }
983 eassert (byte_pos_2 == target_byte_pos);
984 /* At this point, pos_2 is at target_pos. */
985
986 struct ts_linecol target_linecol;
987 target_linecol.pos = target_pos;
988 target_linecol.byte_pos = target_byte_pos;
989 target_linecol.line = cache.line + line_delta;
990 eassert (cache.line + line_delta > 0);
991
992 /* Calculate the column. */
993 if (line_delta == 0)
994 {
995 target_linecol.col = cache.col - (cache.byte_pos - target_byte_pos);
996 }
997 else
998 {
999 /* We need to find the previous newline in order to calculate the
1000 column. Use POS_2 instead of POS_2 - 1, this way, if POS_2 is
1001 at BOL, we stay in the same place. */
1002 pos_2 = find_newline (pos_2, byte_pos_2, BEG, BEG_BYTE, -1,
1003 NULL, &byte_pos_2, false);
1004 target_linecol.col = target_byte_pos - byte_pos_2;
1005 }
1006
1007 if (TREE_SITTER_DEBUG_LINECOL)
1008 {
1009 treesit_validate_linecol ("target", target_linecol);
1010 }
1011
1012 return target_linecol;
1013}
1014
1015/* Return a TSPoint given POS and VISIBLE_BEG. VISIBLE_BEG must be
1016 before POS. */
1017static TSPoint
1018treesit_make_ts_point (struct ts_linecol visible_beg,
1019 struct ts_linecol pos)
1020{
1021 TSPoint point;
1022 if (visible_beg.line == pos.line)
1023 {
1024 point.row = 0;
1025 point.column = pos.col - visible_beg.col;
1026 eassert (point.column >= 0);
1027 }
1028 else
1029 {
1030 point.row = pos.line - visible_beg.line;
1031 eassert (point.row > 0);
1032 point.column = pos.col;
1033 }
1034 return point;
1035}
1036
867/*** Parsing functions */ 1037/*** Parsing functions */
868 1038
869static void 1039static void
@@ -879,28 +1049,34 @@ treesit_check_parser (Lisp_Object obj)
879 larger than UINT32_MAX. */ 1049 larger than UINT32_MAX. */
880static inline void 1050static inline void
881treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte, 1051treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte,
882 ptrdiff_t old_end_byte, ptrdiff_t new_end_byte) 1052 ptrdiff_t old_end_byte, ptrdiff_t new_end_byte,
1053 TSPoint start_point, TSPoint old_end_point,
1054 TSPoint new_end_point)
883{ 1055{
884 eassert (start_byte >= 0); 1056 eassert (start_byte >= 0);
885 eassert (start_byte <= old_end_byte); 1057 eassert (start_byte <= old_end_byte);
886 eassert (start_byte <= new_end_byte); 1058 eassert (start_byte <= new_end_byte);
887 TSPoint dummy_point = {0, 0};
888 eassert (start_byte <= UINT32_MAX); 1059 eassert (start_byte <= UINT32_MAX);
889 eassert (old_end_byte <= UINT32_MAX); 1060 eassert (old_end_byte <= UINT32_MAX);
890 eassert (new_end_byte <= UINT32_MAX); 1061 eassert (new_end_byte <= UINT32_MAX);
891 TSInputEdit edit = {(uint32_t) start_byte, 1062 TSInputEdit edit = {(uint32_t) start_byte,
892 (uint32_t) old_end_byte, 1063 (uint32_t) old_end_byte,
893 (uint32_t) new_end_byte, 1064 (uint32_t) new_end_byte,
894 dummy_point, dummy_point, dummy_point}; 1065 start_point, old_end_point, new_end_point};
895 ts_tree_edit (tree, &edit); 1066 ts_tree_edit (tree, &edit);
896} 1067}
897 1068
898/* Update each parser's tree after the user made an edit. This 1069/* Update each parser's tree after the user made an edit. This function
899 function does not parse the buffer and only updates the tree, so it 1070 does not parse the buffer and only updates the tree, so it should be
900 should be very fast. */ 1071 very fast. If the caller knows there's no parser in the current
901void 1072 buffer, they can pass empty linecol for
902treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte, 1073 START/OLD_END/NEW_END_linecol. */
903 ptrdiff_t new_end_byte) 1074static void
1075treesit_record_change_1 (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
1076 ptrdiff_t new_end_byte,
1077 struct ts_linecol start_linecol,
1078 struct ts_linecol old_end_linecol,
1079 struct ts_linecol new_end_linecol)
904{ 1080{
905 struct buffer *base_buffer = current_buffer; 1081 struct buffer *base_buffer = current_buffer;
906 if (current_buffer->base_buffer) 1082 if (current_buffer->base_buffer)
@@ -920,12 +1096,15 @@ treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
920 { 1096 {
921 eassert (start_byte <= old_end_byte); 1097 eassert (start_byte <= old_end_byte);
922 eassert (start_byte <= new_end_byte); 1098 eassert (start_byte <= new_end_byte);
923 /* Think the recorded change as a delete followed by an 1099 /* Before sending the edit to tree-sitter, we need to first
924 insert, and think of them as moving unchanged text back 1100 clip the beg/end to visible_beg and visible_end of the
925 and forth. After all, the whole point of updating the 1101 parser. A tip for understanding the code below: think the
926 tree is to update the position of unchanged text. */ 1102 recorded change as a delete followed by an insert, and
927 ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg; 1103 think of them as moving unchanged text back and forth.
928 ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end; 1104 After all, the whole point of updating the tree is to
1105 update the position of unchanged text. */
1106 const ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg;
1107 const ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end;
929 eassert (visible_beg >= 0); 1108 eassert (visible_beg >= 0);
930 eassert (visible_beg <= visible_end); 1109 eassert (visible_beg <= visible_end);
931 1110
@@ -949,9 +1128,9 @@ treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
949 eassert (start_offset <= old_end_offset); 1128 eassert (start_offset <= old_end_offset);
950 eassert (start_offset <= new_end_offset); 1129 eassert (start_offset <= new_end_offset);
951 1130
952 treesit_tree_edit_1 (tree, start_offset, old_end_offset, 1131 /* We have the correct offset for start/end now, but don't
953 new_end_offset); 1132 update the tree yet, because we still need to calculate the
954 XTS_PARSER (lisp_parser)->need_reparse = true; 1133 TSPoint, which needs the updated visible_beg linecol. */
955 1134
956 /* VISIBLE_BEG/END records tree-sitter's range of view in 1135 /* VISIBLE_BEG/END records tree-sitter's range of view in
957 the buffer. We need to adjust them when tree-sitter's 1136 the buffer. We need to adjust them when tree-sitter's
@@ -966,19 +1145,99 @@ treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
966 visi_beg_delta = (old_end_byte < visible_beg 1145 visi_beg_delta = (old_end_byte < visible_beg
967 ? new_end_byte - old_end_byte : 0); 1146 ? new_end_byte - old_end_byte : 0);
968 1147
969 XTS_PARSER (lisp_parser)->visible_beg = visible_beg + visi_beg_delta; 1148 ptrdiff_t old_visi_beg = visible_beg;
970 XTS_PARSER (lisp_parser)->visible_end = (visible_end 1149 struct ts_linecol old_visi_beg_linecol
971 + visi_beg_delta 1150 = XTS_PARSER (lisp_parser)->visi_beg_linecol;
972 + (new_end_offset 1151 /* struct ts_linecol old_visi_end_linecol */
973 - old_end_offset)); 1152 /* = XTS_PARSER (lisp_parser)->visi_end_linecol; */
1153
1154 const ptrdiff_t new_visible_beg = visible_beg + visi_beg_delta;
1155 const ptrdiff_t new_visible_end
1156 = (visible_end + visi_beg_delta
1157 + (new_end_offset - old_end_offset));
1158
1159 XTS_PARSER (lisp_parser)->visible_beg = new_visible_beg;
1160 XTS_PARSER (lisp_parser)->visible_end = new_visible_end;
1161 XTS_PARSER (lisp_parser)->visi_beg_linecol
1162 = treesit_linecol_of_pos (BYTE_TO_CHAR (new_visible_beg),
1163 new_visible_beg,
1164 old_visi_beg <= start_byte
1165 ? old_visi_beg_linecol
1166 : start_linecol);
1167 /* FIXME: computing visi_end_linecol from new_end_linecol
1168 could be expensive, we need a more efficient way to compute
1169 it. */
1170 XTS_PARSER (lisp_parser)->visi_end_linecol
1171 = treesit_linecol_of_pos (BYTE_TO_CHAR (new_visible_end),
1172 new_visible_end,
1173 new_end_linecol);
974 1174
975 eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0); 1175 eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0);
976 eassert (XTS_PARSER (lisp_parser)->visible_beg 1176 eassert (XTS_PARSER (lisp_parser)->visible_beg
977 <= XTS_PARSER (lisp_parser)->visible_end); 1177 <= XTS_PARSER (lisp_parser)->visible_end);
1178
1179 /* Now, calculate TSPoints and finally update the tree. */
1180 struct ts_linecol new_begv_linecol
1181 = XTS_PARSER (lisp_parser)->visi_beg_linecol;
1182 TSPoint old_end_point = treesit_make_ts_point (old_visi_beg_linecol,
1183 old_end_linecol);
1184 TSPoint start_point = treesit_make_ts_point (new_begv_linecol,
1185 start_linecol);
1186 TSPoint new_end_point = treesit_make_ts_point (new_begv_linecol,
1187 new_end_linecol);
1188
1189 treesit_tree_edit_1 (tree, start_offset, old_end_offset,
1190 new_end_offset, start_point, old_end_point,
1191 new_end_point);
1192 XTS_PARSER (lisp_parser)->need_reparse = true;
978 } 1193 }
979 } 1194 }
980} 1195}
981 1196
1197/* Return the linecol of POS, calculated from CACHE. But if there's no
1198 parser in the current buffer, skip calculation and return an empty
1199 linecol instead. */
1200struct ts_linecol
1201treesit_linecol_maybe (ptrdiff_t pos, ptrdiff_t pos_byte,
1202 struct ts_linecol cache)
1203{
1204 if (NILP (BVAR (current_buffer, ts_parser_list)))
1205 return TREESIT_EMPTY_LINECOL;
1206
1207 return treesit_linecol_of_pos (pos, pos_byte, cache);
1208}
1209
1210/* Update each parser's tree after the user made an edit. This function
1211 does not parse the buffer and only updates the tree, so it should be
1212 very fast.
1213
1214 This is a wrapper over treesit_record_change that does a bit more
1215 boilerplate work: it (optionally) calculates linecol for new_end,
1216 pass all the positions into treesit_record_change_1 which does the
1217 real work, and finally (optionally) sets buffer's linecol cache to
1218 new_end's linecol.
1219
1220 If NEW_END is next to NEW_END_BYTE in the arglist, caller might
1221 accidentally swap them, so I placed NEW_END at the end of the
1222 arglist. */
1223void
1224treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
1225 ptrdiff_t new_end_byte,
1226 struct ts_linecol start_linecol,
1227 struct ts_linecol old_end_linecol,
1228 ptrdiff_t new_end)
1229{
1230 struct ts_linecol new_end_linecol
1231 = treesit_linecol_maybe (new_end, new_end_byte, start_linecol);
1232
1233 treesit_record_change_1 (start_byte, old_end_byte, new_end_byte,
1234 start_linecol, old_end_linecol, new_end_linecol);
1235
1236 treesit_print_linecol (new_end_linecol);
1237 if (new_end_linecol.pos != 0)
1238 current_buffer->ts_linecol_cache = new_end_linecol;
1239}
1240
982static TSRange *treesit_make_ts_ranges (Lisp_Object, Lisp_Object, 1241static TSRange *treesit_make_ts_ranges (Lisp_Object, Lisp_Object,
983 uint32_t *); 1242 uint32_t *);
984 1243
@@ -1046,6 +1305,7 @@ treesit_sync_visible_region (Lisp_Object parser)
1046 1305
1047 ptrdiff_t visible_beg = XTS_PARSER (parser)->visible_beg; 1306 ptrdiff_t visible_beg = XTS_PARSER (parser)->visible_beg;
1048 ptrdiff_t visible_end = XTS_PARSER (parser)->visible_end; 1307 ptrdiff_t visible_end = XTS_PARSER (parser)->visible_end;
1308
1049 eassert (0 <= visible_beg); 1309 eassert (0 <= visible_beg);
1050 eassert (visible_beg <= visible_end); 1310 eassert (visible_beg <= visible_end);
1051 1311
@@ -1066,39 +1326,72 @@ treesit_sync_visible_region (Lisp_Object parser)
1066 from ________|xxxx|__ 1326 from ________|xxxx|__
1067 to |xxxx|__________ */ 1327 to |xxxx|__________ */
1068 1328
1329 struct ts_linecol buffer_linecol_cache = buffer->ts_linecol_cache;
1330 struct ts_linecol visi_beg_linecol = XTS_PARSER (parser)->visi_beg_linecol;
1331 struct ts_linecol visi_end_linecol = XTS_PARSER (parser)->visi_end_linecol;
1332 struct ts_linecol buffer_begv_linecol
1333 = treesit_linecol_of_pos (BUF_BEGV (buffer), BUF_BEGV_BYTE (buffer),
1334 visi_beg_linecol);
1335 struct ts_linecol buffer_zv_linecol
1336 = treesit_linecol_of_pos (BUF_ZV (buffer), BUF_ZV_BYTE (buffer),
1337 buffer_linecol_cache);
1338 eassert (visi_beg_linecol.byte_pos == visible_beg);
1339
1069 /* 1. Make sure visible_beg <= BUF_BEGV_BYTE. */ 1340 /* 1. Make sure visible_beg <= BUF_BEGV_BYTE. */
1070 if (visible_beg > BUF_BEGV_BYTE (buffer)) 1341 if (visible_beg > BUF_BEGV_BYTE (buffer))
1071 { 1342 {
1343 TSPoint new_end = treesit_make_ts_point (buffer_begv_linecol,
1344 visi_beg_linecol);
1072 /* Tree-sitter sees: insert at the beginning. */ 1345 /* Tree-sitter sees: insert at the beginning. */
1073 treesit_tree_edit_1 (tree, 0, 0, visible_beg - BUF_BEGV_BYTE (buffer)); 1346 treesit_tree_edit_1 (tree, 0, 0, visible_beg - BUF_BEGV_BYTE (buffer),
1347 TREESIT_TS_POINT_1_0, TREESIT_TS_POINT_1_0,
1348 new_end);
1074 visible_beg = BUF_BEGV_BYTE (buffer); 1349 visible_beg = BUF_BEGV_BYTE (buffer);
1350 visi_beg_linecol = buffer_begv_linecol;
1075 eassert (visible_beg <= visible_end); 1351 eassert (visible_beg <= visible_end);
1076 } 1352 }
1077 /* 2. Make sure visible_end = BUF_ZV_BYTE. */ 1353 /* 2. Make sure visible_end = BUF_ZV_BYTE. */
1078 if (visible_end < BUF_ZV_BYTE (buffer)) 1354 if (visible_end < BUF_ZV_BYTE (buffer))
1079 { 1355 {
1356 TSPoint start = treesit_make_ts_point (visi_beg_linecol,
1357 visi_end_linecol);
1358 TSPoint new_end = treesit_make_ts_point (visi_beg_linecol,
1359 buffer_zv_linecol);
1080 /* Tree-sitter sees: insert at the end. */ 1360 /* Tree-sitter sees: insert at the end. */
1081 treesit_tree_edit_1 (tree, visible_end - visible_beg, 1361 treesit_tree_edit_1 (tree, visible_end - visible_beg,
1082 visible_end - visible_beg, 1362 visible_end - visible_beg,
1083 BUF_ZV_BYTE (buffer) - visible_beg); 1363 BUF_ZV_BYTE (buffer) - visible_beg,
1364 start, start, new_end);
1084 visible_end = BUF_ZV_BYTE (buffer); 1365 visible_end = BUF_ZV_BYTE (buffer);
1366 visi_end_linecol = buffer_zv_linecol;
1085 eassert (visible_beg <= visible_end); 1367 eassert (visible_beg <= visible_end);
1086 } 1368 }
1087 else if (visible_end > BUF_ZV_BYTE (buffer)) 1369 else if (visible_end > BUF_ZV_BYTE (buffer))
1088 { 1370 {
1371 TSPoint start = treesit_make_ts_point (visi_beg_linecol,
1372 buffer_zv_linecol);
1373 TSPoint old_end = treesit_make_ts_point (visi_beg_linecol,
1374 visi_end_linecol);
1089 /* Tree-sitter sees: delete at the end. */ 1375 /* Tree-sitter sees: delete at the end. */
1090 treesit_tree_edit_1 (tree, BUF_ZV_BYTE (buffer) - visible_beg, 1376 treesit_tree_edit_1 (tree, BUF_ZV_BYTE (buffer) - visible_beg,
1091 visible_end - visible_beg, 1377 visible_end - visible_beg,
1092 BUF_ZV_BYTE (buffer) - visible_beg); 1378 BUF_ZV_BYTE (buffer) - visible_beg,
1379 start, old_end, start);
1093 visible_end = BUF_ZV_BYTE (buffer); 1380 visible_end = BUF_ZV_BYTE (buffer);
1381 visi_end_linecol = buffer_zv_linecol;
1094 eassert (visible_beg <= visible_end); 1382 eassert (visible_beg <= visible_end);
1095 } 1383 }
1096 /* 3. Make sure visible_beg = BUF_BEGV_BYTE. */ 1384 /* 3. Make sure visible_beg = BUF_BEGV_BYTE. */
1097 if (visible_beg < BUF_BEGV_BYTE (buffer)) 1385 if (visible_beg < BUF_BEGV_BYTE (buffer))
1098 { 1386 {
1387 TSPoint old_end = treesit_make_ts_point (visi_beg_linecol,
1388 buffer_begv_linecol);
1099 /* Tree-sitter sees: delete at the beginning. */ 1389 /* Tree-sitter sees: delete at the beginning. */
1100 treesit_tree_edit_1 (tree, 0, BUF_BEGV_BYTE (buffer) - visible_beg, 0); 1390 treesit_tree_edit_1 (tree, 0, BUF_BEGV_BYTE (buffer) - visible_beg, 0,
1391 TREESIT_TS_POINT_1_0, old_end,
1392 TREESIT_TS_POINT_1_0);
1101 visible_beg = BUF_BEGV_BYTE (buffer); 1393 visible_beg = BUF_BEGV_BYTE (buffer);
1394 visi_beg_linecol = buffer_begv_linecol;
1102 eassert (visible_beg <= visible_end); 1395 eassert (visible_beg <= visible_end);
1103 } 1396 }
1104 eassert (0 <= visible_beg); 1397 eassert (0 <= visible_beg);
@@ -1108,6 +1401,8 @@ treesit_sync_visible_region (Lisp_Object parser)
1108 1401
1109 XTS_PARSER (parser)->visible_beg = visible_beg; 1402 XTS_PARSER (parser)->visible_beg = visible_beg;
1110 XTS_PARSER (parser)->visible_end = visible_end; 1403 XTS_PARSER (parser)->visible_end = visible_end;
1404 XTS_PARSER (parser)->visi_beg_linecol = visi_beg_linecol;
1405 XTS_PARSER (parser)->visi_end_linecol = visi_end_linecol;
1111 1406
1112 /* Fix ranges so that the ranges stays with in visible_end. Here we 1407 /* Fix ranges so that the ranges stays with in visible_end. Here we
1113 try to do minimal work so that the ranges is minimally correct and 1408 try to do minimal work so that the ranges is minimally correct and
@@ -1381,6 +1676,19 @@ make_treesit_parser (Lisp_Object buffer, TSParser *parser,
1381 lisp_parser->need_to_gc_buffer = false; 1676 lisp_parser->need_to_gc_buffer = false;
1382 lisp_parser->within_reparse = false; 1677 lisp_parser->within_reparse = false;
1383 eassert (lisp_parser->visible_beg <= lisp_parser->visible_end); 1678 eassert (lisp_parser->visible_beg <= lisp_parser->visible_end);
1679
1680 struct buffer *old_buf = current_buffer;
1681 set_buffer_internal (XBUFFER (buffer));
1682
1683 /* treesit_linecol_of_pos doesn't signal, so no need to
1684 unwind-protect. */
1685 lisp_parser->visi_beg_linecol = treesit_linecol_of_pos (BEGV, BEGV_BYTE,
1686 TREESIT_BOB_LINECOL);
1687 lisp_parser->visi_end_linecol
1688 = treesit_linecol_of_pos (ZV, ZV_BYTE, lisp_parser->visi_beg_linecol);
1689
1690 set_buffer_internal (old_buf);
1691
1384 return make_lisp_ptr (lisp_parser, Lisp_Vectorlike); 1692 return make_lisp_ptr (lisp_parser, Lisp_Vectorlike);
1385} 1693}
1386 1694
@@ -4376,6 +4684,66 @@ nodes in the subtree, including NODE. */)
4376 } 4684 }
4377} 4685}
4378 4686
4687DEFUN ("treesit--linecol-at", Ftreesit__linecol_at,
4688 Streesit__linecol_at, 1, 1, 0,
4689 doc: /* Test buffer-local linecol cache.
4690
4691Calculate the line and column at POS using the buffer-local cache,
4692return the line and column in the form of
4693
4694 (LINE . COL)
4695
4696This is used for testing and debugging only. */)
4697 (Lisp_Object pos)
4698{
4699 CHECK_NUMBER (pos);
4700 struct ts_linecol pos_linecol
4701 = treesit_linecol_of_pos (XFIXNUM (pos), CHAR_TO_BYTE (XFIXNUM (pos)),
4702 current_buffer->ts_linecol_cache);
4703 return Fcons (make_fixnum (pos_linecol.line), make_fixnum (pos_linecol.col));
4704}
4705
4706DEFUN ("treesit--linecol-cache-set", Ftreesit__linecol_cache_set,
4707 Streesit__linecol_cache_set, 4, 4, 0,
4708 doc: /* Set the linecol cache for the current buffer.
4709
4710This is used for testing and debugging only. */)
4711 (Lisp_Object line, Lisp_Object col, Lisp_Object pos, Lisp_Object bytepos)
4712{
4713 CHECK_FIXNUM (line);
4714 CHECK_FIXNUM (col);
4715 CHECK_FIXNUM (pos);
4716 CHECK_FIXNUM (bytepos);
4717
4718 current_buffer->ts_linecol_cache.line = XFIXNUM (line);
4719 current_buffer->ts_linecol_cache.col = XFIXNUM (col);
4720 current_buffer->ts_linecol_cache.pos = XFIXNUM (pos);
4721 current_buffer->ts_linecol_cache.byte_pos = XFIXNUM (bytepos);
4722
4723 return Qnil;
4724}
4725
4726DEFUN ("treesit--linecol-cache", Ftreesit__linecol_cache,
4727 Streesit__linecol_cache, 0, 0, 0,
4728 doc: /* Return the buffer-local linecol cache for debugging.
4729
4730Return a plist (:line LINE :col COL :pos POS :bytepos BYTEPOS). This is
4731used for testing and debugging only. */)
4732 (void)
4733{
4734 struct ts_linecol cache = current_buffer->ts_linecol_cache;
4735
4736 Lisp_Object plist = (list4 (QCpos, make_fixnum (cache.pos),
4737 QCbytepos, make_fixnum (cache.byte_pos)));
4738 plist = Fcons (make_fixnum (cache.col), plist);
4739 plist = Fcons (QCcol, plist);
4740 plist = Fcons (make_fixnum (cache.line), plist);
4741 plist = Fcons (QCline, plist);
4742
4743 return plist;
4744}
4745
4746
4379#endif /* HAVE_TREE_SITTER */ 4747#endif /* HAVE_TREE_SITTER */
4380 4748
4381DEFUN ("treesit-available-p", Ftreesit_available_p, 4749DEFUN ("treesit-available-p", Ftreesit_available_p,
@@ -4418,6 +4786,11 @@ syms_of_treesit (void)
4418 DEFSYM (QCequal, ":equal"); 4786 DEFSYM (QCequal, ":equal");
4419 DEFSYM (QCmatch, ":match"); 4787 DEFSYM (QCmatch, ":match");
4420 DEFSYM (QCpred, ":pred"); 4788 DEFSYM (QCpred, ":pred");
4789 DEFSYM (QCline, ":line");
4790 DEFSYM (QCcol, ":col");
4791 DEFSYM (QCpos, ":pos");
4792 DEFSYM (QCbytepos, ":bytepos");
4793
4421 4794
4422 DEFSYM (Qnot_found, "not-found"); 4795 DEFSYM (Qnot_found, "not-found");
4423 DEFSYM (Qsymbol_error, "symbol-error"); 4796 DEFSYM (Qsymbol_error, "symbol-error");
@@ -4649,6 +5022,10 @@ applies to LANGUAGE-A will be redirected to LANGUAGE-B instead. */);
4649 defsubr (&Streesit_induce_sparse_tree); 5022 defsubr (&Streesit_induce_sparse_tree);
4650 defsubr (&Streesit_node_match_p); 5023 defsubr (&Streesit_node_match_p);
4651 defsubr (&Streesit_subtree_stat); 5024 defsubr (&Streesit_subtree_stat);
5025
5026 defsubr (&Streesit__linecol_at);
5027 defsubr (&Streesit__linecol_cache);
5028 defsubr (&Streesit__linecol_cache_set);
4652#endif /* HAVE_TREE_SITTER */ 5029#endif /* HAVE_TREE_SITTER */
4653 defsubr (&Streesit_available_p); 5030 defsubr (&Streesit_available_p);
4654#ifdef WINDOWSNT 5031#ifdef WINDOWSNT
diff --git a/src/treesit.h b/src/treesit.h
index 0d4635f4253..b6a3f3a652c 100644
--- a/src/treesit.h
+++ b/src/treesit.h
@@ -26,6 +26,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
26 26
27#include <tree_sitter/api.h> 27#include <tree_sitter/api.h>
28#include "lisp.h" 28#include "lisp.h"
29#include "buffer.h"
29 30
30INLINE_HEADER_BEGIN 31INLINE_HEADER_BEGIN
31 32
@@ -97,6 +98,14 @@ struct Lisp_TS_Parser
97 (ref:visible-beg-null) in treesit.c for more explanation. */ 98 (ref:visible-beg-null) in treesit.c for more explanation. */
98 ptrdiff_t visible_beg; 99 ptrdiff_t visible_beg;
99 ptrdiff_t visible_end; 100 ptrdiff_t visible_end;
101 /* Caches the line and column number of VISIBLE_BEG. It's always
102 valid and matches VISIBLE_BEG (because it's updated at each buffer
103 edit). (It has to be, because in treesit_record_change, we need to
104 calculate the line/col offset of old_end_linecol, the exact reason
105 why is left as an exercise to the reader.) */
106 struct ts_linecol visi_beg_linecol;
107 /* Similar to VISI_BEG_LINECOL but caches VISIBLE_END. */
108 struct ts_linecol visi_end_linecol;
100 /* This counter is incremented every time a change is made to the 109 /* This counter is incremented every time a change is made to the
101 buffer in treesit_record_change. The node retrieved from this parser 110 buffer in treesit_record_change. The node retrieved from this parser
102 inherits this timestamp. This way we can make sure the node is 111 inherits this timestamp. This way we can make sure the node is
@@ -222,7 +231,19 @@ CHECK_TS_COMPILED_QUERY (Lisp_Object query)
222 231
223INLINE_HEADER_END 232INLINE_HEADER_END
224 233
225extern void treesit_record_change (ptrdiff_t, ptrdiff_t, ptrdiff_t); 234/* A linecol_cache that points to BOB, this is always valid. */
235static const struct ts_linecol TREESIT_BOB_LINECOL = { 1, 1, 1, 0 };
236/* An uninitialized linecol. */
237static const struct ts_linecol TREESIT_EMPTY_LINECOL = { 0, 0, 0, 0 };
238static const TSPoint TREESIT_TS_POINT_1_0 = { 1, 0 };
239
240extern struct ts_linecol linecol_offset (struct ts_linecol,
241 struct ts_linecol);
242extern struct ts_linecol treesit_linecol_maybe (ptrdiff_t, ptrdiff_t,
243 struct ts_linecol);
244extern void treesit_record_change (ptrdiff_t, ptrdiff_t, ptrdiff_t,
245 struct ts_linecol, struct ts_linecol,
246 ptrdiff_t);
226extern Lisp_Object make_treesit_parser (Lisp_Object, TSParser *, TSTree *, 247extern Lisp_Object make_treesit_parser (Lisp_Object, TSParser *, TSTree *,
227 Lisp_Object, Lisp_Object); 248 Lisp_Object, Lisp_Object);
228extern Lisp_Object make_treesit_node (Lisp_Object, TSNode); 249extern Lisp_Object make_treesit_node (Lisp_Object, TSNode);
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el
index caacb74315d..11d5e1b2c8c 100644
--- a/test/src/treesit-tests.el
+++ b/test/src/treesit-tests.el
@@ -224,6 +224,51 @@
224 (kill-buffer base) 224 (kill-buffer base)
225 (kill-buffer indirect)))) 225 (kill-buffer indirect))))
226 226
227;;; Linecol
228
229(ert-deftest treesit-linecol-basic ()
230 "Tests for basic lincol synchronization."
231 (with-temp-buffer
232 (should (equal (treesit--linecol-cache)
233 '(:line 1 :col 0 :pos 1 :bytepos 1)))
234 (should (equal (treesit--linecol-at (point))
235 '(1 . 0)))
236 (insert "\n")
237 ;; Buffer content: a single newline.
238 (should (equal (treesit--linecol-at (point))
239 '(2 . 0)))
240
241 (treesit--linecol-cache-set 2 0 2 2)
242 (should (equal (treesit--linecol-cache)
243 '(:line 2 :col 0 :pos 2 :bytepos 2)))
244
245 (goto-char (point-min))
246 (should (equal (treesit--linecol-at (point))
247 '(1 . 0)))
248
249 (insert "0123456789")
250 ;; Buffer content: ten chars followed by a newline.
251 (treesit--linecol-cache-set 1 0 1 1)
252 (should (equal (treesit--linecol-at (point))
253 '(1 . 10)))
254
255 (goto-char (point-max))
256 (should (equal (treesit--linecol-at (point))
257 '(2 . 0)))
258
259 (treesit--linecol-cache-set 1 5 6 6)
260 (should (equal (treesit--linecol-at (point))
261 '(2 . 0)))
262
263 (treesit--linecol-cache-set 2 0 12 12)
264 ;; Position 6 is in the middle of the first line.
265 (should (equal (treesit--linecol-at 6)
266 '(1 . 5)))
267 ;; Position 11 is at the end of the line.
268 (should (equal (treesit--linecol-at 11)
269 '(1 . 10)))
270 ))
271
227;;; Tree traversal 272;;; Tree traversal
228 273
229(ert-deftest treesit-search-subtree () 274(ert-deftest treesit-search-subtree ()