Add undo method to SpyInput

This commit is contained in:
Max Brunsfeld 2015-09-19 14:46:14 -07:00
parent 511e52ab55
commit 010a606b76
4 changed files with 68 additions and 32 deletions

View file

@ -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() {

View file

@ -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;

View file

@ -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);

View file

@ -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);