Init new repository
This commit is contained in:
249
examples/text_editor/buffer.lc
Normal file
249
examples/text_editor/buffer.lc
Normal file
@@ -0,0 +1,249 @@
|
||||
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;
|
||||
}
|
||||
|
||||
ClampInt :: proc(val: int, min: int, max: int): int {
|
||||
result := val;
|
||||
if (val < min) result = min;
|
||||
if (val > max) result = max;
|
||||
return result;
|
||||
}
|
||||
|
||||
MinInt :: proc(a: int, b: int): int {
|
||||
result := a;
|
||||
if (a > b) result = b;
|
||||
return result;
|
||||
}
|
||||
|
||||
MaxInt :: proc(a: int, b: int): int {
|
||||
result := b;
|
||||
if (a > b) result = a;
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user