1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00
This commit is contained in:
Yuan Fu 2025-03-18 17:26:26 -07:00
parent 72f6cf8e77
commit 289e1bdae2
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
8 changed files with 617 additions and 47 deletions

View file

@ -641,6 +641,13 @@ even if it is dead. The return value is never nil. */)
bset_width_table (b, Qnil); bset_width_table (b, Qnil);
b->prevent_redisplay_optimizations_p = 1; b->prevent_redisplay_optimizations_p = 1;
#ifdef HAVE_TREE_SITTER
b->ts_linecol_cache.pos = 1;
b->ts_linecol_cache.byte_pos = 1;
b->ts_linecol_cache.line = 1;
b->ts_linecol_cache.col = 0;
#endif
/* An ordinary buffer normally doesn't need markers /* An ordinary buffer normally doesn't need markers
to handle BEGV and ZV. */ to handle BEGV and ZV. */
bset_pt_marker (b, Qnil); bset_pt_marker (b, Qnil);
@ -867,6 +874,13 @@ Interactively, CLONE and INHIBIT-BUFFER-HOOKS are nil. */)
b->bidi_paragraph_cache = 0; b->bidi_paragraph_cache = 0;
bset_width_table (b, Qnil); bset_width_table (b, Qnil);
#ifdef HAVE_TREE_SITTER
b->ts_linecol_cache.pos = 1;
b->ts_linecol_cache.byte_pos = 1;
b->ts_linecol_cache.line = 1;
b->ts_linecol_cache.col = 0;
#endif
name = Fcopy_sequence (name); name = Fcopy_sequence (name);
set_string_intervals (name, NULL); set_string_intervals (name, NULL);
bset_name (b, name); bset_name (b, name);
@ -2618,6 +2632,11 @@ results, see Info node `(elisp)Swapping Text'. */)
bset_point_before_scroll (current_buffer, Qnil); bset_point_before_scroll (current_buffer, Qnil);
bset_point_before_scroll (other_buffer, Qnil); bset_point_before_scroll (other_buffer, Qnil);
#ifdef HAVE_TREE_SITTER
swapfield_ (ts_parser_list, Lisp_Object);
swapfield (ts_linecol_cache, struct ts_linecol);
#endif
modiff_incr (&current_buffer->text->modiff, 1); modiff_incr (&current_buffer->text->modiff, 1);
modiff_incr (&other_buffer->text->modiff, 1); modiff_incr (&other_buffer->text->modiff, 1);
modiff_incr (&current_buffer->text->chars_modiff, 1); modiff_incr (&current_buffer->text->chars_modiff, 1);
@ -5019,8 +5038,6 @@ DEFUN ("overlay-tree", Foverlay_tree, Soverlay_tree, 0, 1, 0,
} }
#endif #endif
/* Initialize the buffer routines. */ /* Initialize the buffer routines. */
void void
syms_of_buffer (void) syms_of_buffer (void)

View file

@ -220,6 +220,23 @@ extern ptrdiff_t advance_to_char_boundary (ptrdiff_t byte_pos);
/* Define the actual buffer data structures. */ /* Define the actual buffer data structures. */
/* This data structure stores the cache of a position and its line and
column number. The column number is counted in bytes. The line
number and column number don't consider narrowing. */
struct ts_linecol
{
/* A position in the buffer. */
ptrdiff_t pos;
/* The byte position of POS. */
ptrdiff_t byte_pos;
/* The line number of this position. */
ptrdiff_t line;
/* The column number (in bytes) of this position. Unlike Emacs'
convention, this is 0-based (because tree-sitter column is
0-based). Simply the byte offset from BOL (or BOB). */
ptrdiff_t col;
};
/* This data structure describes the actual text contents of a buffer. /* This data structure describes the actual text contents of a buffer.
It is shared between indirect buffers and their base buffer. */ It is shared between indirect buffers and their base buffer. */
@ -700,6 +717,19 @@ struct buffer
/* The interval tree containing this buffer's overlays. */ /* The interval tree containing this buffer's overlays. */
struct itree_tree *overlays; struct itree_tree *overlays;
/* Right now only tree-sitter makes use of this, so I don't want
non-tree-sitter build to pay for it. If something else can make
use of this, we can remove the gate. */
#ifdef HAVE_TREE_SITTER
/* Cache of line and column number of a position. The position cached
is usually near point. Tree-sitter uses this cache to calculate
line and column of the beginning and end of buffer edits. This
cache is refreshed in buffer edit functions, so it's always
up-to-date. Usually, the newly calculated position and line/column
are saved to this field. Initialized to position 1. */
struct ts_linecol ts_linecol_cache;
#endif
/* Changes in the buffer are recorded here for undo, and t means /* Changes in the buffer are recorded here for undo, and t means
don't record anything. This information belongs to the base don't record anything. This information belongs to the base
buffer of an indirect buffer. But we can't store it in the buffer of an indirect buffer. But we can't store it in the

View file

@ -543,6 +543,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
ptrdiff_t start_byte = CHAR_TO_BYTE (start); ptrdiff_t start_byte = CHAR_TO_BYTE (start);
ptrdiff_t old_end_byte = CHAR_TO_BYTE (end); ptrdiff_t old_end_byte = CHAR_TO_BYTE (end);
struct ts_linecol start_linecol
= treesit_linecol_maybe (start, start_byte,
current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (end, old_end_byte,
current_buffer->ts_linecol_cache);
#endif #endif
ptrdiff_t orig_end = end; ptrdiff_t orig_end = end;
@ -565,8 +571,11 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
update_compositions (start, end, CHECK_ALL); update_compositions (start, end, CHECK_ALL);
} }
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
treesit_record_change (start_byte, old_end_byte, ptrdiff_t new_end = orig_end + added;
CHAR_TO_BYTE (orig_end + added)); ptrdiff_t new_end_byte = CHAR_TO_BYTE (new_end);
treesit_record_change (start_byte, old_end_byte, new_end_byte,
start_linecol, old_end_linecol, new_end);
#endif #endif
return orig_end + added; return orig_end + added;

View file

@ -2234,6 +2234,19 @@ Both characters must have the same length of multi-byte form. */)
= !NILP (BVAR (current_buffer, enable_multibyte_characters)); = !NILP (BVAR (current_buffer, enable_multibyte_characters));
int fromc, toc; int fromc, toc;
#ifdef HAVE_TREE_SITTER
ptrdiff_t start_char = fix_position (start);
ptrdiff_t old_end_char = fix_position (end);
ptrdiff_t start_byte = CHAR_TO_BYTE (start_char);
ptrdiff_t old_end_byte = CHAR_TO_BYTE (old_end_char);
struct ts_linecol start_linecol
= treesit_linecol_maybe (start_char, start_byte,
current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (old_end_char, old_end_byte,
current_buffer->ts_linecol_cache);
#endif
restart: restart:
validate_region (&start, &end); validate_region (&start, &end);
@ -2334,7 +2347,8 @@ Both characters must have the same length of multi-byte form. */)
if (changed > 0) if (changed > 0)
{ {
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
treesit_record_change (changed, last_changed, last_changed); treesit_record_change (start_byte, old_end_byte, old_end_byte,
start_linecol, old_end_linecol, old_end_char);
#endif #endif
signal_after_change (changed, signal_after_change (changed,
last_changed - changed, last_changed - changed); last_changed - changed, last_changed - changed);
@ -2521,6 +2535,14 @@ It returns the number of characters changed. */)
} }
else else
{ {
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (pos, pos_byte,
current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (pos + 1, pos_byte + len,
start_linecol);
#endif
record_change (pos, 1); record_change (pos, 1);
while (str_len-- > 0) while (str_len-- > 0)
*p++ = *str++; *p++ = *str++;
@ -2528,12 +2550,13 @@ It returns the number of characters changed. */)
update_compositions (pos, pos + 1, CHECK_BORDER); update_compositions (pos, pos + 1, CHECK_BORDER);
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
/* In the previous branch, replace_range() notifies /* In the previous branch, replace_range() notifies
changes to tree-sitter, but in this branch, we changes to tree-sitter, but in this branch, we
modified buffer content manually, so we need to modified buffer content manually, so we need to
notify tree-sitter manually. */ notify tree-sitter manually. */
treesit_record_change (pos_byte, pos_byte + len, treesit_record_change (pos_byte, pos_byte + len,
pos_byte + len); pos_byte + len, start_linecol,
old_end_linecol, pos + 1);
#endif #endif
} }
characters_changed++; characters_changed++;
@ -4481,6 +4504,15 @@ ring. */)
start1_byte = CHAR_TO_BYTE (start1); start1_byte = CHAR_TO_BYTE (start1);
end2_byte = CHAR_TO_BYTE (end2); end2_byte = CHAR_TO_BYTE (end2);
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (start1, start1_byte,
current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (end2, end2_byte,
current_buffer->ts_linecol_cache);
#endif
/* Make sure the gap won't interfere, by moving it out of the text /* Make sure the gap won't interfere, by moving it out of the text
we will operate on. */ we will operate on. */
if (start1 < gap && gap < end2) if (start1 < gap && gap < end2)
@ -4620,10 +4652,8 @@ ring. */)
} }
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
/* I don't think it's common to transpose two far-apart regions, so treesit_record_change (start1_byte, end2_byte, end2_byte,
amalgamating the edit into one should be fine. This is what the start_linecol, old_end_linecol, end2);
signal_after_change below does, too. */
treesit_record_change (start1_byte, end2_byte, end2_byte);
#endif #endif
signal_after_change (start1, end2 - start1, end2 - start1); signal_after_change (start1, end2 - start1, end2 - start1);

View file

@ -891,6 +891,11 @@ insert_1_both (const char *string,
if (NILP (BVAR (current_buffer, enable_multibyte_characters))) if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
nchars = nbytes; nchars = nbytes;
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (PT, PT_BYTE, current_buffer->ts_linecol_cache);
#endif
if (prepare) if (prepare)
/* Do this before moving and increasing the gap, /* Do this before moving and increasing the gap,
because the before-change hooks might move the gap because the before-change hooks might move the gap
@ -945,7 +950,9 @@ insert_1_both (const char *string,
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
eassert (nbytes >= 0); eassert (nbytes >= 0);
eassert (PT_BYTE >= 0); eassert (PT_BYTE >= 0);
treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes);
treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes,
start_linecol, start_linecol, PT + nchars);
#endif #endif
adjust_point (nchars, nbytes); adjust_point (nchars, nbytes);
@ -1017,6 +1024,11 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
= count_size_as_multibyte (SDATA (string) + pos_byte, = count_size_as_multibyte (SDATA (string) + pos_byte,
nbytes); nbytes);
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (PT, PT_BYTE, current_buffer->ts_linecol_cache);
#endif
/* Do this before moving and increasing the gap, /* Do this before moving and increasing the gap,
because the before-change hooks might move the gap because the before-change hooks might move the gap
or make it smaller. */ or make it smaller. */
@ -1081,7 +1093,9 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
eassert (nbytes >= 0); eassert (nbytes >= 0);
eassert (PT_BYTE >= 0); eassert (PT_BYTE >= 0);
treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes);
treesit_record_change (PT_BYTE, PT_BYTE, PT_BYTE + nbytes,
start_linecol, start_linecol, PT + nchars);
#endif #endif
adjust_point (nchars, outgoing_nbytes); adjust_point (nchars, outgoing_nbytes);
@ -1094,7 +1108,8 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
GPT_ADDR (if not text_at_gap_tail). GPT_ADDR (if not text_at_gap_tail).
Contrary to insert_from_gap, this does not invalidate any cache, Contrary to insert_from_gap, this does not invalidate any cache,
nor update any markers, nor record any buffer modification information nor update any markers, nor record any buffer modification information
of any sort, with the single exception of notifying tree-sitter. */ of any sort, with the single exception of notifying tree-sitter and
updating tree-sitter linecol cache. */
void void
insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail) insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
{ {
@ -1103,6 +1118,8 @@ insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
ptrdiff_t ins_bytepos = GPT_BYTE; ptrdiff_t ins_bytepos = GPT_BYTE;
struct ts_linecol start_linecol
= treesit_linecol_maybe (GPT, GPT_BYTE, current_buffer->ts_linecol_cache);
#endif #endif
GAP_SIZE -= nbytes; GAP_SIZE -= nbytes;
@ -1123,7 +1140,9 @@ insert_from_gap_1 (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
eassert (nbytes >= 0); eassert (nbytes >= 0);
eassert (ins_bytepos >= 0); eassert (ins_bytepos >= 0);
treesit_record_change (ins_bytepos, ins_bytepos, ins_bytepos + nbytes);
treesit_record_change (ins_bytepos, ins_bytepos, ins_bytepos + nbytes,
start_linecol, start_linecol, ins_bytepos + nbytes);
#endif #endif
} }
@ -1186,6 +1205,8 @@ insert_from_buffer (struct buffer *buf,
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
ptrdiff_t obyte = PT_BYTE; ptrdiff_t obyte = PT_BYTE;
struct ts_linecol start_linecol
= treesit_linecol_maybe (opoint, obyte, current_buffer->ts_linecol_cache);
#endif #endif
insert_from_buffer_1 (buf, charpos, nchars, inherit); insert_from_buffer_1 (buf, charpos, nchars, inherit);
@ -1196,7 +1217,9 @@ insert_from_buffer (struct buffer *buf,
eassert (PT_BYTE >= BEG_BYTE); eassert (PT_BYTE >= BEG_BYTE);
eassert (obyte >= BEG_BYTE); eassert (obyte >= BEG_BYTE);
eassert (PT_BYTE >= obyte); eassert (PT_BYTE >= obyte);
treesit_record_change (obyte, obyte, PT_BYTE);
treesit_record_change (obyte, obyte, PT_BYTE,
start_linecol, start_linecol, PT);
#endif #endif
} }
@ -1481,6 +1504,14 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
if (nbytes_del <= 0 && inschars == 0) if (nbytes_del <= 0 && inschars == 0)
return; return;
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (from, from_byte, current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (to, to_byte, current_buffer->ts_linecol_cache);
#endif
ptrdiff_t insbeg_bytes, insend_bytes; ptrdiff_t insbeg_bytes, insend_bytes;
ptrdiff_t insbytes; ptrdiff_t insbytes;
unsigned char *insbeg_ptr; unsigned char *insbeg_ptr;
@ -1623,7 +1654,9 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
eassert (to_byte >= from_byte); eassert (to_byte >= from_byte);
eassert (outgoing_insbytes >= 0); eassert (outgoing_insbytes >= 0);
eassert (from_byte >= 0); eassert (from_byte >= 0);
treesit_record_change (from_byte, to_byte, from_byte + outgoing_insbytes);
treesit_record_change (from_byte, to_byte, from_byte + outgoing_insbytes,
start_linecol, old_end_linecol, from + inschars);
#endif #endif
/* Relocate point as if it were a marker. */ /* Relocate point as if it were a marker. */
@ -1948,6 +1981,13 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
nchars_del = to - from; nchars_del = to - from;
nbytes_del = to_byte - from_byte; nbytes_del = to_byte - from_byte;
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (from, from_byte, current_buffer->ts_linecol_cache);
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (to, to_byte, current_buffer->ts_linecol_cache);
#endif
/* Make sure the gap is somewhere in or next to what we are deleting. */ /* Make sure the gap is somewhere in or next to what we are deleting. */
if (from > GPT) if (from > GPT)
gap_right (from, from_byte); gap_right (from, from_byte);
@ -2007,7 +2047,8 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
#ifdef HAVE_TREE_SITTER #ifdef HAVE_TREE_SITTER
eassert (from_byte <= to_byte); eassert (from_byte <= to_byte);
eassert (from_byte >= 0); eassert (from_byte >= 0);
treesit_record_change (from_byte, to_byte, from_byte); treesit_record_change (from_byte, to_byte, from_byte,
start_linecol, old_end_linecol, from);
#endif #endif
return deletion; return deletion;

View file

@ -864,6 +864,176 @@ loaded or the file name couldn't be determined, return nil. */)
} }
/*** Linecol functions */
#define TREE_SITTER_DEBUG_LINECOL true
static void
treesit_print_linecol (struct ts_linecol linecol)
{
printf ("{ line=%ld col=%ld pos=%ld byte_pos=%ld }\n", linecol.line, linecol.col, linecol.pos, linecol.byte_pos);
}
static void
treesit_validate_linecol (const char *name, struct ts_linecol linecol)
{
eassert (linecol.pos <= Z);
if (linecol.pos > Z)
{
printf ("OUT OF RANGE\n");
treesit_print_linecol (linecol);
printf ("Z: %ld\n", Z);
}
ptrdiff_t true_line_count = count_lines (BEG, linecol.byte_pos) + 1;
eassert (true_line_count == linecol.line);
if (true_line_count != linecol.line)
{
printf ("MISMATCH\n");
printf ("%s: ", name);
treesit_print_linecol (linecol);
printf ("true: %ld\n", true_line_count);
}
}
/* Calculate and return the line and column number of BYTE_POS by
scanning newlines from CACHE. CACHE must be valid. */
static struct ts_linecol
treesit_linecol_of_pos (ptrdiff_t target_pos, ptrdiff_t target_byte_pos,
struct ts_linecol cache)
{
eassert (target_pos == target_byte_pos);
if (TREE_SITTER_DEBUG_LINECOL)
{
/* eassert (true_line_count == cache.line); */
treesit_validate_linecol ("cache", cache);
}
/* When we finished searching for newlines between CACHE and
TARGET_POS, BYTE_POS_2 is at TARGET_POS, and BYTE_POS_1 is at the
previous newline. If TARGET_POS happends to be on a newline,
BYTE_POS_1 will be on that position. BYTE_POS_1 is used for
calculating the column. (If CACHE and TARGET_POS are in the same
line, BYTE_POS_1 is unset and we don't use it.) */
ptrdiff_t byte_pos_1 = 0;
ptrdiff_t pos_2 = 0;
ptrdiff_t byte_pos_2 = 0;
/* Number of lines between CACHE and TARGET_POS. */
ptrdiff_t line_delta = 0;
if (target_byte_pos == cache.byte_pos)
return cache;
/* Search forward. */
if (cache.byte_pos < target_byte_pos)
{
pos_2 = cache.pos;
byte_pos_2 = cache.byte_pos;
while (byte_pos_2 < target_byte_pos)
{
ptrdiff_t counted = 0;
pos_2 = find_newline (pos_2, byte_pos_2, target_pos, target_byte_pos,
1, &counted, &byte_pos_2, false);
if (counted > 0) byte_pos_1 = byte_pos_2;
line_delta += counted;
/* printf ("byte_pos_2=%ld counted=%ld line_delta=%ld\n", byte_pos_2, counted, line_delta); */
}
eassert (byte_pos_2 == target_byte_pos);
/* At this point, byte_pos_2 is at target_pos, and byte_pos_1 is
at the previous newline if we went across any. */
struct ts_linecol target_linecol;
target_linecol.pos = target_pos;
target_linecol.byte_pos = target_byte_pos;
target_linecol.line = cache.line + line_delta;
/* If we moved across any newline, use the previous newline to
calculate the column; if we stayed at the same line, use the
cached column to calculate the new column. */
target_linecol.col = line_delta > 0
? target_byte_pos - byte_pos_1
: target_byte_pos - cache.byte_pos + cache.col;
if (TREE_SITTER_DEBUG_LINECOL)
{
/* eassert (true_line_count == target_linecol.line); */
treesit_validate_linecol ("target", target_linecol);
}
return target_linecol;
}
/* Search backward. */
printf ("BACK\n");
pos_2 = cache.pos + 1;
/* The "+1" Cancels out with the "-1" in the first iteration. */
byte_pos_2 = cache.byte_pos + 1;
while (byte_pos_2 > target_byte_pos)
{
ptrdiff_t counted = 0;
/* pos_2 - 1 won't underflow because of the loop condition. */
pos_2 = find_newline (pos_2 - 1, byte_pos_2 - 1,
target_pos, target_byte_pos,
-1, &counted, &byte_pos_2, false);
line_delta += counted;
}
eassert (byte_pos_2 == target_byte_pos);
/* At this point, pos_2 is at target_pos. */
struct ts_linecol target_linecol;
target_linecol.pos = target_pos;
target_linecol.byte_pos = target_byte_pos;
target_linecol.line = cache.line + line_delta;
eassert (cache.line + line_delta > 0);
/* Calculate the column. */
if (line_delta == 0)
{
target_linecol.col = cache.col - (cache.byte_pos - target_byte_pos);
}
else
{
/* We need to find the previous newline in order to calculate the
column. Use POS_2 instead of POS_2 - 1, this way, if POS_2 is
at BOL, we stay in the same place. */
pos_2 = find_newline (pos_2, byte_pos_2, BEG, BEG_BYTE, -1,
NULL, &byte_pos_2, false);
target_linecol.col = target_byte_pos - byte_pos_2;
}
if (TREE_SITTER_DEBUG_LINECOL)
{
treesit_validate_linecol ("target", target_linecol);
}
return target_linecol;
}
/* Return a TSPoint given POS and VISIBLE_BEG. VISIBLE_BEG must be
before POS. */
static TSPoint
treesit_make_ts_point (struct ts_linecol visible_beg,
struct ts_linecol pos)
{
TSPoint point;
if (visible_beg.line == pos.line)
{
point.row = 0;
point.column = pos.col - visible_beg.col;
eassert (point.column >= 0);
}
else
{
point.row = pos.line - visible_beg.line;
eassert (point.row > 0);
point.column = pos.col;
}
return point;
}
/*** Parsing functions */ /*** Parsing functions */
static void static void
@ -879,28 +1049,34 @@ treesit_check_parser (Lisp_Object obj)
larger than UINT32_MAX. */ larger than UINT32_MAX. */
static inline void static inline void
treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte, treesit_tree_edit_1 (TSTree *tree, ptrdiff_t start_byte,
ptrdiff_t old_end_byte, ptrdiff_t new_end_byte) ptrdiff_t old_end_byte, ptrdiff_t new_end_byte,
TSPoint start_point, TSPoint old_end_point,
TSPoint new_end_point)
{ {
eassert (start_byte >= 0); eassert (start_byte >= 0);
eassert (start_byte <= old_end_byte); eassert (start_byte <= old_end_byte);
eassert (start_byte <= new_end_byte); eassert (start_byte <= new_end_byte);
TSPoint dummy_point = {0, 0};
eassert (start_byte <= UINT32_MAX); eassert (start_byte <= UINT32_MAX);
eassert (old_end_byte <= UINT32_MAX); eassert (old_end_byte <= UINT32_MAX);
eassert (new_end_byte <= UINT32_MAX); eassert (new_end_byte <= UINT32_MAX);
TSInputEdit edit = {(uint32_t) start_byte, TSInputEdit edit = {(uint32_t) start_byte,
(uint32_t) old_end_byte, (uint32_t) old_end_byte,
(uint32_t) new_end_byte, (uint32_t) new_end_byte,
dummy_point, dummy_point, dummy_point}; start_point, old_end_point, new_end_point};
ts_tree_edit (tree, &edit); ts_tree_edit (tree, &edit);
} }
/* Update each parser's tree after the user made an edit. This /* Update each parser's tree after the user made an edit. This function
function does not parse the buffer and only updates the tree, so it does not parse the buffer and only updates the tree, so it should be
should be very fast. */ very fast. If the caller knows there's no parser in the current
void buffer, they can pass empty linecol for
treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte, START/OLD_END/NEW_END_linecol. */
ptrdiff_t new_end_byte) static void
treesit_record_change_1 (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
ptrdiff_t new_end_byte,
struct ts_linecol start_linecol,
struct ts_linecol old_end_linecol,
struct ts_linecol new_end_linecol)
{ {
struct buffer *base_buffer = current_buffer; struct buffer *base_buffer = current_buffer;
if (current_buffer->base_buffer) if (current_buffer->base_buffer)
@ -920,12 +1096,15 @@ treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
{ {
eassert (start_byte <= old_end_byte); eassert (start_byte <= old_end_byte);
eassert (start_byte <= new_end_byte); eassert (start_byte <= new_end_byte);
/* Think the recorded change as a delete followed by an /* Before sending the edit to tree-sitter, we need to first
insert, and think of them as moving unchanged text back clip the beg/end to visible_beg and visible_end of the
and forth. After all, the whole point of updating the parser. A tip for understanding the code below: think the
tree is to update the position of unchanged text. */ recorded change as a delete followed by an insert, and
ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg; think of them as moving unchanged text back and forth.
ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end; After all, the whole point of updating the tree is to
update the position of unchanged text. */
const ptrdiff_t visible_beg = XTS_PARSER (lisp_parser)->visible_beg;
const ptrdiff_t visible_end = XTS_PARSER (lisp_parser)->visible_end;
eassert (visible_beg >= 0); eassert (visible_beg >= 0);
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
@ -949,9 +1128,9 @@ treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
eassert (start_offset <= old_end_offset); eassert (start_offset <= old_end_offset);
eassert (start_offset <= new_end_offset); eassert (start_offset <= new_end_offset);
treesit_tree_edit_1 (tree, start_offset, old_end_offset, /* We have the correct offset for start/end now, but don't
new_end_offset); update the tree yet, because we still need to calculate the
XTS_PARSER (lisp_parser)->need_reparse = true; TSPoint, which needs the updated visible_beg linecol. */
/* VISIBLE_BEG/END records tree-sitter's range of view in /* VISIBLE_BEG/END records tree-sitter's range of view in
the buffer. We need to adjust them when tree-sitter's 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,
visi_beg_delta = (old_end_byte < visible_beg visi_beg_delta = (old_end_byte < visible_beg
? new_end_byte - old_end_byte : 0); ? new_end_byte - old_end_byte : 0);
XTS_PARSER (lisp_parser)->visible_beg = visible_beg + visi_beg_delta; ptrdiff_t old_visi_beg = visible_beg;
XTS_PARSER (lisp_parser)->visible_end = (visible_end struct ts_linecol old_visi_beg_linecol
+ visi_beg_delta = XTS_PARSER (lisp_parser)->visi_beg_linecol;
+ (new_end_offset /* struct ts_linecol old_visi_end_linecol */
- old_end_offset)); /* = XTS_PARSER (lisp_parser)->visi_end_linecol; */
const ptrdiff_t new_visible_beg = visible_beg + visi_beg_delta;
const ptrdiff_t new_visible_end
= (visible_end + visi_beg_delta
+ (new_end_offset - old_end_offset));
XTS_PARSER (lisp_parser)->visible_beg = new_visible_beg;
XTS_PARSER (lisp_parser)->visible_end = new_visible_end;
XTS_PARSER (lisp_parser)->visi_beg_linecol
= treesit_linecol_of_pos (BYTE_TO_CHAR (new_visible_beg),
new_visible_beg,
old_visi_beg <= start_byte
? old_visi_beg_linecol
: start_linecol);
/* FIXME: computing visi_end_linecol from new_end_linecol
could be expensive, we need a more efficient way to compute
it. */
XTS_PARSER (lisp_parser)->visi_end_linecol
= treesit_linecol_of_pos (BYTE_TO_CHAR (new_visible_end),
new_visible_end,
new_end_linecol);
eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0); eassert (XTS_PARSER (lisp_parser)->visible_beg >= 0);
eassert (XTS_PARSER (lisp_parser)->visible_beg eassert (XTS_PARSER (lisp_parser)->visible_beg
<= XTS_PARSER (lisp_parser)->visible_end); <= XTS_PARSER (lisp_parser)->visible_end);
/* Now, calculate TSPoints and finally update the tree. */
struct ts_linecol new_begv_linecol
= XTS_PARSER (lisp_parser)->visi_beg_linecol;
TSPoint old_end_point = treesit_make_ts_point (old_visi_beg_linecol,
old_end_linecol);
TSPoint start_point = treesit_make_ts_point (new_begv_linecol,
start_linecol);
TSPoint new_end_point = treesit_make_ts_point (new_begv_linecol,
new_end_linecol);
treesit_tree_edit_1 (tree, start_offset, old_end_offset,
new_end_offset, start_point, old_end_point,
new_end_point);
XTS_PARSER (lisp_parser)->need_reparse = true;
} }
} }
} }
/* Return the linecol of POS, calculated from CACHE. But if there's no
parser in the current buffer, skip calculation and return an empty
linecol instead. */
struct ts_linecol
treesit_linecol_maybe (ptrdiff_t pos, ptrdiff_t pos_byte,
struct ts_linecol cache)
{
if (NILP (BVAR (current_buffer, ts_parser_list)))
return TREESIT_EMPTY_LINECOL;
return treesit_linecol_of_pos (pos, pos_byte, cache);
}
/* 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.
This is a wrapper over treesit_record_change that does a bit more
boilerplate work: it (optionally) calculates linecol for new_end,
pass all the positions into treesit_record_change_1 which does the
real work, and finally (optionally) sets buffer's linecol cache to
new_end's linecol.
If NEW_END is next to NEW_END_BYTE in the arglist, caller might
accidentally swap them, so I placed NEW_END at the end of the
arglist. */
void
treesit_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
ptrdiff_t new_end_byte,
struct ts_linecol start_linecol,
struct ts_linecol old_end_linecol,
ptrdiff_t new_end)
{
struct ts_linecol new_end_linecol
= treesit_linecol_maybe (new_end, new_end_byte, start_linecol);
treesit_record_change_1 (start_byte, old_end_byte, new_end_byte,
start_linecol, old_end_linecol, new_end_linecol);
treesit_print_linecol (new_end_linecol);
if (new_end_linecol.pos != 0)
current_buffer->ts_linecol_cache = new_end_linecol;
}
static TSRange *treesit_make_ts_ranges (Lisp_Object, Lisp_Object, static TSRange *treesit_make_ts_ranges (Lisp_Object, Lisp_Object,
uint32_t *); uint32_t *);
@ -1046,6 +1305,7 @@ treesit_sync_visible_region (Lisp_Object parser)
ptrdiff_t visible_beg = XTS_PARSER (parser)->visible_beg; ptrdiff_t visible_beg = XTS_PARSER (parser)->visible_beg;
ptrdiff_t visible_end = XTS_PARSER (parser)->visible_end; ptrdiff_t visible_end = XTS_PARSER (parser)->visible_end;
eassert (0 <= visible_beg); eassert (0 <= visible_beg);
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
@ -1066,39 +1326,72 @@ treesit_sync_visible_region (Lisp_Object parser)
from ________|xxxx|__ from ________|xxxx|__
to |xxxx|__________ */ to |xxxx|__________ */
struct ts_linecol buffer_linecol_cache = buffer->ts_linecol_cache;
struct ts_linecol visi_beg_linecol = XTS_PARSER (parser)->visi_beg_linecol;
struct ts_linecol visi_end_linecol = XTS_PARSER (parser)->visi_end_linecol;
struct ts_linecol buffer_begv_linecol
= treesit_linecol_of_pos (BUF_BEGV (buffer), BUF_BEGV_BYTE (buffer),
visi_beg_linecol);
struct ts_linecol buffer_zv_linecol
= treesit_linecol_of_pos (BUF_ZV (buffer), BUF_ZV_BYTE (buffer),
buffer_linecol_cache);
eassert (visi_beg_linecol.byte_pos == visible_beg);
/* 1. Make sure visible_beg <= BUF_BEGV_BYTE. */ /* 1. Make sure visible_beg <= BUF_BEGV_BYTE. */
if (visible_beg > BUF_BEGV_BYTE (buffer)) if (visible_beg > BUF_BEGV_BYTE (buffer))
{ {
TSPoint new_end = treesit_make_ts_point (buffer_begv_linecol,
visi_beg_linecol);
/* Tree-sitter sees: insert at the beginning. */ /* Tree-sitter sees: insert at the beginning. */
treesit_tree_edit_1 (tree, 0, 0, visible_beg - BUF_BEGV_BYTE (buffer)); treesit_tree_edit_1 (tree, 0, 0, visible_beg - BUF_BEGV_BYTE (buffer),
TREESIT_TS_POINT_1_0, TREESIT_TS_POINT_1_0,
new_end);
visible_beg = BUF_BEGV_BYTE (buffer); visible_beg = BUF_BEGV_BYTE (buffer);
visi_beg_linecol = buffer_begv_linecol;
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
} }
/* 2. Make sure visible_end = BUF_ZV_BYTE. */ /* 2. Make sure visible_end = BUF_ZV_BYTE. */
if (visible_end < BUF_ZV_BYTE (buffer)) if (visible_end < BUF_ZV_BYTE (buffer))
{ {
TSPoint start = treesit_make_ts_point (visi_beg_linecol,
visi_end_linecol);
TSPoint new_end = treesit_make_ts_point (visi_beg_linecol,
buffer_zv_linecol);
/* Tree-sitter sees: insert at the end. */ /* Tree-sitter sees: insert at the end. */
treesit_tree_edit_1 (tree, visible_end - visible_beg, treesit_tree_edit_1 (tree, visible_end - visible_beg,
visible_end - visible_beg, visible_end - visible_beg,
BUF_ZV_BYTE (buffer) - visible_beg); BUF_ZV_BYTE (buffer) - visible_beg,
start, start, new_end);
visible_end = BUF_ZV_BYTE (buffer); visible_end = BUF_ZV_BYTE (buffer);
visi_end_linecol = buffer_zv_linecol;
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
} }
else if (visible_end > BUF_ZV_BYTE (buffer)) else if (visible_end > BUF_ZV_BYTE (buffer))
{ {
TSPoint start = treesit_make_ts_point (visi_beg_linecol,
buffer_zv_linecol);
TSPoint old_end = treesit_make_ts_point (visi_beg_linecol,
visi_end_linecol);
/* Tree-sitter sees: delete at the end. */ /* Tree-sitter sees: delete at the end. */
treesit_tree_edit_1 (tree, BUF_ZV_BYTE (buffer) - visible_beg, treesit_tree_edit_1 (tree, BUF_ZV_BYTE (buffer) - visible_beg,
visible_end - visible_beg, visible_end - visible_beg,
BUF_ZV_BYTE (buffer) - visible_beg); BUF_ZV_BYTE (buffer) - visible_beg,
start, old_end, start);
visible_end = BUF_ZV_BYTE (buffer); visible_end = BUF_ZV_BYTE (buffer);
visi_end_linecol = buffer_zv_linecol;
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
} }
/* 3. Make sure visible_beg = BUF_BEGV_BYTE. */ /* 3. Make sure visible_beg = BUF_BEGV_BYTE. */
if (visible_beg < BUF_BEGV_BYTE (buffer)) if (visible_beg < BUF_BEGV_BYTE (buffer))
{ {
TSPoint old_end = treesit_make_ts_point (visi_beg_linecol,
buffer_begv_linecol);
/* Tree-sitter sees: delete at the beginning. */ /* Tree-sitter sees: delete at the beginning. */
treesit_tree_edit_1 (tree, 0, BUF_BEGV_BYTE (buffer) - visible_beg, 0); treesit_tree_edit_1 (tree, 0, BUF_BEGV_BYTE (buffer) - visible_beg, 0,
TREESIT_TS_POINT_1_0, old_end,
TREESIT_TS_POINT_1_0);
visible_beg = BUF_BEGV_BYTE (buffer); visible_beg = BUF_BEGV_BYTE (buffer);
visi_beg_linecol = buffer_begv_linecol;
eassert (visible_beg <= visible_end); eassert (visible_beg <= visible_end);
} }
eassert (0 <= visible_beg); eassert (0 <= visible_beg);
@ -1108,6 +1401,8 @@ treesit_sync_visible_region (Lisp_Object parser)
XTS_PARSER (parser)->visible_beg = visible_beg; XTS_PARSER (parser)->visible_beg = visible_beg;
XTS_PARSER (parser)->visible_end = visible_end; XTS_PARSER (parser)->visible_end = visible_end;
XTS_PARSER (parser)->visi_beg_linecol = visi_beg_linecol;
XTS_PARSER (parser)->visi_end_linecol = visi_end_linecol;
/* Fix ranges so that the ranges stays with in visible_end. Here we /* Fix ranges so that the ranges stays with in visible_end. Here we
try to do minimal work so that the ranges is minimally correct and 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,
lisp_parser->need_to_gc_buffer = false; lisp_parser->need_to_gc_buffer = false;
lisp_parser->within_reparse = false; lisp_parser->within_reparse = false;
eassert (lisp_parser->visible_beg <= lisp_parser->visible_end); eassert (lisp_parser->visible_beg <= lisp_parser->visible_end);
struct buffer *old_buf = current_buffer;
set_buffer_internal (XBUFFER (buffer));
/* treesit_linecol_of_pos doesn't signal, so no need to
unwind-protect. */
lisp_parser->visi_beg_linecol = treesit_linecol_of_pos (BEGV, BEGV_BYTE,
TREESIT_BOB_LINECOL);
lisp_parser->visi_end_linecol
= treesit_linecol_of_pos (ZV, ZV_BYTE, lisp_parser->visi_beg_linecol);
set_buffer_internal (old_buf);
return make_lisp_ptr (lisp_parser, Lisp_Vectorlike); return make_lisp_ptr (lisp_parser, Lisp_Vectorlike);
} }
@ -4376,6 +4684,66 @@ nodes in the subtree, including NODE. */)
} }
} }
DEFUN ("treesit--linecol-at", Ftreesit__linecol_at,
Streesit__linecol_at, 1, 1, 0,
doc: /* Test buffer-local linecol cache.
Calculate the line and column at POS using the buffer-local cache,
return the line and column in the form of
(LINE . COL)
This is used for testing and debugging only. */)
(Lisp_Object pos)
{
CHECK_NUMBER (pos);
struct ts_linecol pos_linecol
= treesit_linecol_of_pos (XFIXNUM (pos), CHAR_TO_BYTE (XFIXNUM (pos)),
current_buffer->ts_linecol_cache);
return Fcons (make_fixnum (pos_linecol.line), make_fixnum (pos_linecol.col));
}
DEFUN ("treesit--linecol-cache-set", Ftreesit__linecol_cache_set,
Streesit__linecol_cache_set, 4, 4, 0,
doc: /* Set the linecol cache for the current buffer.
This is used for testing and debugging only. */)
(Lisp_Object line, Lisp_Object col, Lisp_Object pos, Lisp_Object bytepos)
{
CHECK_FIXNUM (line);
CHECK_FIXNUM (col);
CHECK_FIXNUM (pos);
CHECK_FIXNUM (bytepos);
current_buffer->ts_linecol_cache.line = XFIXNUM (line);
current_buffer->ts_linecol_cache.col = XFIXNUM (col);
current_buffer->ts_linecol_cache.pos = XFIXNUM (pos);
current_buffer->ts_linecol_cache.byte_pos = XFIXNUM (bytepos);
return Qnil;
}
DEFUN ("treesit--linecol-cache", Ftreesit__linecol_cache,
Streesit__linecol_cache, 0, 0, 0,
doc: /* Return the buffer-local linecol cache for debugging.
Return a plist (:line LINE :col COL :pos POS :bytepos BYTEPOS). This is
used for testing and debugging only. */)
(void)
{
struct ts_linecol cache = current_buffer->ts_linecol_cache;
Lisp_Object plist = (list4 (QCpos, make_fixnum (cache.pos),
QCbytepos, make_fixnum (cache.byte_pos)));
plist = Fcons (make_fixnum (cache.col), plist);
plist = Fcons (QCcol, plist);
plist = Fcons (make_fixnum (cache.line), plist);
plist = Fcons (QCline, plist);
return plist;
}
#endif /* HAVE_TREE_SITTER */ #endif /* HAVE_TREE_SITTER */
DEFUN ("treesit-available-p", Ftreesit_available_p, DEFUN ("treesit-available-p", Ftreesit_available_p,
@ -4418,6 +4786,11 @@ syms_of_treesit (void)
DEFSYM (QCequal, ":equal"); DEFSYM (QCequal, ":equal");
DEFSYM (QCmatch, ":match"); DEFSYM (QCmatch, ":match");
DEFSYM (QCpred, ":pred"); DEFSYM (QCpred, ":pred");
DEFSYM (QCline, ":line");
DEFSYM (QCcol, ":col");
DEFSYM (QCpos, ":pos");
DEFSYM (QCbytepos, ":bytepos");
DEFSYM (Qnot_found, "not-found"); DEFSYM (Qnot_found, "not-found");
DEFSYM (Qsymbol_error, "symbol-error"); DEFSYM (Qsymbol_error, "symbol-error");
@ -4649,6 +5022,10 @@ applies to LANGUAGE-A will be redirected to LANGUAGE-B instead. */);
defsubr (&Streesit_induce_sparse_tree); defsubr (&Streesit_induce_sparse_tree);
defsubr (&Streesit_node_match_p); defsubr (&Streesit_node_match_p);
defsubr (&Streesit_subtree_stat); defsubr (&Streesit_subtree_stat);
defsubr (&Streesit__linecol_at);
defsubr (&Streesit__linecol_cache);
defsubr (&Streesit__linecol_cache_set);
#endif /* HAVE_TREE_SITTER */ #endif /* HAVE_TREE_SITTER */
defsubr (&Streesit_available_p); defsubr (&Streesit_available_p);
#ifdef WINDOWSNT #ifdef WINDOWSNT

View file

@ -26,6 +26,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <tree_sitter/api.h> #include <tree_sitter/api.h>
#include "lisp.h" #include "lisp.h"
#include "buffer.h"
INLINE_HEADER_BEGIN INLINE_HEADER_BEGIN
@ -97,6 +98,14 @@ struct Lisp_TS_Parser
(ref:visible-beg-null) in treesit.c for more explanation. */ (ref:visible-beg-null) in treesit.c for more explanation. */
ptrdiff_t visible_beg; ptrdiff_t visible_beg;
ptrdiff_t visible_end; ptrdiff_t visible_end;
/* Caches the line and column number of VISIBLE_BEG. It's always
valid and matches VISIBLE_BEG (because it's updated at each buffer
edit). (It has to be, because in treesit_record_change, we need to
calculate the line/col offset of old_end_linecol, the exact reason
why is left as an exercise to the reader.) */
struct ts_linecol visi_beg_linecol;
/* Similar to VISI_BEG_LINECOL but caches VISIBLE_END. */
struct ts_linecol visi_end_linecol;
/* This counter is incremented every time a change is made to the /* This counter is incremented every time a change is made to the
buffer in treesit_record_change. The node retrieved from this parser buffer in treesit_record_change. The node retrieved from this parser
inherits this timestamp. This way we can make sure the node is inherits this timestamp. This way we can make sure the node is
@ -222,7 +231,19 @@ CHECK_TS_COMPILED_QUERY (Lisp_Object query)
INLINE_HEADER_END INLINE_HEADER_END
extern void treesit_record_change (ptrdiff_t, ptrdiff_t, ptrdiff_t); /* A linecol_cache that points to BOB, this is always valid. */
static const struct ts_linecol TREESIT_BOB_LINECOL = { 1, 1, 1, 0 };
/* An uninitialized linecol. */
static const struct ts_linecol TREESIT_EMPTY_LINECOL = { 0, 0, 0, 0 };
static const TSPoint TREESIT_TS_POINT_1_0 = { 1, 0 };
extern struct ts_linecol linecol_offset (struct ts_linecol,
struct ts_linecol);
extern struct ts_linecol treesit_linecol_maybe (ptrdiff_t, ptrdiff_t,
struct ts_linecol);
extern void treesit_record_change (ptrdiff_t, ptrdiff_t, ptrdiff_t,
struct ts_linecol, struct ts_linecol,
ptrdiff_t);
extern Lisp_Object make_treesit_parser (Lisp_Object, TSParser *, TSTree *, extern Lisp_Object make_treesit_parser (Lisp_Object, TSParser *, TSTree *,
Lisp_Object, Lisp_Object); Lisp_Object, Lisp_Object);
extern Lisp_Object make_treesit_node (Lisp_Object, TSNode); extern Lisp_Object make_treesit_node (Lisp_Object, TSNode);

View file

@ -224,6 +224,51 @@
(kill-buffer base) (kill-buffer base)
(kill-buffer indirect)))) (kill-buffer indirect))))
;;; Linecol
(ert-deftest treesit-linecol-basic ()
"Tests for basic lincol synchronization."
(with-temp-buffer
(should (equal (treesit--linecol-cache)
'(:line 1 :col 0 :pos 1 :bytepos 1)))
(should (equal (treesit--linecol-at (point))
'(1 . 0)))
(insert "\n")
;; Buffer content: a single newline.
(should (equal (treesit--linecol-at (point))
'(2 . 0)))
(treesit--linecol-cache-set 2 0 2 2)
(should (equal (treesit--linecol-cache)
'(:line 2 :col 0 :pos 2 :bytepos 2)))
(goto-char (point-min))
(should (equal (treesit--linecol-at (point))
'(1 . 0)))
(insert "0123456789")
;; Buffer content: ten chars followed by a newline.
(treesit--linecol-cache-set 1 0 1 1)
(should (equal (treesit--linecol-at (point))
'(1 . 10)))
(goto-char (point-max))
(should (equal (treesit--linecol-at (point))
'(2 . 0)))
(treesit--linecol-cache-set 1 5 6 6)
(should (equal (treesit--linecol-at (point))
'(2 . 0)))
(treesit--linecol-cache-set 2 0 12 12)
;; Position 6 is in the middle of the first line.
(should (equal (treesit--linecol-at 6)
'(1 . 5)))
;; Position 11 is at the end of the line.
(should (equal (treesit--linecol-at 11)
'(1 . 10)))
))
;;; Tree traversal ;;; Tree traversal
(ert-deftest treesit-search-subtree () (ert-deftest treesit-search-subtree ()