Files
lib_compiler/examples/text_editor/buffer.lc
2024-06-08 15:26:38 +02:00

230 lines
6.6 KiB
Plaintext

Buffer :: struct {
data: *u8;
len: int;
cap: int;
lines: Lines;
}
Lines :: struct {
data: *Range;
len: int;
cap: int;
}
Range :: struct {
min: int;
max: int;
}
LineInfo :: struct {
number: int;
range: Range;
}
GetRangeSize :: proc(range: Range): int {
result := range.max - range.min;
return result;
}
AdjustUTF8PosUnsafe :: proc(buffer: *Buffer, pos: int, direction: int = 1): int {
for pos >= 0 && pos < buffer.len && IsUTF8ContinuationByte(buffer.data[pos]) {
pos += direction;
}
return pos;
}
AdjustUTF8Pos :: proc(buffer: *Buffer, pos: int, direction: int = 1): int {
assert(direction == 1 || direction == -1);
pos = AdjustUTF8PosUnsafe(buffer, pos, direction);
pos = ClampInt(pos, 0, buffer.len - 1);
if (buffer.data) assert(!IsUTF8ContinuationByte(buffer.data[pos]));
return pos;
}
AdjustRange :: proc(buffer: *Buffer, range: *Range) {
range.min = AdjustUTF8Pos(buffer, range.min, direction = -1);
range.max = AdjustUTF8Pos(buffer, range.max, direction = +1);
}
ReplaceText :: proc(buffer: *Buffer, replace: Range, with: String) {
AdjustRange(buffer, &replace);
new_buffer_len := buffer.len + :int(with.len) - GetRangeSize(replace);
new_buffer_size := new_buffer_len + 1; // possible addition of a null terminator
if new_buffer_size > buffer.cap {
new_buffer_cap := MaxInt(4096, new_buffer_size * 2);
new_buffer := malloc(:usize(new_buffer_cap));
if (buffer.data) {
memcpy(new_buffer, buffer.data, :usize(buffer.len));
free(buffer.data);
}
buffer.data = new_buffer;
buffer.cap = new_buffer_cap;
}
old_text_range: Range = replace;
new_text_range: Range = {old_text_range.min, old_text_range.min + :int(with.len)};
right_range: Range = {old_text_range.max, buffer.len};
memmove(&buffer.data[new_text_range.max], &buffer.data[right_range.min], :usize(GetRangeSize(right_range)));
memmove(&buffer.data[new_text_range.min], with.str, :usize(GetRangeSize(new_text_range)));
buffer.len = new_buffer_len;
if buffer.len && buffer.data[buffer.len - 1] != 0 {
buffer.data[buffer.len] = 0;
buffer.len += 1;
assert(buffer.len <= buffer.cap);
}
UpdateBufferLines(buffer);
}
AddLine :: proc(lines: *Lines, line: Range) {
if lines.len + 1 > lines.cap {
new_cap := MaxInt(16, lines.cap * 2);
lines.data = realloc(lines.data, :usize(new_cap) * sizeof(:Range));
lines.cap = new_cap;
}
lines.data[lines.len] = line;
lines.len += 1;
}
UpdateBufferLines :: proc(buffer: *Buffer) {
buffer.lines.len = 0;
line: Range = {0, 0};
for i := 0; i < buffer.len; i += 1 {
if buffer.data[i] == '\n' {
AddLine(&buffer.lines, line);
line.min = i + 1;
line.max = i + 1;
} else {
line.max += 1;
}
}
line.min = AdjustUTF8Pos(buffer, line.min);
line.max = AdjustUTF8Pos(buffer, line.max);
AddLine(&buffer.lines, line);
}
AllocStringFromBuffer :: proc(buffer: *Buffer, range: Range, null_terminate: bool = false): String {
AdjustRange(buffer, &range);
size := GetRangeSize(range);
alloc_size := :usize(size);
if (null_terminate) alloc_size += 1;
data := malloc(alloc_size);
memcpy(data, &buffer.data[range.min], :usize(size));
result: String = {data, size};
if (null_terminate) result.str[result.len] = 0;
return result;
}
GetStringFromBuffer :: proc(buffer: *Buffer, range: Range): String {
AdjustRange(buffer, &range);
size := GetRangeSize(range);
result: String = {:*char(&buffer.data[range.min]), size};
return result;
}
AddText :: proc(buffer: *Buffer, text: String) {
end_of_buffer: Range = {buffer.len - 1, buffer.len - 1};
ReplaceText(buffer, end_of_buffer, text);
}
FindLineOfPos :: proc(buffer: *Buffer, pos: int): LineInfo {
for i := 0; i < buffer.lines.len; i += 1 {
line := buffer.lines.data[i];
if pos >= line.min && pos <= line.max {
return {i, line};
}
}
return {};
}
IsLineValid :: proc(buffer: *Buffer, line: int): bool {
result := line >= 0 && line < buffer.lines.len;
return result;
}
GetLine :: proc(buffer: *Buffer, line: int): LineInfo {
line = ClampInt(line, 0, buffer.lines.len - 1);
range := buffer.lines.data[line];
return {line, range};
}
IsPosInBounds :: proc(buffer: *Buffer, pos: int): bool {
result := buffer.len != 0 && pos >= 0 && pos < buffer.len;
return result;
}
GetChar :: proc(buffer: *Buffer, pos: int): u8 {
result: u8;
if IsPosInBounds(buffer, pos) {
result = buffer.data[pos];
}
return result;
}
GetUTF32 :: proc(buffer: *Buffer, pos: int, codepoint_size: *int = nil): u32 {
if !IsPosInBounds(buffer, pos) return 0;
p := &buffer.data[pos];
max := buffer.len - pos;
utf32 := UTF8ToUTF32(:*uchar(p), max);
assert(utf32.error == 0);
if (utf32.error != 0) return 0;
if (codepoint_size) codepoint_size[0] = utf32.advance;
return utf32.out_str;
}
IsWhitespace :: proc(w: u8): bool {
result := w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
return result;
}
SeekOnWordBoundary :: proc(buffer: *Buffer, pos: int, direction: int = GO_FORWARD): int {
assert(direction == GO_FORWARD || direction == GO_BACKWARD);
check_pos: int;
if direction == GO_FORWARD {
check_pos = AdjustUTF8Pos(buffer, pos + 1, direction);
pos = check_pos;
// this difference here because the Backward for loop is not inclusive.
// It doesn't move an inch forward after "pos"
// - - - - pos - -
// It goes backward on first Advance
} else {
check_pos = AdjustUTF8Pos(buffer, pos - 1, direction);
}
cursor_char := GetChar(buffer, check_pos);
standing_on_whitespace := IsWhitespace(cursor_char);
seek_whitespace := standing_on_whitespace == false;
seek_word := standing_on_whitespace;
end := buffer.len - 1;
if (direction == GO_BACKWARD) end = 0;
result := end;
iter := Iterate(buffer, pos, end, direction);
prev_pos := iter.pos;
for IsValid(iter); Advance(&iter) {
if seek_word && !IsWhitespace(:u8(iter.item)) {
result = prev_pos;
break;
}
if seek_whitespace && IsWhitespace(:u8(iter.item)) {
if direction == GO_FORWARD {
result = iter.pos;
} else {
result = prev_pos;
}
break;
}
prev_pos = iter.pos;
}
return result;
}