begin prototype, transcript browser
This commit is contained in:
952
src/ui/buffer16.c
Normal file
952
src/ui/buffer16.c
Normal file
@@ -0,0 +1,952 @@
|
||||
#define BUFFER_DEBUG 1
|
||||
|
||||
gb i64 buffer_raw_ids;
|
||||
|
||||
fn void buffer16_dealloc_history_entries(buffer16_t *buffer, array_history16_t *entries);
|
||||
fn void buffer16_dealloc_history_array(buffer16_t *buffer, array_history16_t *entries);
|
||||
fn void buffer16_multi_cursor_apply_edits(buffer16_t *buffer, array_edit16_t edits);
|
||||
|
||||
///////////////////////////////
|
||||
// caret helpers
|
||||
///////////////////////////////
|
||||
|
||||
fn i64 caret_get_front(caret_t caret) {
|
||||
i64 result = caret.pos[caret.ifront];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 caret_get_back(caret_t caret) {
|
||||
i64 result = caret.pos[(caret.ifront + 1) % 2];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn caret_t caret_make(i64 front, i64 back) {
|
||||
caret_t result = {0};
|
||||
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;
|
||||
}
|
||||
|
||||
fn caret_t caret_set_front(caret_t caret, i64 pos) {
|
||||
i64 back = caret_get_back(caret);
|
||||
caret_t result = caret_make(pos, back);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn caret_t caret_set_back(caret_t caret, i64 pos) {
|
||||
i64 front = caret_get_front(caret);
|
||||
caret_t result = caret_make(front, pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn b32 carets_are_equal(caret_t a, caret_t b) {
|
||||
b32 result = r1i64_are_equal(a.range, b.range) && a.ifront == b.ifront;
|
||||
return result;
|
||||
}
|
||||
|
||||
fn b32 carets_overlap(caret_t a, caret_t b) {
|
||||
b32 result = r1i64_overlap(a.range, b.range);
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// buffer helpers
|
||||
///////////////////////////////
|
||||
|
||||
fn i64 buffer16_pos_to_line(buffer16_t *buffer, i64 pos) {
|
||||
// @todo: refactor this to not modify line_starts
|
||||
assert(buffer->flags.line_starts);
|
||||
array_add(&buffer->line_starts, buffer->len + 1);
|
||||
|
||||
// binary search
|
||||
i64 low = 0;
|
||||
|
||||
// -2 here because we use 2 indices and combine them into one line range so we
|
||||
// don't want to access last item since that would index past array.
|
||||
i64 high = buffer->line_starts.len - 2;
|
||||
i64 result = 0;
|
||||
|
||||
while (low <= high) {
|
||||
i64 mid = low + (high - low) / 2;
|
||||
r1i64_t range = {buffer->line_starts.data[mid], buffer->line_starts.data[mid + 1]};
|
||||
if (pos >= range.min && pos < range.max) {
|
||||
result = mid;
|
||||
break;
|
||||
}
|
||||
|
||||
if (range.min < pos) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
array_pop(&buffer->line_starts);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_get_line_range_full(buffer16_t *buffer, i64 line, i64 *eof) {
|
||||
assert(line < buffer->line_starts.len);
|
||||
r1i64_t result = {buffer->line_starts.data[line], buffer->len};
|
||||
if (line + 1 < buffer->line_starts.len) {
|
||||
result.max = buffer->line_starts.data[line + 1];
|
||||
} else {
|
||||
*eof = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_get_line_range(buffer16_t *buffer, i64 line) {
|
||||
i64 eof = 0;
|
||||
r1i64_t line_range = buffer16_get_line_range_full(buffer, line, &eof);
|
||||
line_range.max = line_range.max - 1 + eof;
|
||||
return line_range;
|
||||
}
|
||||
|
||||
fn xy_t buffer16_pos_to_xy(buffer16_t *buffer, i64 pos) {
|
||||
i64 eof = 0;
|
||||
i64 line = buffer16_pos_to_line(buffer, pos);
|
||||
r1i64_t line_range = buffer16_get_line_range_full(buffer, line, &eof);
|
||||
i64 col = pos - line_range.min;
|
||||
xy_t result = {col, line};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 buffer16_xy_to_pos(buffer16_t *buffer, xy_t xy) {
|
||||
xy.line = CLAMP(xy.line, 0, buffer->line_starts.len - 1);
|
||||
i64 eof = 0;
|
||||
r1i64_t line_range = buffer16_get_line_range_full(buffer, xy.line, &eof);
|
||||
i64 pos = CLAMP(xy.col + line_range.min, line_range.min, line_range.max);
|
||||
return pos;
|
||||
}
|
||||
|
||||
fn i64 buffer16_xy_to_pos_without_new_line(buffer16_t *buffer, xy_t xy) {
|
||||
xy.line = CLAMP(xy.line, 0, buffer->line_starts.len - 1);
|
||||
i64 eof = 0;
|
||||
r1i64_t line_range = buffer16_get_line_range_full(buffer, xy.line, &eof);
|
||||
i64 pos = CLAMP(xy.col + line_range.min, line_range.min, line_range.max - 1 + eof);
|
||||
return pos;
|
||||
}
|
||||
|
||||
fn s16_t buffer16_get_string(buffer16_t *buffer, r1i64_t range) {
|
||||
range = r1i64_clamp(range, 0, buffer->len);
|
||||
s16_t result = s16_make(buffer->str + range.min, r1i64_size(range));
|
||||
return result;
|
||||
}
|
||||
|
||||
fn s16_t buffer16_get_line_string(buffer16_t *buffer, i64 line) {
|
||||
r1i64_t range = buffer16_get_line_range(buffer, line);
|
||||
s16_t string = buffer16_get_string(buffer, range);
|
||||
return string;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_get_range_end(buffer16_t *buffer) {
|
||||
r1i64_t result = {buffer->len, buffer->len};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_get_range(buffer16_t *buffer) {
|
||||
r1i64_t result = {0, buffer->len};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 buffer16_clamp_pos(buffer16_t *buffer, i64 pos) {
|
||||
i64 result = CLAMP(pos, 0, buffer->len);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_clamp_range(buffer16_t *buffer, r1i64_t range) {
|
||||
r1i64_t result = {buffer16_clamp_pos(buffer, range.min), buffer16_clamp_pos(buffer, range.max)};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_word_start(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
for (i64 i = pos - 1; i >= 0; i -= 1) {
|
||||
if (!char16_is_word(buffer->str[i]))
|
||||
break;
|
||||
pos = i;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_word_end(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
for (i64 i = pos;; i += 1) {
|
||||
pos = i;
|
||||
// this is because buffer end terminates the loop
|
||||
// too early and we cannot establish the proper range
|
||||
// semantics - proper max is one past last index
|
||||
if (!(i < buffer->len))
|
||||
break;
|
||||
if (!char16_is_word(buffer->str[i]))
|
||||
break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_next_word_end(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
u16 prev = 0;
|
||||
for (i64 i = pos;; i += 1) {
|
||||
pos = i;
|
||||
// this is because buffer end terminates the loop
|
||||
// too early and we cannot establish the proper range
|
||||
// semantics - proper max is one past last index
|
||||
if (!(i < buffer->len))
|
||||
break;
|
||||
if (prev == L'\n' || (prev && prev != buffer->str[i]) || char16_is_word(buffer->str[i])) {
|
||||
break;
|
||||
}
|
||||
prev = buffer->str[i];
|
||||
}
|
||||
i64 result = prev == L'\n' ? pos : buffer16_get_word_end(buffer, pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_prev_word_start(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
u16 prev = 0;
|
||||
i64 i = pos - 1;
|
||||
for (; i >= 0; i -= 1) {
|
||||
if (prev == L'\n' || (prev && prev != buffer->str[i]) || char16_is_word(buffer->str[i])) {
|
||||
break;
|
||||
}
|
||||
pos = i;
|
||||
prev = buffer->str[i];
|
||||
}
|
||||
b32 new_line = prev == L'\n';
|
||||
i64 result = new_line ? pos : buffer16_get_word_start(buffer, pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_line_start(buffer16_t *buffer, i64 pos, i64 *eof) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
i64 line = buffer16_pos_to_line(buffer, pos);
|
||||
r1i64_t range = buffer16_get_line_range_full(buffer, line, eof);
|
||||
return range.min;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_line_end(buffer16_t *buffer, i64 pos, i64 *eof) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
i64 line = buffer16_pos_to_line(buffer, pos);
|
||||
r1i64_t range = buffer16_get_line_range_full(buffer, line, eof);
|
||||
return range.max;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_line_start_full(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
i64 line = buffer16_pos_to_line(buffer, pos);
|
||||
r1i64_t range = buffer16_get_line_range(buffer, line);
|
||||
return range.min;
|
||||
}
|
||||
|
||||
fn i64 buffer16_get_line_end_full(buffer16_t *buffer, i64 pos) {
|
||||
pos = CLAMP(pos, (i64)0, buffer->len);
|
||||
i64 line = buffer16_pos_to_line(buffer, pos);
|
||||
r1i64_t range = buffer16_get_line_range(buffer, line);
|
||||
return range.max;
|
||||
}
|
||||
|
||||
fn r1i64_t buffer16_enclose_word(buffer16_t *buffer, i64 pos) {
|
||||
r1i64_t result = {buffer16_get_word_start(buffer, pos), buffer16_get_word_end(buffer, pos)};
|
||||
return result;
|
||||
}
|
||||
|
||||
fn u16 buffer16_get_char(buffer16_t *buffer, i64 pos) {
|
||||
if (pos >= 0 && pos < buffer->len) {
|
||||
return buffer->str[pos];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn i64 buffer16_offset_pos_by_line(buffer16_t *buffer, i64 pos, i64 line_offset) {
|
||||
xy_t xy = buffer16_pos_to_xy(buffer, pos);
|
||||
i64 result = buffer16_xy_to_pos_without_new_line(buffer, (xy_t){xy.col, xy.line + line_offset});
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// raw buffer operations
|
||||
///////////////////////////////
|
||||
|
||||
fn void buffer16_raw_grow(buffer16_t *buffer, i64 change_size) {
|
||||
i64 new_size = buffer->len + change_size;
|
||||
if (new_size > buffer->cap) {
|
||||
i64 outside = new_size - buffer->cap;
|
||||
i64 new_cap = (buffer->cap + outside) * 2;
|
||||
u16 *new_array = alloc_array(buffer->alo, u16, new_cap);
|
||||
memory_copy(new_array, buffer->data, buffer->len * sizeof(u16));
|
||||
dealloc(buffer->alo, buffer->data);
|
||||
buffer->cap = new_cap;
|
||||
buffer->data = new_array;
|
||||
}
|
||||
}
|
||||
|
||||
fn void buffer16_raw_offset_all_lines_forward(buffer16_t *buffer, i64 line, i64 *_offset) {
|
||||
i64 offset = *_offset;
|
||||
*_offset = 0;
|
||||
if (offset == 0) return;
|
||||
|
||||
for (i64 i = line; i < buffer->line_starts.len; i += 1) {
|
||||
buffer->line_starts.data[i] += offset;
|
||||
}
|
||||
}
|
||||
|
||||
fn void buffer16_raw_update_lines(buffer16_t *buffer, r1i64_t range, s16_t string) {
|
||||
if (buffer->flags.line_starts == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
i64 min_line_number = buffer16_pos_to_line(buffer, range.min);
|
||||
assert(min_line_number < buffer->line_starts.len);
|
||||
// Update lines remove
|
||||
{
|
||||
i64 line_offset = 0;
|
||||
i64 lines_to_remove = min_line_number + 1;
|
||||
i64 lines_to_remove_count = 0;
|
||||
for (i64 i = range.min; i < range.max; i += 1) {
|
||||
u16 c = buffer->data[i];
|
||||
if (c == '\n') {
|
||||
lines_to_remove_count += 1;
|
||||
}
|
||||
line_offset -= 1;
|
||||
}
|
||||
array_deln(&buffer->line_starts, lines_to_remove, lines_to_remove_count);
|
||||
buffer16_raw_offset_all_lines_forward(buffer, min_line_number + 1, &line_offset);
|
||||
}
|
||||
|
||||
// Update lines add
|
||||
i64 line_offset = 0;
|
||||
i64 nl = min_line_number + 1;
|
||||
for (i64 i = 0; i < string.len; i += 1) {
|
||||
nl = min_line_number + 1;
|
||||
b32 next_line_valid = nl < buffer->line_starts.len;
|
||||
|
||||
if (string.str[i] == L'\n') {
|
||||
i64 new_line_pos = range.min + i + 1;
|
||||
line_offset += 1;
|
||||
|
||||
array_insert(&buffer->line_starts, nl, new_line_pos);
|
||||
buffer16_raw_offset_all_lines_forward(buffer, nl + 1, &line_offset);
|
||||
min_line_number = nl;
|
||||
} else if (next_line_valid) {
|
||||
line_offset += 1;
|
||||
}
|
||||
}
|
||||
buffer16_raw_offset_all_lines_forward(buffer, nl, &line_offset);
|
||||
}
|
||||
|
||||
fn void buffer16_raw_validate_line_starts(buffer16_t *buffer) {
|
||||
i64 line = 0;
|
||||
for (i64 i = 0; i < buffer->len; i += 1) {
|
||||
i64 l = buffer16_pos_to_line(buffer, i);
|
||||
assert(l == line);
|
||||
if (buffer->data[i] == L'\n') line += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn void buffer16_raw_replace_text(buffer16_t *buffer, r1i64_t range, s16_t string) {
|
||||
assert(range.max >= range.min);
|
||||
assert(range.max >= 0 && range.max <= buffer->len);
|
||||
assert(range.min >= 0 && range.min <= buffer->len);
|
||||
buffer->flags.dirty = true;
|
||||
buffer->change_id += 1;
|
||||
|
||||
i64 size_to_remove = range.max - range.min;
|
||||
i64 size_to_add = string.len;
|
||||
i64 change_size = size_to_add - size_to_remove;
|
||||
assert(change_size + buffer->len >= 0);
|
||||
buffer16_raw_grow(buffer, change_size);
|
||||
|
||||
i64 range_size = range.max - range.min;
|
||||
u16 *begin_remove = buffer->data + range.min;
|
||||
u16 *end_remove = begin_remove + range_size;
|
||||
i64 remain_len = buffer->len - (range.min + range_size);
|
||||
|
||||
buffer16_raw_update_lines(buffer, range, string);
|
||||
|
||||
u16 *begin_add = begin_remove;
|
||||
u16 *end_add = begin_add + string.len;
|
||||
memory_move(end_add, end_remove, remain_len * sizeof(u16));
|
||||
memory_copy(begin_add, string.str, string.len * sizeof(u16));
|
||||
buffer->len = buffer->len + change_size;
|
||||
|
||||
#if BUFFER_DEBUG
|
||||
buffer16_raw_validate_line_starts(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// buffer multicursor + history
|
||||
///////////////////////////////
|
||||
|
||||
fn void buffer16_init(alo_t alo, buffer16_t *buffer, s8_t name, i64 size) {
|
||||
buffer->id = (buffer16_id_t){++buffer_raw_ids};
|
||||
buffer->name = name;
|
||||
buffer->alo = alo;
|
||||
buffer->cap = size;
|
||||
buffer->data = alloc_array(alo, u16, size);
|
||||
array_init(alo, &buffer->line_starts, 128);
|
||||
array_init(alo, &buffer->undo_stack, 128);
|
||||
array_init(alo, &buffer->redo_stack, 128);
|
||||
buffer->flags.line_starts = true;
|
||||
buffer->flags.history = true;
|
||||
array_add(&buffer->line_starts, 0);
|
||||
}
|
||||
|
||||
fn void buffer16_deinit(buffer16_t *buffer) {
|
||||
// @todo: verify this works
|
||||
array_dealloc(&buffer->line_starts);
|
||||
dealloc(buffer->alo, buffer->data);
|
||||
buffer16_dealloc_history_array(buffer, &buffer->undo_stack);
|
||||
buffer16_dealloc_history_array(buffer, &buffer->redo_stack);
|
||||
}
|
||||
|
||||
fn void buffer16_save_history_before_merge_cursor(buffer16_t *buffer, array_history16_t *stack, array_caret_t *carets) {
|
||||
if (!buffer->flags.history) {
|
||||
return;
|
||||
}
|
||||
|
||||
history16_t node = {0};
|
||||
array_copy(carets->alo, &node.carets, carets);
|
||||
array_add(stack, node);
|
||||
}
|
||||
|
||||
fn void buffer16_save_history_before_apply_edits(buffer16_t *buffer, array_history16_t *stack, array_edit16_t *edits) {
|
||||
if (!buffer->flags.history) {
|
||||
return;
|
||||
}
|
||||
|
||||
history16_t *entry = &array_last(stack);
|
||||
array_copy(buffer->alo, &entry->edits, edits);
|
||||
|
||||
// make reverse edits
|
||||
array_for(edit16_t, it, &entry->edits) {
|
||||
r1i64_t new_range = {it->range.min, it->range.min + it->string.len};
|
||||
s16_t string = buffer16_get_string(buffer, it->range);
|
||||
it->string = s16_copy_ex(buffer->alo, string);
|
||||
it->range = new_range;
|
||||
}
|
||||
|
||||
ma_temp_t scratch = ma_begin_scratch1(buffer->alo.object);
|
||||
array_edit16_t temp_edit = {0};
|
||||
array_copy(malot(scratch), &temp_edit, &entry->edits);
|
||||
|
||||
// fix reverse edits
|
||||
array_for(edit16_t, edit, edits) {
|
||||
i64 remove_size = r1i64_size(edit->range);
|
||||
i64 insert_size = edit->string.len;
|
||||
i64 offset = insert_size - remove_size;
|
||||
for (i64 i = 0; i < entry->edits.len; i += 1) {
|
||||
edit16_t *new_edit = entry->edits.data + i;
|
||||
edit16_t *old_edit = temp_edit.data + i;
|
||||
if (old_edit->range.min > edit->range.min) {
|
||||
new_edit->range.min += offset;
|
||||
new_edit->range.max += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ma_end_scratch(scratch);
|
||||
}
|
||||
|
||||
fn void buffer16_redo_edit(buffer16_t *buffer, array_caret_t *carets) {
|
||||
if (!buffer->flags.history || buffer->redo_stack.len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
history16_t entry = array_pop(&buffer->redo_stack);
|
||||
buffer16_save_history_before_merge_cursor(buffer, &buffer->undo_stack, carets);
|
||||
buffer16_save_history_before_apply_edits(buffer, &buffer->undo_stack, &entry.edits);
|
||||
buffer16_multi_cursor_apply_edits(buffer, entry.edits);
|
||||
|
||||
array_dealloc(carets);
|
||||
*carets = entry.carets;
|
||||
|
||||
array_for(edit16_t, it, &entry.edits) {
|
||||
dealloc(buffer->alo, it->string.str);
|
||||
}
|
||||
array_dealloc(&entry.edits);
|
||||
}
|
||||
|
||||
fn void buffer16_undo_edit(buffer16_t *buffer, array_caret_t *carets) {
|
||||
if (!buffer->flags.history || buffer->undo_stack.len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
history16_t entry = array_pop(&buffer->undo_stack);
|
||||
|
||||
buffer16_save_history_before_merge_cursor(buffer, &buffer->redo_stack, carets);
|
||||
buffer16_save_history_before_apply_edits(buffer, &buffer->redo_stack, &entry.edits);
|
||||
buffer16_multi_cursor_apply_edits(buffer, entry.edits);
|
||||
|
||||
array_dealloc(carets);
|
||||
*carets = entry.carets;
|
||||
|
||||
array_for(edit16_t, it, &entry.edits) {
|
||||
dealloc(buffer->alo, it->string.str);
|
||||
}
|
||||
array_dealloc(&entry.edits);
|
||||
}
|
||||
|
||||
fn void buffer16__apply_edits(buffer16_t *buffer, array_edit16_t *edits) {
|
||||
assert(buffer->edit_phase == 1);
|
||||
buffer->edit_phase += 1;
|
||||
buffer16_save_history_before_apply_edits(buffer, &buffer->undo_stack, edits);
|
||||
buffer16_multi_cursor_apply_edits(buffer, *edits);
|
||||
}
|
||||
|
||||
fn void buffer16_dealloc_history_entries(buffer16_t *buffer, array_history16_t *entries) {
|
||||
for (history16_t *it = entries->data; it < entries->data + entries->len; it += 1) {
|
||||
array_dealloc(&it->carets);
|
||||
for (edit16_t *edit_it = it->edits.data; edit_it < it->edits.data + it->edits.len; edit_it += 1) {
|
||||
dealloc(buffer->alo, edit_it->string.str);
|
||||
}
|
||||
array_dealloc(&it->edits);
|
||||
}
|
||||
entries->len = 0;
|
||||
}
|
||||
|
||||
|
||||
fn void buffer16_dealloc_history_array(buffer16_t *buffer, array_history16_t *entries) {
|
||||
buffer16_dealloc_history_entries(buffer, entries);
|
||||
array_dealloc(entries);
|
||||
}
|
||||
|
||||
// @note: !!
|
||||
// We can invoke this before caret altering commands to save caret history
|
||||
// and then call some editing command to edit which is not going to save carets
|
||||
fn array_edit16_t buffer16_begin_edit(alo_t alo, buffer16_t *buffer, array_caret_t *carets) {
|
||||
assert(buffer->edit_phase == 0 || buffer->edit_phase == 1);
|
||||
if (buffer->edit_phase == 0) {
|
||||
buffer->edit_phase += 1;
|
||||
assert(carets->len);
|
||||
buffer16_save_history_before_merge_cursor(buffer, &buffer->undo_stack, carets);
|
||||
buffer16_dealloc_history_entries(buffer, &buffer->redo_stack);
|
||||
}
|
||||
array_edit16_t edits = (array_edit16_t){.alo = alo};
|
||||
return edits;
|
||||
}
|
||||
|
||||
fn void buffer16_pre_begin_edit_save_caret_history(buffer16_t *buffer, array_caret_t *carets) {
|
||||
buffer16_begin_edit(buffer->alo, buffer, carets);
|
||||
}
|
||||
|
||||
fn void caret_assert_ranges(array_caret_t carets) {
|
||||
array_for(caret_t, it, &carets) {
|
||||
assert(it->range.max >= it->range.min);
|
||||
}
|
||||
}
|
||||
|
||||
fn void buffer16_adjust_carets(array_edit16_t *edits, array_caret_t *carets) {
|
||||
ma_temp_t scratch = ma_begin_scratch1(edits->alo.object);
|
||||
assert(edits->alo.object == carets->alo.object);
|
||||
|
||||
array_caret_t new_carets = {0};
|
||||
array_copy(malot(scratch), &new_carets, carets);
|
||||
array_for(edit16_t, it, edits) {
|
||||
i64 remove_size = r1i64_size(it->range);
|
||||
i64 insert_size = it->string.len;
|
||||
i64 offset = insert_size - remove_size;
|
||||
for (i64 i = 0; i < carets->len; i += 1) {
|
||||
caret_t *old_caret = carets->data + i;
|
||||
caret_t *new_caret = new_carets.data + i;
|
||||
|
||||
if (old_caret->range.min > it->range.min) {
|
||||
new_caret->range.min += offset;
|
||||
new_caret->range.max += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < carets->len; i += 1) {
|
||||
carets->data[i] = new_carets.data[i];
|
||||
}
|
||||
|
||||
ma_end_scratch(scratch);
|
||||
}
|
||||
|
||||
fn void buffer16_end_edit(buffer16_t *buffer, array_edit16_t *edits, array_caret_t *carets, b32 kill_selection) {
|
||||
buffer16__apply_edits(buffer, edits);
|
||||
|
||||
assert(buffer->edit_phase == 2);
|
||||
buffer->edit_phase -= 1;
|
||||
|
||||
#if BUFFER_DEBUG
|
||||
if (buffer->flags.history) {
|
||||
history16_t *entry = &array_last(&buffer->undo_stack);
|
||||
assert(entry->carets.len);
|
||||
assert(entry->edits.len);
|
||||
for (i64 i = 0; i < edits->len - 1; i += 1) {
|
||||
assert(edits->data[i].range.min <= edits->data[i + 1].range.min);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Adjust carets
|
||||
// this one also moves the carets forward if they are aligned with edit
|
||||
ma_temp_t scratch = ma_begin_scratch1(buffer->alo.object);
|
||||
assert(buffer->alo.object == carets->alo.object);
|
||||
assert(buffer->alo.object == edits->alo.object);
|
||||
|
||||
array_caret_t new_carets = {0};
|
||||
array_copy(malot(scratch), &new_carets, carets);
|
||||
array_for(edit16_t, it, edits) {
|
||||
i64 remove_size = r1i64_size(it->range);
|
||||
i64 insert_size = it->string.len;
|
||||
i64 offset = insert_size - remove_size;
|
||||
|
||||
for (i64 i = 0; i < carets->len; i += 1) {
|
||||
caret_t *old_caret = carets->data + i;
|
||||
caret_t *new_caret = new_carets.data + i;
|
||||
|
||||
if (old_caret->range.min == it->range.min) {
|
||||
new_caret->range.min += insert_size;
|
||||
} else if (old_caret->range.min > it->range.min) {
|
||||
new_caret->range.min += offset;
|
||||
}
|
||||
|
||||
if (old_caret->range.max == it->range.max) {
|
||||
new_caret->range.max += insert_size;
|
||||
} else if (old_caret->range.max > it->range.max) {
|
||||
new_caret->range.max += offset;
|
||||
}
|
||||
|
||||
assert(new_caret->range.max >= new_caret->range.min);
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < carets->len; i += 1) {
|
||||
carets->data[i] = new_carets.data[i];
|
||||
if (kill_selection) {
|
||||
carets->data[i].range.max = carets->data[i].range.min;
|
||||
}
|
||||
}
|
||||
|
||||
ma_end_scratch(scratch);
|
||||
}
|
||||
|
||||
fn void caret_merge_sort(i64 Count, caret_t *First, caret_t *Temp) {
|
||||
// SortKey = range.min
|
||||
if (Count == 1) {
|
||||
// NOTE(casey): No work to do.
|
||||
} else if (Count == 2) {
|
||||
caret_t *EntryA = First;
|
||||
caret_t *EntryB = First + 1;
|
||||
if (EntryA->range.min > EntryB->range.min) {
|
||||
SWAP_PTR(caret_t, EntryA, EntryB);
|
||||
}
|
||||
} else {
|
||||
i64 Half0 = Count / 2;
|
||||
i64 Half1 = Count - Half0;
|
||||
|
||||
assert(Half0 >= 1);
|
||||
assert(Half1 >= 1);
|
||||
|
||||
caret_t *InHalf0 = First;
|
||||
caret_t *InHalf1 = First + Half0;
|
||||
caret_t *End = First + Count;
|
||||
|
||||
caret_merge_sort(Half0, InHalf0, Temp);
|
||||
caret_merge_sort(Half1, InHalf1, Temp);
|
||||
|
||||
caret_t *ReadHalf0 = InHalf0;
|
||||
caret_t *ReadHalf1 = InHalf1;
|
||||
|
||||
caret_t *Out = Temp;
|
||||
for (i64 Index = 0;
|
||||
Index < Count;
|
||||
++Index) {
|
||||
if (ReadHalf0 == InHalf1) {
|
||||
*Out++ = *ReadHalf1++;
|
||||
} else if (ReadHalf1 == End) {
|
||||
*Out++ = *ReadHalf0++;
|
||||
} else if (ReadHalf0->range.min < ReadHalf1->range.min) {
|
||||
*Out++ = *ReadHalf0++;
|
||||
} else {
|
||||
*Out++ = *ReadHalf1++;
|
||||
}
|
||||
}
|
||||
assert(Out == (Temp + Count));
|
||||
assert(ReadHalf0 == InHalf1);
|
||||
assert(ReadHalf1 == End);
|
||||
|
||||
// TODO(casey): Not really necessary if we ping-pong
|
||||
for (i64 Index = 0;
|
||||
Index < Count;
|
||||
++Index) {
|
||||
First[Index] = Temp[Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void edit16_merge_sort(i64 Count, edit16_t *First, edit16_t *Temp) {
|
||||
// SortKey = range.min
|
||||
if (Count == 1) {
|
||||
// NOTE(casey): No work to do.
|
||||
} else if (Count == 2) {
|
||||
edit16_t *EntryA = First;
|
||||
edit16_t *EntryB = First + 1;
|
||||
if (EntryA->range.min > EntryB->range.min) {
|
||||
SWAP_PTR(edit16_t, EntryA, EntryB);
|
||||
}
|
||||
} else {
|
||||
i64 Half0 = Count / 2;
|
||||
i64 Half1 = Count - Half0;
|
||||
|
||||
assert(Half0 >= 1);
|
||||
assert(Half1 >= 1);
|
||||
|
||||
edit16_t *InHalf0 = First;
|
||||
edit16_t *InHalf1 = First + Half0;
|
||||
edit16_t *End = First + Count;
|
||||
|
||||
edit16_merge_sort(Half0, InHalf0, Temp);
|
||||
edit16_merge_sort(Half1, InHalf1, Temp);
|
||||
|
||||
edit16_t *ReadHalf0 = InHalf0;
|
||||
edit16_t *ReadHalf1 = InHalf1;
|
||||
|
||||
edit16_t *Out = Temp;
|
||||
for (i64 Index = 0;
|
||||
Index < Count;
|
||||
++Index) {
|
||||
if (ReadHalf0 == InHalf1) {
|
||||
*Out++ = *ReadHalf1++;
|
||||
} else if (ReadHalf1 == End) {
|
||||
*Out++ = *ReadHalf0++;
|
||||
} else if (ReadHalf0->range.min < ReadHalf1->range.min) {
|
||||
*Out++ = *ReadHalf0++;
|
||||
} else {
|
||||
*Out++ = *ReadHalf1++;
|
||||
}
|
||||
}
|
||||
assert(Out == (Temp + Count));
|
||||
assert(ReadHalf0 == InHalf1);
|
||||
assert(ReadHalf1 == End);
|
||||
|
||||
// TODO(casey): Not really necessary if we ping-pong
|
||||
for (i64 Index = 0;
|
||||
Index < Count;
|
||||
++Index) {
|
||||
First[Index] = Temp[Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void buffer16_multi_cursor_apply_edits(buffer16_t *buffer, array_edit16_t edits) {
|
||||
#if BUFFER_DEBUG
|
||||
assert(buffer->line_starts.len);
|
||||
assert(edits.len);
|
||||
array_for(edit16_t, it, &edits) {
|
||||
assert(it->range.min >= 0);
|
||||
assert(it->range.max >= it->range.min);
|
||||
assert(it->range.max <= buffer->len);
|
||||
}
|
||||
|
||||
array_for(edit16_t, it1, &edits) {
|
||||
array_for(edit16_t, it2, &edits) {
|
||||
if (it1 == it2) continue;
|
||||
|
||||
b32 a2_inside = it2->range.min >= it1->range.min && it2->range.min < it1->range.max;
|
||||
assert(!a2_inside);
|
||||
|
||||
b32 b2_inside = it2->range.max > it1->range.min && it2->range.max <= it1->range.max;
|
||||
assert(!b2_inside);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// We need to sort from lowest to highest based on range.min
|
||||
{
|
||||
ma_temp_t scratch = ma_begin_scratch1(buffer->alo.object);
|
||||
array_edit16_t edits_copy = {0};
|
||||
array_copy(malot(scratch), &edits_copy, &edits);
|
||||
if (edits.len > 1) edit16_merge_sort(edits.len, edits_copy.data, edits.data);
|
||||
edits = edits_copy;
|
||||
ma_end_scratch(scratch);
|
||||
}
|
||||
|
||||
#if BUFFER_DEBUG
|
||||
for (i64 i = 0; i < edits.len - 1; i += 1) {
|
||||
assert(edits.data[i].range.min <= edits.data[i + 1].range.min);
|
||||
}
|
||||
#endif
|
||||
|
||||
// @optimize: we can do all edits in one go with less memory copies probably
|
||||
// or something else entirely
|
||||
i64 offset = 0;
|
||||
array_for(edit16_t, it, &edits) {
|
||||
it->range.min += offset;
|
||||
it->range.max += offset;
|
||||
offset += it->string.len - r1i64_size(it->range);
|
||||
buffer16_raw_replace_text(buffer, it->range, it->string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn void buffer16_add_edit(array_edit16_t *edits, r1i64_t range, s16_t string) {
|
||||
array_add(edits, (edit16_t){range, string});
|
||||
}
|
||||
|
||||
// Merge carets that overlap, this needs to be handled before any edits to
|
||||
// make sure overlapping edits won't happen.
|
||||
//
|
||||
// mouse_selection_anchor is special case for mouse handling !
|
||||
fn void buffer16_merge_carets(buffer16_t *buffer, array_caret_t *carets) {
|
||||
array_for(caret_t, it, carets) {
|
||||
it->range = buffer16_clamp_range(buffer, it->range);
|
||||
}
|
||||
caret_t first_caret = carets->data[0];
|
||||
|
||||
ma_temp_t scratch = ma_begin_scratch1(buffer->alo.object);
|
||||
array_caret_t c1 = {0};
|
||||
array_copy(malot(scratch), &c1, carets);
|
||||
if (carets->len > 1) {
|
||||
caret_merge_sort(carets->len, c1.data, carets->data);
|
||||
}
|
||||
carets->len = 0;
|
||||
|
||||
i64 first_caret_index = 0;
|
||||
array_add(carets, c1.data[0]);
|
||||
for (i64 i = 1; i < c1.len; i += 1) {
|
||||
caret_t *it = c1.data + i;
|
||||
caret_t *last = &array_last(carets);
|
||||
|
||||
if (r1i64_overlap(it->range, last->range)) {
|
||||
last->range.max = MAX(last->range.max, it->range.max);
|
||||
} else {
|
||||
array_add(carets, *it);
|
||||
}
|
||||
|
||||
if (carets_are_equal(*it, first_caret)) {
|
||||
first_caret_index = carets->len - 1;
|
||||
}
|
||||
}
|
||||
|
||||
SWAP(caret_t, carets->data[first_caret_index], carets->data[0]);
|
||||
ma_end_scratch(scratch);
|
||||
}
|
||||
|
||||
fn_test void buffer16_test(void) {
|
||||
ma_temp_t scratch = ma_begin_scratch();
|
||||
|
||||
{
|
||||
s16_t string = s16("thing itself");
|
||||
buffer16_t *buffer = ma_push_type(scratch.arena, buffer16_t);
|
||||
buffer16_init(malot(scratch), buffer, S8_FILE_AND_LINE, 16);
|
||||
buffer16_raw_replace_text(buffer, r1i64_null, string);
|
||||
assert(s16_are_equal(buffer->string, string));
|
||||
assert(buffer->cap == 16);
|
||||
assert(buffer->line_starts.len == 1);
|
||||
|
||||
buffer16_raw_replace_text(buffer, r1i64(5, 6), s16("|||"));
|
||||
assert(s16_are_equal(buffer->string, s16("thing|||itself")));
|
||||
assert(buffer->line_starts.len == 1);
|
||||
|
||||
buffer16_raw_replace_text(buffer, buffer16_get_range_end(buffer), s16("|||MEMES|||AndStuff"));
|
||||
assert(s16_are_equal(buffer->string, s16("thing|||itself|||MEMES|||AndStuff")));
|
||||
assert(buffer->cap > 16);
|
||||
assert(buffer->line_starts.len == 1);
|
||||
|
||||
buffer16_raw_replace_text(buffer, buffer16_get_range_end(buffer), s16("\nnext line"));
|
||||
assert(buffer->line_starts.len == 2);
|
||||
|
||||
buffer16_raw_replace_text(buffer, r1i64(4, 4), s16("\nnext line"));
|
||||
buffer16_raw_replace_text(buffer, r1i64(20, 20), s16("\nnext line"));
|
||||
buffer16_raw_replace_text(buffer, r1i64(14, 15), s16("\nnext line"));
|
||||
buffer16_raw_replace_text(buffer, r1i64(0, 0), s16("\nnext line"));
|
||||
buffer16_raw_replace_text(buffer, r1i64(9, 10), s16("\nnext line"));
|
||||
assert(buffer->line_starts.len == 7);
|
||||
|
||||
buffer16_raw_replace_text(buffer, r1i64(8, 12), s16_null);
|
||||
assert(buffer->line_starts.len == 6);
|
||||
}
|
||||
|
||||
{
|
||||
s16_t string = s16("thing itself_and meme");
|
||||
buffer16_t *buffer = ma_push_type(scratch.arena, buffer16_t);
|
||||
buffer16_init(malot(scratch), buffer, S8_FILE_AND_LINE, 10);
|
||||
buffer16_raw_replace_text(buffer, r1i64_null, string);
|
||||
r1i64_t range = buffer16_enclose_word(buffer, 18);
|
||||
buffer16_raw_replace_text(buffer, range, s16("cat"));
|
||||
assert(s16_are_equal(buffer->string, s16("thing itself_and cat")));
|
||||
|
||||
i64 eof = 0;
|
||||
r1i64_t rng = buffer16_get_line_range_full(buffer, 0, &eof);
|
||||
s16_t string_of_range = buffer16_get_string(buffer, rng);
|
||||
assert(eof == 1);
|
||||
assert(s16_are_equal(string_of_range, s16("thing itself_and cat")));
|
||||
}
|
||||
|
||||
{
|
||||
buffer16_t *buffer = ma_push_type(scratch.arena, buffer16_t);
|
||||
buffer16_init(malot(scratch), buffer, S8_FILE_AND_LINE, 16);
|
||||
for (int i = 0; i < 16; i += 1) {
|
||||
buffer16_raw_replace_text(buffer, r1i64_null, s16("line of memes\n"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
s16_t string = s16("some thing or another and stuff like that");
|
||||
s16_t meme_string = s16("meme meme or another and stuff like that");
|
||||
buffer16_t *buffer = ma_push_type(scratch.arena, buffer16_t);
|
||||
buffer16_init(malot(scratch), buffer, S8_FILE_AND_LINE, 128);
|
||||
buffer16_raw_replace_text(buffer, r1i64_null, string);
|
||||
|
||||
{
|
||||
array_caret_t carets = {0};
|
||||
array_init(malot(scratch), &carets, 32);
|
||||
array_add(&carets, caret_make(0, 4));
|
||||
array_add(&carets, caret_make(3, 8));
|
||||
array_add(&carets, caret_make(3, 8));
|
||||
array_add(&carets, caret_make(3, 8));
|
||||
array_add(&carets, caret_make(3, 8));
|
||||
array_add(&carets, caret_make(3, 6));
|
||||
array_add(&carets, caret_make(3, 6));
|
||||
buffer16_merge_carets(buffer, &carets);
|
||||
assert(carets.len == 1);
|
||||
assert(carets.data[0].range.min == 0 && carets.data[0].range.max == 8);
|
||||
}
|
||||
|
||||
{
|
||||
array_caret_t carets = {0};
|
||||
array_init(malot(scratch), &carets, 32);
|
||||
array_add(&carets, caret_make(0, 4));
|
||||
array_add(&carets, caret_make(0, 3));
|
||||
array_add(&carets, caret_make(5, 10));
|
||||
|
||||
array_edit16_t edits = buffer16_begin_edit(malot(scratch), buffer, &carets);
|
||||
|
||||
buffer16_merge_carets(buffer, &carets);
|
||||
assert(carets.len == 2);
|
||||
|
||||
array_for(caret_t, it, &carets) {
|
||||
buffer16_add_edit(&edits, it->range, s16("meme"));
|
||||
}
|
||||
|
||||
buffer16_end_edit(buffer, &edits, &carets, kill_selection);
|
||||
assert(s16_are_equal(buffer->string, meme_string));
|
||||
|
||||
for (i32 i = 0; i < 32; i += 1) {
|
||||
buffer16_undo_edit(buffer, &carets);
|
||||
assert(s16_are_equal(buffer->string, string));
|
||||
buffer16_redo_edit(buffer, &carets);
|
||||
assert(s16_are_equal(buffer->string, meme_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ma_end_scratch(scratch);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
136
src/ui/buffer16.h
Normal file
136
src/ui/buffer16.h
Normal file
@@ -0,0 +1,136 @@
|
||||
typedef struct caret_t caret_t;
|
||||
struct caret_t {
|
||||
i64 ifront;
|
||||
union {
|
||||
r1i64_t range;
|
||||
i64 pos[2];
|
||||
};
|
||||
};
|
||||
|
||||
typedef struct xy_t xy_t;
|
||||
struct xy_t {
|
||||
i64 col;
|
||||
i64 line;
|
||||
};
|
||||
|
||||
typedef struct edit16_t edit16_t;
|
||||
struct edit16_t {
|
||||
r1i64_t range;
|
||||
s16_t string;
|
||||
};
|
||||
|
||||
// @todo: redo tree
|
||||
typedef array(edit16_t) array_edit16_t;
|
||||
typedef array(caret_t) array_caret_t;
|
||||
typedef struct history16_t history16_t;
|
||||
struct history16_t {
|
||||
array_edit16_t edits;
|
||||
array_caret_t carets;
|
||||
};
|
||||
|
||||
typedef array(history16_t) array_history16_t;
|
||||
typedef struct buffer16_id_t buffer16_id_t;
|
||||
struct buffer16_id_t { i64 e; };
|
||||
|
||||
typedef struct buffer16_t buffer16_t;
|
||||
struct buffer16_t {
|
||||
s8_t name;
|
||||
buffer16_id_t id;
|
||||
|
||||
i32 change_id;
|
||||
i8 edit_phase;
|
||||
|
||||
struct {
|
||||
b8 dirty: 1;
|
||||
b8 history: 1;
|
||||
b8 line_starts: 1;
|
||||
} flags;
|
||||
|
||||
union {
|
||||
s16_t string;
|
||||
u16 *data;
|
||||
struct {
|
||||
u16 *str;
|
||||
i64 len;
|
||||
};
|
||||
};
|
||||
i64 cap;
|
||||
array_i64_t line_starts;
|
||||
|
||||
array_history16_t undo_stack;
|
||||
array_history16_t redo_stack;
|
||||
alo_t alo;
|
||||
};
|
||||
|
||||
|
||||
typedef struct view_id_t view_id_t;
|
||||
struct view_id_t { i64 e; };
|
||||
typedef struct view_t view_t;
|
||||
struct view_t {
|
||||
view_id_t id;
|
||||
buffer16_id_t active_buffer;
|
||||
v2i32_t scroll;
|
||||
array_caret_t carets;
|
||||
|
||||
caret_t main_caret_on_begin_frame;
|
||||
b32 update_scroll;
|
||||
};
|
||||
|
||||
const b32 dont_kill_selection = false;
|
||||
const b32 kill_selection = true;
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
// caret helpers
|
||||
fn caret_t caret_make(i64 front, i64 back);
|
||||
fn i64 caret_get_front(caret_t caret);
|
||||
fn i64 caret_get_back(caret_t caret);
|
||||
fn caret_t caret_set_front(caret_t caret, i64 pos);
|
||||
fn caret_t caret_set_back(caret_t caret, i64 pos);
|
||||
fn b32 carets_are_equal(caret_t a, caret_t b);
|
||||
fn b32 carets_overlap(caret_t a, caret_t b);
|
||||
|
||||
///////////////////////////////
|
||||
// buffer helpers
|
||||
fn i64 buffer16_pos_to_line(buffer16_t *buffer, i64 pos);
|
||||
fn r1i64_t buffer16_get_line_range(buffer16_t *buffer, i64 line);
|
||||
fn r1i64_t buffer16_get_line_range_full(buffer16_t *buffer, i64 line, i64 *eof);
|
||||
fn xy_t buffer16_pos_to_xy(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_xy_to_pos(buffer16_t *buffer, xy_t xy);
|
||||
fn i64 buffer16_xy_to_pos_without_new_line(buffer16_t *buffer, xy_t xy);
|
||||
fn s16_t buffer16_get_string(buffer16_t *buffer, r1i64_t range);
|
||||
fn s16_t buffer16_get_line_string(buffer16_t *buffer, i64 line);
|
||||
fn r1i64_t buffer16_get_range_end(buffer16_t *buffer);
|
||||
fn r1i64_t buffer16_get_range(buffer16_t *buffer);
|
||||
fn i64 buffer16_clamp_pos(buffer16_t *buffer, i64 pos);
|
||||
fn r1i64_t buffer16_clamp_range(buffer16_t *buffer, r1i64_t range);
|
||||
fn i64 buffer16_get_word_start(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_get_word_end(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_get_next_word_end(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_get_prev_word_start(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_get_line_start(buffer16_t *buffer, i64 pos, i64 *eof);
|
||||
fn i64 buffer16_get_line_end(buffer16_t *buffer, i64 pos, i64 *eof);
|
||||
fn i64 buffer16_get_line_start_full(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_get_line_end_full(buffer16_t *buffer, i64 pos);
|
||||
fn r1i64_t buffer16_enclose_word(buffer16_t *buffer, i64 pos);
|
||||
fn u16 buffer16_get_char(buffer16_t *buffer, i64 pos);
|
||||
fn i64 buffer16_offset_pos_by_line(buffer16_t *buffer, i64 pos, i64 line_offset);
|
||||
|
||||
///////////////////////////////
|
||||
// buffer raw textural operations. no history
|
||||
fn void buffer16_raw_grow(buffer16_t *buffer, i64 change_size);
|
||||
fn void buffer16_raw_replace_text(buffer16_t *buffer, r1i64_t range, s16_t string);
|
||||
|
||||
///////////////////////////////
|
||||
// buffer multicursor + history
|
||||
fn void buffer16_init(alo_t alo, buffer16_t *buffer, s8_t name, i64 size);
|
||||
fn void buffer16_deinit(buffer16_t *buffer);
|
||||
fn array_edit16_t buffer16_begin_edit(alo_t alo, buffer16_t *buffer, array_caret_t *carets);
|
||||
fn void buffer16_add_edit(array_edit16_t *edits, r1i64_t range, s16_t string);
|
||||
fn void buffer16_end_edit(buffer16_t *buffer, array_edit16_t *edits, array_caret_t *carets, b32 kill_selection);
|
||||
fn void buffer16_merge_carets(buffer16_t *buffer, array_caret_t *carets);
|
||||
fn void buffer16_adjust_carets(array_edit16_t *edits, array_caret_t *carets);
|
||||
fn void buffer16_pre_begin_edit_save_caret_history(buffer16_t *buffer, array_caret_t *carets);
|
||||
|
||||
fn void buffer16_redo_edit(buffer16_t *buffer, array_caret_t *carets);
|
||||
fn void buffer16_undo_edit(buffer16_t *buffer, array_caret_t *carets);
|
||||
17
src/ui/ui.c
17
src/ui/ui.c
@@ -1,4 +1,5 @@
|
||||
#include "ui.gen.c"
|
||||
#include "buffer16.c"
|
||||
|
||||
fn ui_caret_t ui_caret_clamp(ui_caret_t c, i32 min, i32 max) {
|
||||
return (ui_caret_t){CLAMP(c.e[0],min,max), CLAMP(c.e[1],min,max), c.ifront};
|
||||
@@ -517,15 +518,15 @@ fn_test void ui_test_text_replace(void) {
|
||||
assert(s8_are_equal(ti.string, s8("qaer")));
|
||||
}
|
||||
|
||||
#define ui_text_input(...) ui__text_input(UILOC, __VA_ARGS__)
|
||||
fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, b32 sim_even_if_no_focus) {
|
||||
ui_box_t *box = ui_box(.loc = loc, .string = s8("text_input"), .flags = { .draw_border = true, .draw_rect = true, .draw_text = true, .keyboard_nav = true });
|
||||
#define ui_text_input(ti, ...) ui__text_input(UILOC, ti, (ui_box_flags_t){ .draw_border = true, .draw_rect = true, .draw_text = true, .keyboard_nav = true, __VA_ARGS__ })
|
||||
fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, ui_box_flags_t flags) {
|
||||
ui_box_t *box = ui_box(.loc = loc, .string = s8("text_input"), .flags = flags);
|
||||
|
||||
box->text_input = ti;
|
||||
box->custom_draw = ui_text_input_draw;
|
||||
ui_signal_t signal = ui_signal_from_box(box);
|
||||
|
||||
b32 sim = sim_even_if_no_focus || ui_is_focused_box(box);
|
||||
b32 sim = flags.sim_even_if_no_focus || ui_is_focused_box(box);
|
||||
if (sim) {
|
||||
app_event_t *ev = ui->event;
|
||||
i32 sel_size = r1i32_size(ti->caret.range);
|
||||
@@ -1198,7 +1199,7 @@ fn void ui_draw(void) {
|
||||
}
|
||||
|
||||
fn void ui_begin_frame(app_frame_t *frame) {
|
||||
ui = tcx->data[tcx_slot_ui];
|
||||
ui = tcx->ui_ctx;
|
||||
ui->frame = frame;
|
||||
}
|
||||
|
||||
@@ -1234,8 +1235,8 @@ fn void ui_reload(void) {
|
||||
}
|
||||
|
||||
fn void ui_init(ma_arena_t *arena) {
|
||||
tcx->data[tcx_slot_ui] = ma_push_type(arena, ui_t);
|
||||
ui = tcx->data[tcx_slot_ui];
|
||||
tcx->ui_ctx = ma_push_type(arena, ui_t);
|
||||
ui = tcx->ui_ctx;
|
||||
ui->box_arena = ma_push_arena(arena, mib(1));
|
||||
ui_reload();
|
||||
}
|
||||
@@ -1288,7 +1289,7 @@ fn void ui_demo_everything_lister(void) {
|
||||
locl ui_text_input_t text_input;
|
||||
text_input.str = buff;
|
||||
text_input.cap = lengthof(buff);
|
||||
ui_signal_t ti_sig = ui_text_input(&text_input, true);
|
||||
ui_signal_t ti_sig = ui_text_input(&text_input, .sim_even_if_no_focus = true);
|
||||
if (lister_just_opened) text_input.len = 0;
|
||||
|
||||
ma_temp_t scratch = ma_begin_scratch();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "buffer16.h"
|
||||
|
||||
typedef struct ui_code_loc_t ui_code_loc_t;
|
||||
struct ui_code_loc_t {
|
||||
char *file;
|
||||
@@ -22,6 +24,8 @@ struct ui_box_flags_t {
|
||||
b8 children_sum_x: 1;
|
||||
b8 children_sum_y: 1;
|
||||
b8 keyboard_nav: 1;
|
||||
|
||||
b8 sim_even_if_no_focus: 1;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -101,6 +105,7 @@ struct ui_box_t {
|
||||
b32 expanded;
|
||||
|
||||
ui_text_input_t *text_input;
|
||||
view_id_t view;
|
||||
};
|
||||
|
||||
typedef struct ui_signal_t ui_signal_t;
|
||||
|
||||
Reference in New Issue
Block a user