Buffer multi cursor
This commit is contained in:
@@ -157,7 +157,7 @@ Range GetLine(Buffer &buffer, Int line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Int GetLineNumber(Buffer &buffer, Int pos) {
|
Int GetLineNumber(Buffer &buffer, Int pos) {
|
||||||
Add(&buffer.line_starts, buffer.len);
|
Add(&buffer.line_starts, buffer.len + 1);
|
||||||
|
|
||||||
// binary search
|
// binary search
|
||||||
Int low = 0;
|
Int low = 0;
|
||||||
@@ -223,41 +223,6 @@ void ValidateLineStarts(Buffer *buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MergeCursors(Array<Cursor> *cursors) {
|
|
||||||
Scratch scratch;
|
|
||||||
// Merge cursors that overlap, this needs to be handled before any edits to
|
|
||||||
// make sure overlapping edits won't happen.
|
|
||||||
|
|
||||||
// @optimize @refactor: this is retarded, I hit so many array removal bugs here, without allocation please
|
|
||||||
Array<Cursor *> deleted_cursors = {scratch};
|
|
||||||
ForItem(cursor, *cursors) {
|
|
||||||
if (Contains(deleted_cursors, &cursor)) goto end_of_cursor_loop;
|
|
||||||
|
|
||||||
For(*cursors) {
|
|
||||||
if (&it == &cursor) continue;
|
|
||||||
bool a = cursor.range.max >= it.range.min && cursor.range.max <= it.range.max;
|
|
||||||
bool b = cursor.range.min >= it.range.min && cursor.range.min <= it.range.max;
|
|
||||||
if ((a || b) && !Contains(deleted_cursors, &it)) {
|
|
||||||
Add(&deleted_cursors, &it);
|
|
||||||
cursor.range.max = Max(cursor.range.max, it.range.max);
|
|
||||||
cursor.range.min = Min(cursor.range.min, it.range.min);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end_of_cursor_loop:;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array<Cursor> new_cursors = {cursors->allocator};
|
|
||||||
For(*cursors) {
|
|
||||||
if (Contains(deleted_cursors, &it) == false) {
|
|
||||||
Add(&new_cursors, it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert(new_cursors.len <= cursors->len);
|
|
||||||
Dealloc(cursors);
|
|
||||||
*cursors = new_cursors;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateLines(Buffer *buffer, Range range, String16 string) {
|
void UpdateLines(Buffer *buffer, Range range, String16 string) {
|
||||||
Array<Int> &ls = buffer->line_starts;
|
Array<Int> &ls = buffer->line_starts;
|
||||||
Int min_line_number = GetLineNumber(*buffer, range.min);
|
Int min_line_number = GetLineNumber(*buffer, range.min);
|
||||||
@@ -299,7 +264,7 @@ void UpdateLines(Buffer *buffer, Range range, String16 string) {
|
|||||||
OffsetAllLinesForward(buffer, nl, &line_offset);
|
OffsetAllLinesForward(buffer, nl, &line_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _ReplaceText(Buffer *buffer, Range range, String16 string) {
|
void ReplaceText(Buffer *buffer, Range range, String16 string) {
|
||||||
Assert(range.max >= range.min);
|
Assert(range.max >= range.min);
|
||||||
Assert(range.max >= 0 && range.max <= buffer->len);
|
Assert(range.max >= 0 && range.max <= buffer->len);
|
||||||
Assert(range.min >= 0 && range.min <= buffer->len);
|
Assert(range.min >= 0 && range.min <= buffer->len);
|
||||||
@@ -326,103 +291,6 @@ void _ReplaceText(Buffer *buffer, Range range, String16 string) {
|
|||||||
ValidateLineStarts(buffer);
|
ValidateLineStarts(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
|
|
||||||
// SortKey = range.min
|
|
||||||
if (Count == 1) {
|
|
||||||
// NOTE(casey): No work to do.
|
|
||||||
} else if (Count == 2) {
|
|
||||||
Edit *EntryA = First;
|
|
||||||
Edit *EntryB = First + 1;
|
|
||||||
if (EntryA->range.min > EntryB->range.min) {
|
|
||||||
Swap(EntryA, EntryB);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int64_t Half0 = Count / 2;
|
|
||||||
int64_t Half1 = Count - Half0;
|
|
||||||
|
|
||||||
Assert(Half0 >= 1);
|
|
||||||
Assert(Half1 >= 1);
|
|
||||||
|
|
||||||
Edit *InHalf0 = First;
|
|
||||||
Edit *InHalf1 = First + Half0;
|
|
||||||
Edit *End = First + Count;
|
|
||||||
|
|
||||||
MergeSort(Half0, InHalf0, Temp);
|
|
||||||
MergeSort(Half1, InHalf1, Temp);
|
|
||||||
|
|
||||||
Edit *ReadHalf0 = InHalf0;
|
|
||||||
Edit *ReadHalf1 = InHalf1;
|
|
||||||
|
|
||||||
Edit *Out = Temp;
|
|
||||||
for (int64_t 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 (int64_t Index = 0;
|
|
||||||
Index < Count;
|
|
||||||
++Index) {
|
|
||||||
First[Index] = Temp[Index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
|
||||||
#if DEBUG_BUILD
|
|
||||||
Assert(buffer->line_starts.len);
|
|
||||||
Assert(edits.len);
|
|
||||||
For(edits) {
|
|
||||||
Assert(it.range.min >= 0);
|
|
||||||
Assert(it.range.max >= it.range.min);
|
|
||||||
Assert(it.range.max <= buffer->len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure edit ranges don't overlap
|
|
||||||
ForItem(it1, edits) {
|
|
||||||
ForItem(it2, edits) {
|
|
||||||
if (&it1 == &it2) continue;
|
|
||||||
|
|
||||||
bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max;
|
|
||||||
Assert(!a2_inside);
|
|
||||||
|
|
||||||
bool 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 higest based on range.min
|
|
||||||
{
|
|
||||||
Scratch scratch((Arena *)buffer->line_starts.allocator.object);
|
|
||||||
Array<Edit> edits_copy = TightCopy(scratch, edits);
|
|
||||||
MergeSort(edits.len, edits_copy.data, edits.data);
|
|
||||||
edits = edits_copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @optimize:
|
|
||||||
For(edits) _ReplaceText(buffer, it.range, it.string);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplaceText(Buffer *buffer, Range range, String16 string) {
|
|
||||||
Scratch scratch((Arena *)buffer->line_starts.allocator.object);
|
|
||||||
Array<Edit> edits = {scratch};
|
|
||||||
Add(&edits, {range, string});
|
|
||||||
ApplyEdits(buffer, edits);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestBuffer() {
|
void TestBuffer() {
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
{
|
{
|
||||||
@@ -534,4 +402,11 @@ void TestBuffer() {
|
|||||||
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
ReplaceText(&buffer, {22, 23}, L"\n\n\nThing\nmeme\n\n\n");
|
||||||
ValidateLineStarts(&buffer);
|
ValidateLineStarts(&buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Buffer buffer = {};
|
||||||
|
InitBuffer(scratch, &buffer);
|
||||||
|
ReplaceText(&buffer, {}, L"Thing\nmeme");
|
||||||
|
ReplaceText(&buffer, GetEndAsRange(buffer), L"\nnewThing");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
src/text_editor/buffer_multi_cursor.cpp
Normal file
152
src/text_editor/buffer_multi_cursor.cpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
void MergeCursors(Array<Cursor> *cursors) {
|
||||||
|
Scratch scratch;
|
||||||
|
// Merge cursors that overlap, this needs to be handled before any edits to
|
||||||
|
// make sure overlapping edits won't happen.
|
||||||
|
|
||||||
|
// @optimize @refactor: this is retarded, I hit so many array removal bugs here, without allocation please
|
||||||
|
Array<Cursor *> deleted_cursors = {scratch};
|
||||||
|
ForItem(cursor, *cursors) {
|
||||||
|
if (Contains(deleted_cursors, &cursor)) goto end_of_cursor_loop;
|
||||||
|
|
||||||
|
For(*cursors) {
|
||||||
|
if (&it == &cursor) continue;
|
||||||
|
bool a = cursor.range.max >= it.range.min && cursor.range.max <= it.range.max;
|
||||||
|
bool b = cursor.range.min >= it.range.min && cursor.range.min <= it.range.max;
|
||||||
|
if ((a || b) && !Contains(deleted_cursors, &it)) {
|
||||||
|
Add(&deleted_cursors, &it);
|
||||||
|
cursor.range.max = Max(cursor.range.max, it.range.max);
|
||||||
|
cursor.range.min = Min(cursor.range.min, it.range.min);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end_of_cursor_loop:;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<Cursor> new_cursors = {cursors->allocator};
|
||||||
|
For(*cursors) {
|
||||||
|
if (Contains(deleted_cursors, &it) == false) {
|
||||||
|
Add(&new_cursors, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(new_cursors.len <= cursors->len);
|
||||||
|
Dealloc(cursors);
|
||||||
|
*cursors = new_cursors;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
|
||||||
|
// SortKey = range.min
|
||||||
|
if (Count == 1) {
|
||||||
|
// NOTE(casey): No work to do.
|
||||||
|
} else if (Count == 2) {
|
||||||
|
Edit *EntryA = First;
|
||||||
|
Edit *EntryB = First + 1;
|
||||||
|
if (EntryA->range.min > EntryB->range.min) {
|
||||||
|
Swap(EntryA, EntryB);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int64_t Half0 = Count / 2;
|
||||||
|
int64_t Half1 = Count - Half0;
|
||||||
|
|
||||||
|
Assert(Half0 >= 1);
|
||||||
|
Assert(Half1 >= 1);
|
||||||
|
|
||||||
|
Edit *InHalf0 = First;
|
||||||
|
Edit *InHalf1 = First + Half0;
|
||||||
|
Edit *End = First + Count;
|
||||||
|
|
||||||
|
MergeSort(Half0, InHalf0, Temp);
|
||||||
|
MergeSort(Half1, InHalf1, Temp);
|
||||||
|
|
||||||
|
Edit *ReadHalf0 = InHalf0;
|
||||||
|
Edit *ReadHalf1 = InHalf1;
|
||||||
|
|
||||||
|
Edit *Out = Temp;
|
||||||
|
for (int64_t 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 (int64_t Index = 0;
|
||||||
|
Index < Count;
|
||||||
|
++Index) {
|
||||||
|
First[Index] = Temp[Index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||||
|
#if DEBUG_BUILD
|
||||||
|
Assert(buffer->line_starts.len);
|
||||||
|
Assert(edits.len);
|
||||||
|
For(edits) {
|
||||||
|
Assert(it.range.min >= 0);
|
||||||
|
Assert(it.range.max >= it.range.min);
|
||||||
|
Assert(it.range.max <= buffer->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure edit ranges don't overlap
|
||||||
|
ForItem(it1, edits) {
|
||||||
|
ForItem(it2, edits) {
|
||||||
|
if (&it1 == &it2) continue;
|
||||||
|
|
||||||
|
bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max;
|
||||||
|
Assert(!a2_inside);
|
||||||
|
|
||||||
|
bool 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
|
||||||
|
{
|
||||||
|
Scratch scratch((Arena *)buffer->line_starts.allocator.object);
|
||||||
|
Array<Edit> edits_copy = TightCopy(scratch, edits);
|
||||||
|
MergeSort(edits.len, edits_copy.data, edits.data);
|
||||||
|
edits = edits_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo: optimize
|
||||||
|
Int offset = 0;
|
||||||
|
For(edits) {
|
||||||
|
it.range.min += offset;
|
||||||
|
it.range.max += offset;
|
||||||
|
offset += it.string.len - GetSize(it.range);
|
||||||
|
ReplaceText(buffer, it.range, it.string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddEdit(Array<Edit> *e, Range range, String16 string) {
|
||||||
|
Add(e, {range, string});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestBufferMultiCursor() {
|
||||||
|
Scratch scratch;
|
||||||
|
|
||||||
|
{
|
||||||
|
Buffer buffer = {};
|
||||||
|
InitBuffer(scratch, &buffer);
|
||||||
|
ReplaceText(&buffer, {}, L"Testing\nthings");
|
||||||
|
|
||||||
|
Array<Edit> edits = {scratch};
|
||||||
|
AddEdit(&edits, {0, 7}, L"t");
|
||||||
|
AddEdit(&edits, {8, 9}, L"T");
|
||||||
|
AddEdit(&edits, GetEndAsRange(buffer), L"\nnewThing");
|
||||||
|
ApplyEdits(&buffer, edits);
|
||||||
|
String16 s = GetString(buffer);
|
||||||
|
Assert(s == L"t\nThings\nnewThing");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@
|
|||||||
#include "string16.cpp"
|
#include "string16.cpp"
|
||||||
|
|
||||||
#include "buffer.cpp"
|
#include "buffer.cpp"
|
||||||
|
#include "buffer_multi_cursor.cpp"
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
InitScratch();
|
InitScratch();
|
||||||
Test();
|
Test();
|
||||||
TestBuffer();
|
TestBuffer();
|
||||||
|
TestBufferMultiCursor();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
InitWindow(1280, 720, "hello :)");
|
InitWindow(1280, 720, "hello :)");
|
||||||
|
|||||||
Reference in New Issue
Block a user