206 lines
6.4 KiB
C++
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);
|
|
} |