text editor

This commit is contained in:
Krzosa Karol
2025-04-10 11:01:32 +02:00
parent 00777a24d5
commit 13295a2fcd
4 changed files with 367 additions and 9 deletions

View File

@@ -170,3 +170,8 @@ fn ma_temp_t ma_begin_scratch1(ma_arena_t *conflict) {
ma_arena_t *conflicts[] = {conflict};
return ma_begin_scratch_ex(conflicts, 1);
}
fn ma_temp_t ma_begin_scratch2(ma_arena_t *a, ma_arena_t *b) {
ma_arena_t *conflicts[] = {a, b};
return ma_begin_scratch_ex(conflicts, 2);
}

View File

@@ -115,6 +115,7 @@ union convert_f32_i32_t { f32 f; i32 i; };
#define PASTE_(a, b) a##b
#define PASTE(a, b) PASTE_(a, b)
#define SWAP(t, a, b) do { t PASTE(temp__, __LINE__) = a; a = b; b = PASTE(temp__, __LINE__); } while(0)
#define SWAP_PTR(t, a, b) do { t PASTE(temp__, __LINE__) = *a; *a = *b; *b = PASTE(temp__, __LINE__); } while(0)
#define CODE(...) #__VA_ARGS__
#if PLATFORM_CL || (PLATFORM_CLANG && PLATFORM_WINDOWS)

View File

@@ -45,7 +45,7 @@ typedef array(i64) array_i64_t;
#define arrisz(x) sizeof((x)->data[0])
#define arrcst(x) ((array_void_t *)(x))
#define array_init(a, this, count) (array__init((a), arrcst(this), arrisz(this), (count)))
#define array_add(this, item) (array__grow(arrcst(this), arrisz(this), 1), (this)->data[(this)->len++] = (item))
#define array_add(this, ...) (array__grow(arrcst(this), arrisz(this), 1), (this)->data[(this)->len++] = __VA_ARGS__)
#define array_pop(this) (assert_expr((this)->len > 0), (this)->data[--(this)->len])
#define array_dealloc(this) (dealloc((this)->alo, (this)->data))
#define array_addn(this, n) (array__grow(arrcst(this), arrisz(this), (n)), (this)->len += (n), &(this)->data[(this)->len - (n)])

View File

@@ -65,6 +65,10 @@ struct buffer16_t {
};
gb i64 buffer_raw_ids;
const b32 dont_kill_selection = false;
const b32 kill_selection = true;
fn void buffer16_multi_cursor_apply_edits(buffer16_t *buffer, array_edit16_t edits);
///////////////////////////////
// buffer helpers
@@ -468,8 +472,7 @@ fn void buffer16_save_history_before_apply_edits(buffer16_t *buffer, array_histo
array_copy(buffer->alo, &entry->edits, edits);
// make reverse edits
for (i64 i = 0; i < edits->len; i += 1) {
edit16_t *it = edits->data + i;
array_for(edit16_t, it, &entry->edits) {
it->range = (r1i64_t){it->range.min, it->range.min + it->string.len};
it->string = s16_alo_copy(buffer->alo, buffer16_get_string(buffer, it->range));
}
@@ -505,7 +508,7 @@ fn void buffer16_redo_edit(buffer16_t *buffer, array_caret_t *carets) {
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); // @todo
buffer16_multi_cursor_apply_edits(buffer, entry.edits);
array_dealloc(carets);
*carets = entry.carets;
@@ -514,15 +517,32 @@ fn void buffer16_redo_edit(buffer16_t *buffer, array_caret_t *carets) {
dealloc(buffer->alo, it->string.str);
}
array_dealloc(&entry.edits);
}
fn void buffer16_apply_edits(buffer16_t *buffer, array_edit16_t *edits) {
fn void buffer16_undo_edit(buffer16_t *buffer, array_caret_t *carets) {
if (!buffer->flags.history || buffer->redo_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, entry.edits); // @todo
// @todo: ...
buffer16_multi_cursor_apply_edits(buffer, *edits);
}
fn void buffer16_dealloc_history_entries(buffer16_t *buffer, array_history16_t *entries) {
@@ -571,6 +591,296 @@ fn void caret_assert_ranges(array_caret_t carets) {
}
}
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(ma_temp_alo(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(ma_temp_alo(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);
}
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];
}
}
}
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(ma_temp_alo(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(ma_temp_alo(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();
@@ -631,7 +941,49 @@ fn_test void buffer16_test(void) {
}
{
buffer16_t *buffer = ma_push_type(scratch.arena, buffer16_t);
buffer16_raw_init(ma_temp_alo(scratch), buffer, S8_FILE_AND_LINE, 128);
buffer16_raw_replace_text(buffer, r1i64_null, s16("some thing or another and stuff like that"));
{
array_caret_t carets = {0};
array_init(ma_temp_alo(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(ma_temp_alo(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(ma_temp_alo(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, s16("meme meme or another and stuff like that")));
}
}
ma_end_scratch(scratch);
exit(0);
}
}