Undo redo
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user