From dc839cb3e0bf1fd5718c12b5dd5d7fd4f7b555a0 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sat, 24 Jan 2026 13:48:08 +0100 Subject: [PATCH] Fix the line moving, really hard code ... --- src/text_editor/buffer.cpp | 81 ++++++----- src/text_editor/commands.cpp | 8 +- src/text_editor/commands_clipboard.cpp | 4 +- src/text_editor/globals.cpp | 9 ++ src/text_editor/text_editor.cpp | 3 + src/text_editor/text_editor.h | 6 +- src/text_editor/view.cpp | 190 +++++++++++++++---------- 7 files changed, 183 insertions(+), 118 deletions(-) diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index d47e85b..2ed8c52 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1,4 +1,4 @@ -#define BUFFER_DEBUG 0 +#define BUFFER_DEBUG DEBUG_BUILD API Range MakeRange(Int a, Int b) { Range result = {Min(a, b), Max(a, b)}; @@ -669,6 +669,9 @@ void UpdateLines(Buffer *buffer, Range range, String16 string) { } void RawValidateLineStarts(Buffer *buffer) { + if (buffer->no_line_starts) { + return; + } Int line = 0; for (Int i = 0; i < buffer->len; i += 1) { Int l = PosToLine(buffer, i); @@ -795,7 +798,9 @@ void MergeSort(int64_t Count, T *First, T *Temp) { API void ApplyEditsMultiCursor(Buffer *buffer, Array edits) { ProfileFunction(); #if BUFFER_DEBUG - Assert(buffer->line_starts.len); + if (buffer->no_line_starts == false) { + Assert(buffer->line_starts.len); + } Assert(edits.len); For(edits) { Assert(it.range.min >= 0); @@ -1038,7 +1043,9 @@ API void AdjustCarets(Array edits, Array *carets) { for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i]; } -API void EndEdit(Buffer *buffer, Array *edits, Array *carets, bool kill_selection) { +constexpr bool EndEdit_KillSelection = true; +constexpr bool EndEdit_SkipFixingCaretsIWantToDoThatMyself = true; +API void EndEdit(Buffer *buffer, Array *edits, Array *carets, bool kill_selection, bool skip_fixing_carets_user_will_do_that_himself = false) { ProfileFunction(); { @@ -1068,40 +1075,42 @@ API void EndEdit(Buffer *buffer, Array *edits, Array *carets, bool } #endif - // Adjust carets - // this one also moves the carets forward if they are aligned with edit - // - Scratch scratch; - Array 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; + if (skip_fixing_carets_user_will_do_that_himself == false) { + // Adjust carets + // this one also moves the carets forward if they are aligned with edit + // + Scratch scratch; + Array 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 (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 += insert_size; + } else if (old_cursor.range.min > edit.range.min) { + new_cursor.range.min += offset; + } + + if (old_cursor.range.max == edit.range.max) { + new_cursor.range.max += insert_size; + } else if (old_cursor.range.max > edit.range.max) { + new_cursor.range.max += offset; + } + + Assert(new_cursor.range.max >= new_cursor.range.min); + } + } 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 += insert_size; - } else if (old_cursor.range.min > edit.range.min) { - new_cursor.range.min += offset; + carets->data[i] = new_carets[i]; + if (kill_selection) { + carets->data[i].range.max = carets->data[i].range.min; } - - if (old_cursor.range.max == edit.range.max) { - new_cursor.range.max += insert_size; - } else if (old_cursor.range.max > edit.range.max) { - new_cursor.range.max += offset; - } - - Assert(new_cursor.range.max >= new_cursor.range.min); - } - } - - for (Int i = 0; i < carets->len; i += 1) { - carets->data[i] = new_carets[i]; - if (kill_selection) { - carets->data[i].range.max = carets->data[i].range.min; } } } @@ -1322,7 +1331,7 @@ void RunBufferTest() { AddEdit(&edits, {0, 7}, u"t"); AddEdit(&edits, {8, 9}, u"T"); AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing"); - EndEdit(&buffer, &edits, &carets, KILL_SELECTION); + EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection); String16 s = GetString(&buffer); Assert(s == u"t\nThings\nnewThing"); DeinitBuffer(&buffer); @@ -1346,7 +1355,7 @@ void RunBufferTest() { AddEdit(&edits, {0, 7}, u"t"); AddEdit(&edits, {8, 9}, u"T"); AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing"); - EndEdit(&buffer, &edits, &carets, KILL_SELECTION); + EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection); String16 s = GetString(&buffer); Assert(s == u"t\nThings\nnewThing"); Assert(buffer.line_starts.len == 0); diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index c59b012..8ef1c24 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -141,7 +141,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) { Array lines_to_skip_triming = {}; if (!trim_lines_with_caret) { - lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); + lines_to_skip_triming = GetSelectedLinesSortedExclusive(scratch, view); } for (Int i = 0; i < buffer->line_starts.len; i += 1) { @@ -160,7 +160,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) { AddEdit(&edits, whitespace_range, u""); } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection); view->update_scroll = false; } @@ -207,7 +207,7 @@ void CMD_FormatSelection() { String16 string16 = {exec_result.buffer->str, exec_result.buffer->len}; AddEdit(&edits, it.range, string16); } - EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION); + EndEdit(primary.buffer, &edits, &primary.view->carets, EndEdit_KillSelection); } RegisterCommand(CMD_FormatSelection, "", ""); void CMD_KillProcess() { @@ -218,12 +218,14 @@ void CMD_KillProcess() { void CMD_MakeFontLarger() { FontSize += 1; ReloadFont(PathToFont, (U32)FontSize); + CMD_CenterView(); } RegisterCommand(CMD_MakeFontLarger, "ctrl-equals", "Increase the font size"); void CMD_MakeFontSmaller() { if (FontSize > 4) { FontSize -= 1; ReloadFont(PathToFont, (U32)FontSize); + CMD_CenterView(); } } RegisterCommand(CMD_MakeFontSmaller, "ctrl-minus", "Decrease the font size"); diff --git a/src/text_editor/commands_clipboard.cpp b/src/text_editor/commands_clipboard.cpp index d4eaf20..695037d 100644 --- a/src/text_editor/commands_clipboard.cpp +++ b/src/text_editor/commands_clipboard.cpp @@ -72,7 +72,7 @@ void ClipboardPaste(View *view) { Array edits = BeginEdit(scratch, buffer, view->carets); MergeCarets(buffer, &view->carets); For(view->carets) AddEdit(&edits, it.range, string); - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); return; } @@ -84,7 +84,7 @@ void ClipboardPaste(View *view) { Caret &it = view->carets[i]; AddEdit(&edits, it.range, string); } - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); } void CMD_Paste() { diff --git a/src/text_editor/globals.cpp b/src/text_editor/globals.cpp index 7482016..bce2f84 100644 --- a/src/text_editor/globals.cpp +++ b/src/text_editor/globals.cpp @@ -10,6 +10,14 @@ bool SearchCaseSensitive = false; bool SearchWordBoundary = false; bool BreakOnError = false; Int ErrorCount; +// String16 InitialScratchContent; +String16 InitialScratchContent = uR"==(0 +1 +2 +3 +4 +5 +6)=="; Allocator SysAllocator = {SystemAllocatorProc}; String ConfigDir; @@ -32,6 +40,7 @@ Array Buffers; View *LogView; Buffer *LogBuffer; +// Replace with ref to null buffer? BufferID NullBufferID; ViewID NullViewID; WindowID NullWindowID; diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 9792d9a..5b2f973 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -963,6 +963,9 @@ int main(int argc, char **argv) View *null_view = CreateView(null_buffer->id); null_view->special = true; Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID); + if (InitialScratchContent.len) { + RawAppend(null_buffer, InitialScratchContent); + } Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "logs", "")); logs_buffer->special = true; diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index 85cf322..74b217d 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -6,7 +6,10 @@ struct ViewID { Int id; View *o; }; struct WindowID { Int id; Window *o; }; union Range { struct { Int min; Int max; }; Int e[2]; }; struct Caret { union { Range range; Int pos[2]; }; Int ifront;}; -struct XY { Int col; Int line; }; +union XY { + struct {Int col; Int line;}; + struct {Int x; Int y; }; +}; typedef void Function(); struct FunctionData { @@ -314,7 +317,6 @@ constexpr int DIR_UP = 3; constexpr int DIR_COUNT = 4; constexpr bool CTRL_PRESSED = true; constexpr bool SHIFT_PRESS = true; -constexpr bool KILL_SELECTION = true; constexpr Int LAST_LINE = INT64_MAX; BSet GetBSet(struct Window *window); diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp index c25065e..21f00d0 100644 --- a/src/text_editor/view.cpp +++ b/src/text_editor/view.cpp @@ -172,6 +172,34 @@ String GetIndentString8(Allocator allocator, Int indent_size) { return res; } +Array GetSelectedLinesSortedExclusive(Allocator allocator, View *view) { + Scratch scratch(allocator); + Buffer *buffer = GetBuffer(view->active_buffer); + Array caret_copy = TightCopy(scratch, view->carets); + Array temp = TightCopy(scratch, view->carets); + if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data); + + Array result = {allocator}; + For(caret_copy) { + Int min_line = PosToLine(buffer, it.range.min); + Int max_line = PosToLine(buffer, it.range.max); + Range line_range = {min_line, max_line + 1}; + + if (result.len == 0) { + Add(&result, line_range); + continue; + } + + Range *last = GetLast(result); + if (AreOverlapping(*last, line_range)) { + last->max = Max(last->max, line_range.max); + } else { + Add(&result, line_range); + } + } + return result; +} + void IndentedNewLine(View *view) { Buffer *buffer = GetBuffer(view->active_buffer); Scratch scratch; @@ -185,7 +213,7 @@ void IndentedNewLine(View *view) { String16 string16 = ToString16(scratch, string); AddEdit(&edits, it.range, string16); } - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); } // WARNING: Don't use in user facing stuff @@ -399,52 +427,90 @@ void MoveCarets(View *view, int direction, bool ctrl = false, bool shift = false } void MoveCaretsLine(View *view, int direction) { + // This is so hard... + // It's because of the corner cases in the implementation, would be nice to simplify this somehow + // but don't know how. The multiple carets constraints + not every line ends with new line make it into + // really tricky code. Assert(direction == DIR_DOWN || direction == DIR_UP); Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); - // @todo: this doesn't work well at the end of buffer - struct XYPair { - XY front; - XY back; - }; - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(buffer, &view->carets); + // Save caret positions to fix them at end to the expected incremented by one positions + struct XYPair { XY front; XY back; }; Array saved_xy = {scratch}; - For(view->carets) { - Int eof_current = 0; - Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max, &eof_current)}; - if (lines_to_move_range.min == 0 && direction == DIR_UP) { - continue; - } - - Int eof = 0; - Int next_line_start = lines_to_move_range.max; - Int next_line_end = GetFullLineEnd(buffer, next_line_start, &eof); - Int prev_line_end = lines_to_move_range.min - 1; - Int prev_line_start = GetFullLineStart(buffer, prev_line_end); - - if (direction == DIR_DOWN && eof) { - continue; - } else if (direction == DIR_UP && eof_current) { - continue; - } - - String16 string = Copy16(scratch, GetString(buffer, lines_to_move_range)); - - AddEdit(&edits, lines_to_move_range, {}); - if (direction == DIR_DOWN) { - AddEdit(&edits, MakeRange(next_line_end), string); - } else { - AddEdit(&edits, MakeRange(prev_line_start), string); - } - + For (view->carets) { Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))}); } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + Int line_offset = direction == DIR_UP ? -1 : +1; + int side_idx = direction == DIR_UP ? 0 : 1; + + Array line_ranges = GetSelectedLinesSortedExclusive(scratch, view); // This is not positions + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(buffer, &view->carets); + ForItem (_lines, line_ranges) { + Range lines = {_lines.min, _lines.max - 1}; // inclusive + Int swap_line = lines.e[side_idx] + line_offset; + + Range total_range = {}; + { + Int total_min_line = 0; + Int total_max_line = 0; + if (direction == DIR_UP) { + total_min_line = swap_line; + total_max_line = lines.max; + if (total_min_line < 0) { + edits.len = 0; + saved_xy.len = 0; + break; + } + } else { + total_min_line = lines.min; + total_max_line = swap_line; + if (total_max_line >= buffer->line_starts.len) { + edits.len = 0; + saved_xy.len = 0; + break; + } + } + + Range arange = GetLineRange(buffer, total_min_line); + Range brange = GetLineRange(buffer, total_max_line); + total_range = {arange.min, brange.max}; + } + + String16 replacement_string = {}; + { + Range selected_range_min = GetLineRange(buffer, lines.min); + Range selected_range_max = GetLineRange(buffer, lines.max); + Range swap_line_range = GetLineRange(buffer, swap_line); + String16 selected_string = GetString(buffer, {selected_range_min.min, selected_range_max.max}); + String16 swap_string = GetString(buffer, swap_line_range); + if (direction == DIR_UP) { + bool ends_with_new_line = selected_string.len == 0 || (selected_string.len && selected_string[selected_string.len - 1] == u'\n'); + bool doesnt_end_with_new_line_special_case = !ends_with_new_line; + if (doesnt_end_with_new_line_special_case) { + if (swap_string.len) swap_string.len -= 1; + selected_string = Concat(scratch, selected_string, u"\n"); + } + replacement_string = Concat(scratch, selected_string, swap_string); + } else { + bool ends_with_new_line = swap_string.len == 0 || (swap_string.len && swap_string[swap_string.len - 1] == u'\n'); + bool doesnt_end_with_new_line_special_case = !ends_with_new_line; + if (doesnt_end_with_new_line_special_case) { + if (selected_string.len) selected_string.len -= 1; + swap_string = Concat(scratch, swap_string, u"\n"); + } + replacement_string = Concat(scratch, swap_string, selected_string); + } + } + + AddEdit(&edits, total_range, replacement_string); + } + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection, EndEdit_SkipFixingCaretsIWantToDoThatMyself); + for (Int i = 0; i < saved_xy.len; i += 1) { Caret &caret = view->carets[i]; XYPair &xypair = saved_xy[i]; @@ -452,9 +518,10 @@ void MoveCaretsLine(View *view, int direction) { xypair.back.line += line_offset; Int front = XYToPos(buffer, xypair.front); Int back = XYToPos(buffer, xypair.back); - caret = MakeCaret(front, back); } + MergeCarets(buffer, &view->carets); + IF_DEBUG(AssertRanges(view->carets)); } void CreateCursorVertical(View *view, int direction) { @@ -463,9 +530,9 @@ void CreateCursorVertical(View *view, int direction) { Int line_offset = direction == DIR_UP ? -1 : 1; - Scratch scratch; + Scratch scratch; Array arr = {scratch}; - For(view->carets) { + For (view->carets) { if (PosToLine(buffer, it.range.min) == PosToLine(buffer, it.range.max)) { Int f = OffsetByLine(buffer, GetFront(it), line_offset); Int b = OffsetByLine(buffer, GetBack(it), line_offset); @@ -514,7 +581,7 @@ void Delete(View *view, int direction, bool ctrl = false) { MergeCarets(buffer, &view->carets); For(view->carets) AddEdit(&edits, it.range, {}); - EndEdit(buffer, &edits, &view->carets, true); + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); } void EncloseSpace(View *view) { @@ -525,34 +592,6 @@ void EncloseSpace(View *view) { } } -Array GetSelectedLinesSorted(Allocator allocator, View *view) { - Scratch scratch(allocator); - Buffer *buffer = GetBuffer(view->active_buffer); - Array caret_copy = TightCopy(scratch, view->carets); - Array temp = TightCopy(scratch, view->carets); - if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data); - - Array result = {allocator}; - For(caret_copy) { - Int min_line = PosToLine(buffer, it.range.min); - Int max_line = PosToLine(buffer, it.range.max); - Range line_range = {min_line, max_line + 1}; - - if (result.len == 0) { - Add(&result, line_range); - continue; - } - - Range *last = GetLast(result); - if (AreOverlapping(*last, line_range)) { - last->max = Max(last->max, line_range.max); - } else { - Add(&result, line_range); - } - } - return result; -} - void IndentSelectedLines(View *view, bool shift = false) { Scratch scratch; Buffer *buffer = GetBuffer(view->active_buffer); @@ -562,7 +601,7 @@ void IndentSelectedLines(View *view, bool shift = false) { char16_t indent_char = GetIndentChar(); String16 indent_string = GetIndentString(scratch, IndentSize); - Array line_ranges_to_indent = GetSelectedLinesSorted(scratch, view); + Array line_ranges_to_indent = GetSelectedLinesSortedExclusive(scratch, view); For(line_ranges_to_indent) { for (Int i = it.min; i < it.max; i += 1) { Range pos_range_of_line = GetLineRange(buffer, i); @@ -580,7 +619,7 @@ void IndentSelectedLines(View *view, bool shift = false) { } } } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection); view->update_scroll = false; } @@ -617,9 +656,9 @@ Array ReplaceEx(Allocator scratch, View *view, String16 string) { For(view->carets) { AddEdit(&edits, it.range, string); } - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); return edits; -} + } void Replace(View *view, String16 string) { Scratch scratch; @@ -646,8 +685,9 @@ void DuplicateLine(View *view, int direction) { Int pos = direction == DIR_UP ? range.min : range.max; AddEdit(&edits, MakeRange(pos), string); } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection); + // Move carets now in the duplicate direction Int coef = direction == DIR_UP ? -1 : 1; for (Int i = 0; i < edits.len; i += 1) { Caret *caret = view->carets.data + i;