From 5d3f1fcf086e3421752b64c0c8d61c7ff7094357 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sun, 21 Jul 2024 17:38:02 +0200 Subject: [PATCH] Command refactor --- src/text_editor/commands.cpp | 1 + src/text_editor/text_editor.cpp | 4 +- src/text_editor/view.cpp | 551 ------------------------------ src/text_editor/view.h | 17 + src/text_editor/view_commands.cpp | 511 +++++++++++++++++++++++++++ 5 files changed, 532 insertions(+), 552 deletions(-) create mode 100644 src/text_editor/commands.cpp delete mode 100644 src/text_editor/view.cpp create mode 100644 src/text_editor/view.h create mode 100644 src/text_editor/view_commands.cpp diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/text_editor/commands.cpp @@ -0,0 +1 @@ + diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index cb010d6..edafd32 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -17,8 +17,10 @@ #include "raylib.h" #include "raylib_utils.cpp" + +#include "view.h" +#include "view_commands.cpp" #include "colors.cpp" -#include "view.cpp" #include "view_draw.cpp" /* diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp deleted file mode 100644 index 60a5d31..0000000 --- a/src/text_editor/view.cpp +++ /dev/null @@ -1,551 +0,0 @@ -struct View { - Font font; - Int font_size; - Int font_spacing; - Int line_spacing; - Int char_spacing; - - bool mouse_selecting; - - Vec2I scroll; - Buffer *buffer; - Array carets; - Rect2I rect; -}; - -Rect2I GetVisibleCells(const View &view); - -Int MoveOnWhitespaceBoundaryForward(Buffer &buffer, Int pos) { - pos = Clamp(pos, (Int)0, buffer.len); - bool standing_on_whitespace = IsWhitespace(buffer.str[pos]); - bool seek_whitespace = standing_on_whitespace == false; - bool seek_word = standing_on_whitespace; - - Int result = buffer.len; - Int prev_pos = pos; - for (Int i = pos; i < buffer.len; i += 1) { - bool whitespace = IsWhitespace(buffer.str[i]); - if (seek_word && !whitespace) { - result = i; - break; - } - if (seek_whitespace && whitespace) { - result = i; - break; - } - prev_pos = i; - } - return result; -} - -Int MoveOnWhitespaceBoundaryBackward(Buffer &buffer, Int pos) { - pos = Clamp(pos - 1, (Int)0, buffer.len); - bool standing_on_whitespace = IsWhitespace(buffer.str[pos]); - bool seek_whitespace = standing_on_whitespace == false; - bool seek_word = standing_on_whitespace; - - Int result = 0; - Int prev_pos = pos; - for (Int i = pos; i >= 0; i -= 1) { - bool whitespace = IsWhitespace(buffer.str[i]); - if (seek_word && !whitespace) { - result = prev_pos; - break; - } - if (seek_whitespace && whitespace) { - result = prev_pos; - break; - } - prev_pos = i; - } - return result; -} - -Int MoveOnWhitespaceBoundaryDown(Buffer &buffer, Int pos) { - Int result = pos; - Int next_line = PosToLine(buffer, pos) + 1; - for (Int line = next_line; line < buffer.line_starts.len; line += 1) { - Range line_range = GetLineRange(buffer, line); - result = line_range.min; - - bool whitespace_line = true; - for (Int i = line_range.min; i < line_range.max; i += 1) { - if (!IsWhitespace(buffer.str[i])) { - whitespace_line = false; - break; - } - } - if (whitespace_line) break; - } - return result; -} - -Int MoveOnWhitespaceBoundaryUp(Buffer &buffer, Int pos) { - Int result = pos; - Int next_line = PosToLine(buffer, pos) - 1; - for (Int line = next_line; line >= 0; line -= 1) { - Range line_range = GetLineRange(buffer, line); - result = line_range.min; - - bool whitespace_line = true; - for (Int i = line_range.min; i < line_range.max; i += 1) { - if (!IsWhitespace(buffer.str[i])) { - whitespace_line = false; - break; - } - } - if (whitespace_line) break; - } - return result; -} - -Int MovePosByXY(Buffer &buffer, Int pos, XY offset) { - XY xy = PosToXY(buffer, pos); - Int result = XYToPosWithoutNL(buffer, {xy.col + offset.col, xy.line + offset.line}); - return result; -} - -Int MoveCaret(Buffer &buffer, Int pos, int direction, bool ctrl_pressed) { - ProfileFunction(); - Assert(direction >= 0 && direction <= 3); - if (ctrl_pressed) { - switch (direction) { - case 0: return MoveOnWhitespaceBoundaryForward(buffer, pos); - case 1: return MoveOnWhitespaceBoundaryBackward(buffer, pos); - case 2: return MoveOnWhitespaceBoundaryDown(buffer, pos); - case 3: return MoveOnWhitespaceBoundaryUp(buffer, pos); - default: return pos; - } - } else { - switch (direction) { - case 0: return Clamp(pos + 1, (Int)0, buffer.len); - case 1: return Clamp(pos - 1, (Int)0, buffer.len); - case 2: return MovePosByXY(buffer, pos, {0, 1}); - case 3: return MovePosByXY(buffer, pos, {0, -1}); - default: return pos; - } - } -} - -void AfterEdit(View *view, Array edits) { - // - // Offset all cursors by edits - // - Scratch scratch; - Array 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 MultiCursorReplace(View *view, String16 string) { - Scratch scratch; - MergeCarets(&view->carets); - Array edits = {scratch}; - For(view->carets) AddEdit(&edits, it.range, string); - ApplyEdits(view->buffer, edits); - AfterEdit(view, edits); -} - -union KeyEncode { - struct { - U32 key; - U8 ctrl : 1; - U8 alt : 1; - U8 shift : 1; - }; - U64 u64; -}; -typedef void CommandProc(View *view); -Table RegisteredBindings; - -KeyEncode Key(int key) { - KeyEncode result = {}; - result.key = key; - return result; -} - -KeyEncode KeyControl(int key) { - KeyEncode result = {}; - result.key = key; - result.ctrl = true; - return result; -} - -KeyEncode KeyShiftControl(int key) { - KeyEncode result = {}; - result.key = key; - result.ctrl = true; - result.shift = true; - return result; -} - -KeyEncode KeyControlAlt(int key) { - KeyEncode result = {}; - result.key = key; - result.ctrl = true; - result.alt = true; - return result; -} - -KeyEncode KeyShiftAlt(int key) { - KeyEncode result = {}; - result.key = key; - result.shift = true; - result.alt = true; - return result; -} - -void RegisterBinding(CommandProc *proc, KeyEncode key) { - RegisteredBindings.insert(key.u64, proc); -} - -void CommandClearCarets(View *view) { - view->carets.len = 1; -} - -void HandleKeybindings(View *_view) { - ProfileFunction(); - View &view = *_view; - Buffer &buf = *view.buffer; - Caret main_caret_on_begin_frame = view.carets[0]; - - if (RegisteredBindings.cap == 0) { - RegisterBinding(CommandClearCarets, Key(KEY_ESCAPE)); - } - - { - KeyEncode key = {}; - key.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); - key.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); - key.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); - - int keys[] = { - KEY_APOSTROPHE, - KEY_COMMA, - KEY_MINUS, - KEY_PERIOD, - KEY_SLASH, - KEY_ZERO, - KEY_ONE, - KEY_TWO, - KEY_THREE, - KEY_FOUR, - KEY_FIVE, - KEY_SIX, - KEY_SEVEN, - KEY_EIGHT, - KEY_NINE, - KEY_SEMICOLON, - KEY_EQUAL, - KEY_A, - KEY_B, - KEY_C, - KEY_D, - KEY_E, - KEY_F, - KEY_G, - KEY_H, - KEY_I, - KEY_J, - KEY_K, - KEY_L, - KEY_M, - KEY_N, - KEY_O, - KEY_P, - KEY_Q, - KEY_R, - KEY_S, - KEY_T, - KEY_U, - KEY_V, - KEY_W, - KEY_X, - KEY_Y, - KEY_Z, - KEY_LEFT_BRACKET, - KEY_BACKSLASH, - KEY_RIGHT_BRACKET, - KEY_GRAVE, - KEY_SPACE, - KEY_ESCAPE, - KEY_ENTER, - KEY_TAB, - KEY_BACKSPACE, - KEY_INSERT, - KEY_DELETE, - KEY_RIGHT, - KEY_LEFT, - KEY_DOWN, - KEY_UP, - KEY_PAGE_UP, - KEY_PAGE_DOWN, - KEY_HOME, - KEY_END, - KEY_CAPS_LOCK, - KEY_SCROLL_LOCK, - KEY_NUM_LOCK, - KEY_PRINT_SCREEN, - KEY_PAUSE, - KEY_F1, - KEY_F2, - KEY_F3, - KEY_F4, - KEY_F5, - KEY_F6, - KEY_F7, - KEY_F8, - KEY_F9, - KEY_F10, - KEY_F11, - KEY_F12, - }; - - for (int i = 0; i < sizeof(keys); i += 1) { - bool press = IsKeyPressed(keys[i]) || IsKeyPressedRepeat(keys[i]); - if (!press) continue; - key.key = keys[i]; - - CommandProc *proc = RegisteredBindings.get(key.u64, NULL); - if (proc) proc(&view); - } - } - - if (IsKeyDown(KEY_F1)) { - view.scroll.x -= (Int)(GetMouseWheelMove() * 48); - } else { - view.scroll.y -= (Int)(GetMouseWheelMove() * 48); - } - - if (IsKeyDown(KEY_F2)) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - LoadBigLine(view.buffer); - } else { - LoadBigText(view.buffer); - } - } - - if (IsKeyPressed(KEY_ESCAPE) || IsKeyPressedRepeat(KEY_ESCAPE)) { - view.carets.len = 1; - } - - if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT) && (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN))) { - For(view.carets) it = MakeCaret(GetFront(it)); - MergeCarets(&view.carets); - - Scratch scratch; - Array edits = {scratch}; - For(view.carets) { - Int line = PosToLine(buf, it.range.min); - Range range = GetLineRange(buf, line); - String16 string = Copy(scratch, GetString(buf, range)); - AddEdit(&edits, {range.max, range.max}, string); - } - ApplyEdits(&buf, edits); - AfterEdit(&view, edits); - - For(view.carets) it = MakeCaret(MoveCaret(buf, it.range.min, 2, false)); - } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT) && (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP))) { - For(view.carets) it = MakeCaret(GetFront(it)); - MergeCarets(&view.carets); - - Scratch scratch; - Array edits = {scratch}; - For(view.carets) { - Int line = PosToLine(buf, it.range.min); - Range range = GetLineRange(buf, line); - String16 string = Copy(scratch, GetString(buf, range)); - AddEdit(&edits, {range.min, range.min}, string); - } - ApplyEdits(&buf, edits); - AfterEdit(&view, edits); - - For(view.carets) it = MakeCaret(MoveCaret(buf, it.range.min, 3, false)); - } else { - int keys[4] = {KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP}; - for (int i = 0; i < 4; i += 1) { - if (IsKeyPressed(keys[i]) || IsKeyPressedRepeat(keys[i])) { - For(view.carets) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - Int front = GetFront(it); - Int new_front = MoveCaret(buf, front, i, IsKeyDown(KEY_LEFT_CONTROL)); - it = ChangeFront(it, new_front); - } else { - Int p = keys[i] == KEY_RIGHT || keys[i] == KEY_DOWN ? it.range.max : it.range.min; - if (GetSize(it.range) == 0) { - it = MakeCaret(MoveCaret(buf, p, i, IsKeyDown(KEY_LEFT_CONTROL))); - } else { - it = MakeCaret(p); - } - } - } - } - } - } - - int key_page_down = IsKeyPressed(KEY_PAGE_DOWN) || IsKeyPressedRepeat(KEY_PAGE_DOWN); - int key_page_up = IsKeyPressed(KEY_PAGE_UP) || IsKeyPressedRepeat(KEY_PAGE_UP); - if (key_page_up || key_page_down) { - Rect2I visible_cells_rect = GetVisibleCells(view); - Int y = GetSize(visible_cells_rect).y - 2; - if (key_page_up) y = -y; - For(view.carets) { - XY xy = PosToXY(buf, GetFront(it)); - xy.line += y; - Int pos = XYToPos(buf, xy); - if (IsKeyDown(KEY_LEFT_SHIFT)) { - it = ChangeFront(it, pos); - } else { - it = MakeCaret(pos); - } - } - } - - { - int keys[2] = {KEY_HOME, KEY_END}; - for (Int i = 0; i < 2; i += 1) { - if (IsKeyPressed(keys[i]) || IsKeyPressedRepeat(keys[i])) { - For(view.carets) { - Int end_of_buffer = 0; - Range line_range = GetLineRange(buf, PosToLine(buf, GetFront(it)), &end_of_buffer); - - Int diff = keys[i] == KEY_END ? i - end_of_buffer : 0; - if (IsKeyDown(KEY_LEFT_SHIFT)) { - it = ChangeFront(it, line_range.e[i] - diff); - } else { - it.range.max = it.range.min = line_range.e[i] - diff; - } - } - } - } - } - - if (IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER)) { - MultiCursorReplace(&view, L"\n"); - } - - { - int keys[] = {KEY_DELETE, KEY_BACKSPACE}; - for (int i = 0; i < 2; i += 1) { - if (IsKeyPressed(keys[i]) || IsKeyPressedRepeat(keys[i])) { - // Select things to delete - For(view.carets) { - if (GetSize(it.range)) continue; - Int pos = MoveCaret(buf, it.range.min, i, IsKeyDown(KEY_LEFT_CONTROL)); - it = MakeCaret(pos, it.range.min); - } - - MultiCursorReplace(&view, {}); - } - } - } - - for (int c = GetCharPressed(); c; c = GetCharPressed()) { - // we interpret 2 byte sequences as 1 byte when rendering but we still - // want to read them properly. - String16 string = L"?"; - UTF16Result result = UTF32ToUTF16((uint32_t)c); - if (!result.error) string = {(wchar_t *)result.out_str, result.len}; - MultiCursorReplace(&view, string); - } - - { - ProfileScope(mouse); - - Vec2 _mouse = GetMousePosition(); - bool mouse_in_view = CheckCollisionPointRec(_mouse, ToRectangle(view.rect)); - Vec2I mouse = ToVec2I(_mouse); - - if (!view.mouse_selecting) { - if (mouse_in_view) { - SetMouseCursor(MOUSE_CURSOR_IBEAM); - } else { - SetMouseCursor(MOUSE_CURSOR_DEFAULT); - } - } - - Vec2I mworld = mouse - view.rect.min + view.scroll; - Vec2I pos = mworld / Vec2I{view.char_spacing, view.line_spacing}; - XY xy = {(Int)(pos.x), (Int)(pos.y)}; - Int p = XYToPosWithoutNL(buf, xy); - - if (mouse_in_view && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - if (!IsKeyDown(KEY_LEFT_CONTROL)) { - view.carets.len = 0; - } - Add(&view.carets, MakeCaret(p, p)); - } else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - view.mouse_selecting = true; - } - } - - if (view.mouse_selecting) { - if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT)) view.mouse_selecting = false; - Caret &caret = *GetLast(view.carets); - caret = ChangeFront(caret, p); - MergeCarets(&view.carets); - } - } - - // Scrolling with caret - if (!AreEqual(main_caret_on_begin_frame, view.carets[0])) { - Caret c = view.carets[0]; - Int front = GetFront(c); - XY xy = PosToXY(buf, front); - - Rect2I visible = GetVisibleCells(view); - Vec2I visible_cells = GetSize(visible); - Vec2I visible_size = visible_cells * Vec2I{view.char_spacing, view.line_spacing}; - Vec2I rect_size = GetSize(view.rect); - - if (xy.line > visible.max.y - 2) { - Int set_view_at_line = xy.line - (visible_cells.y - 1); - Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y); - view.scroll.y = (set_view_at_line * view.line_spacing) + cut_off_y; - } - - if (xy.line < visible.min.y + 1) { - view.scroll.y = xy.line * view.line_spacing; - } - - if (xy.col >= visible.max.x - 1) { - Int set_view_at_line = xy.col - (visible_cells.x - 1); - Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x); - view.scroll.x = (set_view_at_line * view.char_spacing) + cut_off_x; - } - - if (xy.col <= visible.min.x) { - view.scroll.x = xy.col * view.char_spacing; - } - } - - // Clip scroll - { - ProfileScope(clip_scroll); - Int last_line = LastLine(view.buffer[0]); - view.scroll.y = Clamp(view.scroll.y, (Int)0, Max((Int)0, (last_line - 1) * view.line_spacing)); - - // @note: - // GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for - // calculating this value incrementally but do we even need X scrollbar or x clipping? - view.scroll.x = ClampBottom(view.scroll.x, (Int)0); - } -} \ No newline at end of file diff --git a/src/text_editor/view.h b/src/text_editor/view.h new file mode 100644 index 0000000..5c48eae --- /dev/null +++ b/src/text_editor/view.h @@ -0,0 +1,17 @@ +struct View { + Font font; + Int font_size; + Int font_spacing; + Int line_spacing; + Int char_spacing; + + bool mouse_selecting; + + Vec2I scroll; + Buffer *buffer; + Array carets; + Rect2I rect; +}; + +Rect2I GetVisibleCells(const View &view); +void AfterEdit(View *view, Array edits); \ No newline at end of file diff --git a/src/text_editor/view_commands.cpp b/src/text_editor/view_commands.cpp new file mode 100644 index 0000000..a6f5976 --- /dev/null +++ b/src/text_editor/view_commands.cpp @@ -0,0 +1,511 @@ +Int MoveOnWhitespaceBoundaryForward(Buffer &buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer.len); + bool standing_on_whitespace = IsWhitespace(buffer.str[pos]); + bool seek_whitespace = standing_on_whitespace == false; + bool seek_word = standing_on_whitespace; + + Int result = buffer.len; + Int prev_pos = pos; + for (Int i = pos; i < buffer.len; i += 1) { + bool whitespace = IsWhitespace(buffer.str[i]); + if (seek_word && !whitespace) { + result = i; + break; + } + if (seek_whitespace && whitespace) { + result = i; + break; + } + prev_pos = i; + } + return result; +} + +Int MoveOnWhitespaceBoundaryBackward(Buffer &buffer, Int pos) { + pos = Clamp(pos - 1, (Int)0, buffer.len); + bool standing_on_whitespace = IsWhitespace(buffer.str[pos]); + bool seek_whitespace = standing_on_whitespace == false; + bool seek_word = standing_on_whitespace; + + Int result = 0; + Int prev_pos = pos; + for (Int i = pos; i >= 0; i -= 1) { + bool whitespace = IsWhitespace(buffer.str[i]); + if (seek_word && !whitespace) { + result = prev_pos; + break; + } + if (seek_whitespace && whitespace) { + result = prev_pos; + break; + } + prev_pos = i; + } + return result; +} + +Int MoveOnWhitespaceBoundaryDown(Buffer &buffer, Int pos) { + Int result = pos; + Int next_line = PosToLine(buffer, pos) + 1; + for (Int line = next_line; line < buffer.line_starts.len; line += 1) { + Range line_range = GetLineRange(buffer, line); + result = line_range.min; + + bool whitespace_line = true; + for (Int i = line_range.min; i < line_range.max; i += 1) { + if (!IsWhitespace(buffer.str[i])) { + whitespace_line = false; + break; + } + } + if (whitespace_line) break; + } + return result; +} + +Int MoveOnWhitespaceBoundaryUp(Buffer &buffer, Int pos) { + Int result = pos; + Int next_line = PosToLine(buffer, pos) - 1; + for (Int line = next_line; line >= 0; line -= 1) { + Range line_range = GetLineRange(buffer, line); + result = line_range.min; + + bool whitespace_line = true; + for (Int i = line_range.min; i < line_range.max; i += 1) { + if (!IsWhitespace(buffer.str[i])) { + whitespace_line = false; + break; + } + } + if (whitespace_line) break; + } + return result; +} + +Int MovePosByXY(Buffer &buffer, Int pos, XY offset) { + XY xy = PosToXY(buffer, pos); + Int result = XYToPosWithoutNL(buffer, {xy.col + offset.col, xy.line + offset.line}); + return result; +} + +const int DIR_RIGHT = 0; +const int DIR_LEFT = 1; +const int DIR_DOWN = 2; +const int DIR_UP = 3; + +Int MovePos(Buffer &buffer, Int pos, int direction, bool ctrl_pressed) { + ProfileFunction(); + Assert(direction >= 0 && direction <= 3); + if (ctrl_pressed) { + switch (direction) { + case DIR_RIGHT: return MoveOnWhitespaceBoundaryForward(buffer, pos); + case DIR_LEFT: return MoveOnWhitespaceBoundaryBackward(buffer, pos); + case DIR_DOWN: return MoveOnWhitespaceBoundaryDown(buffer, pos); + case DIR_UP: return MoveOnWhitespaceBoundaryUp(buffer, pos); + default: return pos; + } + } else { + switch (direction) { + case DIR_RIGHT: return Clamp(pos + 1, (Int)0, buffer.len); + case DIR_LEFT: return Clamp(pos - 1, (Int)0, buffer.len); + case DIR_DOWN: return MovePosByXY(buffer, pos, {0, 1}); + case DIR_UP: return MovePosByXY(buffer, pos, {0, -1}); + default: return pos; + } + } +} + +void AfterEdit(View *view, Array edits) { + // + // Offset all cursors by edits + // + Scratch scratch; + Array 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; + MergeCarets(&view->carets); + Array edits = {scratch}; + For(view->carets) AddEdit(&edits, it.range, string); + ApplyEdits(view->buffer, edits); + AfterEdit(view, edits); +} + +void Command_DuplicateLine(View *view, int direction) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + For(view->carets) it = MakeCaret(GetFront(it)); + MergeCarets(&view->carets); + + Scratch scratch; + Array edits = {scratch}; + For(view->carets) { + Int line = PosToLine(*view->buffer, it.range.min); + Range range = GetLineRange(*view->buffer, line); + String16 string = Copy(scratch, GetString(*view->buffer, range)); + Int pos = direction == DIR_UP ? range.min : range.max; + AddEdit(&edits, Rng(pos), string); + } + ApplyEdits(view->buffer, edits); + AfterEdit(view, edits); + + For(view->carets) it = MakeCaret(MovePos(*view->buffer, it.range.min, direction, false)); +} + +bool SHIFT_PRESSED = true; +void Command_MoveCursorsByPageSize(View *_view, int direction, bool shift = false) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + View &view = *_view; + Buffer &buf = *view.buffer; + + Rect2I visible_cells_rect = GetVisibleCells(view); + Int y = GetSize(visible_cells_rect).y - 2; + if (direction == DIR_UP) y = -y; + + For(view.carets) { + XY xy = PosToXY(buf, GetFront(it)); + xy.line += y; + Int pos = XYToPos(buf, xy); + if (shift) { + it = ChangeFront(it, pos); + } else { + it = MakeCaret(pos); + } + } +} + +void Command_MoveCursorsToSide(View *_view, int direction, bool shift = false) { + Assert(direction == DIR_LEFT || direction == DIR_RIGHT); + View &view = *_view; + Buffer &buf = *view.buffer; + + For(view.carets) { + Int end_of_buffer = 0; + Range line_range = GetLineRange(buf, PosToLine(buf, GetFront(it)), &end_of_buffer); + + Int pos = line_range.min; + if (direction == DIR_RIGHT) { + pos = line_range.max - (1 - end_of_buffer); + } + + if (shift) { + it = ChangeFront(it, pos); + } else { + it.range.max = it.range.min = pos; + } + } +} + +bool CTRL_PRESSED = true; +void Command_Delete(View *_view, int direction, bool ctrl = false) { + Assert(direction == DIR_LEFT || direction == DIR_RIGHT); + View &view = *_view; + Buffer &buf = *view.buffer; + + // Select things to delete + For(view.carets) { + if (GetSize(it.range)) continue; + Int pos = MovePos(buf, it.range.min, direction, ctrl); + it = MakeCaret(pos, it.range.min); + } + + Command_Replace(&view, {}); +} + +inline bool Press(int key) { + bool result = IsKeyPressed(key) || IsKeyPressedRepeat(key); + return result; +} + +inline bool Shift() { + bool result = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); + return result; +} + +inline bool Ctrl() { + bool result = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); + return result; +} + +inline bool Alt() { + bool result = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); + return result; +} + +inline bool CtrlPress(int key) { + bool result = Press(key) && Ctrl(); + return result; +} + +inline bool ShiftPress(int key) { + bool result = Press(key) && Shift(); + return result; +} + +inline bool CtrlShiftPress(int key) { + bool result = Press(key) && Shift() && Ctrl(); + return result; +} + +inline bool CtrlAltPress(int key) { + bool result = Press(key) && Ctrl() && Alt(); + return result; +} + +void HandleKeybindings(View *_view) { + ProfileFunction(); + View &view = *_view; + Buffer &buf = *view.buffer; + Caret main_caret_on_begin_frame = view.carets[0]; + + if (IsKeyDown(KEY_F1)) { + view.scroll.x -= (Int)(GetMouseWheelMove() * 48); + } else { + view.scroll.y -= (Int)(GetMouseWheelMove() * 48); + } + + if (CtrlPress(KEY_F2)) { + LoadBigLine(view.buffer); + } else if (Press(KEY_F2)) { + LoadBigText(view.buffer); + } + + if (Press(KEY_ESCAPE)) { + view.carets.len = 1; + } + + if (CtrlAltPress(KEY_DOWN)) { + Command_DuplicateLine(&view, DIR_DOWN); + } else if (CtrlShiftPress(KEY_DOWN)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_DOWN, true)); + } else if (CtrlPress(KEY_DOWN)) { + For(view.carets) it = MakeCaret(MovePos(buf, it.range.max, DIR_DOWN, true)); + } else if (ShiftPress(KEY_DOWN)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_DOWN, false)); + } else if (Press(KEY_DOWN)) { + For(view.carets) { + if (GetSize(it.range) == 0) { + it = MakeCaret(MovePos(buf, it.range.max, DIR_DOWN, false)); + } else { + it = MakeCaret(it.range.max); + } + } + } + + if (CtrlAltPress(KEY_UP)) { + Command_DuplicateLine(&view, DIR_UP); + } else if (CtrlShiftPress(KEY_UP)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_UP, true)); + } else if (CtrlPress(KEY_UP)) { + For(view.carets) it = MakeCaret(MovePos(buf, it.range.min, DIR_UP, true)); + } else if (ShiftPress(KEY_UP)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_UP, false)); + } else if (Press(KEY_UP)) { + For(view.carets) { + if (GetSize(it.range) == 0) { + it = MakeCaret(MovePos(buf, it.range.min, DIR_UP, false)); + } else { + it = MakeCaret(it.range.min); + } + } + } + + if (CtrlShiftPress(KEY_LEFT)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_LEFT, true)); + } else if (CtrlPress(KEY_LEFT)) { + For(view.carets) { + if (GetSize(it.range) != 0 && GetFront(it) != it.range.min) { + it = MakeCaret(it.range.min); + } else { + it = MakeCaret(MovePos(buf, it.range.min, DIR_LEFT, true)); + } + } + } else if (ShiftPress(KEY_LEFT)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_LEFT, false)); + } else if (Press(KEY_LEFT)) { + For(view.carets) { + if (GetSize(it.range) == 0) { + it = MakeCaret(MovePos(buf, it.range.min, DIR_LEFT, false)); + } else { + it = MakeCaret(it.range.min); + } + } + } + + if (CtrlShiftPress(KEY_RIGHT)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_RIGHT, true)); + } else if (CtrlPress(KEY_RIGHT)) { + For(view.carets) { + if (GetSize(it.range) != 0 && GetFront(it) != it.range.max) { + it = MakeCaret(it.range.max); + } else { + it = MakeCaret(MovePos(buf, it.range.max, DIR_RIGHT, true)); + } + } + } else if (ShiftPress(KEY_RIGHT)) { + For(view.carets) it = ChangeFront(it, MovePos(buf, GetFront(it), DIR_RIGHT, false)); + } else if (Press(KEY_RIGHT)) { + For(view.carets) { + if (GetSize(it.range) == 0) { + it = MakeCaret(MovePos(buf, it.range.max, DIR_RIGHT, false)); + } else { + it = MakeCaret(it.range.max); + } + } + } + + if (ShiftPress(KEY_PAGE_UP)) { + Command_MoveCursorsByPageSize(&view, DIR_UP, SHIFT_PRESSED); + } else if (Press(KEY_PAGE_UP)) { + Command_MoveCursorsByPageSize(&view, DIR_UP); + } + + if (ShiftPress(KEY_PAGE_DOWN)) { + Command_MoveCursorsByPageSize(&view, DIR_DOWN, SHIFT_PRESSED); + } else if (Press(KEY_PAGE_DOWN)) { + Command_MoveCursorsByPageSize(&view, DIR_DOWN); + } + + if (ShiftPress(KEY_HOME)) { + Command_MoveCursorsToSide(&view, DIR_LEFT, SHIFT_PRESSED); + } else if (Press(KEY_HOME)) { + Command_MoveCursorsToSide(&view, DIR_LEFT); + } + + if (ShiftPress(KEY_END)) { + Command_MoveCursorsToSide(&view, DIR_RIGHT, SHIFT_PRESSED); + } else if (Press(KEY_END)) { + Command_MoveCursorsToSide(&view, DIR_RIGHT); + } + + if (Press(KEY_ENTER)) { + Command_Replace(&view, L"\n"); + } + + if (Press(KEY_TAB)) { + Command_Replace(&view, L" "); + } + + if (CtrlPress(KEY_BACKSPACE)) { + Command_Delete(&view, DIR_LEFT, CTRL_PRESSED); + } else if (Press(KEY_BACKSPACE)) { + Command_Delete(&view, DIR_LEFT); + } + + if (CtrlPress(KEY_DELETE)) { + Command_Delete(&view, DIR_RIGHT, CTRL_PRESSED); + } else if (Press(KEY_DELETE)) { + Command_Delete(&view, DIR_RIGHT); + } + + for (int c = GetCharPressed(); c; c = GetCharPressed()) { + // we interpret 2 byte sequences as 1 byte when rendering but we still + // want to read them properly. + String16 string = L"?"; + UTF16Result result = UTF32ToUTF16((uint32_t)c); + if (!result.error) string = {(wchar_t *)result.out_str, result.len}; + Command_Replace(&view, string); + } + + { + ProfileScope(mouse); + + Vec2 _mouse = GetMousePosition(); + bool mouse_in_view = CheckCollisionPointRec(_mouse, ToRectangle(view.rect)); + Vec2I mouse = ToVec2I(_mouse); + + if (!view.mouse_selecting) { + if (mouse_in_view) { + SetMouseCursor(MOUSE_CURSOR_IBEAM); + } else { + SetMouseCursor(MOUSE_CURSOR_DEFAULT); + } + } + + Vec2I mworld = mouse - view.rect.min + view.scroll; + Vec2I pos = mworld / Vec2I{view.char_spacing, view.line_spacing}; + XY xy = {(Int)(pos.x), (Int)(pos.y)}; + Int p = XYToPosWithoutNL(buf, xy); + + if (mouse_in_view && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + if (!IsKeyDown(KEY_LEFT_CONTROL)) { + view.carets.len = 0; + } + Add(&view.carets, MakeCaret(p, p)); + } else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + view.mouse_selecting = true; + } + } + + if (view.mouse_selecting) { + if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT)) view.mouse_selecting = false; + Caret &caret = *GetLast(view.carets); + caret = ChangeFront(caret, p); + MergeCarets(&view.carets); + } + } + + // Scrolling with caret + if (!AreEqual(main_caret_on_begin_frame, view.carets[0])) { + Caret c = view.carets[0]; + Int front = GetFront(c); + XY xy = PosToXY(buf, front); + + Rect2I visible = GetVisibleCells(view); + Vec2I visible_cells = GetSize(visible); + Vec2I visible_size = visible_cells * Vec2I{view.char_spacing, view.line_spacing}; + Vec2I rect_size = GetSize(view.rect); + + if (xy.line > visible.max.y - 2) { + Int set_view_at_line = xy.line - (visible_cells.y - 1); + Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y); + view.scroll.y = (set_view_at_line * view.line_spacing) + cut_off_y; + } + + if (xy.line < visible.min.y + 1) { + view.scroll.y = xy.line * view.line_spacing; + } + + if (xy.col >= visible.max.x - 1) { + Int set_view_at_line = xy.col - (visible_cells.x - 1); + Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x); + view.scroll.x = (set_view_at_line * view.char_spacing) + cut_off_x; + } + + if (xy.col <= visible.min.x) { + view.scroll.x = xy.col * view.char_spacing; + } + } + + // Clip scroll + { + ProfileScope(clip_scroll); + Int last_line = LastLine(view.buffer[0]); + view.scroll.y = Clamp(view.scroll.y, (Int)0, Max((Int)0, (last_line - 1) * view.line_spacing)); + + // @note: + // GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for + // calculating this value incrementally but do we even need X scrollbar or x clipping? + view.scroll.x = ClampBottom(view.scroll.x, (Int)0); + } +} \ No newline at end of file