Buffer work
This commit is contained in:
@@ -493,7 +493,7 @@ Array<T> Copy(Allocator alo, Array<T> array) {
|
|||||||
template <class T>
|
template <class T>
|
||||||
Array<T> TightCopy(Allocator alo, Array<T> array) {
|
Array<T> TightCopy(Allocator alo, Array<T> array) {
|
||||||
Array<T> result = {alo};
|
Array<T> result = {alo};
|
||||||
result.reserve(array.len);
|
Reserve(&result, array.len);
|
||||||
memcpy(result.data, array.data, sizeof(T) * array.len);
|
memcpy(result.data, array.data, sizeof(T) * array.len);
|
||||||
result.len = array.len;
|
result.len = array.len;
|
||||||
return result;
|
return result;
|
||||||
@@ -526,6 +526,24 @@ void RemoveByIndex(Array<T> *arr, int64_t index) {
|
|||||||
arr->len -= 1;
|
arr->len -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RemoveManyByIndex(Array<T> *arr, int64_t index, int64_t count) {
|
||||||
|
if (count == 0) return;
|
||||||
|
Assert(index >= 0 && index < arr->len);
|
||||||
|
Assert((index + count) > 0 && (index + count) <= arr->len);
|
||||||
|
int64_t right_len = arr->len - index - count;
|
||||||
|
memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T));
|
||||||
|
arr->len -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RemoveMany(Array<T> *arr, T &item, int64_t count) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
Assert(&item >= arr->begin() && &item < arr->end());
|
||||||
|
int64_t index = GetIndex(*arr, item);
|
||||||
|
RemoveManyByIndex(arr, index, count);
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void Remove(Array<T> *arr, T &item) {
|
void Remove(Array<T> *arr, T &item) {
|
||||||
Assert(arr->len > 0);
|
Assert(arr->len > 0);
|
||||||
|
|||||||
@@ -1,34 +1,61 @@
|
|||||||
/*
|
/*
|
||||||
https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation
|
https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation
|
||||||
*/
|
*/
|
||||||
// struct Pos {
|
|
||||||
// S32 line;
|
|
||||||
// S32 col;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// struct Range {
|
|
||||||
// Pos from; // including
|
|
||||||
// Pos to; // one past last
|
|
||||||
// };
|
|
||||||
|
|
||||||
struct Range {
|
struct Range {
|
||||||
Int min;
|
Int min;
|
||||||
Int max;
|
Int max;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Cursor {
|
||||||
|
union {
|
||||||
|
Range range;
|
||||||
|
Int pos[2];
|
||||||
|
};
|
||||||
|
Int ifront;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pos {
|
||||||
|
Int line;
|
||||||
|
Int col;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Edit {
|
||||||
|
Range range;
|
||||||
|
String16 string;
|
||||||
|
};
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
U16 *data;
|
union {
|
||||||
|
U16 *data;
|
||||||
|
wchar_t *str;
|
||||||
|
};
|
||||||
Int len;
|
Int len;
|
||||||
Int cap;
|
Int cap;
|
||||||
Array<Int> line_starts;
|
Array<Int> line_starts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HistoryEntry {
|
||||||
|
Array<Edit> edits;
|
||||||
|
Array<Cursor> cursors;
|
||||||
|
};
|
||||||
|
|
||||||
Int GetSize(Range range) {
|
Int GetSize(Range range) {
|
||||||
Assert(range.max >= range.min);
|
Assert(range.max >= range.min);
|
||||||
Int result = range.max - range.min;
|
Int result = range.max - range.min;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Range MakeRange(Int a, Int b) {
|
||||||
|
Range result = {Min(a, b), Max(a, b)};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range MakeRange(Int a) {
|
||||||
|
Range result = {a, a};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
String16 GetString(Buffer &buffer, Range range = {0, INT64_MAX}) {
|
String16 GetString(Buffer &buffer, Range range = {0, INT64_MAX}) {
|
||||||
range.min = Clamp(range.min, (Int)0, buffer.len);
|
range.min = Clamp(range.min, (Int)0, buffer.len);
|
||||||
range.max = Clamp(range.max, (Int)0, buffer.len);
|
range.max = Clamp(range.max, (Int)0, buffer.len);
|
||||||
@@ -36,6 +63,18 @@ String16 GetString(Buffer &buffer, Range range = {0, INT64_MAX}) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Int Clamp(const Buffer &buffer, Int pos) {
|
||||||
|
Int result = Clamp(pos, (Int)0, buffer.len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range Clamp(const Buffer &buffer, Range range) {
|
||||||
|
Range result = {};
|
||||||
|
result.min = Clamp(buffer, range.min);
|
||||||
|
result.max = Clamp(buffer, range.max);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Range GetEndAsRange(Buffer &buffer) {
|
Range GetEndAsRange(Buffer &buffer) {
|
||||||
Range result = {buffer.len, buffer.len};
|
Range result = {buffer.len, buffer.len};
|
||||||
return result;
|
return result;
|
||||||
@@ -51,6 +90,63 @@ Range GetRange(Buffer &buffer) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Int GetFront(Cursor cursor) {
|
||||||
|
Int result = cursor.pos[cursor.ifront];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int GetBack(Cursor cursor) {
|
||||||
|
Int index = (cursor.ifront + 1) % 2;
|
||||||
|
Int result = cursor.pos[index];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor MakeCursor(Int front, Int back) {
|
||||||
|
Cursor result = {};
|
||||||
|
if (front >= back) {
|
||||||
|
result.range.min = back;
|
||||||
|
result.range.max = front;
|
||||||
|
result.ifront = 1;
|
||||||
|
} else {
|
||||||
|
result.range.min = front;
|
||||||
|
result.range.max = back;
|
||||||
|
result.ifront = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor ChangeBack(Cursor cursor, Int back) {
|
||||||
|
Int front = GetFront(cursor);
|
||||||
|
Cursor result = MakeCursor(front, back);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor ChangeFront(Cursor cursor, Int front) {
|
||||||
|
Int back = GetBack(cursor);
|
||||||
|
Cursor result = MakeCursor(front, back);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InBounds(const Buffer &buffer, Int pos) {
|
||||||
|
bool result = pos >= 0 && pos < buffer.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreEqual(Range a, Range b) {
|
||||||
|
bool result = a.min == b.min && a.max == b.max;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreEqual(Cursor a, Cursor b) {
|
||||||
|
bool result = AreEqual(a.range, b.range) && a.ifront == b.ifront;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InRange(Int a, Range b) {
|
||||||
|
bool result = a >= b.min && a < b.max;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Range GetLine(Buffer &buffer, Int line) {
|
Range GetLine(Buffer &buffer, Int line) {
|
||||||
Range result = {0, buffer.len};
|
Range result = {0, buffer.len};
|
||||||
result.min = buffer.line_starts[line];
|
result.min = buffer.line_starts[line];
|
||||||
@@ -96,7 +192,7 @@ void Grow(Buffer *buffer, Int change_size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffsetAllLinesPast(Buffer *buffer, Int line, Int *_offset) {
|
void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) {
|
||||||
Int offset = *_offset;
|
Int offset = *_offset;
|
||||||
*_offset = 0;
|
*_offset = 0;
|
||||||
if (offset == 0) return;
|
if (offset == 0) return;
|
||||||
@@ -106,7 +202,57 @@ void OffsetAllLinesPast(Buffer *buffer, Int line, Int *_offset) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplaceText(Buffer *buffer, Range range, String16 string) {
|
void ValidateLineStarts(Buffer *buffer) {
|
||||||
|
Int line = 0;
|
||||||
|
for (Int i = 0; i < buffer->len; i += 1) {
|
||||||
|
Int l = GetLineNumber(*buffer, i);
|
||||||
|
Assert(l == line);
|
||||||
|
if (buffer->data[i] == L'\n') line += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLines(Buffer *buffer, Range range, String16 string) {
|
||||||
|
Array<Int> &ls = buffer->line_starts;
|
||||||
|
Int min_line_number = GetLineNumber(*buffer, range.min);
|
||||||
|
Assert(min_line_number < ls.len);
|
||||||
|
// Update lines remove
|
||||||
|
{
|
||||||
|
Int line_offset = 0;
|
||||||
|
Int lines_to_remove = min_line_number + 1;
|
||||||
|
Int lines_to_remove_count = 0;
|
||||||
|
for (Int i = range.min; i < range.max; i += 1) {
|
||||||
|
wchar_t c = buffer->data[i];
|
||||||
|
if (c == '\n') {
|
||||||
|
lines_to_remove_count += 1;
|
||||||
|
}
|
||||||
|
line_offset -= 1;
|
||||||
|
}
|
||||||
|
RemoveManyByIndex(&ls, lines_to_remove, lines_to_remove_count);
|
||||||
|
OffsetAllLinesForward(buffer, min_line_number + 1, &line_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update lines add
|
||||||
|
Int line_offset = 0;
|
||||||
|
Int nl = min_line_number + 1;
|
||||||
|
for (Int i = 0; i < string.len; i += 1) {
|
||||||
|
nl = min_line_number + 1;
|
||||||
|
bool next_line_valid = nl < ls.len;
|
||||||
|
|
||||||
|
if (string[i] == L'\n') {
|
||||||
|
Int new_line_pos = range.min + i + 1;
|
||||||
|
line_offset += 1;
|
||||||
|
|
||||||
|
Insert(&ls, new_line_pos, nl);
|
||||||
|
OffsetAllLinesForward(buffer, nl + 1, &line_offset);
|
||||||
|
min_line_number = nl;
|
||||||
|
} else if (next_line_valid) {
|
||||||
|
line_offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OffsetAllLinesForward(buffer, nl, &line_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _ReplaceText(Buffer *buffer, Range range, String16 string) {
|
||||||
Assert(range.max >= range.min);
|
Assert(range.max >= range.min);
|
||||||
Assert(range.max >= 0 && range.max <= buffer->len);
|
Assert(range.max >= 0 && range.max <= buffer->len);
|
||||||
Assert(range.min >= 0 && range.min <= buffer->len);
|
Assert(range.min >= 0 && range.min <= buffer->len);
|
||||||
@@ -122,42 +268,111 @@ void ReplaceText(Buffer *buffer, Range range, String16 string) {
|
|||||||
U16 *end_remove = begin_remove + range_size;
|
U16 *end_remove = begin_remove + range_size;
|
||||||
Int remain_len = buffer->len - (range.min + range_size);
|
Int remain_len = buffer->len - (range.min + range_size);
|
||||||
|
|
||||||
Int new_buffer_len = buffer->len + change_size;
|
UpdateLines(buffer, range, string);
|
||||||
Array<Int> &ls = buffer->line_starts;
|
|
||||||
Int min_line_number = GetLineNumber(*buffer, range.min);
|
|
||||||
Assert(min_line_number < ls.len);
|
|
||||||
{
|
|
||||||
// Update line starts with removed string
|
|
||||||
// for (Int i = range.min; i < range.max; i += 1) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @todo fuck I need to update all lines after edition point
|
|
||||||
// Update line starts with added string
|
|
||||||
Int line_update_counter = 0;
|
|
||||||
Int nl = min_line_number + 1;
|
|
||||||
for (Int i = 0; i < string.len; i += 1) {
|
|
||||||
nl = min_line_number + 1;
|
|
||||||
bool next_line_valid = nl < ls.len;
|
|
||||||
|
|
||||||
if (string[i] == L'\n') {
|
|
||||||
Int new_line_pos = range.min + i + 1;
|
|
||||||
line_update_counter += 1;
|
|
||||||
|
|
||||||
Insert(&ls, new_line_pos, nl);
|
|
||||||
OffsetAllLinesPast(buffer, nl + 1, &line_update_counter);
|
|
||||||
min_line_number = nl;
|
|
||||||
} else if (next_line_valid) {
|
|
||||||
line_update_counter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OffsetAllLinesPast(buffer, nl, &line_update_counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
U16 *begin_add = begin_remove;
|
U16 *begin_add = begin_remove;
|
||||||
U16 *end_add = begin_add + string.len;
|
U16 *end_add = begin_add + string.len;
|
||||||
MemoryMove(end_add, end_remove, remain_len * sizeof(U16));
|
MemoryMove(end_add, end_remove, remain_len * sizeof(U16));
|
||||||
MemoryCopy(begin_add, string.data, string.len * sizeof(U16));
|
MemoryCopy(begin_add, string.data, string.len * sizeof(U16));
|
||||||
buffer->len = new_buffer_len;
|
buffer->len = buffer->len + change_size;
|
||||||
|
|
||||||
|
ValidateLineStarts(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
|
||||||
|
// SortKey = range.min
|
||||||
|
if (Count == 1) {
|
||||||
|
// NOTE(casey): No work to do.
|
||||||
|
} else if (Count == 2) {
|
||||||
|
Edit *EntryA = First;
|
||||||
|
Edit *EntryB = First + 1;
|
||||||
|
if (EntryA->range.min > EntryB->range.min) {
|
||||||
|
Swap(EntryA, EntryB);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int64_t Half0 = Count / 2;
|
||||||
|
int64_t Half1 = Count - Half0;
|
||||||
|
|
||||||
|
Assert(Half0 >= 1);
|
||||||
|
Assert(Half1 >= 1);
|
||||||
|
|
||||||
|
Edit *InHalf0 = First;
|
||||||
|
Edit *InHalf1 = First + Half0;
|
||||||
|
Edit *End = First + Count;
|
||||||
|
|
||||||
|
MergeSort(Half0, InHalf0, Temp);
|
||||||
|
MergeSort(Half1, InHalf1, Temp);
|
||||||
|
|
||||||
|
Edit *ReadHalf0 = InHalf0;
|
||||||
|
Edit *ReadHalf1 = InHalf1;
|
||||||
|
|
||||||
|
Edit *Out = Temp;
|
||||||
|
for (int64_t Index = 0;
|
||||||
|
Index < Count;
|
||||||
|
++Index) {
|
||||||
|
if (ReadHalf0 == InHalf1) {
|
||||||
|
*Out++ = *ReadHalf1++;
|
||||||
|
} else if (ReadHalf1 == End) {
|
||||||
|
*Out++ = *ReadHalf0++;
|
||||||
|
} else if (ReadHalf0->range.min < ReadHalf1->range.min) {
|
||||||
|
*Out++ = *ReadHalf0++;
|
||||||
|
} else {
|
||||||
|
*Out++ = *ReadHalf1++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(Out == (Temp + Count));
|
||||||
|
Assert(ReadHalf0 == InHalf1);
|
||||||
|
Assert(ReadHalf1 == End);
|
||||||
|
|
||||||
|
// TODO(casey): Not really necessary if we ping-pong
|
||||||
|
for (int64_t Index = 0;
|
||||||
|
Index < Count;
|
||||||
|
++Index) {
|
||||||
|
First[Index] = Temp[Index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||||
|
#if DEBUG_BUILD
|
||||||
|
Assert(buffer->line_starts.len);
|
||||||
|
Assert(edits.len);
|
||||||
|
For(edits) {
|
||||||
|
Assert(it.range.min >= 0);
|
||||||
|
Assert(it.range.max >= it.range.min);
|
||||||
|
Assert(it.range.max <= buffer->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure edit ranges don't overlap
|
||||||
|
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
|
||||||
|
|
||||||
|
// We need to sort from lowest to higest based on range.min
|
||||||
|
{
|
||||||
|
Scratch scratch((Arena *)buffer->line_starts.allocator.object);
|
||||||
|
Array<Edit> edits_copy = TightCopy(scratch, edits);
|
||||||
|
MergeSort(edits.len, edits_copy.data, edits.data);
|
||||||
|
edits = edits_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
For(edits) _ReplaceText(buffer, it.range, it.string);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplaceText(Buffer *buffer, Range range, String16 string) {
|
||||||
|
Scratch scratch((Arena *)buffer->line_starts.allocator.object);
|
||||||
|
Array<Edit> edits = {scratch};
|
||||||
|
Add(&edits, {range, string});
|
||||||
|
ApplyEdits(buffer, edits);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestBuffer() {
|
void TestBuffer() {
|
||||||
@@ -229,6 +444,46 @@ void TestBuffer() {
|
|||||||
Assert(buffer.data[38] == L'\n');
|
Assert(buffer.data[38] == L'\n');
|
||||||
Assert(GetLineNumber(buffer, 39) == 3);
|
Assert(GetLineNumber(buffer, 39) == 3);
|
||||||
Assert(buffer.data[39] == L't');
|
Assert(buffer.data[39] == L't');
|
||||||
|
|
||||||
|
ReplaceText(&buffer, GetBeginAsRange(buffer), L"a");
|
||||||
|
Assert(buffer.line_starts.len == 4);
|
||||||
|
Assert(GetLineNumber(buffer, 13) == 0);
|
||||||
|
Assert(GetLineNumber(buffer, 14) == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Buffer buffer = {};
|
||||||
|
InitBuffer(scratch, &buffer);
|
||||||
|
ReplaceText(&buffer, {}, L"Thing\nmeme");
|
||||||
|
Assert(GetLineNumber(buffer, 5) == 0);
|
||||||
|
Assert(GetLineNumber(buffer, 6) == 1);
|
||||||
|
ReplaceText(&buffer, {0, 1}, L"");
|
||||||
|
Assert(GetLineNumber(buffer, 4) == 0);
|
||||||
|
Assert(GetLineNumber(buffer, 5) == 1);
|
||||||
|
ReplaceText(&buffer, {4, 5}, L"");
|
||||||
|
Assert(buffer.line_starts.len == 1);
|
||||||
|
ValidateLineStarts(&buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Buffer buffer = {};
|
||||||
|
InitBuffer(scratch, &buffer);
|
||||||
|
ReplaceText(&buffer, {}, L"Thing\nmeme");
|
||||||
|
ReplaceText(&buffer, {0, 5}, L"per\ncop");
|
||||||
|
Assert(buffer.line_starts.len == (Int)3);
|
||||||
|
Assert(GetLineNumber(buffer, 3) == 0);
|
||||||
|
Assert(GetLineNumber(buffer, 4) == 1);
|
||||||
|
ValidateLineStarts(&buffer);
|
||||||
|
|
||||||
|
ReplaceText(&buffer, {0, 8}, L"Thing\nmeme");
|
||||||
|
ReplaceText(&buffer, {0, 3}, L"Thing\nmeme");
|
||||||
|
ReplaceText(&buffer, {9, 13}, L"Thing\nmeme");
|
||||||
|
ReplaceText(&buffer, {4, 5}, L"Thing\nmeme\n\n\n");
|
||||||
|
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
||||||
|
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
||||||
|
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
||||||
|
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
||||||
|
ValidateLineStarts(&buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
wchar_t ToLowerCase(wchar_t a) {
|
wchar_t ToLowerCase(wchar_t a) {
|
||||||
if (a >= 'A' && a <= 'Z') a += 32;
|
if (a >= 'A' && a <= 'Z') a += 32;
|
||||||
return a;
|
return a;
|
||||||
|
|||||||
Reference in New Issue
Block a user