Skip to content

Commit 82c9385

Browse files
author
Max Brunsfeld
authored
Merge pull request tree-sitter#38 from tree-sitter/changed-ranges
Report text ranges whose syntactic meaning have changed after re-parsing
2 parents 6bde4c5 + 5638fea commit 82c9385

26 files changed

+1030
-326
lines changed

include/tree_sitter/runtime.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,25 @@ typedef struct {
3434
void (*log)(void *payload, TSLogType, const char *);
3535
} TSLogger;
3636

37-
typedef struct {
38-
size_t position;
39-
size_t chars_inserted;
40-
size_t chars_removed;
41-
} TSInputEdit;
42-
4337
typedef struct {
4438
size_t row;
4539
size_t column;
4640
} TSPoint;
4741

42+
typedef struct {
43+
size_t start_byte;
44+
size_t bytes_removed;
45+
size_t bytes_added;
46+
TSPoint start_point;
47+
TSPoint extent_removed;
48+
TSPoint extent_added;
49+
} TSInputEdit;
50+
51+
typedef struct {
52+
TSPoint start;
53+
TSPoint end;
54+
} TSRange;
55+
4856
typedef struct {
4957
const void *data;
5058
size_t offset[3];
@@ -98,6 +106,7 @@ void ts_document_set_logger(TSDocument *, TSLogger);
98106
void ts_document_print_debugging_graphs(TSDocument *, bool);
99107
void ts_document_edit(TSDocument *, TSInputEdit);
100108
int ts_document_parse(TSDocument *);
109+
int ts_document_parse_and_get_changed_ranges(TSDocument *, TSRange **, size_t *);
101110
void ts_document_invalidate(TSDocument *);
102111
TSNode ts_document_root_node(const TSDocument *);
103112
size_t ts_document_parse_count(const TSDocument *);

spec/helpers/point_helpers.cc

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "./point_helpers.h"
12
#include <string>
23
#include <ostream>
34
#include "runtime/length.h"
@@ -9,8 +10,12 @@ bool operator==(const TSPoint &left, const TSPoint &right) {
910
return left.row == right.row && left.column == right.column;
1011
}
1112

12-
std::ostream &operator<<(std::ostream &stream, const TSPoint &point) {
13-
return stream << "{" << point.row << ", " << point.column << "}";
13+
bool operator==(const TSRange &left, const TSRange &right) {
14+
return left.start == right.start && left.end == right.end;
15+
}
16+
17+
bool operator==(const TSLength &left, const TSLength &right) {
18+
return ts_length_eq(left, right);
1419
}
1520

1621
bool operator<(const TSPoint &left, const TSPoint &right) {
@@ -23,3 +28,16 @@ bool operator<(const TSPoint &left, const TSPoint &right) {
2328
bool operator>(const TSPoint &left, const TSPoint &right) {
2429
return right < left;
2530
}
31+
32+
std::ostream &operator<<(std::ostream &stream, const TSPoint &point) {
33+
return stream << "{" << point.row << ", " << point.column << "}";
34+
}
35+
36+
std::ostream &operator<<(std::ostream &stream, const TSRange &range) {
37+
return stream << "{" << range.start << ", " << range.end << "}";
38+
}
39+
40+
ostream &operator<<(ostream &stream, const TSLength &length) {
41+
return stream << "{chars:" << length.chars << ", bytes:" <<
42+
length.bytes << ", extent:" << length.extent << "}";
43+
}

spec/helpers/point_helpers.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
#ifndef HELPERS_POINT_HELPERS_H_
22
#define HELPERS_POINT_HELPERS_H_
33

4+
#include "runtime/length.h"
5+
#include <ostream>
6+
47
bool operator==(const TSPoint &left, const TSPoint &right);
58

69
bool operator<(const TSPoint &left, const TSPoint &right);
710

811
bool operator>(const TSPoint &left, const TSPoint &right);
912

13+
bool operator==(const TSRange &left, const TSRange &right);
14+
15+
bool operator==(const TSLength &left, const TSLength &right);
16+
1017
std::ostream &operator<<(std::ostream &stream, const TSPoint &point);
1118

19+
std::ostream &operator<<(std::ostream &stream, const TSRange &range);
20+
21+
std::ostream &operator<<(std::ostream &stream, const TSLength &length);
22+
1223
#endif // HELPERS_POINT_HELPERS_H_

spec/helpers/scope_sequence.cc

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include "./scope_sequence.h"
2+
3+
#include "bandit/bandit.h"
4+
#include <sstream>
5+
#include "helpers/stream_methods.h"
6+
#include "helpers/point_helpers.h"
7+
8+
using std::string;
9+
using std::cout;
10+
11+
static void append_text_to_scope_sequence(ScopeSequence *sequence,
12+
ScopeStack *current_scopes,
13+
const std::string &text,
14+
size_t length) {
15+
for (size_t i = 0; i < length; i++) {
16+
string character(1, text[sequence->size()]);
17+
sequence->push_back(*current_scopes);
18+
sequence->back().push_back("'" + character + "'");
19+
}
20+
}
21+
22+
static void append_to_scope_sequence(ScopeSequence *sequence,
23+
ScopeStack *current_scopes,
24+
TSNode node, TSDocument *document,
25+
const std::string &text) {
26+
append_text_to_scope_sequence(sequence, current_scopes, text, ts_node_start_byte(node) - sequence->size());
27+
28+
string scope = ts_node_type(node, document);
29+
current_scopes->push_back(scope);
30+
size_t child_count = ts_node_child_count(node);
31+
if (child_count > 0) {
32+
for (size_t i = 0; i < child_count; i++) {
33+
TSNode child = ts_node_child(node, i);
34+
append_to_scope_sequence(sequence, current_scopes, child, document, text);
35+
}
36+
} else {
37+
size_t length = ts_node_end_byte(node) - ts_node_start_byte(node);
38+
append_text_to_scope_sequence(sequence, current_scopes, text, length);
39+
}
40+
current_scopes->pop_back();
41+
}
42+
43+
ScopeSequence build_scope_sequence(TSDocument *document, const std::string &text) {
44+
ScopeSequence sequence;
45+
ScopeStack current_scopes;
46+
TSNode node = ts_document_root_node(document);
47+
append_to_scope_sequence(&sequence, &current_scopes, node, document, text);
48+
return sequence;
49+
}
50+
51+
bool operator<=(const TSPoint &left, const TSPoint &right) {
52+
if (left.row < right.row)
53+
return true;
54+
else if (left.row == right.row)
55+
return left.column <= right.column;
56+
else
57+
return false;
58+
}
59+
60+
void verify_changed_ranges(const ScopeSequence &old_sequence, const ScopeSequence &new_sequence,
61+
const string &text, TSRange *ranges, size_t range_count) {
62+
TSPoint current_position = {0, 0};
63+
for (size_t i = 0; i < old_sequence.size(); i++) {
64+
if (text[i] == '\n') {
65+
current_position.row++;
66+
current_position.column = 0;
67+
continue;
68+
}
69+
70+
const ScopeStack &old_scopes = old_sequence[i];
71+
const ScopeStack &new_scopes = new_sequence[i];
72+
if (old_scopes != new_scopes) {
73+
bool found_containing_range = false;
74+
for (size_t j = 0; j < range_count; j++) {
75+
TSRange range = ranges[j];
76+
if (range.start <= current_position && current_position <= range.end) {
77+
found_containing_range = true;
78+
break;
79+
}
80+
}
81+
82+
if (!found_containing_range) {
83+
std::stringstream message_stream;
84+
message_stream << "Found changed scope outside of any invalidated range;\n";
85+
message_stream << "Position: " << current_position << "\n";
86+
message_stream << "Byte index: " << i << "\n";
87+
size_t line_start_index = i - current_position.column;
88+
size_t line_end_index = text.find_first_of('\n', i);
89+
message_stream << "Line: " << text.substr(line_start_index, line_end_index - line_start_index) << "\n";
90+
for (size_t j = 0; j < current_position.column + string("Line: ").size(); j++)
91+
message_stream << " ";
92+
message_stream << "^\n";
93+
message_stream << "Old scopes: " << old_scopes << "\n";
94+
message_stream << "New scopes: " << new_scopes << "\n";
95+
message_stream << "Invalidated ranges:\n";
96+
for (size_t j = 0; j < range_count; j++) {
97+
message_stream << " " << ranges[j] << "\n";
98+
}
99+
Assert::Failure(message_stream.str());
100+
}
101+
}
102+
103+
current_position.column++;
104+
}
105+
}

spec/helpers/scope_sequence.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef HELPERS_SCOPE_SEQUENCE_H_
2+
#define HELPERS_SCOPE_SEQUENCE_H_
3+
4+
#include <string>
5+
#include <vector>
6+
#include "tree_sitter/runtime.h"
7+
8+
typedef std::string Scope;
9+
typedef std::vector<Scope> ScopeStack;
10+
typedef std::vector<ScopeStack> ScopeSequence;
11+
12+
ScopeSequence build_scope_sequence(TSDocument *document, const std::string &text);
13+
14+
void verify_changed_ranges(const ScopeSequence &old, const ScopeSequence &new_sequence, const std::string &text, TSRange *ranges, size_t range_count);
15+
16+
#endif // HELPERS_SCOPE_SEQUENCE_H_

spec/helpers/spy_input.cc

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <algorithm>
55
#include <assert.h>
66

7+
using std::pair;
78
using std::string;
89

910
static const size_t UTF8_MAX_CHAR_SIZE = 4;
@@ -68,34 +69,63 @@ TSInput SpyInput::input() {
6869
return result;
6970
}
7071

71-
TSInputEdit SpyInput::replace(size_t start_char, size_t chars_removed, string text) {
72-
string text_removed = swap_substr(start_char, chars_removed, text);
73-
size_t chars_inserted = string_char_count(encoding, text);
74-
undo_stack.push_back(SpyInputEdit{start_char, chars_inserted, text_removed});
75-
return {start_char, chars_inserted, chars_removed};
72+
static TSPoint get_extent(string text) {
73+
TSPoint result = {0, 0};
74+
for (auto i = text.begin(); i != text.end(); i++) {
75+
if (*i == '\n') {
76+
result.row++;
77+
result.column = 0;
78+
} else {
79+
result.column++;
80+
}
81+
}
82+
return result;
83+
}
84+
85+
TSInputEdit SpyInput::replace(size_t start_byte, size_t bytes_removed, string text) {
86+
auto swap = swap_substr(start_byte, bytes_removed, text);
87+
size_t bytes_added = text.size();
88+
undo_stack.push_back(SpyInputEdit{start_byte, bytes_added, swap.first});
89+
TSInputEdit result = {};
90+
result.start_byte = start_byte;
91+
result.bytes_added = bytes_added;
92+
result.bytes_removed = bytes_removed;
93+
result.start_point = swap.second;
94+
result.extent_removed = get_extent(swap.first);
95+
result.extent_added = get_extent(text);
96+
return result;
7697
}
7798

7899
TSInputEdit SpyInput::undo() {
79100
SpyInputEdit entry = undo_stack.back();
80101
undo_stack.pop_back();
81-
swap_substr(entry.position, entry.chars_removed, entry.text_inserted);
82-
size_t chars_inserted = string_char_count(encoding, entry.text_inserted);
83-
return TSInputEdit{entry.position, chars_inserted, entry.chars_removed};
102+
auto swap = swap_substr(entry.start_byte, entry.bytes_removed, entry.text_inserted);
103+
TSInputEdit result;
104+
result.start_byte = entry.start_byte;
105+
result.bytes_removed = entry.bytes_removed;
106+
result.bytes_added = entry.text_inserted.size();
107+
result.start_point = swap.second;
108+
result.extent_removed = get_extent(swap.first);
109+
result.extent_added = get_extent(entry.text_inserted);
110+
return result;
84111
}
85112

86-
string SpyInput::swap_substr(size_t start_char, size_t chars_removed, string text) {
87-
long start_byte = string_byte_for_character(encoding, content, 0, start_char);
88-
assert(start_byte >= 0);
89-
90-
long bytes_removed = string_byte_for_character(encoding, content, start_byte, chars_removed);
91-
if (bytes_removed < 0)
92-
bytes_removed = content.size() - start_byte;
113+
pair<string, TSPoint> SpyInput::swap_substr(size_t start_byte, size_t bytes_removed, string text) {
114+
TSPoint start_position = {0, 0};
115+
for (auto i = content.begin(), n = content.begin() + start_byte; i < n; i++) {
116+
if (*i == '\n') {
117+
start_position.row++;
118+
start_position.column = 0;
119+
} else {
120+
start_position.column++;
121+
}
122+
}
93123

94124
string text_removed = content.substr(start_byte, bytes_removed);
95125
content.erase(start_byte, bytes_removed);
96126
content.insert(start_byte, text);
97127

98-
return text_removed;
128+
return {text_removed, start_position};
99129
}
100130

101131
void SpyInput::clear() {

spec/helpers/spy_input.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
#include "tree_sitter/runtime.h"
77

88
struct SpyInputEdit {
9-
size_t position;
10-
size_t chars_removed;
9+
size_t start_byte;
10+
size_t bytes_removed;
1111
std::string text_inserted;
1212
};
1313

@@ -20,7 +20,7 @@ class SpyInput {
2020

2121
static const char * read(void *, size_t *);
2222
static int seek(void *, size_t, size_t);
23-
std::string swap_substr(size_t, size_t, std::string);
23+
std::pair<std::string, TSPoint> swap_substr(size_t, size_t, std::string);
2424

2525
public:
2626
SpyInput(std::string content, size_t chars_per_chunk);

spec/helpers/tree_helpers.cc

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ bool operator==(const TSNode &left, const TSNode &right) {
4040
return ts_node_eq(left, right);
4141
}
4242

43-
bool operator==(const TSLength &left, const TSLength &right) {
44-
return ts_length_eq(left, right);
45-
}
46-
4743
bool operator==(const std::vector<TSTree *> &vec, const TreeArray &array) {
4844
if (vec.size() != array.size)
4945
return false;
@@ -52,8 +48,3 @@ bool operator==(const std::vector<TSTree *> &vec, const TreeArray &array) {
5248
return false;
5349
return true;
5450
}
55-
56-
ostream &operator<<(ostream &stream, const TSLength &length) {
57-
return stream << "{chars:" << length.chars << ", bytes:" <<
58-
length.bytes << ", rows:" << length.rows << ", columns:" << length.columns << "}";
59-
}

spec/helpers/tree_helpers.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ TSTree ** tree_array(std::vector<TSTree *> trees);
1010

1111
std::ostream &operator<<(std::ostream &stream, const TSTree *tree);
1212
std::ostream &operator<<(std::ostream &stream, const TSNode &node);
13-
std::ostream &operator<<(std::ostream &stream, const TSLength &length);
1413
bool operator==(const TSNode &left, const TSNode &right);
15-
bool operator==(const TSLength &left, const TSLength &right);
1614
bool operator==(const std::vector<TSTree *> &right, const TreeArray &array);
1715

1816
#endif // HELPERS_TREE_HELPERS_H_

0 commit comments

Comments
 (0)