From 7655ece178af5dfe59e192a08923db52825cfa78 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sat, 3 Aug 2024 12:58:53 +0200 Subject: [PATCH] Implement a lot of position moving functions --- src/basic/string16.cpp | 10 ++ src/text_editor/buffer.cpp | 99 ++------------- src/text_editor/buffer_helpers.cpp | 179 ++++++++++++++++++++++++++++ src/text_editor/buffer_history.cpp | 24 ++-- src/text_editor/commands_window.cpp | 8 +- src/text_editor/text_editor.h | 2 +- src/text_editor/todo.txt | 4 +- 7 files changed, 228 insertions(+), 98 deletions(-) diff --git a/src/basic/string16.cpp b/src/basic/string16.cpp index 72ea6bd..879446c 100644 --- a/src/basic/string16.cpp +++ b/src/basic/string16.cpp @@ -18,6 +18,16 @@ bool IsSymbol(wchar_t w) { return result; } +bool IsNonWord(wchar_t w) { + bool result = IsSymbol(w) || IsWhitespace(w); + return result; +} + +bool IsWord(wchar_t w) { + bool result = IsSymbol(w) || IsWhitespace(w); + return !result; +} + bool IsAlphabetic(wchar_t a) { bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); return result; diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 6ef7f92..41fdef9 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -27,91 +27,6 @@ void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) { } } -Int LastLine(Buffer &buffer) { - Int result = buffer.line_starts.len - 1; - return result; -} - -const Int LAST_LINE = INT64_MAX; -Range GetLineRange(Buffer &buffer, Int line, Int *end_of_buffer = NULL) { - Range result = {buffer.line_starts[line], buffer.len}; - if (line + 1 < buffer.line_starts.len) { - result.max = buffer.line_starts[line + 1]; - } else if (end_of_buffer) { - *end_of_buffer = 1; - } - return result; -} - -String16 GetLineString(Buffer &buffer, Int line, Int *end_of_buffer = NULL) { - Range range = GetLineRange(buffer, line, end_of_buffer); - String16 string = GetString(buffer, range); - return string; -} - -Range GetLineRangeWithoutNL(Buffer &buffer, Int line) { - Int end_of_buffer = 0; - Range line_range = GetLineRange(buffer, line, &end_of_buffer); - line_range.max = line_range.max - 1 + end_of_buffer; - return line_range; -} - -String16 GetLineStringWithoutNL(Buffer &buffer, Int line) { - Range range = GetLineRangeWithoutNL(buffer, line); - String16 string = GetString(buffer, range); - return string; -} - -Int PosToLine(Buffer &buffer, Int pos) { - Add(&buffer.line_starts, buffer.len + 1); - - // binary search - Int low = 0; - Int high = buffer.line_starts.len - 2; - Int result = 0; - - while (low <= high) { - Int mid = low + (high - low) / 2; - Range range = {buffer.line_starts[mid], buffer.line_starts[mid + 1]}; - if (pos >= range.min && pos < range.max) { - result = mid; - break; - } - - if (range.min < pos) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - Pop(&buffer.line_starts); - return result; -} - -XY PosToXY(Buffer &buffer, Int pos) { - Int line = PosToLine(buffer, pos); - Range line_range = GetLineRange(buffer, line); - Int col = pos - line_range.min; - XY result = {col, line}; - return result; -} - -Int XYToPos(Buffer &buffer, XY xy) { - xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1); - Range line_range = GetLineRange(buffer, xy.line); - Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max); - return pos; -} - -Int XYToPosWithoutNL(Buffer &buffer, XY xy) { - xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1); - Int end_of_buffer = 0; - Range line_range = GetLineRange(buffer, xy.line, &end_of_buffer); - Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max - 1 + end_of_buffer); - return pos; -} - void UpdateLines(Buffer *buffer, Range range, String16 string) { ProfileFunction(); Array &ls = buffer->line_starts; @@ -204,4 +119,16 @@ void Appendf(Buffer *buffer, const char *fmt, ...) { STRING_FORMAT(scratch, fmt, string); String16 string16 = ToString16(scratch, string); ReplaceText(buffer, GetEndAsRange(*buffer), string16); -} \ No newline at end of file +} + +String16 GetLineString(Buffer &buffer, Int line, Int *end_of_buffer = NULL) { + Range range = GetLineRange(buffer, line, end_of_buffer); + String16 string = GetString(buffer, range); + return string; +} + +String16 GetLineStringWithoutNL(Buffer &buffer, Int line) { + Range range = GetLineRangeWithoutNL(buffer, line); + String16 string = GetString(buffer, range); + return string; +} diff --git a/src/text_editor/buffer_helpers.cpp b/src/text_editor/buffer_helpers.cpp index 3da85d0..e200a41 100644 --- a/src/text_editor/buffer_helpers.cpp +++ b/src/text_editor/buffer_helpers.cpp @@ -142,4 +142,183 @@ Range operator-(Range a, Int value) { Range operator-=(Range &range, Int value) { range = range - value; return range; +} + +Int LastLine(Buffer &buffer) { + Int result = buffer.line_starts.len - 1; + return result; +} + +const Int LAST_LINE = INT64_MAX; +Range GetLineRange(Buffer &buffer, Int line, Int *end_of_buffer = NULL) { + Range result = {buffer.line_starts[line], buffer.len}; + if (line + 1 < buffer.line_starts.len) { + result.max = buffer.line_starts[line + 1]; + } else if (end_of_buffer) { + *end_of_buffer = 1; + } + return result; +} + +Range GetLineRangeWithoutNL(Buffer &buffer, Int line) { + Int end_of_buffer = 0; + Range line_range = GetLineRange(buffer, line, &end_of_buffer); + line_range.max = line_range.max - 1 + end_of_buffer; + return line_range; +} + +Int PosToLine(Buffer &buffer, Int pos) { + Add(&buffer.line_starts, buffer.len + 1); + + // binary search + Int low = 0; + Int high = buffer.line_starts.len - 2; + Int result = 0; + + while (low <= high) { + Int mid = low + (high - low) / 2; + Range range = {buffer.line_starts[mid], buffer.line_starts[mid + 1]}; + if (pos >= range.min && pos < range.max) { + result = mid; + break; + } + + if (range.min < pos) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + Pop(&buffer.line_starts); + return result; +} + +XY PosToXY(Buffer &buffer, Int pos) { + Int line = PosToLine(buffer, pos); + Range line_range = GetLineRange(buffer, line); + Int col = pos - line_range.min; + XY result = {col, line}; + return result; +} + +Int XYToPos(Buffer &buffer, XY xy) { + xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1); + Range line_range = GetLineRange(buffer, xy.line); + Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max); + return pos; +} + +Int XYToPosWithoutNL(Buffer &buffer, XY xy) { + xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1); + Int end_of_buffer = 0; + Range line_range = GetLineRange(buffer, xy.line, &end_of_buffer); + Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max - 1 + end_of_buffer); + return pos; +} + +Int GetWordStart(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + for (Int i = pos - 1; i >= 0; i -= 1) { + if (IsNonWord(buffer->str[i])) break; + pos = i; + } + return pos; +} + +Int GetWordEnd(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + for (Int i = pos; i < buffer->len; i += 1) { + pos = i; + if (IsNonWord(buffer->str[i])) break; + } + return pos; +} + +Int GetNextWordEnd(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + wchar_t prev = 0; + for (Int i = pos; i < buffer->len; i += 1) { + pos = i; + if ((prev && prev != buffer->str[i]) || IsWord(buffer->str[i])) { + break; + } + prev = buffer->str[i]; + } + Int result = GetWordEnd(buffer, pos); + return result; +} + +Int GetPrevWordStart(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + wchar_t prev = 0; + for (Int i = pos - 1; i >= 0; i -= 1) { + if ((prev && prev != buffer->str[i]) || IsWord(buffer->str[i])) { + break; + } + pos = i; + prev = buffer->str[i]; + } + Int result = GetWordStart(buffer, pos); + return result; +} + +Int GetLineStart(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + Int line = PosToLine(*buffer, pos); + Range range = GetLineRangeWithoutNL(*buffer, line); + return range.min; +} + +Int GetLineEnd(Buffer *buffer, Int pos) { + pos = Clamp(pos, (Int)0, buffer->len); + Int line = PosToLine(*buffer, pos); + Range range = GetLineRangeWithoutNL(*buffer, line); + return range.max; +} + +Int GetBufferEnd(Buffer *buffer) { + return buffer->len; +} + +Int GetBufferStart(Buffer *buffer) { + return 0; +} + +Int GetNextEmptyLineStart(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 = GetLineRangeWithoutNL(*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 GetPrevEmptyLineStart(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 = GetLineRangeWithoutNL(*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; } \ No newline at end of file diff --git a/src/text_editor/buffer_history.cpp b/src/text_editor/buffer_history.cpp index 2e92c0e..e1273dc 100644 --- a/src/text_editor/buffer_history.cpp +++ b/src/text_editor/buffer_history.cpp @@ -79,8 +79,8 @@ void UndoEdit(Buffer *buffer, Array *carets) { void ApplyEdits(Buffer *buffer, Array &edits) { ProfileFunction(); - Assert(buffer->debug_edit_phase == 1); - buffer->debug_edit_phase += 1; + Assert(buffer->edit_phase == 1); + buffer->edit_phase += 1; SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, edits); _ApplyEdits(buffer, edits); } @@ -94,19 +94,25 @@ void ClearRedoStack(Buffer *buffer) { buffer->redo_stack.len = 0; } +// @note: !! +// We can invoke this before caret altering commands to save caret history +// and then call some editing command to edit which is not going to save carets +// @todo: this needs to be actually tested though!!! void BeforeEdit(Buffer *buffer, Array &carets) { - Assert(buffer->debug_edit_phase == 0); - buffer->debug_edit_phase += 1; - Assert(carets.len); - SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, carets); - ClearRedoStack(buffer); + Assert(buffer->edit_phase == 0 || buffer->edit_phase == 1); + if (buffer->edit_phase == 0) { + buffer->edit_phase += 1; + Assert(carets.len); + SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, carets); + ClearRedoStack(buffer); + } } bool KILL_SELECTION = true; void AfterEdit(Buffer *buffer, Array *edits, Array *carets, bool kill_selection = true) { ProfileFunction(); - Assert(buffer->debug_edit_phase == 2); - buffer->debug_edit_phase -= 2; + Assert(buffer->edit_phase == 2); + buffer->edit_phase -= 2; #if BUFFER_DEBUG if (buffer->no_history == false) { diff --git a/src/text_editor/commands_window.cpp b/src/text_editor/commands_window.cpp index 0b48a44..96c2b51 100644 --- a/src/text_editor/commands_window.cpp +++ b/src/text_editor/commands_window.cpp @@ -513,7 +513,13 @@ void WindowCommand(Event event, Window *window, View *view) { } if (Ctrl(SDLK_W)) { - Command_TrimTrailingWhitespace(view, false); + Int pos = view->carets[0].range.min; + view->carets[0] = MakeCaret(GetPrevWordStart(buffer, pos)); + } + + if (Ctrl(SDLK_R)) { + Int pos = view->carets[0].range.min; + view->carets[0] = MakeCaret(GetWordStart(buffer, pos)); } if (CtrlShift(SDLK_K)) { diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index f0d6d97..18c21ff 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -35,7 +35,7 @@ struct Buffer { Array undo_stack; Array redo_stack; - int debug_edit_phase; + int edit_phase; bool no_history; bool dirty; }; diff --git a/src/text_editor/todo.txt b/src/text_editor/todo.txt index e1a5688..d41c5e0 100644 --- a/src/text_editor/todo.txt +++ b/src/text_editor/todo.txt @@ -4,9 +4,11 @@ - don't trim lines with cursor or selection on it - fix history of ctrl + enter +- maybe my mouse selection is wrong? seems like on double click lite and sublime start to enclosing words!! +- click 3 times to select line +- we could rewrite kill lines with simpler commands - extend selection to encompass lines->replace - improve cursor movement, it's too clunky, too much stopping - better enclosures -- kill selected lines - should be able click on title bar of windows which disappear on losing focus - search backwards - load selected string or auto enclosed word when midclick?, ctrl + click, ctrl + e?