Major fixes:

- ChunkIterator is now bidirectional
- LineIterator is now also bidirectional
- Undo/redo history operations
- Replace operation
- Range based api functions
- And more minor bug fixes
This commit is contained in:
2026-08-03 20:26:02 +01:00
parent 557edb5191
commit 8c61f186cf
18 changed files with 443 additions and 188 deletions
+2 -2
View File
@@ -25,13 +25,13 @@ MRUBY_CFLAGS ?= -I./libs/mruby/build/host/include
MRUBY_LIBS ?= -L./libs/mruby/build/host/lib -lmruby MRUBY_LIBS ?= -L./libs/mruby/build/host/lib -lmruby
CFLAGS_DEBUG :=\ CFLAGS_DEBUG :=\
-std=c++20 -Wall -Wextra -fno-rtti \ -std=c++23 -Wall -Wextra -fno-rtti \
-Og -fno-inline -gsplit-dwarf \ -Og -fno-inline -gsplit-dwarf \
-g -fno-omit-frame-pointer -ffast-math \ -g -fno-omit-frame-pointer -ffast-math \
-I./include -I./libs/unicode_width -I./include -I./libs/unicode_width
CFLAGS_RELEASE :=\ CFLAGS_RELEASE :=\
-std=c++20 -O3 -mtune=generic -fno-rtti \ -std=c++23 -O3 -mtune=generic -fno-rtti \
-fvisibility=hidden -static -ffast-math \ -fvisibility=hidden -static -ffast-math \
-fomit-frame-pointer -DNDEBUG -s \ -fomit-frame-pointer -DNDEBUG -s \
-I./include -I./libs/unicode_width -I./include -I./libs/unicode_width
+1 -3
View File
@@ -29,12 +29,10 @@ inline int read_file(const char *path, char **text, uint32_t *len) {
if (read != *len) { if (read != *len) {
free(*text); free(*text);
*text = NULL; *text = nullptr;
*len = 0; *len = 0;
return 0; return 0;
} }
(*text)[*len] = '\0'; // handy if treating it as a C string
return 1; return 1;
} }
-5
View File
@@ -1,8 +1,3 @@
#pragma once #pragma once
#include "pch.h" #include "pch.h"
struct Point {
uint32_t row;
uint32_t col;
};
+2 -5
View File
@@ -1,12 +1,11 @@
#pragma once #pragma once
#include "../constants.h"
#include "buffer.h" #include "buffer.h"
#include "pch.h" #include "pch.h"
struct AppendBuffer : Buffer { struct AppendBuffer : Buffer {
static constexpr uint32_t CHUNK_SIZE = 1 << 16; using TChunk = char[APPEND_CHUNK_SIZE];
using TChunk = char[CHUNK_SIZE];
std::vector<TChunk *> buf; std::vector<TChunk *> buf;
TChunk *t_current; TChunk *t_current;
uint32_t current_offset{0}; uint32_t current_offset{0};
@@ -21,6 +20,4 @@ struct AppendBuffer : Buffer {
const char *read(uint32_t pos, uint32_t *out_len); const char *read(uint32_t pos, uint32_t *out_len);
inline uint32_t length(); inline uint32_t length();
inline void new_text_chunk();
}; };
+2 -2
View File
@@ -7,8 +7,8 @@ struct OriginalBuffer : Buffer {
char *buf; char *buf;
uint32_t len; uint32_t len;
// TODO: replace with file io or even lazy loaded file io for large files/logs etc. later. // Add 2 modes here, normal and lazy, lazy copies the file to a safe location and uses mmap.
OriginalBuffer(char *buf, uint32_t len); OriginalBuffer(std::string path);
~OriginalBuffer(); ~OriginalBuffer();
+11
View File
@@ -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"
);
+8 -2
View File
@@ -3,14 +3,20 @@
#include "../shard.h" #include "../shard.h"
#include "pch.h" #include "pch.h"
enum struct Direction : uint8_t {
Forward,
Backward
};
struct ChunkIterator { struct ChunkIterator {
Direction dir;
Shard *root = nullptr; Shard *root = nullptr;
std::vector<Shard *> stack; std::vector<Shard *> stack;
Petal *petal = nullptr; Petal *petal = nullptr;
uint32_t petal_offset = 0; uint32_t petal_offset = 0;
uint32_t global_offset; uint32_t global_offset = 0;
ChunkIterator(Shard *r); ChunkIterator(Shard *r, Direction dir);
~ChunkIterator(); ~ChunkIterator();
+4 -6
View File
@@ -6,14 +6,12 @@
struct LineIterator { struct LineIterator {
ChunkIterator it; ChunkIterator it;
Direction dir;
const char *cursor = nullptr; const char *chunk;
bool eof = false; uint32_t len;
uint32_t remaining = 0;
uint32_t line_offset = 0;
LineIterator(Shard *r, uint32_t start_line);
LineIterator(Shard *r, uint32_t start_line, Direction dir);
~LineIterator() = default; ~LineIterator() = default;
bool next(std::string &line); bool next(std::string &line);
+1
View File
@@ -2,6 +2,7 @@
#include "buffer/buffer.h" #include "buffer/buffer.h"
#include "buffer/original.h" #include "buffer/original.h"
#include "constants.h"
#include "pch.h" #include "pch.h"
#include "utils/utils.h" #include "utils/utils.h"
+33 -11
View File
@@ -2,14 +2,29 @@
#include "buffer/append.h" #include "buffer/append.h"
#include "buffer/original.h" #include "buffer/original.h"
#include "constants.h"
#include "iterators/line.h" #include "iterators/line.h"
#include "pch.h" #include "pch.h"
#include "search.h" #include "search.h"
#include "shard.h" #include "shard.h"
#include "utils/utils.h" #include "utils/utils.h"
struct Point {
uint32_t row;
uint32_t col;
};
struct Range {
Point start;
Point end;
};
struct Vase { struct Vase {
Vase(char *data, uint32_t length); OriginalBuffer original;
AppendBuffer append;
Shard *root;
Vase(std::string path);
~Vase(); ~Vase();
@@ -20,26 +35,33 @@ struct Vase {
LineIterator iterate(uint32_t line); 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 insert(Point *point, const char *data, uint32_t len);
void erase(Point *point, int64_t amount); void erase(Point *point, int64_t amount);
bool jump(Point *point, int64_t amount); void erase(Range range);
bool clamp(Point *point); 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( void regex_search_replace(
std::string_view pattern, std::string_view pattern,
Point start, Point end, Point start, Point end,
std::string_view replace, std::string_view options std::string_view replace, std::string_view options
); );
private: bool undo();
OriginalBuffer original; bool redo();
AppendBuffer append;
Shard *root;
/*std::vector<Shard *> undo; // TODO: later void snapshot();
uint16_t top; // of the undo stack. void prune_history(uint32_t n);
uint16_t max; // for redo when no edits have been done after some undo.*/
private:
std::vector<Shard *> history;
uint32_t history_top;
uint32_t offset_of(Point point); uint32_t offset_of(Point point);
static void flatten(Shard *s, std::string &out); static void flatten(Shard *s, std::string &out);
void _insert(Point *point, const char *data, uint32_t len);
}; };
+5 -27
View File
@@ -1,39 +1,17 @@
#include "io/file.h"
#include "pch.h" #include "pch.h"
#include "vase/iterators/chunk.h" #include "vase/iterators/chunk.h"
#include "vase/iterators/line.h" #include "vase/iterators/line.h"
#include "vase/shard.h"
#include "vase/vase.h" #include "vase/vase.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc < 2)
#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
throw std::runtime_error("Please give filename."); throw std::runtime_error("Please give filename.");
uint32_t len; Vase vase = Vase(std::string(argv[1]));
char *text;
read_file(path, &text, &len);
#endif std::cout << "\n"
<< vase.to_string()
Vase vase = Vase(text, len); << "\n";
return 0; return 0;
} }
+16 -15
View File
@@ -1,7 +1,8 @@
#include "vase/buffer/append.h" #include "vase/buffer/append.h"
AppendBuffer::AppendBuffer() { AppendBuffer::AppendBuffer() {
new_text_chunk(); t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
} }
AppendBuffer::~AppendBuffer() { AppendBuffer::~AppendBuffer() {
@@ -10,8 +11,11 @@ AppendBuffer::~AppendBuffer() {
} }
uint32_t AppendBuffer::key(char c) { uint32_t AppendBuffer::key(char c) {
if (t_offset == CHUNK_SIZE) if (t_offset == APPEND_CHUNK_SIZE) {
new_text_chunk(); t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
t_offset = 0;
}
(*t_current)[t_offset++] = c; (*t_current)[t_offset++] = c;
return current_offset++; 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 AppendBuffer::append(const char *text, uint32_t length) {
uint32_t start = current_offset; uint32_t start = current_offset;
while (length > 0) { while (length > 0) {
if (t_offset == CHUNK_SIZE) if (t_offset == APPEND_CHUNK_SIZE) {
new_text_chunk(); t_current = (TChunk *)malloc(sizeof(TChunk));
uint32_t copy = std::min(length, CHUNK_SIZE - t_offset); 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); memcpy((*t_current) + t_offset, text, copy);
t_offset += copy; t_offset += copy;
current_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) { const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) {
if (pos >= current_offset) if (pos >= current_offset)
return nullptr; return nullptr;
uint32_t local_offset = pos % CHUNK_SIZE; uint32_t local_offset = pos % APPEND_CHUNK_SIZE;
if (out_len) { if (out_len) {
uint32_t remaining = current_offset - pos; 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); *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() { inline uint32_t AppendBuffer::length() {
return current_offset; return current_offset;
} }
inline void AppendBuffer::new_text_chunk() {
t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
t_offset = 0;
}
+4 -1
View File
@@ -1,6 +1,9 @@
#include "vase/buffer/original.h" #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() { OriginalBuffer::~OriginalBuffer() {
free(buf); free(buf);
+78 -25
View File
@@ -1,6 +1,7 @@
#include "vase/iterators/chunk.h" #include "vase/iterators/chunk.h"
ChunkIterator::ChunkIterator(Shard *r) : root(r) { ChunkIterator::ChunkIterator(Shard *r, Direction dir)
: dir(dir), root(r) {
if (!root) if (!root)
return; return;
Shard::retain(root); Shard::retain(root);
@@ -14,42 +15,56 @@ void ChunkIterator::seek_offset(uint32_t offset) {
stack.clear(); stack.clear();
petal = nullptr; petal = nullptr;
petal_offset = 0; petal_offset = 0;
global_offset = 0; if (!root)
if (!root || offset >= root->length)
return; return;
if (offset >= root->length)
offset = root->length - 1;
uint32_t target = offset; uint32_t target = offset;
Shard *curr = root; Shard *curr = root;
while (curr) { while (curr) {
if (curr->kind == Shard::ShardKind::Petal) { if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr; petal = (Petal *)curr;
petal_offset = target; petal_offset = target;
global_offset += target;
return; return;
} else { } else {
auto *b = (Branch *)curr; auto *b = (Branch *)curr;
uint32_t left_len = b->left->length; uint32_t left_len = b->left->length;
if (target < left_len) { if (target < left_len) {
if (dir == Direction::Forward)
stack.push_back(b->right); stack.push_back(b->right);
curr = b->left; curr = b->left;
} else { } else {
target -= left_len; target -= left_len;
global_offset += b->left->length; if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += left_len;
curr = b->right; curr = b->right;
} }
} }
} }
std::unreachable();
} }
void ChunkIterator::seek_line(uint32_t line) { void ChunkIterator::seek_line(uint32_t line) {
stack.clear(); stack.clear();
petal = nullptr; petal = nullptr;
petal_offset = 0; petal_offset = 0;
global_offset = 0; bool last_line = false;
if (!root || line > root->lines) if (!root)
return; return;
if (line > root->lines + 1)
line = root->lines + 1;
if (line == root->lines + 1)
last_line = true;
Shard *curr = root; Shard *curr = root;
while (curr) { while (curr) {
if (curr->kind == Shard::ShardKind::Petal) { if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr; petal = (Petal *)curr;
if (last_line) {
petal_offset = petal->length;
global_offset += petal->length;
} else {
const char *text = nullptr; const char *text = nullptr;
uint32_t remaining = 0; uint32_t remaining = 0;
uint32_t offset = 0; uint32_t offset = 0;
@@ -70,63 +85,101 @@ void ChunkIterator::seek_line(uint32_t line) {
text = c + 1; text = c + 1;
line--; line--;
} }
if (dir == Direction::Backward)
--offset;
petal_offset = offset; petal_offset = offset;
global_offset += offset;
}
return; return;
} else { } else {
auto *b = (Branch *)curr; auto *b = (Branch *)curr;
uint32_t left_lines = b->left->lines; uint32_t left_lines = b->left->lines;
if (line <= left_lines) { if (line <= left_lines) {
if (dir == Direction::Forward)
stack.push_back(b->right); stack.push_back(b->right);
curr = b->left; curr = b->left;
} else { } else {
line -= left_lines; line -= left_lines;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += b->left->length; global_offset += b->left->length;
curr = b->right; curr = b->right;
} }
} }
} }
std::unreachable();
} }
uint32_t ChunkIterator::byte_offset() { uint32_t ChunkIterator::byte_offset() {
return global_offset + petal_offset; return global_offset;
} }
bool ChunkIterator::next(const char **data, uint32_t *out_len) { bool ChunkIterator::next(const char **data, uint32_t *out_len) {
if (dir == Direction::Forward) {
while (true) { while (true) {
if (petal) { if (petal) {
if (petal_offset < petal->length) { 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;
petal = nullptr; petal = nullptr;
continue; 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); uint32_t take = std::min(got, remaining);
*data = chunk; *data = chunk;
*out_len = take; *out_len = take;
petal_offset += take; petal_offset += take;
return true; return true;
} }
global_offset += petal->length; if (stack.empty())
petal = nullptr; 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; petal_offset = 0;
} }
} 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()) if (stack.empty())
return false; return false;
Shard *s = stack.back(); auto s = stack.back();
stack.pop_back(); stack.pop_back();
if (!s) while (s->kind == Shard::ShardKind::Branch) {
continue; Branch *b = (Branch *)s;
if (s->kind == Shard::ShardKind::Petal) {
petal = (Petal *)s;
petal_offset = 0;
} else {
auto *b = (Branch *)s;
stack.push_back(b->right);
stack.push_back(b->left); stack.push_back(b->left);
s = b->right;
}
petal = (Petal *)s;
petal_offset = petal->length;
} }
} }
} }
+53 -28
View File
@@ -1,40 +1,65 @@
#include "vase/iterators/line.h" #include "vase/iterators/line.h"
LineIterator::LineIterator(Shard *r, uint32_t start_line) : it(r) { LineIterator::LineIterator(Shard *r, uint32_t start_line, Direction dir)
it.seek_line(start_line); : 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) { bool LineIterator::next(std::string &line) {
line.clear(); line.clear();
line_offset = it.byte_offset() - remaining; if (!chunk)
return false;
if (dir == Direction::Forward) {
while (true) { while (true) {
if (remaining == 0) { const char *nl = (const char *)memchr(chunk, '\n', len);
uint32_t got; if (!nl) {
if (!it.next(&cursor, &got)) { if (len && chunk[len - 1] == '\r')
eof = true;
break;
}
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; --len;
line.append(cursor, len); line.append(chunk, len);
cursor = end + 1; if (!it.next(&chunk, &len)) {
remaining -= (uint32_t)(len + 1); chunk = nullptr;
return true;
}
continue;
}
const char *end = nl;
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; return true;
} }
line.append(cursor, remaining);
cursor += remaining;
remaining = 0;
} }
return !line.empty();
}
uint32_t LineIterator::byte_offset() {
return line_offset;
} }
+1 -1
View File
@@ -83,7 +83,7 @@ std::vector<RegexMatch> regex_search(
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); 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); it.seek_offset(start_offset);
const char *data; const char *data;
+3 -4
View File
@@ -195,7 +195,7 @@ Shard *merge_leaves(Shard *a, Shard *b) {
Shard *append_leaf(Shard *root, Shard *leaf) { Shard *append_leaf(Shard *root, Shard *leaf) {
if (!root) if (!root)
return leaf; 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); return merge_leaves(root, leaf);
Branch *b = (Branch *)root; Branch *b = (Branch *)root;
auto new_right = append_leaf(b->right, leaf); 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) { Shard *create_shards(Buffer *o) {
constexpr uint32_t PETAL_SIZE = 32 * 1024;
std::vector<Shard *> pieces; std::vector<Shard *> pieces;
uint32_t pos = 0; uint32_t pos = 0;
const uint32_t total = o->length(); 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) { while (pos < total) {
uint32_t len = 0; uint32_t len = 0;
const char *data = o->read(pos, &len); 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; uint32_t lines = 0;
const char *p = data; const char *p = data;
const char *end = data + take; const char *end = data + take;
+192 -24
View File
@@ -3,12 +3,18 @@
#include "vase/iterators/line.h" #include "vase/iterators/line.h"
#include "vase/search.h" #include "vase/search.h"
Vase::Vase(char *data, uint32_t length) Vase::Vase(std::string path)
: original(data, length), append(), : original(path), append(),
root(create_shards(&original)) {} root(create_shards(&original)) {
history.push_back(root);
Shard::retain(root);
history_top = 0;
}
Vase::~Vase() { Vase::~Vase() {
Shard::release(root); Shard::release(root);
for (auto s : history)
Shard::release(s);
} }
uint32_t Vase::length() { uint32_t Vase::length() {
@@ -21,13 +27,56 @@ std::string Vase::to_string() {
return out; 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') if (key == '\n')
*point = {point->row + 1, 0}; *point = {point->row + 1, 0};
else else
point->col++; point->col++;
uint32_t pos = append.key(key); uint32_t pos = append.key(key);
// limit petal size to 32KiB here later.
Shard *inserted = new Petal(1, key == '\n', &append, pos); Shard *inserted = new Petal(1, key == '\n', &append, pos);
auto [left, right] = split_shard(root, offset_of(*point)); auto [left, right] = split_shard(root, offset_of(*point));
Shard *left2 = append_leaf(left, inserted); 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) { void Vase::insert(Point *point, const char *data, uint32_t len) {
while (len) {
uint32_t chunk_size = std::min<uint32_t>(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) if (len == 0)
return; return;
uint32_t offset = offset_of(*point); 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) { 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) if (amount == 0)
return; return;
uint32_t start; Point start = *point;
uint32_t count; Point end = *point;
if (amount < 0) { if (amount < 0)
count = std::min<uint32_t>(-amount, offset); move_clusters(&start, amount);
start = offset - count; else
} else { move_clusters(&end, amount);
start = offset; uint32_t start_offset = offset_of(start);
count = amount; uint32_t end_offset = offset_of(end);
} if (start_offset > end_offset)
auto [a, b] = split_shard(root, start); 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); auto [d, c] = split_shard(b, count);
Shard *new_root = concat_shard(a, c); Shard *new_root = concat_shard(a, c);
Shard::release(a); Shard::release(a);
@@ -102,6 +160,30 @@ void Vase::erase(Point *point, int64_t amount) {
Shard::release(d); Shard::release(d);
Shard::release(root); Shard::release(root);
root = new_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) { 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) { uint32_t Vase::offset_of(Point point) {
LineIterator it(root, point.row); LineIterator it(root, point.row, Direction::Forward);
std::string line; std::string line;
uint32_t offset = 0; uint32_t offset = 0;
if (it.next(line)) { if (it.next(line)) {
@@ -142,14 +224,100 @@ uint32_t Vase::offset_of(Point point) {
return it.byte_offset() + offset; return it.byte_offset() + offset;
} }
bool Vase::jump(Point *, int64_t) { void Vase::move_clusters(Point *point, int64_t amount) {
// todo. if (amount == 0)
return false; 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<uint32_t> 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 *) { void Vase::clamp(Point *point) {
// todo. if (point->row > root->lines)
return false; 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 { struct ReplacePart {