From a329bc24243df9417d6922aca7ef20abf73b3c97 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Thu, 20 Jun 2024 16:35:18 +0200 Subject: [PATCH] Improving the buffer and bug fixing --- src/pdf_browser/basic.h | 9 +- src/text_editor/buffer.cpp | 324 ++++++++++++++++++++++--------------- 2 files changed, 200 insertions(+), 133 deletions(-) diff --git a/src/pdf_browser/basic.h b/src/pdf_browser/basic.h index cb40439..dad7b94 100644 --- a/src/pdf_browser/basic.h +++ b/src/pdf_browser/basic.h @@ -6,7 +6,10 @@ #include #include #include -#define Assert(x) assert(x) +#define Assert(x) \ + if (!(x)) { \ + __debugbreak(); \ + } #if defined(__APPLE__) && defined(__MACH__) #define OS_MAC 1 @@ -969,8 +972,8 @@ inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) { struct Scratch { TempArena checkpoint; Scratch() { this->checkpoint = GetScratch(); } - Scratch(TempArena conflict) { this->checkpoint = GetScratch(conflict.arena); } - Scratch(TempArena c1, TempArena c2) { this->checkpoint = GetScratch(c1.arena, c2.arena); } + Scratch(Arena *conflict) { this->checkpoint = GetScratch(conflict); } + Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); } ~Scratch() { EndTemp(checkpoint); } operator Arena *() { return checkpoint.arena; } operator Allocator() { return *checkpoint.arena; } diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index fe7873a..d2ba769 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1,3 +1,22 @@ + +/* +Ranges are from 0 to n+1 (one past last index). + <0,4> = 0,1,2,3 + +These seem the best, I tried other formations but these are +much better then the rest. + +First of all you can represent the cursor at the end of the buffer +by doing: . This action in itself doesn't +select anything, the formation doesn't force you to index the +buffer and so on, it's reduced to a pure position. + +This property of being able to represent pure positions makes +it possible to clamp the values to the same range <0, buffer_len>, +and if the things are past the range we end up with a pure value. +Very nice behaviour, The program won't delete anything. + +*/ struct Range { int64_t min; int64_t max; // one past last index @@ -10,12 +29,12 @@ struct Edit { }; struct Buffer { - Allocator allocator; - char *data[2]; - int64_t cap; - int64_t len; - int bi; // current buffer index - Array lines; + Allocator allocator; + char *data[2]; + int64_t cap; + int64_t len; + int bi; // current buffer index + Array lines; }; int64_t GetRangeSize(Range range) { @@ -28,132 +47,22 @@ Range GetRange(const Buffer &buffer) { return result; } -int64_t ClampMax(Buffer *buffer, int64_t pos) { +int64_t Clamp(Buffer *buffer, int64_t pos) { int64_t result = Clamp(pos, (int64_t)0, buffer->len); return result; } -int64_t ClampMin(Buffer *buffer, int64_t pos) { - int64_t end_of_buffer = Max((int64_t)0, buffer->len - 1); - int64_t result = Clamp(pos, (int64_t)0, end_of_buffer); + +Range Clamp(Buffer *buffer, Range range) { + Range result = {}; + result.min = Clamp(buffer, range.min); + result.max = Clamp(buffer, range.max); return result; } -void ApplyEdits(Buffer *buffer, Array edits) { - int64_t size_to_delete = 0; - int64_t size_to_insert = 0; - For(edits) { - it.range.min = ClampMin(buffer, it.range.min); - it.range.max = ClampMax(buffer, it.range.max); - size_to_delete += GetRangeSize(it.range); - size_to_insert += it.string.len; - } - -#if DEBUG_BUILD - ForItem(it1, edits) { - ForItem(it2, edits) { - if (&it1 == &it2) continue; - - bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max; - Assert(!a2_inside); - - bool b2_inside = it2.range.max > it1.range.min && it2.range.max <= it1.range.max; - Assert(!b2_inside); - } - } -#endif - - int64_t len_offset = size_to_insert - size_to_delete; - int64_t allocated_size_required = Max((int64_t)0, len_offset); - if (buffer->len + allocated_size_required > buffer->cap) { - int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096); - if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator(); - - { - char *data = AllocArray(buffer->allocator, char, new_cap); - Assert(data); - memcpy(data, buffer->data[0], buffer->len); - Dealloc(buffer->allocator, &buffer->data[0]); - buffer->data[0] = data; - } - { - char *data = AllocArray(buffer->allocator, char, new_cap); - Assert(data); - memcpy(data, buffer->data[1], buffer->len); - Dealloc(buffer->allocator, &buffer->data[1]); - buffer->data[1] = data; - } - buffer->cap = new_cap; - } - - Scratch scratch; - Array writes = {scratch}; - int64_t prev_source = 0; - int64_t prev_dest = 0; - - For(edits) { - Range source_range = {prev_source, it.range.min}; - if (GetRangeSize(source_range) != 0) { - String source_string = {}; - source_string.data = buffer->data[buffer->bi] + source_range.min; - source_string.len = GetRangeSize(source_range); - Range dest_range = {prev_dest, prev_dest + source_string.len}; - writes.add({dest_range, source_string}); - - prev_dest = dest_range.max; - } - - Range dest_range = {prev_dest, prev_dest + it.string.len}; - writes.add({dest_range, it.string}); - prev_dest = dest_range.max; - prev_source = it.range.max; - } - - // Add remaining range - Range source_range = {prev_source, buffer->len}; - if (GetRangeSize(source_range)) { - String source_string = {}; - source_string.data = buffer->data[buffer->bi] + source_range.min; - source_string.len = GetRangeSize(source_range); - Range dest_range = {prev_dest, prev_dest + source_string.len}; - writes.add({dest_range, source_string}); - } - -#if DEBUG_BUILD - for (int64_t i = 0; i < writes.len - 1; i += 1) { - Assert(writes[i].range.max == writes[i + 1].range.min); - } -#endif - - int64_t new_buffer_len = 0; - int dsti = (buffer->bi + 1) % 2; - For(writes) { - memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len); - new_buffer_len += it.string.len; - } - buffer->bi = dsti; - Assert(new_buffer_len == buffer->len + len_offset); - buffer->len = new_buffer_len; - - String string = {buffer->data[buffer->bi], buffer->len}; - buffer->lines = Split(buffer->allocator, string, "\n"); -} - void AddEdit(Array *edits, Range range, String string) { edits->add({range, string}); } -int64_t AdjustUTF8Pos(Buffer *buffer, int64_t pos, int64_t direction = 1) { - int64_t result = pos; - for (; result >= 0 && result < buffer->len;) { - if (IsUTF8ContinuationByte(buffer->data[buffer->bi][0])) { - result += direction; - } else { - break; - } - } - return result; -} - bool InBounds(Buffer *buffer, int64_t pos) { bool result = pos >= 0 && pos < buffer->len; return result; @@ -169,12 +78,140 @@ char *GetCharP(Buffer *buffer, int64_t pos) { return buffer->data[buffer->bi] + pos; } +String GetString(Buffer *buffer, Range range) { + range = Clamp(buffer, range); + String result = {GetCharP(buffer, range.min), GetRangeSize(range)}; + return result; +} + +void ApplyEdits(Buffer *buffer, Array edits) { + int64_t size_to_delete = 0; + int64_t size_to_insert = 0; + For(edits) { + it.range.min = Clamp(buffer, it.range.min); + it.range.max = Clamp(buffer, it.range.max); + size_to_delete += GetRangeSize(it.range); + size_to_insert += it.string.len; + } + +#if DEBUG_BUILD + ForItem(it1, edits) { + ForItem(it2, edits) { + if (&it1 == &it2) continue; + + bool a2_inside = it2.range.min >= it1.range.min && it2.range.min <= it1.range.max; + Assert(!a2_inside); + + bool b2_inside = it2.range.max >= it1.range.min && it2.range.max <= it1.range.max; + Assert(!b2_inside); + } + } +#endif + + int64_t len_offset = size_to_insert - size_to_delete; + int64_t allocated_size_required = Max((int64_t)0, len_offset); + if (buffer->len + allocated_size_required > buffer->cap) { + int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096); + if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator(); + + for (int i = 0; i < 2; i += 1) { + char *data = AllocArray(buffer->allocator, char, new_cap); + Assert(data); + memcpy(data, buffer->data[i], buffer->len); + Dealloc(buffer->allocator, &buffer->data[i]); + buffer->data[i] = data; + } + buffer->cap = new_cap; + } + + int srci = buffer->bi; + int dsti = (buffer->bi + 1) % 2; + + Scratch scratch((Arena *)buffer->allocator.object); + Array writes = {scratch}; + int64_t prev_source = 0; + int64_t prev_dest = 0; + + For(edits) { + Range source_range = {prev_source, it.range.min}; + if (GetRangeSize(source_range) != 0) { + String source_string = {}; + source_string.data = buffer->data[srci] + source_range.min; + source_string.len = GetRangeSize(source_range); + Range dest_range = {prev_dest, prev_dest + source_string.len}; + writes.add({dest_range, source_string}); + + prev_dest = dest_range.max; + } + + Range dest_range = {prev_dest, prev_dest + it.string.len}; + writes.add({dest_range, it.string}); + prev_dest = dest_range.max; + prev_source = it.range.max; + } + + // Add remaining range + Range source_range = {prev_source, buffer->len}; + if (GetRangeSize(source_range)) { + String source_string = {}; + source_string.data = buffer->data[srci] + source_range.min; + source_string.len = GetRangeSize(source_range); + Range dest_range = {prev_dest, prev_dest + source_string.len}; + writes.add({dest_range, source_string}); + } + +#if DEBUG_BUILD + for (int64_t i = 0; i < writes.len - 1; i += 1) { + Assert(writes[i].range.max == writes[i + 1].range.min); + } +#endif + + int64_t new_buffer_len = 0; + For(writes) { + memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len); + new_buffer_len += it.string.len; + } + buffer->bi = dsti; + Assert(new_buffer_len == buffer->len + len_offset); + buffer->len = new_buffer_len; + + // Update lines + { + String delimiter = "\n"; + String string = {buffer->data[dsti], buffer->len}; + buffer->lines.allocator = buffer->allocator; + buffer->lines.clear(); + + int64_t index = 0; + int64_t base_index = 0; + while (Seek(string, delimiter, &index)) { + buffer->lines.add({base_index, base_index + index}); + base_index += index + delimiter.len; + string = string.skip(index + delimiter.len); + } + buffer->lines.add({base_index, base_index + string.len}); + } +} + +int64_t AdjustUTF8Pos(Buffer *buffer, int64_t pos, int64_t direction = 1) { + int64_t result = pos; + for (; result >= 0 && result < buffer->len;) { + char c = GetChar(buffer, pos); + if (IsUTF8ContinuationByte(c)) { + result += direction; + } else { + break; + } + } + return result; +} + uint32_t GetUTF32(Buffer *buffer, int64_t pos, int64_t *codepoint_size) { if (!InBounds(buffer, pos)) { return 0; } - char *p = buffer->data[buffer->bi] + pos; + char *p = GetCharP(buffer, pos); int64_t max = buffer->len - pos; UTF32Result utf32 = UTF8ToUTF32(p, (int)max); Assert(utf32.error == 0); @@ -232,8 +269,8 @@ BufferIter Iterate(Buffer *buffer, Range range, int64_t direction = ITERATE_FORW Assert(direction == ITERATE_FORWARD || direction == ITERATE_BACKWARD); Assert(!IsUTF8ContinuationByte(GetChar(buffer, range.min))); Assert(range.max >= range.min); - range.min = ClampMin(buffer, range.min); - range.max = ClampMax(buffer, range.max); + range.min = Clamp(buffer, range.min); + range.max = Clamp(buffer, range.max); BufferIter result = {buffer, range.min, range.max, direction}; if (direction == ITERATE_BACKWARD) { @@ -255,7 +292,12 @@ void RunBufferTests() { String string = {buffer.data[buffer.bi], buffer.len}; Assert(string == "Things and other things"); Assert(buffer.lines.len == 1); - Assert(buffer.lines[0] == "Things and other things"); + Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things"); + + edits.clear(); + AddEdit(&edits, {1000, 1000}, " memes"); + ApplyEdits(&buffer, edits); + Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things memes"); } { Buffer buffer = {scratch}; @@ -275,7 +317,7 @@ void RunBufferTests() { String string = {buffer.data[buffer.bi], buffer.len}; Assert(string == "Memes dna BigOther things"); Assert(buffer.lines.len == 1); - Assert(buffer.lines[0] == "Memes dna BigOther things"); + Assert(GetString(&buffer, buffer.lines[0]) == "Memes dna BigOther things"); } { Buffer buffer = {scratch}; @@ -287,9 +329,9 @@ void RunBufferTests() { }); ApplyEdits(&buffer, edits); Assert(buffer.lines.len == 3); - Assert(buffer.lines[1] == "Things and other things"); - Assert(buffer.lines[0] == "Things and other things"); - Assert(buffer.lines[2] == ""); + Assert(GetString(&buffer, buffer.lines[1]) == "Things and other things"); + Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things"); + Assert(GetString(&buffer, buffer.lines[2]) == ""); { Array s = {scratch}; @@ -334,4 +376,26 @@ void RunBufferTests() { Assert(str.len == b.len); } } + + { + Arena *arena = AllocArena(); + Buffer buffer = {*arena}; + Array edits = {*arena}; + edits.add({ + {0, 0}, + "Things and other things\n" + "Things and other things\n" + }); + + int iters = 100; + for (int i = 0; i < iters; i += 1) { + ApplyEdits(&buffer, edits); + for (int64_t j = 0; j < i; j += 1) { + String string = GetString(&buffer, {edits[0].string.len * j, edits[0].string.len * (j + 1)}); + Assert(string == edits[0].string); + } + } + Assert(edits[0].string.len * iters == buffer.len); + Assert(buffer.lines.len == iters * 2 + 1); + } } \ No newline at end of file