diff --git a/Makefile b/Makefile index 27e1770..9474637 100644 --- a/Makefile +++ b/Makefile @@ -25,13 +25,13 @@ MRUBY_CFLAGS ?= -I./libs/mruby/build/host/include MRUBY_LIBS ?= -L./libs/mruby/build/host/lib -lmruby CFLAGS_DEBUG :=\ - -std=c++20 -Wall -Wextra -fno-rtti \ + -std=c++23 -Wall -Wextra -fno-rtti \ -Og -fno-inline -gsplit-dwarf \ -g -fno-omit-frame-pointer -ffast-math \ -I./include -I./libs/unicode_width CFLAGS_RELEASE :=\ - -std=c++20 -O3 -mtune=generic -fno-rtti \ + -std=c++23 -O3 -mtune=generic -fno-rtti \ -fvisibility=hidden -static -ffast-math \ -fomit-frame-pointer -DNDEBUG -s \ -I./include -I./libs/unicode_width diff --git a/include/io/file.h b/include/io/file.h index eb30a4e..6724f98 100644 --- a/include/io/file.h +++ b/include/io/file.h @@ -29,12 +29,10 @@ inline int read_file(const char *path, char **text, uint32_t *len) { if (read != *len) { free(*text); - *text = NULL; + *text = nullptr; *len = 0; return 0; } - (*text)[*len] = '\0'; // handy if treating it as a C string - return 1; } diff --git a/include/utils/utils.h b/include/utils/utils.h index 6271b7d..b0c558d 100644 --- a/include/utils/utils.h +++ b/include/utils/utils.h @@ -1,8 +1,3 @@ #pragma once #include "pch.h" - -struct Point { - uint32_t row; - uint32_t col; -}; diff --git a/include/vase/buffer/append.h b/include/vase/buffer/append.h index 30bb2b4..c37c233 100644 --- a/include/vase/buffer/append.h +++ b/include/vase/buffer/append.h @@ -1,12 +1,11 @@ #pragma once +#include "../constants.h" #include "buffer.h" #include "pch.h" struct AppendBuffer : Buffer { - static constexpr uint32_t CHUNK_SIZE = 1 << 16; - - using TChunk = char[CHUNK_SIZE]; + using TChunk = char[APPEND_CHUNK_SIZE]; std::vector buf; TChunk *t_current; uint32_t current_offset{0}; @@ -21,6 +20,4 @@ struct AppendBuffer : Buffer { const char *read(uint32_t pos, uint32_t *out_len); inline uint32_t length(); - - inline void new_text_chunk(); }; diff --git a/include/vase/buffer/original.h b/include/vase/buffer/original.h index ca164ed..cd56d32 100644 --- a/include/vase/buffer/original.h +++ b/include/vase/buffer/original.h @@ -7,8 +7,8 @@ struct OriginalBuffer : Buffer { char *buf; uint32_t len; - // TODO: replace with file io or even lazy loaded file io for large files/logs etc. later. - OriginalBuffer(char *buf, uint32_t len); + // Add 2 modes here, normal and lazy, lazy copies the file to a safe location and uses mmap. + OriginalBuffer(std::string path); ~OriginalBuffer(); diff --git a/include/vase/constants.h b/include/vase/constants.h new file mode 100644 index 0000000..c4de939 --- /dev/null +++ b/include/vase/constants.h @@ -0,0 +1,11 @@ +#pragma once + +#include "pch.h" + +constexpr uint32_t APPEND_CHUNK_SIZE = 64 * 1024; +constexpr uint32_t PETAL_SIZE_MAX = 32 * 1024; + +static_assert( + APPEND_CHUNK_SIZE > PETAL_SIZE_MAX, + "PETAL_SIZE_MAX must be smaller than APPEND_CHUNK_SIZE" +); diff --git a/include/vase/iterators/chunk.h b/include/vase/iterators/chunk.h index 8d33b75..41d999f 100644 --- a/include/vase/iterators/chunk.h +++ b/include/vase/iterators/chunk.h @@ -3,14 +3,20 @@ #include "../shard.h" #include "pch.h" +enum struct Direction : uint8_t { + Forward, + Backward +}; + struct ChunkIterator { + Direction dir; Shard *root = nullptr; std::vector stack; Petal *petal = nullptr; uint32_t petal_offset = 0; - uint32_t global_offset; + uint32_t global_offset = 0; - ChunkIterator(Shard *r); + ChunkIterator(Shard *r, Direction dir); ~ChunkIterator(); diff --git a/include/vase/iterators/line.h b/include/vase/iterators/line.h index ab49310..e2a68ed 100644 --- a/include/vase/iterators/line.h +++ b/include/vase/iterators/line.h @@ -6,14 +6,12 @@ struct LineIterator { ChunkIterator it; + Direction dir; - const char *cursor = nullptr; - bool eof = false; - uint32_t remaining = 0; - uint32_t line_offset = 0; - - LineIterator(Shard *r, uint32_t start_line); + const char *chunk; + uint32_t len; + LineIterator(Shard *r, uint32_t start_line, Direction dir); ~LineIterator() = default; bool next(std::string &line); diff --git a/include/vase/shard.h b/include/vase/shard.h index 3f12427..8b2c467 100644 --- a/include/vase/shard.h +++ b/include/vase/shard.h @@ -2,6 +2,7 @@ #include "buffer/buffer.h" #include "buffer/original.h" +#include "constants.h" #include "pch.h" #include "utils/utils.h" diff --git a/include/vase/vase.h b/include/vase/vase.h index 41fe041..d366863 100644 --- a/include/vase/vase.h +++ b/include/vase/vase.h @@ -2,14 +2,29 @@ #include "buffer/append.h" #include "buffer/original.h" +#include "constants.h" #include "iterators/line.h" #include "pch.h" #include "search.h" #include "shard.h" #include "utils/utils.h" +struct Point { + uint32_t row; + uint32_t col; +}; + +struct Range { + Point start; + Point end; +}; + struct Vase { - Vase(char *data, uint32_t length); + OriginalBuffer original; + AppendBuffer append; + Shard *root; + + Vase(std::string path); ~Vase(); @@ -20,26 +35,33 @@ struct Vase { LineIterator iterate(uint32_t line); - void input(Point *point, char key); + void insert(Point *point, char key); void insert(Point *point, const char *data, uint32_t len); void erase(Point *point, int64_t amount); - bool jump(Point *point, int64_t amount); - bool clamp(Point *point); + void erase(Range range); + void replace(Range range, const char *data, uint32_t len); + + void move_clusters(Point *point, int64_t amount); + void move_lines(Point *point, int64_t amount); + void clamp(Point *point); + void regex_search_replace( std::string_view pattern, Point start, Point end, std::string_view replace, std::string_view options ); -private: - OriginalBuffer original; - AppendBuffer append; - Shard *root; + bool undo(); + bool redo(); - /*std::vector undo; // TODO: later - uint16_t top; // of the undo stack. - uint16_t max; // for redo when no edits have been done after some undo.*/ + void snapshot(); + void prune_history(uint32_t n); + +private: + std::vector history; + uint32_t history_top; uint32_t offset_of(Point point); static void flatten(Shard *s, std::string &out); + void _insert(Point *point, const char *data, uint32_t len); }; diff --git a/src/main.cc b/src/main.cc index 8559376..d9d85a3 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,39 +1,17 @@ -#include "io/file.h" #include "pch.h" #include "vase/iterators/chunk.h" #include "vase/iterators/line.h" -#include "vase/shard.h" #include "vase/vase.h" int main(int argc, char *argv[]) { - -#ifdef DEBUG - - const char *text_o = - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz1\n" - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz2\n" - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz3\n" - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz4"; - - uint32_t len = strlen(text_o); - char *text = strdup(text_o); - -#else - - const char *path; - - if (argc >= 2) - path = argv[1]; - else + if (argc < 2) throw std::runtime_error("Please give filename."); - uint32_t len; - char *text; - read_file(path, &text, &len); + Vase vase = Vase(std::string(argv[1])); -#endif - - Vase vase = Vase(text, len); + std::cout << "\n" + << vase.to_string() + << "\n"; return 0; } diff --git a/src/vase/buffer/append.cc b/src/vase/buffer/append.cc index c6ccac4..e798ce1 100644 --- a/src/vase/buffer/append.cc +++ b/src/vase/buffer/append.cc @@ -1,7 +1,8 @@ #include "vase/buffer/append.h" AppendBuffer::AppendBuffer() { - new_text_chunk(); + t_current = (TChunk *)malloc(sizeof(TChunk)); + buf.push_back(t_current); } AppendBuffer::~AppendBuffer() { @@ -10,8 +11,11 @@ AppendBuffer::~AppendBuffer() { } uint32_t AppendBuffer::key(char c) { - if (t_offset == CHUNK_SIZE) - new_text_chunk(); + if (t_offset == APPEND_CHUNK_SIZE) { + t_current = (TChunk *)malloc(sizeof(TChunk)); + buf.push_back(t_current); + t_offset = 0; + } (*t_current)[t_offset++] = c; return current_offset++; } @@ -19,9 +23,12 @@ uint32_t AppendBuffer::key(char c) { uint32_t AppendBuffer::append(const char *text, uint32_t length) { uint32_t start = current_offset; while (length > 0) { - if (t_offset == CHUNK_SIZE) - new_text_chunk(); - uint32_t copy = std::min(length, CHUNK_SIZE - t_offset); + if (t_offset == APPEND_CHUNK_SIZE) { + t_current = (TChunk *)malloc(sizeof(TChunk)); + buf.push_back(t_current); + t_offset = 0; + } + uint32_t copy = std::min(length, APPEND_CHUNK_SIZE - t_offset); memcpy((*t_current) + t_offset, text, copy); t_offset += copy; current_offset += copy; @@ -34,21 +41,15 @@ uint32_t AppendBuffer::append(const char *text, uint32_t length) { const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) { if (pos >= current_offset) return nullptr; - uint32_t local_offset = pos % CHUNK_SIZE; + uint32_t local_offset = pos % APPEND_CHUNK_SIZE; if (out_len) { uint32_t remaining = current_offset - pos; - uint32_t until_chunk_end = CHUNK_SIZE - local_offset; + uint32_t until_chunk_end = APPEND_CHUNK_SIZE - local_offset; *out_len = std::min(remaining, until_chunk_end); } - return &(*buf[pos / CHUNK_SIZE])[local_offset]; + return &(*buf[pos / APPEND_CHUNK_SIZE])[local_offset]; } inline uint32_t AppendBuffer::length() { return current_offset; } - -inline void AppendBuffer::new_text_chunk() { - t_current = (TChunk *)malloc(sizeof(TChunk)); - buf.push_back(t_current); - t_offset = 0; -} diff --git a/src/vase/buffer/original.cc b/src/vase/buffer/original.cc index 9d49a29..716c6a2 100644 --- a/src/vase/buffer/original.cc +++ b/src/vase/buffer/original.cc @@ -1,6 +1,9 @@ #include "vase/buffer/original.h" +#include "io/file.h" -OriginalBuffer::OriginalBuffer(char *buf, uint32_t len) : buf(buf), len(len) {} +OriginalBuffer::OriginalBuffer(std::string path) { + read_file(path.c_str(), &buf, &len); +} OriginalBuffer::~OriginalBuffer() { free(buf); diff --git a/src/vase/iterators/chunk.cc b/src/vase/iterators/chunk.cc index 3c16bb0..9db32e4 100644 --- a/src/vase/iterators/chunk.cc +++ b/src/vase/iterators/chunk.cc @@ -1,6 +1,7 @@ #include "vase/iterators/chunk.h" -ChunkIterator::ChunkIterator(Shard *r) : root(r) { +ChunkIterator::ChunkIterator(Shard *r, Direction dir) + : dir(dir), root(r) { if (!root) return; Shard::retain(root); @@ -14,119 +15,171 @@ void ChunkIterator::seek_offset(uint32_t offset) { stack.clear(); petal = nullptr; petal_offset = 0; - global_offset = 0; - if (!root || offset >= root->length) + if (!root) return; + if (offset >= root->length) + offset = root->length - 1; uint32_t target = offset; Shard *curr = root; while (curr) { if (curr->kind == Shard::ShardKind::Petal) { petal = (Petal *)curr; petal_offset = target; + global_offset += target; return; } else { auto *b = (Branch *)curr; uint32_t left_len = b->left->length; if (target < left_len) { - stack.push_back(b->right); + if (dir == Direction::Forward) + stack.push_back(b->right); curr = b->left; } else { target -= left_len; - global_offset += b->left->length; + if (dir == Direction::Backward) + stack.push_back(b->left); + global_offset += left_len; curr = b->right; } } } + std::unreachable(); } void ChunkIterator::seek_line(uint32_t line) { stack.clear(); petal = nullptr; petal_offset = 0; - global_offset = 0; - if (!root || line > root->lines) + bool last_line = false; + if (!root) return; + if (line > root->lines + 1) + line = root->lines + 1; + if (line == root->lines + 1) + last_line = true; Shard *curr = root; while (curr) { if (curr->kind == Shard::ShardKind::Petal) { petal = (Petal *)curr; - const char *text = nullptr; - uint32_t remaining = 0; - uint32_t offset = 0; - while (line) { - if (!text) { - uint32_t got = 0; - text = petal->source->read(petal->pos + offset, &got); - remaining = std::min(got, petal->length - offset); + if (last_line) { + petal_offset = petal->length; + global_offset += petal->length; + } else { + const char *text = nullptr; + uint32_t remaining = 0; + uint32_t offset = 0; + while (line) { + if (!text) { + uint32_t got = 0; + text = petal->source->read(petal->pos + offset, &got); + remaining = std::min(got, petal->length - offset); + } + const char *c = (const char *)memchr(text, '\n', remaining); + if (!c) { + offset += remaining; + text = nullptr; + continue; + } + remaining -= (c - text) + 1; + offset += (c - text) + 1; + text = c + 1; + line--; } - const char *c = (const char *)memchr(text, '\n', remaining); - if (!c) { - offset += remaining; - text = nullptr; - continue; - } - remaining -= (c - text) + 1; - offset += (c - text) + 1; - text = c + 1; - line--; + if (dir == Direction::Backward) + --offset; + petal_offset = offset; + global_offset += offset; } - petal_offset = offset; return; } else { auto *b = (Branch *)curr; uint32_t left_lines = b->left->lines; if (line <= left_lines) { - stack.push_back(b->right); + if (dir == Direction::Forward) + stack.push_back(b->right); curr = b->left; } else { line -= left_lines; + if (dir == Direction::Backward) + stack.push_back(b->left); global_offset += b->left->length; curr = b->right; } } } + std::unreachable(); } uint32_t ChunkIterator::byte_offset() { - return global_offset + petal_offset; + return global_offset; } bool ChunkIterator::next(const char **data, uint32_t *out_len) { - while (true) { - if (petal) { - if (petal_offset < petal->length) { - uint32_t remaining = petal->length - petal_offset; - uint32_t read_pos = petal->pos + petal_offset; - uint32_t got = 0; - const char *chunk = petal->source->read(read_pos, &got); - if (got == 0) { - petal_offset = petal->length; + if (dir == Direction::Forward) { + while (true) { + if (petal) { + if (petal_offset == petal->length) { petal = nullptr; continue; } + uint32_t remaining = petal->length - petal_offset; + uint32_t got = 0; + const char *chunk = petal->source->read(petal->pos + petal_offset, &got); + if (got == 0) { + petal = nullptr; + petal_offset = 0; + return false; + } uint32_t take = std::min(got, remaining); *data = chunk; *out_len = take; petal_offset += take; return true; } - global_offset += petal->length; - petal = nullptr; - petal_offset = 0; - } - if (stack.empty()) - return false; - Shard *s = stack.back(); - stack.pop_back(); - if (!s) - continue; - if (s->kind == Shard::ShardKind::Petal) { + if (stack.empty()) + return false; + auto s = stack.back(); + stack.pop_back(); + while (s->kind == Shard::ShardKind::Branch) { + Branch *b = (Branch *)s; + stack.push_back(b->right); + s = b->left; + } petal = (Petal *)s; petal_offset = 0; - } else { - auto *b = (Branch *)s; - stack.push_back(b->right); - stack.push_back(b->left); + } + } else { + while (true) { + if (petal) { + if (petal_offset == 0) { + petal = nullptr; + continue; + } + uint32_t got = 0; + *data = petal->source->read(petal->pos, &got); + if (petal_offset > got) { + uint32_t got2 = 0; + *data = petal->source->read(petal->pos + got, &got2); + *out_len = std::min(got2, petal_offset - got); + petal_offset = got; + return true; + } else { + *out_len = std::min(got, petal_offset); + petal_offset = 0; + return true; + } + } + if (stack.empty()) + return false; + auto s = stack.back(); + stack.pop_back(); + while (s->kind == Shard::ShardKind::Branch) { + Branch *b = (Branch *)s; + stack.push_back(b->left); + s = b->right; + } + petal = (Petal *)s; + petal_offset = petal->length; } } } diff --git a/src/vase/iterators/line.cc b/src/vase/iterators/line.cc index 3710b91..17cad93 100644 --- a/src/vase/iterators/line.cc +++ b/src/vase/iterators/line.cc @@ -1,40 +1,65 @@ #include "vase/iterators/line.h" -LineIterator::LineIterator(Shard *r, uint32_t start_line) : it(r) { - it.seek_line(start_line); +LineIterator::LineIterator(Shard *r, uint32_t start_line, Direction dir) + : it(r, dir), dir(dir) { + it.seek_line(start_line + (dir == Direction::Backward)); + if (!it.next(&chunk, &len)) + chunk = nullptr; +} + +uint32_t LineIterator::byte_offset() { + return 0; } bool LineIterator::next(std::string &line) { line.clear(); - line_offset = it.byte_offset() - remaining; - while (true) { - if (remaining == 0) { - uint32_t got; - if (!it.next(&cursor, &got)) { - eof = true; - break; + if (!chunk) + return false; + if (dir == Direction::Forward) { + while (true) { + const char *nl = (const char *)memchr(chunk, '\n', len); + if (!nl) { + if (len && chunk[len - 1] == '\r') + --len; + line.append(chunk, len); + if (!it.next(&chunk, &len)) { + chunk = nullptr; + return true; + } + continue; } - line_offset = it.byte_offset() - remaining; - remaining = got; - } - const char *nl = (const char *)memchr(cursor, '\n', remaining); - if (nl) { const char *end = nl; - uint32_t len = nl - cursor; - if (len && cursor[len - 1] == '\r') - --len; - line.append(cursor, len); - cursor = end + 1; - remaining -= (uint32_t)(len + 1); + if (end > chunk && *(end - 1) == '\r') + --end; + line.append(chunk, end); + uint32_t consumed = (nl - chunk) + 1; + chunk += consumed; + len -= consumed; + return true; + } + } else { + while (true) { + const char *nl = (const char *)memrchr(chunk, '\n', len); + if (!nl) { + if (len && chunk[len - 1] == '\n') + --len; + if (len && chunk[len - 1] == '\r') + --len; + line.insert(0, chunk, len); + if (!it.next(&chunk, &len)) { + chunk = nullptr; + return true; + } + continue; + } + const char *end = chunk + len; + const char *start = nl + 1; + const char *line_end = end; + if (line_end > start && *(line_end - 1) == '\r') + --line_end; + line.insert(0, start, line_end - start); + len = nl - chunk; return true; } - line.append(cursor, remaining); - cursor += remaining; - remaining = 0; } - return !line.empty(); -} - -uint32_t LineIterator::byte_offset() { - return line_offset; } diff --git a/src/vase/search.cc b/src/vase/search.cc index 54c2032..1c9a699 100644 --- a/src/vase/search.cc +++ b/src/vase/search.cc @@ -83,7 +83,7 @@ std::vector regex_search( pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); - ChunkIterator it(root); + ChunkIterator it(root, Direction::Forward); it.seek_offset(start_offset); const char *data; diff --git a/src/vase/shard.cc b/src/vase/shard.cc index 7546602..8587dad 100644 --- a/src/vase/shard.cc +++ b/src/vase/shard.cc @@ -195,7 +195,7 @@ Shard *merge_leaves(Shard *a, Shard *b) { Shard *append_leaf(Shard *root, Shard *leaf) { if (!root) return leaf; - if (root->kind == Shard::ShardKind::Petal && root->length < 32 * 1024) + if (root->kind == Shard::ShardKind::Petal && root->length < PETAL_SIZE_MAX) return merge_leaves(root, leaf); Branch *b = (Branch *)root; auto new_right = append_leaf(b->right, leaf); @@ -221,16 +221,15 @@ Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi) { } Shard *create_shards(Buffer *o) { - constexpr uint32_t PETAL_SIZE = 32 * 1024; std::vector pieces; uint32_t pos = 0; const uint32_t total = o->length(); - pieces.reserve((total + PETAL_SIZE - 1) / PETAL_SIZE); + pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX); while (pos < total) { uint32_t len = 0; const char *data = o->read(pos, &len); - uint32_t take = std::min(len, PETAL_SIZE); + uint32_t take = std::min(len, PETAL_SIZE_MAX); uint32_t lines = 0; const char *p = data; const char *end = data + take; diff --git a/src/vase/vase.cc b/src/vase/vase.cc index fa399a8..5efd044 100644 --- a/src/vase/vase.cc +++ b/src/vase/vase.cc @@ -3,12 +3,18 @@ #include "vase/iterators/line.h" #include "vase/search.h" -Vase::Vase(char *data, uint32_t length) - : original(data, length), append(), - root(create_shards(&original)) {} +Vase::Vase(std::string path) + : original(path), append(), + root(create_shards(&original)) { + history.push_back(root); + Shard::retain(root); + history_top = 0; +} Vase::~Vase() { Shard::release(root); + for (auto s : history) + Shard::release(s); } uint32_t Vase::length() { @@ -21,13 +27,56 @@ std::string Vase::to_string() { return out; } -void Vase::input(Point *point, char key) { +LineIterator Vase::iterate(uint32_t line) { + return LineIterator(root, line, Direction::Forward); +} + +bool Vase::undo() { + if (history_top == 0) + return false; + Shard::release(root); + history_top--; + root = history[history_top]; + Shard::retain(root); + return true; +} + +bool Vase::redo() { + if (history_top + 1 >= history.size()) + return false; + Shard::release(root); + history_top++; + root = history[history_top]; + Shard::retain(root); + return true; +} + +void Vase::snapshot() { + if (history[history_top] == root) + return; + while (history.size() > history_top + 1) { + Shard::release(history.back()); + history.pop_back(); + } + Shard::retain(root); + history.push_back(root); + history_top++; +} + +void Vase::prune_history(uint32_t n) { + n = std::min(n, history_top); + for (uint32_t i = 0; i < n; ++i) + Shard::release(history[i]); + history.erase(history.begin(), history.begin() + n); + history_top -= n; +} + +void Vase::insert(Point *point, char key) { if (key == '\n') *point = {point->row + 1, 0}; else point->col++; uint32_t pos = append.key(key); - // limit petal size to 32KiB here later. Shard *inserted = new Petal(1, key == '\n', &append, pos); auto [left, right] = split_shard(root, offset_of(*point)); Shard *left2 = append_leaf(left, inserted); @@ -41,6 +90,15 @@ void Vase::input(Point *point, char key) { } void Vase::insert(Point *point, const char *data, uint32_t len) { + while (len) { + uint32_t chunk_size = std::min(len, PETAL_SIZE_MAX); + _insert(point, data, chunk_size); + len -= chunk_size; + data += chunk_size; + } +} + +void Vase::_insert(Point *point, const char *data, uint32_t len) { if (len == 0) return; uint32_t offset = offset_of(*point); @@ -80,20 +138,20 @@ void Vase::insert(Point *point, const char *data, uint32_t len) { } void Vase::erase(Point *point, int64_t amount) { - // wrong: havta make amount be in clusters not bytes - uint32_t offset = offset_of(*point); if (amount == 0) return; - uint32_t start; - uint32_t count; - if (amount < 0) { - count = std::min(-amount, offset); - start = offset - count; - } else { - start = offset; - count = amount; - } - auto [a, b] = split_shard(root, start); + Point start = *point; + Point end = *point; + if (amount < 0) + move_clusters(&start, amount); + else + move_clusters(&end, amount); + uint32_t start_offset = offset_of(start); + uint32_t end_offset = offset_of(end); + if (start_offset > end_offset) + std::swap(start_offset, end_offset); + uint32_t count = end_offset - start_offset; + auto [a, b] = split_shard(root, start_offset); auto [d, c] = split_shard(b, count); Shard *new_root = concat_shard(a, c); Shard::release(a); @@ -102,6 +160,30 @@ void Vase::erase(Point *point, int64_t amount) { Shard::release(d); Shard::release(root); root = new_root; + if (amount < 0) + *point = start; +} + +void Vase::erase(Range range) { + Point start = range.start; + Point end = range.end; + uint32_t start_offset = offset_of(start); + uint32_t end_offset = offset_of(end); + uint32_t count = end_offset - start_offset; + auto [a, b] = split_shard(root, start_offset); + auto [d, c] = split_shard(b, count); + Shard *new_root = concat_shard(a, c); + Shard::release(a); + Shard::release(b); + Shard::release(c); + Shard::release(d); + Shard::release(root); + root = new_root; +} + +void Vase::replace(Range range, const char *data, uint32_t len) { + erase(range); + insert(&range.start, data, len); } void Vase::flatten(Shard *s, std::string &out) { @@ -125,7 +207,7 @@ void Vase::flatten(Shard *s, std::string &out) { } uint32_t Vase::offset_of(Point point) { - LineIterator it(root, point.row); + LineIterator it(root, point.row, Direction::Forward); std::string line; uint32_t offset = 0; if (it.next(line)) { @@ -142,14 +224,100 @@ uint32_t Vase::offset_of(Point point) { return it.byte_offset() + offset; } -bool Vase::jump(Point *, int64_t) { - // todo. - return false; +void Vase::move_clusters(Point *point, int64_t amount) { + if (amount == 0) + return; + if (amount < 0) { + amount = -amount; + LineIterator it(root, point->row, Direction::Backward); + while (amount > 0) { + std::string line; + if (!it.next(line)) + return; + std::vector clusters; + const char *ptr = line.data(); + uint32_t remaining = line.size(); + uint32_t byte = 0; + while (remaining) { + clusters.push_back(byte); + uint32_t len = + grapheme_next_character_break_utf8(ptr, remaining); + ptr += len; + remaining -= len; + byte += len; + } + while (amount > 0 && point->col > 0) { + point->col--; + amount--; + } + if (amount == 0) + return; + if (point->row == 0) + return; + point->row--; + point->col = clusters.size(); + amount--; + } + } else { + LineIterator it(root, point->row, Direction::Forward); + while (amount > 0) { + std::string line; + if (!it.next(line)) + return; + if (point->col > 0 || amount > 0) { + const char *ptr = line.data(); + uint32_t remaining = line.size(); + uint32_t col = 0; + while (col < point->col && remaining) { + uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + ptr += len; + remaining -= len; + col++; + } + while (amount > 0 && remaining) { + uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + ptr += len; + remaining -= len; + point->col++; + amount--; + } + } + if (amount > 0) { + point->row++; + point->col = 0; + amount--; + } + } + } } -bool Vase::clamp(Point *) { - // todo. - return false; +void Vase::clamp(Point *point) { + if (point->row > root->lines) + point->row = root->lines; + LineIterator it(root, point->row, Direction::Forward); + std::string line; + if (!it.next(line)) { + point->col = 0; + return; + } + uint32_t clusters = 0; + const char *ptr = line.data(); + uint32_t remaining = line.size(); + while (remaining) { + uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + ptr += len; + remaining -= len; + clusters++; + } + if (point->col > clusters) + point->col = clusters; +} + +void Vase::move_lines(Point *point, int64_t amount) { + if (point->row + amount < 0) + return; + point->row += amount; + clamp(point); } struct ReplacePart {