Files
text_editor/src/text_editor/buffer.cpp
2024-07-27 15:04:21 +02:00

206 lines
6.4 KiB
C++

/*
https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation
*/
#define BUFFER_DEBUG 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;
}
}
Int LastLine(Buffer &buffer) {
Int result = buffer.line_starts.len - 1;
return result;
}
const Int LAST_LINE = INT64_MAX;
Range GetLineRange(Buffer &buffer, Int line, Int *end_of_buffer = NULL) {
Range result = {buffer.line_starts[line], buffer.len};
if (line + 1 < buffer.line_starts.len) {
result.max = buffer.line_starts[line + 1];
} else if (end_of_buffer) {
*end_of_buffer = 1;
}
return result;
}
String16 GetLineString(Buffer &buffer, Int line, Int *end_of_buffer = NULL) {
Range range = GetLineRange(buffer, line, end_of_buffer);
String16 string = GetString(buffer, range);
return string;
}
Range GetLineRangeWithoutNL(Buffer &buffer, Int line) {
Int end_of_buffer = 0;
Range line_range = GetLineRange(buffer, line, &end_of_buffer);
line_range.max = line_range.max - 1 + end_of_buffer;
return line_range;
}
String16 GetLineStringWithoutNL(Buffer &buffer, Int line) {
Range range = GetLineRangeWithoutNL(buffer, line);
String16 string = GetString(buffer, range);
return string;
}
Int PosToLine(Buffer &buffer, Int pos) {
Add(&buffer.line_starts, buffer.len + 1);
// binary search
Int low = 0;
Int high = buffer.line_starts.len - 2;
Int result = 0;
while (low <= high) {
Int mid = low + (high - low) / 2;
Range range = {buffer.line_starts[mid], buffer.line_starts[mid + 1]};
if (pos >= range.min && pos < range.max) {
result = mid;
break;
}
if (range.min < pos) {
low = mid + 1;
} else {
high = mid - 1;
}
}
Pop(&buffer.line_starts);
return result;
}
XY PosToXY(Buffer &buffer, Int pos) {
Int line = PosToLine(buffer, pos);
Range line_range = GetLineRange(buffer, line);
Int col = pos - line_range.min;
XY result = {col, line};
return result;
}
Int XYToPos(Buffer &buffer, XY xy) {
xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1);
Range line_range = GetLineRange(buffer, xy.line);
Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max);
return pos;
}
Int XYToPosWithoutNL(Buffer &buffer, XY xy) {
xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1);
Int end_of_buffer = 0;
Range line_range = GetLineRange(buffer, xy.line, &end_of_buffer);
Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max - 1 + end_of_buffer);
return pos;
}
void UpdateLines(Buffer *buffer, Range range, String16 string) {
ProfileFunction();
Array<Int> &ls = buffer->line_starts;
Int min_line_number = PosToLine(*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 ValidateLineStarts(Buffer *buffer) {
Int line = 0;
for (Int i = 0; i < buffer->len; i += 1) {
Int l = PosToLine(*buffer, i);
Assert(l == line);
if (buffer->data[i] == L'\n') line += 1;
}
}
void ReplaceText(Buffer *buffer, Range range, String16 string) {
ProfileFunction();
Assert(range.max >= range.min);
Assert(range.max >= 0 && range.max <= buffer->len);
Assert(range.min >= 0 && range.min <= buffer->len);
buffer->dirty = true;
Int size_to_remove = range.max - range.min;
Int size_to_add = string.len;
Int change_size = size_to_add - size_to_remove;
Assert(change_size + buffer->len >= 0);
Grow(buffer, change_size);
Int range_size = range.max - range.min;
U16 *begin_remove = buffer->data + range.min;
U16 *end_remove = begin_remove + range_size;
Int remain_len = buffer->len - (range.min + range_size);
UpdateLines(buffer, range, string);
U16 *begin_add = begin_remove;
U16 *end_add = begin_add + string.len;
MemoryMove(end_add, end_remove, remain_len * sizeof(U16));
MemoryCopy(begin_add, string.data, string.len * sizeof(U16));
buffer->len = buffer->len + change_size;
#if BUFFER_DEBUG
ValidateLineStarts(buffer);
#endif
}
void Append(Buffer *buffer, String16 string) {
ReplaceText(buffer, GetEndAsRange(*buffer), string);
}
void Appendf(Buffer *buffer, const char *fmt, ...) {
Scratch scratch;
STRING_FORMAT(scratch, fmt, string);
String16 string16 = ToString16(scratch, string);
ReplaceText(buffer, GetEndAsRange(*buffer), string16);
}