Split buffer into multiple files
This commit is contained in:
@@ -2,149 +2,35 @@
|
||||
https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation
|
||||
*/
|
||||
|
||||
struct Range {
|
||||
Int min;
|
||||
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 {
|
||||
union {
|
||||
U16 *data;
|
||||
wchar_t *str;
|
||||
};
|
||||
Int len;
|
||||
Int cap;
|
||||
Array<Int> line_starts;
|
||||
};
|
||||
|
||||
struct HistoryEntry {
|
||||
Array<Edit> edits;
|
||||
Array<Cursor> cursors;
|
||||
};
|
||||
|
||||
Int GetSize(Range range) {
|
||||
Assert(range.max >= range.min);
|
||||
Int result = range.max - range.min;
|
||||
return result;
|
||||
void InitBuffer(Allocator allocator, Buffer *buffer) {
|
||||
buffer->cap = 4096;
|
||||
buffer->data = AllocArray(allocator, U16, buffer->cap);
|
||||
buffer->line_starts.allocator = allocator;
|
||||
Add(&buffer->line_starts, (Int)0);
|
||||
}
|
||||
|
||||
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}) {
|
||||
range.min = Clamp(range.min, (Int)0, buffer.len);
|
||||
range.max = Clamp(range.max, (Int)0, buffer.len);
|
||||
String16 result = {(wchar_t *)buffer.data + range.min, GetSize(range)};
|
||||
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 result = {buffer.len, buffer.len};
|
||||
return result;
|
||||
}
|
||||
|
||||
Range GetBeginAsRange(Buffer &buffer) {
|
||||
Range result = {0, 0};
|
||||
return result;
|
||||
}
|
||||
|
||||
Range GetRange(Buffer &buffer) {
|
||||
Range result = {0, buffer.len};
|
||||
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;
|
||||
void Grow(Buffer *buffer, Int change_size) {
|
||||
Int new_size = buffer->len + change_size;
|
||||
if (new_size > buffer->cap) {
|
||||
Allocator alo = buffer->line_starts.allocator;
|
||||
Int outside = new_size - buffer->cap;
|
||||
Int new_cap = (buffer->cap + outside) * 2;
|
||||
U16 *new_array = AllocArray(alo, U16, new_cap);
|
||||
MemoryCopy(new_array, buffer->data, buffer->len * sizeof(U16));
|
||||
Dealloc(alo, &buffer->data);
|
||||
buffer->cap = new_cap;
|
||||
buffer->data = new_array;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Cursor ChangeBack(Cursor cursor, Int back) {
|
||||
Int front = GetFront(cursor);
|
||||
Cursor result = MakeCursor(front, back);
|
||||
return result;
|
||||
}
|
||||
void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) {
|
||||
Int offset = *_offset;
|
||||
*_offset = 0;
|
||||
if (offset == 0) return;
|
||||
|
||||
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;
|
||||
for (Int i = line; i < buffer->line_starts.len; i += 1) {
|
||||
buffer->line_starts[i] += offset;
|
||||
}
|
||||
}
|
||||
|
||||
Range GetLine(Buffer &buffer, Int line) {
|
||||
@@ -183,37 +69,6 @@ Int GetLineNumber(Buffer &buffer, Int pos) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void InitBuffer(Allocator allocator, Buffer *buffer) {
|
||||
buffer->cap = 4096;
|
||||
buffer->data = AllocArray(allocator, U16, buffer->cap);
|
||||
buffer->line_starts.allocator = allocator;
|
||||
Add(&buffer->line_starts, (Int)0);
|
||||
}
|
||||
|
||||
void Grow(Buffer *buffer, Int change_size) {
|
||||
Int new_size = buffer->len + change_size;
|
||||
if (new_size > buffer->cap) {
|
||||
Allocator alo = buffer->line_starts.allocator;
|
||||
Int outside = new_size - buffer->cap;
|
||||
Int new_cap = (buffer->cap + outside) * 2;
|
||||
U16 *new_array = AllocArray(alo, U16, new_cap);
|
||||
MemoryCopy(new_array, buffer->data, buffer->len * sizeof(U16));
|
||||
Dealloc(alo, &buffer->data);
|
||||
buffer->cap = new_cap;
|
||||
buffer->data = new_array;
|
||||
}
|
||||
}
|
||||
|
||||
void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) {
|
||||
Int offset = *_offset;
|
||||
*_offset = 0;
|
||||
if (offset == 0) return;
|
||||
|
||||
for (Int i = line; i < buffer->line_starts.len; i += 1) {
|
||||
buffer->line_starts[i] += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateLineStarts(Buffer *buffer) {
|
||||
Int line = 0;
|
||||
for (Int i = 0; i < buffer->len; i += 1) {
|
||||
|
||||
32
src/text_editor/buffer.h
Normal file
32
src/text_editor/buffer.h
Normal file
@@ -0,0 +1,32 @@
|
||||
struct Buffer {
|
||||
union {
|
||||
U16 *data;
|
||||
wchar_t *str;
|
||||
};
|
||||
Int len;
|
||||
Int cap;
|
||||
Array<Int> line_starts;
|
||||
};
|
||||
|
||||
struct Range {
|
||||
Int min;
|
||||
Int max;
|
||||
};
|
||||
|
||||
struct Cursor {
|
||||
union {
|
||||
Range range;
|
||||
Int pos[2];
|
||||
};
|
||||
Int ifront;
|
||||
};
|
||||
|
||||
struct Pos {
|
||||
Int line;
|
||||
Int col;
|
||||
};
|
||||
|
||||
struct Edit {
|
||||
Range range;
|
||||
String16 string;
|
||||
};
|
||||
106
src/text_editor/buffer_helpers.cpp
Normal file
106
src/text_editor/buffer_helpers.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
Int GetSize(Range range) {
|
||||
Assert(range.max >= range.min);
|
||||
Int result = range.max - range.min;
|
||||
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}) {
|
||||
range.min = Clamp(range.min, (Int)0, buffer.len);
|
||||
range.max = Clamp(range.max, (Int)0, buffer.len);
|
||||
String16 result = {(wchar_t *)buffer.data + range.min, GetSize(range)};
|
||||
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 result = {buffer.len, buffer.len};
|
||||
return result;
|
||||
}
|
||||
|
||||
Range GetBeginAsRange(Buffer &buffer) {
|
||||
Range result = {0, 0};
|
||||
return result;
|
||||
}
|
||||
|
||||
Range GetRange(Buffer &buffer) {
|
||||
Range result = {0, buffer.len};
|
||||
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;
|
||||
}
|
||||
160
src/text_editor/buffer_history.cpp
Normal file
160
src/text_editor/buffer_history.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// @todo:
|
||||
// @idea: maybe redo tree?
|
||||
struct HistoryEntry {
|
||||
Array<Edit> edits;
|
||||
Array<Cursor> cursors;
|
||||
};
|
||||
|
||||
struct History {
|
||||
Array<HistoryEntry> undo_stack;
|
||||
Array<HistoryEntry> redo_stack;
|
||||
int debug_edit_phase;
|
||||
};
|
||||
|
||||
#if 0
|
||||
void SaveHistoryBeforeMergeCursor(Window *window, Array<HistoryEntry> *stack) {
|
||||
ProfileFunction();
|
||||
HistoryEntry *entry = stack->alloc();
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
entry->cursors = window->cursors.tight_copy(sys_allocator);
|
||||
}
|
||||
|
||||
void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
HistoryEntry *entry = stack->last();
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
entry->edits = edits.tight_copy(sys_allocator);
|
||||
|
||||
// Make reverse edits
|
||||
For(entry->edits) {
|
||||
Range new_range = {it.range.min, it.range.min + it.string.len};
|
||||
String string = GetString(window->buffer, it.range);
|
||||
it.string = Copy(sys_allocator, string);
|
||||
it.range = new_range;
|
||||
}
|
||||
|
||||
Array<Edit> temp_edits = entry->edits.tight_copy(sys_allocator);
|
||||
defer { temp_edits.dealloc(); };
|
||||
|
||||
// Fix reverse edits
|
||||
ForItem(edit, edits) {
|
||||
int64_t remove_size = GetRangeSize(edit.range);
|
||||
int64_t insert_size = edit.string.len;
|
||||
int64_t offset = insert_size - remove_size;
|
||||
|
||||
for (int64_t i = 0; i < entry->edits.len; i += 1) {
|
||||
Edit &new_edit = entry->edits.data[i];
|
||||
Edit &old_edit = temp_edits.data[i];
|
||||
if (old_edit.range.min > edit.range.min) {
|
||||
new_edit.range.min += offset;
|
||||
new_edit.range.max += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RedoEdit(Window *window) {
|
||||
ProfileFunction();
|
||||
if (window->history.redo_stack.len <= 0) return;
|
||||
|
||||
HistoryEntry entry = window->history.redo_stack.pop();
|
||||
|
||||
SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack);
|
||||
SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, entry.edits);
|
||||
_ApplyEdits(&window->buffer, entry.edits);
|
||||
|
||||
window->cursors.dealloc();
|
||||
window->cursors = entry.cursors;
|
||||
window->not_regen_layout = false;
|
||||
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
|
||||
entry.edits.dealloc();
|
||||
}
|
||||
|
||||
void UndoEdit(Window *window) {
|
||||
ProfileFunction();
|
||||
if (window->history.undo_stack.len <= 0) return;
|
||||
|
||||
HistoryEntry entry = window->history.undo_stack.pop();
|
||||
|
||||
SaveHistoryBeforeMergeCursor(window, &window->history.redo_stack);
|
||||
SaveHistoryBeforeApplyEdits(window, &window->history.redo_stack, entry.edits);
|
||||
_ApplyEdits(&window->buffer, entry.edits);
|
||||
|
||||
window->cursors.dealloc();
|
||||
window->cursors = entry.cursors;
|
||||
window->not_regen_layout = false;
|
||||
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
|
||||
entry.edits.dealloc();
|
||||
}
|
||||
|
||||
void BeforeEdit(Window *window) {
|
||||
Assert(window->history.debug_edit_phase == 0);
|
||||
window->history.debug_edit_phase += 1;
|
||||
Assert(window->cursors.len);
|
||||
SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack);
|
||||
|
||||
// Clear redo stack
|
||||
{
|
||||
For(window->history.redo_stack) {
|
||||
it.cursors.dealloc();
|
||||
ForItem(edit, it.edits) Dealloc(it.edits.allocator, &edit.string.data);
|
||||
it.edits.dealloc();
|
||||
}
|
||||
window->history.redo_stack.dealloc();
|
||||
}
|
||||
MergeCursors(window);
|
||||
}
|
||||
|
||||
void ApplyEdits(Window *window, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
Assert(window->history.debug_edit_phase == 1);
|
||||
window->history.debug_edit_phase += 1;
|
||||
SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, edits);
|
||||
_ApplyEdits(&window->buffer, edits);
|
||||
}
|
||||
|
||||
void AfterEdit(Window *window, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
Assert(window->history.debug_edit_phase == 2);
|
||||
window->history.debug_edit_phase -= 2;
|
||||
|
||||
HistoryEntry *entry = window->history.undo_stack.last();
|
||||
Assert(entry->cursors.len);
|
||||
Assert(entry->edits.len);
|
||||
|
||||
for (int64_t i = 0; i < edits.len - 1; i += 1) {
|
||||
Assert(edits[i].range.min <= edits[i + 1].range.min);
|
||||
}
|
||||
|
||||
//
|
||||
// Offset all cursors by edits
|
||||
//
|
||||
Scratch scratch;
|
||||
Array<Cursor> new_cursors = window->cursors.tight_copy(scratch);
|
||||
ForItem(edit, edits) {
|
||||
int64_t remove_size = GetRangeSize(edit.range);
|
||||
int64_t insert_size = edit.string.len;
|
||||
int64_t offset = insert_size - remove_size;
|
||||
|
||||
for (int64_t i = 0; i < window->cursors.len; i += 1) {
|
||||
Cursor &old_cursor = window->cursors.data[i];
|
||||
Cursor &new_cursor = new_cursors.data[i];
|
||||
if (old_cursor.range.min == edit.range.min) {
|
||||
new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + insert_size;
|
||||
} else if (old_cursor.range.min > edit.range.min) {
|
||||
new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int64_t i = 0; i < window->cursors.len; i += 1) window->cursors[i] = new_cursors[i];
|
||||
|
||||
// Make sure all cursors are in range
|
||||
For(window->cursors) it.range = Clamp(window->buffer, it.range);
|
||||
|
||||
window->not_regen_layout = false;
|
||||
}
|
||||
#endif
|
||||
@@ -119,7 +119,8 @@ void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||
edits = edits_copy;
|
||||
}
|
||||
|
||||
// @todo: optimize
|
||||
// @optimize: we can do all edits in one go with less memory copies probably
|
||||
// or something else entirely
|
||||
Int offset = 0;
|
||||
For(edits) {
|
||||
it.range.min += offset;
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
#include "new_basic.cpp"
|
||||
#include "string16.cpp"
|
||||
|
||||
#include "buffer.h"
|
||||
#include "buffer_helpers.cpp"
|
||||
#include "buffer.cpp"
|
||||
#include "buffer_multi_cursor.cpp"
|
||||
#include "buffer_history.cpp"
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
int main(void) {
|
||||
|
||||
Reference in New Issue
Block a user