Add undo method to SpyInput
This commit is contained in:
parent
511e52ab55
commit
010a606b76
4 changed files with 68 additions and 32 deletions
|
|
@ -2,11 +2,29 @@
|
|||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include "utf8proc.h"
|
||||
#include <assert.h>
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,22 @@
|
|||
#include <vector>
|
||||
#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<SpyInputEdit> 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<std::string> strings_read;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue