diff --git a/spec/runtime/helpers/spy_input.cc b/spec/runtime/helpers/spy_input.cc index b78a171e..764ae8ed 100644 --- a/spec/runtime/helpers/spy_input.cc +++ b/spec/runtime/helpers/spy_input.cc @@ -2,11 +2,29 @@ #include #include #include "utf8proc.h" +#include using std::string; static const size_t UTF8_MAX_CHAR_SIZE = 4; +static size_t string_char_count(const string &text) { + const char *bytes = text.data(); + size_t len = text.size(); + size_t character = 0, byte = 0; + int32_t dest_char; + + while (byte < len) { + byte += utf8proc_iterate( + (uint8_t *)bytes + byte, + len - byte, + &dest_char); + character++; + } + + return character; +} + static long byte_for_character(const char *str, size_t len, size_t goal_character) { size_t character = 0, byte = 0; int32_t dest_char; @@ -82,18 +100,37 @@ TSInput SpyInput::input() { return result; } -bool SpyInput::insert(size_t char_index, string text) { - long pos = byte_for_character(content.data(), content.size(), char_index); - if (pos < 0) return false; - content.insert(pos, text); - return true; +TSInputEdit SpyInput::replace(size_t start_char, size_t chars_removed, string text) { + string text_removed = swap_substr(start_char, chars_removed, text); + size_t chars_inserted = string_char_count(text); + undo_stack.push_back(SpyInputEdit{start_char, chars_inserted, text_removed}); + return {start_char, chars_inserted, chars_removed}; } -bool SpyInput::erase(size_t char_index, size_t len) { - long pos = byte_for_character(content.data(), content.size(), char_index); - if (pos < 0) return false; - content.erase(pos, len); - return true; +TSInputEdit SpyInput::undo() { + SpyInputEdit entry = undo_stack.back(); + undo_stack.pop_back(); + swap_substr(entry.position, entry.chars_removed, entry.text_inserted); + size_t chars_inserted = string_char_count(entry.text_inserted); + return TSInputEdit{entry.position, chars_inserted, entry.chars_removed}; +} + +string SpyInput::swap_substr(size_t start_char, size_t chars_removed, string text) { + const char *bytes = content.data(); + size_t size = content.size(); + + long start_byte = byte_for_character(bytes, size, start_char); + assert(start_byte >= 0); + + long bytes_removed = byte_for_character(bytes + start_byte, size - start_byte, chars_removed); + if (bytes_removed < 0) + bytes_removed = size - start_byte; + + string text_removed = content.substr(start_byte, bytes_removed); + content.erase(start_byte, bytes_removed); + content.insert(start_byte, text); + + return text_removed; } void SpyInput::clear() { diff --git a/spec/runtime/helpers/spy_input.h b/spec/runtime/helpers/spy_input.h index 7a18e6dd..b5f02eef 100644 --- a/spec/runtime/helpers/spy_input.h +++ b/spec/runtime/helpers/spy_input.h @@ -5,14 +5,22 @@ #include #include "tree_sitter/runtime.h" +struct SpyInputEdit { + size_t position; + size_t chars_removed; + std::string text_inserted; +}; + class SpyInput { size_t chars_per_chunk; size_t buffer_size; char *buffer; size_t byte_offset; + std::vector undo_stack; static const char * read(void *, size_t *); static int seek(void *, TSLength); + std::string swap_substr(size_t, size_t, std::string); public: SpyInput(std::string content, size_t chars_per_chunk); @@ -20,8 +28,8 @@ class SpyInput { TSInput input(); void clear(); - bool insert(size_t position, std::string text); - bool erase(size_t position, size_t len); + TSInputEdit replace(size_t start_char, size_t chars_removed, std::string text); + TSInputEdit undo(); std::string content; std::vector strings_read; diff --git a/spec/runtime/language_specs.cc b/spec/runtime/language_specs.cc index 8fcb7d48..9c957c9d 100644 --- a/spec/runtime/language_specs.cc +++ b/spec/runtime/language_specs.cc @@ -54,38 +54,33 @@ describe("Languages", [&]() { }); it(("handles random insertions in " + entry.description).c_str(), [&]() { - SpyInput reader(entry.input, 3); - ts_document_set_input(doc, reader.input()); + SpyInput input(entry.input, 3); + ts_document_set_input(doc, input.input()); ts_document_parse(doc); string garbage("%^&*"); size_t position = entry.input.size() / 2; - reader.insert(position, garbage); - ts_document_edit(doc, { position, garbage.size(), 0 }); + ts_document_edit(doc, input.replace(position, 0, garbage)); ts_document_parse(doc); - reader.erase(position, garbage.size()); - ts_document_edit(doc, { position, 0, garbage.size() }); + ts_document_edit(doc, input.undo()); ts_document_parse(doc); expect_the_correct_tree(entry.tree_string); }); it(("handles random deletions in " + entry.description).c_str(), [&]() { - SpyInput reader(entry.input, 3); - ts_document_set_input(doc, reader.input()); + SpyInput input(entry.input, 3); + ts_document_set_input(doc, input.input()); ts_document_parse(doc); size_t position = entry.input.size() / 2; - string removed = entry.input.substr(position); - reader.erase(position, removed.size()); - ts_document_edit(doc, { position, 0, removed.size() }); + ts_document_edit(doc, input.replace(position, 5, "")); ts_document_parse(doc); - reader.insert(position, removed); - ts_document_edit(doc, { position, removed.size(), 0 }); + ts_document_edit(doc, input.undo()); ts_document_parse(doc); expect_the_correct_tree(entry.tree_string); diff --git a/spec/runtime/parser_spec.cc b/spec/runtime/parser_spec.cc index 63b81568..36677f0d 100644 --- a/spec/runtime/parser_spec.cc +++ b/spec/runtime/parser_spec.cc @@ -38,8 +38,7 @@ describe("Parser", [&]() { auto insert_text = [&](size_t position, string text) { size_t prev_size = ts_node_size(root).bytes + ts_node_pos(root).bytes; - AssertThat(input->insert(position, text), IsTrue()); - ts_document_edit(doc, { position, text.length(), 0 }); + ts_document_edit(doc, input->replace(position, 0, text)); ts_document_parse(doc); root = ts_document_root_node(doc); @@ -49,8 +48,7 @@ describe("Parser", [&]() { auto delete_text = [&](size_t position, size_t length) { size_t prev_size = ts_node_size(root).bytes + ts_node_pos(root).bytes; - AssertThat(input->erase(position, length), IsTrue()); - ts_document_edit(doc, { position, 0, length }); + ts_document_edit(doc, input->replace(position, length, "")); ts_document_parse(doc); root = ts_document_root_node(doc); @@ -60,10 +58,8 @@ describe("Parser", [&]() { auto replace_text = [&](size_t position, size_t length, string new_text) { size_t prev_size = ts_node_size(root).bytes + ts_node_pos(root).bytes; - AssertThat(input->erase(position, length), IsTrue()); - AssertThat(input->insert(position, new_text), IsTrue()); - ts_document_edit(doc, { position, new_text.size(), length }); + ts_document_edit(doc, input->replace(position, length, new_text)); ts_document_parse(doc); root = ts_document_root_node(doc);