Undo redo

This commit is contained in:
Krzosa Karol
2024-07-22 08:03:22 +02:00
parent 91d2db6116
commit a5069cd148
7 changed files with 116 additions and 148 deletions

View File

@@ -444,7 +444,7 @@ void BoundedAdd(Array<T> *arr, T item) {
template <class T>
T *Alloc(Array<T> *arr, T item) {
TryGrowing();
TryGrowing(arr);
T *ref = arr->data + arr->len++;
ref[0] = item;
return ref;

View File

@@ -162,7 +162,6 @@ void ReplaceText(Buffer *buffer, Range range, String16 string) {
Assert(range.max >= range.min);
Assert(range.max >= 0 && range.max <= buffer->len);
Assert(range.min >= 0 && range.min <= buffer->len);
buffer->change_id += 1;
Int size_to_remove = range.max - range.min;
Int size_to_add = string.len;

View File

@@ -1,15 +1,3 @@
// @todo: gap buffer to improve speed of inserting on big files with only one cursor?
struct Buffer {
union {
U16 *data;
wchar_t *str;
};
Int len;
Int cap;
Array<Int> line_starts;
Int change_id;
};
union Range {
struct {
Int min;
@@ -35,3 +23,24 @@ struct Edit {
Range range;
String16 string;
};
// @idea: maybe redo tree?
struct HistoryEntry {
Array<Edit> edits;
Array<Caret> carets;
};
// @todo: gap buffer to improve speed of inserting on big files with only one cursor?
struct Buffer {
union {
U16 *data;
wchar_t *str;
};
Int len;
Int cap;
Array<Int> line_starts;
Array<HistoryEntry> undo_stack;
Array<HistoryEntry> redo_stack;
int debug_edit_phase;
};

View File

@@ -1,48 +1,32 @@
// @todo:
// @idea: maybe redo tree?
struct HistoryEntry {
Array<Edit> edits;
Array<Caret> carets;
};
struct History {
Array<HistoryEntry> undo_stack;
Array<HistoryEntry> redo_stack;
int debug_edit_phase;
};
#if 0
void SaveHistoryBeforeMergeCursor(Window *window, Array<HistoryEntry> *stack) {
ProfileFunction();
HistoryEntry *entry = stack->alloc();
Allocator sys_allocator = GetSystemAllocator();
entry->cursors = window->cursors.tight_copy(sys_allocator);
void SaveHistoryBeforeMergeCursor(Buffer *buffer, Array<HistoryEntry> *stack, Array<Caret> &carets) {
HistoryEntry *entry = Alloc(stack);
entry->carets = TightCopy(GetSystemAllocator(), carets);
}
void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Array<Edit> edits) {
void SaveHistoryBeforeApplyEdits(Buffer *buffer, Array<HistoryEntry> *stack, Array<Edit> &edits) {
ProfileFunction();
HistoryEntry *entry = stack->last();
HistoryEntry *entry = GetLast(*stack);
Allocator sys_allocator = GetSystemAllocator();
entry->edits = edits.tight_copy(sys_allocator);
entry->edits = TightCopy(sys_allocator, edits);
// Make reverse edits
For(entry->edits) {
Range new_range = {it.range.min, it.range.min + it.string.len};
String string = GetString(window->buffer, it.range);
it.string = Copy(sys_allocator, string);
it.range = new_range;
Range new_range = {it.range.min, it.range.min + it.string.len};
String16 string = GetString(*buffer, it.range);
it.string = Copy(sys_allocator, string);
it.range = new_range;
}
Array<Edit> temp_edits = entry->edits.tight_copy(sys_allocator);
defer { temp_edits.dealloc(); };
Scratch scratch;
Array<Edit> temp_edits = TightCopy(scratch, entry->edits);
// Fix reverse edits
ForItem(edit, edits) {
int64_t remove_size = GetRangeSize(edit.range);
int64_t insert_size = edit.string.len;
int64_t offset = insert_size - remove_size;
Int remove_size = GetSize(edit.range);
Int insert_size = edit.string.len;
Int offset = insert_size - remove_size;
for (int64_t i = 0; i < entry->edits.len; i += 1) {
for (Int i = 0; i < entry->edits.len; i += 1) {
Edit &new_edit = entry->edits.data[i];
Edit &old_edit = temp_edits.data[i];
if (old_edit.range.min > edit.range.min) {
@@ -53,96 +37,94 @@ void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Arr
}
}
void RedoEdit(Window *window) {
void RedoEdit(Buffer *buffer, Array<Caret> *carets) {
ProfileFunction();
if (window->history.redo_stack.len <= 0) return;
if (buffer->redo_stack.len <= 0) return;
HistoryEntry entry = window->history.redo_stack.pop();
HistoryEntry entry = Pop(&buffer->redo_stack);
SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack);
SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, entry.edits);
_ApplyEdits(&window->buffer, entry.edits);
SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets);
SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, entry.edits);
_ApplyEdits(buffer, entry.edits);
window->cursors.dealloc();
window->cursors = entry.cursors;
window->not_regen_layout = false;
Dealloc(carets);
*carets = entry.carets;
Allocator sys_allocator = GetSystemAllocator();
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
entry.edits.dealloc();
Dealloc(&entry.edits);
}
void UndoEdit(Window *window) {
void UndoEdit(Buffer *buffer, Array<Caret> *carets) {
ProfileFunction();
if (window->history.undo_stack.len <= 0) return;
if (buffer->undo_stack.len <= 0) return;
HistoryEntry entry = window->history.undo_stack.pop();
HistoryEntry entry = Pop(&buffer->undo_stack);
SaveHistoryBeforeMergeCursor(window, &window->history.redo_stack);
SaveHistoryBeforeApplyEdits(window, &window->history.redo_stack, entry.edits);
_ApplyEdits(&window->buffer, entry.edits);
SaveHistoryBeforeMergeCursor(buffer, &buffer->redo_stack, *carets);
SaveHistoryBeforeApplyEdits(buffer, &buffer->redo_stack, entry.edits);
_ApplyEdits(buffer, entry.edits);
window->cursors.dealloc();
window->cursors = entry.cursors;
window->not_regen_layout = false;
Dealloc(carets);
*carets = entry.carets;
Allocator sys_allocator = GetSystemAllocator();
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
entry.edits.dealloc();
Dealloc(&entry.edits);
}
void BeforeEdit(Window *window) {
Assert(window->history.debug_edit_phase == 0);
window->history.debug_edit_phase += 1;
Assert(window->cursors.len);
SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack);
void ApplyEdits(Buffer *buffer, Array<Edit> &edits) {
ProfileFunction();
Assert(buffer->debug_edit_phase == 1);
buffer->debug_edit_phase += 1;
SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, edits);
_ApplyEdits(buffer, edits);
}
// Clear redo stack
{
For(window->history.redo_stack) {
it.cursors.dealloc();
ForItem(edit, it.edits) Dealloc(it.edits.allocator, &edit.string.data);
it.edits.dealloc();
}
window->history.redo_stack.dealloc();
void ClearRedoStack(Buffer *buffer) {
ForItem(entry, buffer->redo_stack) {
Dealloc(&entry.carets);
ForItem(edit, entry.edits) Dealloc(entry.edits.allocator, &edit.string.data);
Dealloc(&entry.edits);
}
MergeCursors(window);
buffer->redo_stack.len = 0;
}
void ApplyEdits(Window *window, Array<Edit> edits) {
ProfileFunction();
Assert(window->history.debug_edit_phase == 1);
window->history.debug_edit_phase += 1;
SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, edits);
_ApplyEdits(&window->buffer, edits);
void BeforeEdit(Buffer *buffer, Array<Caret> &carets) {
Assert(buffer->debug_edit_phase == 0);
buffer->debug_edit_phase += 1;
Assert(carets.len);
SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, carets);
ClearRedoStack(buffer);
}
void AfterEdit(Window *window, Array<Edit> edits) {
void AfterEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets) {
ProfileFunction();
Assert(window->history.debug_edit_phase == 2);
window->history.debug_edit_phase -= 2;
Assert(buffer->debug_edit_phase == 2);
buffer->debug_edit_phase -= 2;
HistoryEntry *entry = GetLast(buffer->undo_stack);
HistoryEntry *entry = window->history.undo_stack.last();
Assert(entry->cursors.len);
#if 1
Assert(entry->carets.len);
Assert(entry->edits.len);
for (int64_t i = 0; i < edits.len - 1; i += 1) {
Assert(edits[i].range.min <= edits[i + 1].range.min);
for (Int i = 0; i < edits->len - 1; i += 1) {
Assert(edits->data[i].range.min <= edits->data[i + 1].range.min);
}
#endif
//
// Offset all cursors by edits
// Offset all carets by edits
//
Scratch scratch;
Array<Cursor> new_cursors = window->cursors.tight_copy(scratch);
ForItem(edit, edits) {
int64_t remove_size = GetRangeSize(edit.range);
int64_t insert_size = edit.string.len;
int64_t offset = insert_size - remove_size;
Scratch scratch;
Array<Caret> new_carets = TightCopy(scratch, *carets);
ForItem(edit, *edits) {
Int remove_size = GetSize(edit.range);
Int insert_size = edit.string.len;
Int offset = insert_size - remove_size;
for (int64_t i = 0; i < window->cursors.len; i += 1) {
Cursor &old_cursor = window->cursors.data[i];
Cursor &new_cursor = new_cursors.data[i];
for (Int i = 0; i < carets->len; i += 1) {
Caret &old_cursor = carets->data[i];
Caret &new_cursor = new_carets.data[i];
if (old_cursor.range.min == edit.range.min) {
new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + insert_size;
} else if (old_cursor.range.min > edit.range.min) {
@@ -150,11 +132,9 @@ void AfterEdit(Window *window, Array<Edit> edits) {
}
}
}
for (int64_t i = 0; i < window->cursors.len; i += 1) window->cursors[i] = new_cursors[i];
for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i];
// Make sure all cursors are in range
For(window->cursors) it.range = Clamp(window->buffer, it.range);
window->not_regen_layout = false;
// Make sure all carets are in range
// @todo: remove?
For(*carets) it.range = Clamp(*buffer, it.range);
}
#endif

View File

@@ -5,7 +5,7 @@ void MergeCarets(Array<Caret> *_carets, Range *mouse_selection_anchor = NULL) {
// Merge carets 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
// @optimize @refactor: this is retarded, I hit so many array removal bugs here
Array<Caret *> deleted_carets = {scratch};
ForItem(caret, carets) {
if (Contains(deleted_carets, &caret)) goto end_of_caret_loop;
@@ -19,21 +19,22 @@ void MergeCarets(Array<Caret> *_carets, Range *mouse_selection_anchor = NULL) {
caret.range.max = Max(caret.range.max, it.range.max);
caret.range.min = Min(caret.range.min, it.range.min);
if (mouse_selection_anchor) *mouse_selection_anchor = caret.range;
break;
goto end_of_caret_loop;
}
}
end_of_caret_loop:;
}
Array<Caret> new_carets = {carets.allocator};
Array<Caret> new_carets = {scratch};
For(carets) {
if (Contains(deleted_carets, &it) == false) {
Add(&new_carets, it);
}
}
Assert(new_carets.len <= carets.len);
Dealloc(&carets);
carets = new_carets;
carets.len = 0;
For(new_carets) Add(&carets, it);
}
void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
@@ -91,7 +92,7 @@ void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
}
}
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
void _ApplyEdits(Buffer *buffer, Array<Edit> edits) {
ProfileFunction();
#if BUFFER_DEBUG
Assert(buffer->line_starts.len);
@@ -157,7 +158,7 @@ void TestBufferMultiCaret() {
AddEdit(&edits, {0, 7}, L"t");
AddEdit(&edits, {8, 9}, L"T");
AddEdit(&edits, GetEndAsRange(buffer), L"\nnewThing");
ApplyEdits(&buffer, edits);
_ApplyEdits(&buffer, edits);
String16 s = GetString(buffer);
Assert(s == L"t\nThings\nnewThing");
}

View File

@@ -26,11 +26,9 @@
#include "view_draw.cpp"
/*
- Shify + Alt + Down - make a cursor below
- Ctrl + Z, Ctrl + C - Undo redo history
- Ctrl + D - create new cursor at next occurence of word
- Ctrl + X, Ctrl + C, Ctrl + V - Copy paste
- Mouse anchor point and double click
- Scrollbars
- Line numbers
- file info bar at bottom (line, column, line endings)

View File

@@ -1,41 +1,16 @@
void AfterEdit(View *view, Array<Edit> edits) {
//
// Offset all cursors by edits
//
Scratch scratch;
Array<Caret> new_cursors = TightCopy(scratch, view->carets);
ForItem(edit, edits) {
Int remove_size = GetSize(edit.range);
Int insert_size = edit.string.len;
Int offset = insert_size - remove_size;
for (Int i = 0; i < view->carets.len; i += 1) {
Caret &old_cursor = view->carets.data[i];
Caret &new_cursor = new_cursors.data[i];
if (old_cursor.range.min == edit.range.min) {
new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + insert_size;
} else if (old_cursor.range.min > edit.range.min) {
new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + offset;
}
}
}
for (Int i = 0; i < view->carets.len; i += 1) view->carets[i] = new_cursors[i];
// Make sure all cursors are in range
For(view->carets) it.range = Clamp(*view->buffer, it.range);
}
void Command_Replace(View *view, String16 string) {
Scratch scratch;
BeforeEdit(view->buffer, view->carets);
MergeCarets(&view->carets);
Array<Edit> edits = {scratch};
For(view->carets) AddEdit(&edits, it.range, string);
ApplyEdits(view->buffer, edits);
AfterEdit(view, edits);
AfterEdit(view->buffer, &edits, &view->carets);
}
void Command_DuplicateLine(View *view, int direction) {
Assert(direction == DIR_UP || direction == DIR_DOWN);
BeforeEdit(view->buffer, view->carets);
For(view->carets) it = MakeCaret(GetFront(it));
MergeCarets(&view->carets);
@@ -49,7 +24,7 @@ void Command_DuplicateLine(View *view, int direction) {
AddEdit(&edits, Rng(pos), string);
}
ApplyEdits(view->buffer, edits);
AfterEdit(view, edits);
AfterEdit(view->buffer, &edits, &view->carets);
For(view->carets) it = MakeCaret(MovePos(*view->buffer, it.range.min, direction, false));
}
@@ -241,6 +216,12 @@ void HandleKeybindings(View *_view) {
}
}
if (CtrlShiftPress(KEY_Z)) {
RedoEdit(&buf, &view.carets);
} else if (CtrlPress(KEY_Z)) {
UndoEdit(&buf, &view.carets);
}
if (ShiftPress(KEY_PAGE_UP)) {
Command_MoveCursorsByPageSize(&view, DIR_UP, SHIFT_PRESSED);
} else if (Press(KEY_PAGE_UP)) {