diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 7593330..3f2276d 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -188,3 +188,768 @@ void MouseLoadWord(Event event) { } } } + +void Command_ReplaceWithoutMovingCarets(View *view, Range range, String16 string) { + Buffer *buffer = GetBuffer(view->active_buffer); + Array caret_copy = Copy(GetSystemAllocator(), view->carets); + defer { + Dealloc(&view->carets); + view->carets = caret_copy; + }; + + Scratch scratch; + Command_SelectRangeOneCursor(view, range); + Array edits = Command_ReplaceEx(scratch, view, string); + AdjustCarets(edits, &caret_copy); +} + +void Command_Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line) { + Buffer *buffer = GetBuffer(view->active_buffer); + + bool scroll_to_end = false; + if (scroll_to_end_if_cursor_on_last_line) { + Int line = PosToLine(buffer, GetFront(view->carets[0])); + if (line == buffer->line_starts.len - 1) scroll_to_end = true; + } + + Array caret_copy = {}; + if (!scroll_to_end) caret_copy = Copy(GetSystemAllocator(), view->carets); + defer { + if (!scroll_to_end) { + Dealloc(&view->carets); + view->carets = caret_copy; + } + }; + + Command_SelectRangeOneCursor(view, GetEndAsRange(buffer)); + Command_Replace(view, string); + Command_Replace(view, L"\n"); + + if (scroll_to_end) { + view->carets[0] = MakeCaret(GetEndAsRange(buffer).min); + } +} + +void Command_Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line) { + Scratch scratch; + String16 string16 = ToString16(scratch, string); + Command_Append(view, string16, scroll_to_end_if_cursor_on_last_line); +} + +void ReportErrorf(const char *fmt, ...) { + Scratch scratch; + STRING_FORMAT(scratch, fmt, string); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error!", string.data, NULL); + View *view = GetView(NullViewID); + Command_Append(view, string, true); +} + +void ReportConsolef(const char *fmt, ...) { + Scratch scratch; + STRING_FORMAT(scratch, fmt, string); + View *view = GetView(NullViewID); + Command_Append(view, string, true); +} + +void ReportWarningf(const char *fmt, ...) { + Scratch scratch; + STRING_FORMAT(scratch, fmt, string); + View *null_view = GetView(NullViewID); + Command_Append(null_view, string, true); + + Window *window = GetWindowWithView(null_view->id); + if (!window) window = GetActiveMainSet().window; + CheckpointBeforeGoto(window); + window->active_view = null_view->id; + ActiveWindow = window->id; +} + +void Command_MoveCursorsByPageSize(Window *window, int direction, bool shift = false) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + BSet set = GetBSet(window); + + Rect2I visible_cells_rect = GetVisibleCells(window); + Int y = GetSize(visible_cells_rect).y - 2; + if (direction == DIR_UP) y = -y; + + For(set.view->carets) { + XY xy = PosToXY(set.buffer, GetFront(it)); + if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) { + Range line_range = GetLineRange(set.buffer, xy.line); + xy.col = line_range.max - line_range.min; + } else if (direction == DIR_UP && xy.line == 0) { + xy.col = 0; + } + xy.line += y; + + Int pos = XYToPos(set.buffer, xy); + if (shift) { + it = SetFront(it, pos); + } else { + it = MakeCaret(pos); + } + } +} + +void Command_MoveCursorsToSide(View *view, int direction, bool shift = false) { + Assert(direction == DIR_LEFT || direction == DIR_RIGHT); + Buffer *buffer = GetBuffer(view->active_buffer); + + For(view->carets) { + Int pos = GetFront(it); + if (direction == DIR_RIGHT) { + pos = GetLineEnd(buffer, pos); + } else { + Int indent = GetIndentAtPos(buffer, pos); + Int new_pos = GetLineStart(buffer, pos); + if (new_pos + indent != pos) { + pos = new_pos + indent; + } else { + pos = new_pos; + } + } + + if (shift) { + it = SetFront(it, pos); + } else { + it.range.max = it.range.min = pos; + } + } +} + +Caret MoveCaret(Buffer *buffer, Caret it, int direction, bool ctrl = false, bool shift = false) { + Int front = GetFront(it); + Int range_size = GetSize(it.range); + switch (direction) { + case DIR_UP: { + if (ctrl && shift) { + Int pos = GetPrevEmptyLineStart(buffer, front); + it = SetFront(it, pos); + } else if (ctrl) { + Int pos = GetPrevEmptyLineStart(buffer, it.range.min); + it = MakeCaret(pos); + } else if (shift) { + Int pos = OffsetByLine(buffer, front, -1); + it = SetFront(it, pos); + } else { + if (range_size == 0) { + Int pos = OffsetByLine(buffer, it.range.min, -1); + it = MakeCaret(pos); + } else { + it = MakeCaret(it.range.min); + } + } + } break; + case DIR_DOWN: { + if (ctrl && shift) { + Int pos = GetNextEmptyLineStart(buffer, front); + it = SetFront(it, pos); + } else if (ctrl) { + Int pos = GetNextEmptyLineStart(buffer, it.range.max); + it = MakeCaret(pos); + } else if (shift) { + Int pos = OffsetByLine(buffer, front, 1); + it = SetFront(it, pos); + } else { + if (range_size == 0) { + Int pos = OffsetByLine(buffer, it.range.max, 1); + it = MakeCaret(pos); + } else { + it = MakeCaret(it.range.max); + } + } + } break; + case DIR_LEFT: { + if (ctrl && shift) { + Int pos = GetPrevWordStart(buffer, front); + it = SetFront(it, pos); + } else if (ctrl) { + if (range_size != 0 && front != it.range.min) { + it = MakeCaret(it.range.min); + } else { + Int pos = GetPrevWordStart(buffer, it.range.min); + it = MakeCaret(pos); + } + } else if (shift) { + Int pos = GetPrevChar(buffer, front); + it = SetFront(it, pos); + } else { + if (range_size == 0) { + Int pos = GetPrevChar(buffer, it.range.min); + it = MakeCaret(pos); + } else { + it = MakeCaret(it.range.min); + } + } + } break; + case DIR_RIGHT: { + if (ctrl && shift) { + Int pos = GetNextWordEnd(buffer, front); + it = SetFront(it, pos); + } else if (ctrl) { + if (range_size != 0 && front != it.range.max) { + it = MakeCaret(it.range.max); + } else { + Int pos = GetNextWordEnd(buffer, it.range.max); + it = MakeCaret(pos); + } + } else if (shift) { + Int pos = GetNextChar(buffer, front); + it = SetFront(it, pos); + } else { + if (range_size == 0) { + Int pos = GetNextChar(buffer, it.range.max); + it = MakeCaret(pos); + } else { + it = MakeCaret(it.range.max); + } + } + } break; + } + return it; +} + +void Command_Move(View *view, int direction, bool ctrl = false, bool shift = false) { + Assert(direction < DIR_COUNT); + Buffer *buffer = GetBuffer(view->active_buffer); + For(view->carets) { + it = MoveCaret(buffer, it, direction, ctrl, shift); + } +} + +void Command_MoveLine(View *view, int direction) { + Assert(direction == DIR_DOWN || direction == DIR_UP); + Scratch scratch; + + struct XYPair { + XY front; + XY back; + }; + + Buffer *buffer = GetBuffer(view->active_buffer); + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + Array saved_xy = {scratch}; + For(view->carets) { + Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))}); + Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max)}; + + String16 string = GetString(buffer, lines_to_move_range); + string = Copy(scratch, string); + AddEdit(&edits, lines_to_move_range, {}); + + // @todo: GetPrevLine, GetNextLine, GetNextFull + // GetPrevLineStart, GetNextLineEnd + if (direction == DIR_DOWN) { + Int next_line_start = lines_to_move_range.max; + Int next_line_end = GetFullLineEnd(buffer, next_line_start); + AddEdit(&edits, Rng(next_line_end), string); + } else { + Int prev_line_end = lines_to_move_range.min - 1; + Int prev_line_start = GetFullLineStart(buffer, prev_line_end); + AddEdit(&edits, Rng(prev_line_start), string); + } + } + EndEdit(buffer, &edits, &view->carets); + + Int line_offset = direction == DIR_UP ? -1 : +1; + for (Int i = 0; i < saved_xy.len; i += 1) { + Caret &caret = view->carets[i]; + XYPair &xypair = saved_xy[i]; + xypair.front.line += line_offset; + xypair.back.line += line_offset; + Int front = XYToPos(buffer, xypair.front); + Int back = XYToPos(buffer, xypair.back); + + caret = MakeCaret(front, back); + } +} + +Array Command_ReplaceEx(Allocator scratch, View *view, String16 string) { + Buffer *buffer = GetBuffer(view->active_buffer); + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + For(view->carets) AddEdit(&edits, it.range, string); + EndEdit(buffer, &edits, &view->carets); + return edits; +} + +void Command_Replace(View *view, String16 string) { + Scratch scratch; + Command_ReplaceEx(scratch, view, string); +} + +void Command_DuplicateLine(View *view, int direction) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + Scratch scratch; + + Buffer *buffer = GetBuffer(view->active_buffer); + BeginEdit(scratch, buffer, view->carets); + For(view->carets) it = MakeCaret(GetFront(it)); + MergeCarets(view); + + Array edits = {scratch}; + For(view->carets) { + Int line = PosToLine(buffer, it.range.min); + Range range = GetLineRange(buffer, line); + String16 string = Copy(scratch, GetString(buffer, range)); + Int pos = direction == DIR_UP ? range.min : range.max; + AddEdit(&edits, Rng(pos), string); + } + EndEdit(buffer, &edits, &view->carets); + + Command_Move(view, direction); +} + +Int FindRangeByPos(Array &ranges, Int pos) { + // binary search + Int low = 0; + Int high = ranges.len - 1; + Int result = -1; + + while (low <= high) { + Int mid = low + (high - low) / 2; + Range range = ranges[mid]; + if (pos >= range.min && pos < range.max) { + result = mid; + break; + } + + if (range.min < pos) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return result; +} + +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 Command_IndentSelectedLines(View *view, bool shift = false) { + Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); + + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + + Array line_ranges_to_indent = GetSelectedLinesSorted(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); + + if (!shift) { + String16 whitespace_string = {L" ", StyleIndentSize}; + AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min}, whitespace_string); + } else { + String16 string = GetString(buffer, pos_range_of_line); + Int whitespace_len = 0; + for (Int i = 0; i < StyleIndentSize && i < string.len && string.data[i] == ' '; i += 1) { + whitespace_len += 1; + } + + AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min + whitespace_len}, L""); + } + } + } + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + view->update_scroll = false; +} + +void Command_TrimTrailingWhitespace(View *view, bool dont_trim_lines_with_cursor) { + Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); + + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + + Array lines_to_skip_triming = {}; + if (dont_trim_lines_with_cursor) lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); + + for (Int i = 0; i < buffer->line_starts.len; i += 1) { + Int range_index = FindRangeByPos(lines_to_skip_triming, i); + if (range_index != -1) continue; + + Range range = GetLineRangeWithoutNL(buffer, i); + Int whitespace_end = range.max; + for (; whitespace_end > range.min; whitespace_end -= 1) { + U16 w = buffer->data[whitespace_end - 1]; + bool is_whitespace = w == ' ' || w == '\t' || w == '\v' || w == '\r'; + if (!is_whitespace) break; + } + + Range whitespace_range = {whitespace_end, range.max}; + AddEdit(&edits, whitespace_range, L""); + } + + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + view->update_scroll = false; +} + +void Command_ConvertLineEndings(View *view, bool dont_trim_lines_with_cursor) { + Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); + + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + + Array lines_to_skip_triming = {}; + if (dont_trim_lines_with_cursor) lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); + + for (Int i = 0; i < buffer->line_starts.len; i += 1) { + Int range_index = FindRangeByPos(lines_to_skip_triming, i); + if (range_index != -1) continue; + + Range range = GetLineRangeWithoutNL(buffer, i); + wchar_t cr = GetChar(buffer, range.max - 1); + wchar_t lf = GetChar(buffer, range.max); + if (cr == L'\r' && lf == L'\n') { + AddEdit(&edits, {range.max - 1, range.max}, L""); + } + } + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + view->update_scroll = false; +} + +void SaveBuffer(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + if (StyleTrimWhitespaceOnSave) { + bool dont_trim_lines_with_cursor = true; + Command_TrimTrailingWhitespace(view, dont_trim_lines_with_cursor); + Command_ConvertLineEndings(view, dont_trim_lines_with_cursor); + Assert(view->active_buffer == buffer->id); + } + + // { + // Scratch scratch; + // String string = AllocCharString(scratch, buffer); + // Buffer *temp_buffer = ExecAndWait(scratch, "clang-format", GetDir(buffer), string); + // Command_ReplaceWithoutMovingCarets(view, GetRange(buffer), {temp_buffer->str, temp_buffer->len}); + // } + + Scratch scratch; + String string = AllocCharString(scratch, buffer); + bool success = WriteFile(buffer->name, string); + + if (success) { + buffer->dirty = false; + } else { + ReportWarningf("Failed to save file with name: %.*s", FmtString(buffer->name)); + } +} + +void Command_KillSelectedLines(View *view) { + Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); + + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + + Array lines = GetSelectedLinesSorted(scratch, view); + For(lines) { + + Range add_range = GetLineRange(buffer, it.min); + for (Int i = it.min + 1; i < it.max; i += 1) { + Range line_range = GetLineRange(buffer, i); + add_range.max = Max(line_range.max, add_range.max); + } + AddEdit(&edits, add_range, L""); + } + + For(view->carets) { + Int line = PosToLine(buffer, it.range.min); + Range range = GetLineRange(buffer, line); + it.range.min = it.range.max = range.min; + } + + EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); +} + +void Command_Delete(View *view, int direction, bool ctrl = false) { + Assert(direction == DIR_LEFT || direction == DIR_RIGHT); + Scratch scratch; + + Buffer *buffer = GetBuffer(view->active_buffer); + Array edits = BeginEdit(scratch, buffer, view->carets); + + // Select things to delete + For(view->carets) { + if (GetSize(it.range)) continue; + if (direction == DIR_LEFT) { + + // Delete indent in multiple of IndentSize + Range indent_range = GetIndentRangeAtPos(buffer, it.range.min); + if (ctrl == false && it.range.min > indent_range.min && it.range.max <= indent_range.max) { + Int offset = it.range.min - indent_range.min; + Int to_delete = (offset % (StyleIndentSize)); + if (to_delete == 0) to_delete = StyleIndentSize; + to_delete = Clamp(to_delete, (Int)1, StyleIndentSize); + for (Int i = 0; i < to_delete; i += 1) { + it = MoveCaret(buffer, it, direction, false, SHIFT_PRESSED); + } + + } else { + it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESSED); + } + } else { + it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESSED); + } + } + + MergeCarets(view); + For(view->carets) AddEdit(&edits, it.range, {}); + EndEdit(buffer, &edits, &view->carets); +} + +void Command_SelectAll(View *view, String16 needle) { + Buffer *buffer = GetBuffer(view->active_buffer); + String16 string_buffer = GetString(buffer); + + for (Int pos = 0;;) { + Int index = 0; + String16 medium = Skip(string_buffer, pos); + if (!Seek(medium, needle, &index)) { + break; + } + + Add(&view->carets, MakeCaret(pos + index + needle.len, pos + index)); + pos += needle.len; + } + MergeCarets(view); +} + +void Command_CreateCursorVertical(View *_view, int direction) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + View &view = *_view; + Buffer *buffer = GetBuffer(view.active_buffer); + + Int line_offset = direction == DIR_UP ? -1 : 1; + + Scratch scratch; + Array arr = {scratch}; + 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); + Add(&arr, MakeCaret(f, b)); + } else { + Int pos = direction == DIR_UP ? it.range.min : it.range.max; + Caret caret = MakeCaret(pos); + caret = MoveCaret(buffer, caret, direction); + Add(&arr, caret); + } + } + For(arr) Add(&view.carets, it); + MergeCarets(_view); +} + +void Command_SelectRangeOneCursor(View *view, Caret caret) { + view->carets.len = 1; + view->carets[0] = caret; +} + +void Command_SelectRangeOneCursor(View *view, Range range) { + Command_SelectRangeOneCursor(view, MakeCaret(range.min, range.max)); +} + +void Command_SelectEntireBuffer(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + Command_SelectRangeOneCursor(view, GetRange(buffer)); +} + +// Merge carets that overlap, this needs to be handled before any edits to +// make sure overlapping edits won't happen. +// +// mouse_selection_anchor is special case for mouse handling ! + +void MergeCarets(View *view) { + ProfileFunction(); + + Buffer *buffer = GetBuffer(view->active_buffer); + For(view->carets) it.range = Clamp(buffer, it.range); + Caret first_caret = view->carets.data[0]; + + Scratch scratch; + Array c1 = TightCopy(scratch, view->carets); + if (view->carets.len > 1) MergeSort(view->carets.len, c1.data, view->carets.data); + view->carets.len = 0; + + Int first_caret_index = 0; + Add(&view->carets, c1[0]); + for (Int i = 1; i < c1.len; i += 1) { + Caret &it = c1[i]; + Caret *last = GetLast(view->carets); + + if (AreOverlapping(*last, it)) { + last->range.max = Max(last->range.max, it.range.max); + } else { + Add(&view->carets, it); + } + + if (AreEqual(it, first_caret)) first_caret_index = view->carets.len - 1; + } + + Swap(&view->carets[first_caret_index], &view->carets[0]); +} + +Caret FindPrev(Buffer *buffer, String16 needle, Caret caret) { + Int pos = GetFront(caret); + String16 medium = GetString(buffer, {0, pos}); + + Caret result = caret; + Int index = 0; + if (Seek(medium, needle, &index, SeekFlag_MatchFindLast)) { + result = MakeCaret(index, index + needle.len); + } else { + medium = GetString(buffer); + if (Seek(medium, needle, &index, SeekFlag_MatchFindLast)) { + result = MakeCaret(index, index + needle.len); + } + } + + return result; +} + +Caret FindNext(Buffer *buffer, String16 needle, Caret caret) { + Int pos = GetFront(caret); + String16 medium = GetString(buffer, {pos, INT64_MAX}); + + Caret result = caret; + Int index = 0; + if (Seek(medium, needle, &index)) { + result = MakeCaret(pos + index + needle.len, pos + index); + } else { + medium = GetString(buffer); + if (Seek(medium, needle, &index)) { + result = MakeCaret(index + needle.len, index); + } + } + + return result; +} + +void Command_IdentedNewLine(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + Scratch scratch; + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(view); + For(view->carets) { + Int front = GetFront(it); + Int line = PosToLine(buffer, front); + Int indent = GetLineIndent(buffer, line); + String string = Format(scratch, "\n%.*s", (int)indent, " "); + String16 string16 = ToString16(scratch, string); + AddEdit(&edits, it.range, string16); + } + EndEdit(buffer, &edits, &view->carets); +} + +void Command_Find(View *seek_view, String16 needle, bool forward = true) { + Buffer *seek_buffer = GetBuffer(seek_view->active_buffer); + Caret caret = seek_view->carets[0]; + if (forward) caret = FindNext(seek_buffer, needle, caret); + if (!forward) caret = FindPrev(seek_buffer, needle, caret); + Command_SelectRangeOneCursor(seek_view, caret); + + IF_DEBUG(AssertRanges(seek_view->carets)); +} + +void Command_GotoNextInList(Window *window, Int line_offset = 1) { + Assert(line_offset == 1 || line_offset == -1); + View *active_view = GetView(window->active_view); + + View *view_goto = GetView(window->active_goto_list); + window->active_view = view_goto->id; + + Buffer *buffer_goto = GetBuffer(view_goto->active_buffer); + int64_t pos = GetFront(view_goto->carets[0]); + Int line = PosToLine(buffer_goto, pos); + + bool opened = false; + for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) { + Range line_range = GetLineRangeWithoutNL(buffer_goto, line + line_offset); + String16 line = GetString(buffer_goto, line_range); + view_goto->carets[0] = MakeCaret(line_range.min); + line = Trim(line); + + MergeCarets(view_goto); + IF_DEBUG(AssertRanges(view_goto->carets)); + if (line.len == 0) continue; + + CheckpointBeforeGoto(window, active_view); + Open(line); + opened = true; + break; + } + + if (!opened) window->active_view = active_view->id; +} + +void Command_FuzzySort(View *view, String16 needle) { + Buffer *buffer = GetBuffer(view->active_buffer); + + Scratch scratch; + Array ratings = FuzzySearchLines(scratch, buffer, 0, buffer->line_starts.len, needle); + + Buffer *temp_buffer = CreateTempBuffer(scratch, buffer->cap); + For(ratings) { + String16 s = GetLineStringWithoutNL(buffer, it.index); + if (s.len == 0) continue; + IKnowWhatImDoing_ReplaceText(temp_buffer, GetEndAsRange(temp_buffer), s); + IKnowWhatImDoing_ReplaceText(temp_buffer, GetEndAsRange(temp_buffer), L"\n"); + } + + Command_SelectEntireBuffer(view); + Command_Replace(view, GetString(temp_buffer)); + Command_SelectRangeOneCursor(view, Rng(0)); +} + +void Command_SelectTitlebarCommand(Window *window, String16 needle) { + BSet title = GetTitleSet(window); + String16 buffer_string = GetString(title.buffer); + ActiveWindow = title.window->id; + + Scratch scratch; + String16 quoted16 = {}; + { + String needle8 = ToString(scratch, needle); + String quoted = Format(scratch, "%.*s\")", FmtString(needle8)); + quoted16 = ToString16(scratch, quoted); + } + + int64_t index = 0; + if (Seek(buffer_string, needle, &index)) { + Command_SelectRangeOneCursor(title.view, Rng(index + needle.len)); + } else { + Command_SelectRangeOneCursor(title.view, GetEndAsRange(title.buffer)); + Command_Replace(title.view, quoted16); + Command_SelectRangeOneCursor(title.view, GetEndAsRange(title.buffer) - 2); + } +} diff --git a/src/text_editor/commands_bindings.cpp b/src/text_editor/commands_bindings.cpp new file mode 100644 index 0000000..d82c255 --- /dev/null +++ b/src/text_editor/commands_bindings.cpp @@ -0,0 +1,534 @@ + +void GlobalCommand(Event event) { + ProfileFunction(); + + // + // Window cursor setting + // + Scratch scratch; + Array order = GetWindowZOrder(scratch); + { + static SDL_Cursor *SDL_MouseCursor; + if (SDL_MouseCursor) { + SDL_DestroyCursor(SDL_MouseCursor); + SDL_MouseCursor = NULL; + } + + Vec2I mouse = MouseVec2I(); + For(order) { + Window *window = Windows[it].o; + if (!window->visible) continue; + bool mouse_in_total = CheckCollisionPointRec(mouse, window->total_rect); + bool mouse_in_scrollbar = CheckCollisionPointRec(mouse, window->scrollbar_rect); + + if (!IsDocumentSelectionValid() && (mouse_in_scrollbar || IsScrollbarSelectionValid())) { + SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); + SDL_SetCursor(SDL_MouseCursor); + break; + } else if (mouse_in_total || IsDocumentSelectionValid()) { + SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT); + SDL_SetCursor(SDL_MouseCursor); + break; + } + } + + if (!SDL_MouseCursor) { + SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); + SDL_SetCursor(SDL_MouseCursor); + } + } + + // Handle wheel scrolling + if (event.wheel.x || event.wheel.y) { + Vec2I mouse = MouseVec2I(); + + For(order) { + Window *window = Windows[it].o; + if (!window->visible) continue; + + bool mouse_in_window = CheckCollisionPointRec(mouse, window->total_rect); + if (mouse_in_window) { + View *view = GetView(window->active_view); + view->scroll.y -= (Int)(event.wheel.y * 48); + view->scroll.x += (Int)(event.wheel.x * 48); + break; + } + } + } + + // Handle selected window scrollbar + // @note: the order here assumes that we won't run this code on the + // same event as the scroll was pressed + if (IsScrollbarSelectionValid() && Mouse(LEFT_UP)) { + Assert(DocumentSelected.id == -1); + ScrollbarSelected.id = -1; + } else if (IsScrollbarSelectionValid()) { + // :ScrollbarImprovement + // @todo: it generally works ok but it moves the scrollbar a bit on click + // when mouse is not even moving + Assert(DocumentSelected.id == -1); + Window *window = GetWindow(ScrollbarSelected); + View *view = GetView(window->active_view); + Vec2 mouse_vec2 = MouseVec2(); + Scroller s = ComputeScrollerRect(window); + double size_y = (double)GetSize(window->scrollbar_rect).y; + double p = mouse_vec2.y - window->scrollbar_rect.min.y; + double v = p / size_y; + v = v + (window->mouse_scroller_offset); + view->scroll.y = (Int)(v * (double)s.line_count * (double)FontLineSpacing); + } + + if (DocumentSelected != ActiveWindow) { + DocumentSelected.id = -1; + } else if (IsDocumentSelectionValid() && MouseUp()) { + Assert(ScrollbarSelected.id == -1); + DocumentSelected.id = -1; + } else if (IsDocumentSelectionValid()) { + Assert(ScrollbarSelected.id == -1); + BSet selected = GetBSet(DocumentSelected); + + Vec2I mouse = MouseVec2I(); + // Special case for full-screen where we can have document + // aligned with monitor screen in which case mouse cursor cannot + // be smaller then 0 which means we cannot scroll + if (mouse.y == 0 && selected.window->document_rect.min.y == 0) { + float x, y; + SDL_GetGlobalMouseState(&x, &y); + if (y == 0) { + mouse.y = -10; + } + } + + Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse); + Caret &caret = selected.view->carets[0]; + + caret = SetFrontWithAnchor(caret, DocumentRangeAnchor, p); + } + + // Set active window on click + if (MousePress()) { + Vec2I mouse = MouseVec2I(); + For(order) { + Window *window = Windows[it].o; + if (!window->visible) continue; + bool mouse_in_document = CheckCollisionPointRec(mouse, window->document_rect); + if (mouse_in_document) { + ActiveWindow = window->id; + break; + } + } + } + + if (event.ctrl && event.shift && Mouse(RIGHT)) { + GotoForward(GetActiveMainSet().window); + } else if (event.alt && event.ctrl && Mouse(RIGHT)) { + } else if (event.ctrl && Mouse(RIGHT)) { + GotoBackward(GetActiveMainSet().window); + } else if (event.alt && Mouse(RIGHT)) { + } else if (Mouse(RIGHT)) { + Vec2I mouse = MouseVec2I(); + BSet active = GetActiveSet(); + bool mouse_in_document = CheckCollisionPointRec(mouse, active.window->document_rect); + if (mouse_in_document) { + Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); + Int saved_front = -1; + + IterRemove(active.view->carets) { + IterRemovePrepare(active.view->carets); + if (InBounds(it.range, p)) { + String16 string = GetString(active.buffer, it.range); + SaveStringInClipboard(string); + + remove_item = true; + saved_front = GetFront(it); + } + } + if (active.view->carets.len == 0) Add(&active.view->carets, MakeCaret(saved_front)); + + if (saved_front == -1) { + Int line = PosToLine(active.buffer, p); + Range line_range = GetLineRangeWithoutNL(active.buffer, line); + String16 string = GetString(active.buffer, line_range); + SaveStringInClipboard(string); + } + } + } + + // @todo: maybe move some of this stuff to window command ??? + // for now let's leave it because we are relaying on global state + // - maybe just do the check if active window is matching the DocumentSelected window + // - if scrollbar selected then don't invoke window command + if (event.ctrl && event.shift && Mouse(LEFT)) { + MouseExecWord(event); + } else if (event.ctrl && Mouse(LEFT)) { + MouseLoadWord(event); + } else if (Mouse(LEFT)) { // CTRL SHIFT + Vec2I mouse = MouseVec2I(); + { + Assert(ScrollbarSelected.id == -1); + Assert(DocumentSelected.id == -1); + + BSet active = GetActiveSet(); + bool mouse_in_document = CheckCollisionPointRec(mouse, active.window->document_rect); + bool mouse_in_line_numbers = CheckCollisionPointRec(mouse, active.window->line_numbers_rect); + if (mouse_in_document || mouse_in_line_numbers) { + DocumentSelected = active.window->id; + + Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); + if (event.alt) Insert(&active.view->carets, MakeCaret(p, p), 0); + if (!event.alt && !event.shift) active.view->carets.len = 1; + + Caret &caret = active.view->carets[0]; + if (event.shift) { + if (p <= caret.range.min) { + caret.range.min = p; + caret.ifront = 0; + } else if (p >= caret.range.max) { + caret.range.max = p; + caret.ifront = 1; + } + } else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) { + Range range = EncloseWord(active.buffer, p); + if (event.clicks >= 3) range = EncloseLoadWord(active.buffer, p); + caret = MakeCaret(range.max, range.min); + } else { + caret = MakeCaret(p); + } + MergeCarets(active.view); + DocumentRangeAnchor = caret.range; + } + } + + // Figure out scrollbar click + // :ScrollbarImprovement + // @todo: it generally works ok but it moves the scrollbar a bit on click + // when mouse is not even moving + For(order) { + Window *window = Windows[it].o; + if (!window->visible) continue; + bool mouse_in_scrollbar = CheckCollisionPointRec(mouse, window->scrollbar_rect); + if (mouse_in_scrollbar) { + ScrollbarSelected = window->id; + + View *view = GetView(window->active_view); + Vec2 mouse_vec2 = MouseVec2(); + Scroller s = ComputeScrollerRect(window); + double size_y = (double)GetSize(window->scrollbar_rect).y; + double p = mouse_vec2.y - window->scrollbar_rect.min.y; + if (mouse_vec2.y < s.rect.min.y || mouse_vec2.y > s.rect.max.y) { + view->scroll.y = (Int)(p / size_y * (double)s.line_count * (double)FontLineSpacing); + window->mouse_scroller_offset = -(double)GetSize(s.rect).y / 2.0 / size_y; + } else { + window->mouse_scroller_offset = (s.rect.min.y - p) / size_y; + } + break; + } + } + } + + if (Ctrl(SDLK_P)) { + Command_ListBuffers(); + } + + if (CtrlShift(SDLK_BACKSLASH)) { + AddRowWindow(); + } else if (Ctrl(SDLK_BACKSLASH)) { + AddColumnWindow(); + } + + if (Ctrl(SDLK_0)) { + ToggleVisibility(DebugWindowID); + } + + if (Press(SDLK_F5)) { + AppIsRunning = false; + } + + if (Press(SDLK_F11)) { + ToggleFullscreen(); + } + + if (Ctrl(SDLK_1)) { + Window *window = GetLayoutWindow(0); + if (window) ActiveWindow = window->id; + } + if (Ctrl(SDLK_2)) { + Window *window = GetLayoutWindow(1); + if (window) ActiveWindow = window->id; + } + if (Ctrl(SDLK_3)) { + Window *window = GetLayoutWindow(2); + if (window) ActiveWindow = window->id; + } + + BSet active = GetActiveSet(); + + if (event.kind == EVENT_DROP_FILE) { + WindowOpenBufferView(active.window, event.text); + } + + if (CtrlAlt(SDLK_DOWN)) { + Command_DuplicateLine(active.view, DIR_DOWN); + } else if (AltShift(SDLK_DOWN)) { + Command_CreateCursorVertical(active.view, DIR_DOWN); + } else if (CtrlShift(SDLK_DOWN)) { + Command_Move(active.view, DIR_DOWN, CTRL_PRESSED, SHIFT_PRESSED); + } else if (Alt(SDLK_DOWN)) { + Command_MoveLine(active.view, DIR_DOWN); + } else if (Ctrl(SDLK_DOWN)) { + Command_Move(active.view, DIR_DOWN, CTRL_PRESSED); + } else if (Shift(SDLK_DOWN)) { + Command_Move(active.view, DIR_DOWN, false, SHIFT_PRESSED); + } else if (Press(SDLK_DOWN)) { + Command_Move(active.view, DIR_DOWN); + } + + if (CtrlAlt(SDLK_UP)) { + Command_DuplicateLine(active.view, DIR_UP); + } else if (AltShift(SDLK_UP)) { + Command_CreateCursorVertical(active.view, DIR_UP); + } else if (CtrlShift(SDLK_UP)) { + Command_Move(active.view, DIR_UP, CTRL_PRESSED, SHIFT_PRESSED); + } else if (Alt(SDLK_UP)) { + Command_MoveLine(active.view, DIR_UP); + } else if (Ctrl(SDLK_UP)) { + Command_Move(active.view, DIR_UP, CTRL_PRESSED); + } else if (Shift(SDLK_UP)) { + Command_Move(active.view, DIR_UP, false, SHIFT_PRESSED); + } else if (Press(SDLK_UP)) { + Command_Move(active.view, DIR_UP); + } + + if (CtrlShift(SDLK_LEFT)) { + Command_Move(active.view, DIR_LEFT, CTRL_PRESSED, SHIFT_PRESSED); + } else if (Ctrl(SDLK_LEFT)) { + Command_Move(active.view, DIR_LEFT, CTRL_PRESSED); + } else if (Shift(SDLK_LEFT)) { + Command_Move(active.view, DIR_LEFT, false, SHIFT_PRESSED); + } else if (Press(SDLK_LEFT)) { + Command_Move(active.view, DIR_LEFT); + } + + if (CtrlShift(SDLK_RIGHT)) { + Command_Move(active.view, DIR_RIGHT, CTRL_PRESSED, SHIFT_PRESSED); + } else if (Ctrl(SDLK_RIGHT)) { + Command_Move(active.view, DIR_RIGHT, CTRL_PRESSED); + } else if (Shift(SDLK_RIGHT)) { + Command_Move(active.view, DIR_RIGHT, false, SHIFT_PRESSED); + } else if (Press(SDLK_RIGHT)) { + Command_Move(active.view, DIR_RIGHT); + } + + if (CtrlShift(SDLK_Z)) { + RedoEdit(active.buffer, &active.view->carets); + } else if (Ctrl(SDLK_Z)) { + UndoEdit(active.buffer, &active.view->carets); + } + + if (Ctrl(SDLK_C)) { + Command_Copy(active.view); + } else if (Ctrl(SDLK_V)) { + Command_Paste(active.view); + } else if (Ctrl(SDLK_X)) { + PreBeginEdit_SaveCaretHistory(active.buffer, active.view->carets); + Command_Copy(active.view); + Command_Replace(active.view, L""); + } + + if (Ctrl(SDLK_A)) { + Command_SelectEntireBuffer(active.view); + active.view->update_scroll = false; + } + + if (Shift(SDLK_PAGEUP)) { + Command_MoveCursorsByPageSize(active.window, DIR_UP, SHIFT_PRESSED); + } else if (Press(SDLK_PAGEUP)) { + Command_MoveCursorsByPageSize(active.window, DIR_UP); + } + + if (Shift(SDLK_PAGEDOWN)) { + Command_MoveCursorsByPageSize(active.window, DIR_DOWN, SHIFT_PRESSED); + } else if (Press(SDLK_PAGEDOWN)) { + Command_MoveCursorsByPageSize(active.window, DIR_DOWN); + } + + if (Shift(SDLK_HOME)) { + Command_MoveCursorsToSide(active.view, DIR_LEFT, SHIFT_PRESSED); + } else if (Press(SDLK_HOME)) { + Command_MoveCursorsToSide(active.view, DIR_LEFT); + } + + if (Shift(SDLK_END)) { + Command_MoveCursorsToSide(active.view, DIR_RIGHT, SHIFT_PRESSED); + } else if (Press(SDLK_END)) { + Command_MoveCursorsToSide(active.view, DIR_RIGHT); + } + + if (Shift(SDLK_TAB)) { + Command_IndentSelectedLines(active.view, SHIFT_PRESSED); + } else if (Press(SDLK_TAB)) { + Command_IndentSelectedLines(active.view); + } + + if (CtrlShift(SDLK_K)) { + Command_KillSelectedLines(active.view); + } + + if (Ctrl(SDLK_BACKSPACE)) { + Command_Delete(active.view, DIR_LEFT, CTRL_PRESSED); + } else if (Press(SDLK_BACKSPACE)) { + Command_Delete(active.view, DIR_LEFT); + } + + if (Ctrl(SDLK_DELETE)) { + Command_Delete(active.view, DIR_RIGHT, CTRL_PRESSED); + } else if (Press(SDLK_DELETE)) { + Command_Delete(active.view, DIR_RIGHT); + } + + if (event.kind == EVENT_TEXT_INPUT) { + Scratch scratch; + String string = event.text; + String16 string16 = ToString16(scratch, string); + Command_Replace(active.view, string16); + } + + if (Ctrl(SDLK_D)) { + String16 string = GetString(active.buffer, active.view->carets[0].range); + Caret caret = FindNext(active.buffer, string, active.view->carets[0]); + Insert(&active.view->carets, caret, 0); + MergeCarets(active.view); + } + + if (Shift(SDLK_F3)) { + Scratch scratch; + BSet main = GetActiveMainSet(); + String16 search_string = ToString16(scratch, main.window->search_string); + Caret caret = FindPrev(main.buffer, search_string, main.view->carets[0]); + Command_SelectRangeOneCursor(main.view, caret); + } else if (Press(SDLK_F3)) { + Scratch scratch; + BSet main = GetActiveMainSet(); + String16 search_string = ToString16(scratch, main.window->search_string); + Caret caret = FindNext(main.buffer, search_string, main.view->carets[0]); + Command_SelectRangeOneCursor(main.view, caret); + } + + if (Shift(SDLK_F4)) { + Command_GotoNextInList(active.window, -1); + } else if (Press(SDLK_F4)) { + Command_GotoNextInList(active.window, 1); + } + + if (CtrlShift(SDLK_RETURN)) { + Command_MoveCursorsToSide(active.view, DIR_LEFT); + Command_IdentedNewLine(active.view); + Command_Move(active.view, DIR_UP); + } else if (Ctrl(SDLK_RETURN)) { + Command_MoveCursorsToSide(active.view, DIR_RIGHT); + Command_IdentedNewLine(active.view); + } else if (Press(SDLK_RETURN)) { + Command_IdentedNewLine(active.view); + } + + if (Ctrl(SDLK_F)) { + Command_SelectTitlebarCommand(active.window, L"#Search(\""); + } + + if (Ctrl(SDLK_S)) { + SaveBuffer(active.view); + } + + if (Ctrl(SDLK_PERIOD)) { + BSet main = GetActiveMainSet(); + String name = ChopLastSlash(main.buffer->name); + Open(name); + } + + if (CtrlShift(SDLK_G)) { + } else if (Ctrl(SDLK_G)) { + Command_SelectTitlebarCommand(active.window, L"#FuzzySort(\""); + } + + if (CtrlShift(SDLK_W)) { + GotoForward(GetActiveMainSet().window); + } else if (Ctrl(SDLK_W)) { + GotoBackward(GetActiveMainSet().window); + } + + if (CtrlShift(SDLK_Q)) { + Caret caret = active.view->carets[0]; + Range range = caret.range; + if (GetSize(caret.range) == 0) range = EncloseExecWord(active.buffer, GetFront(caret)); + String16 string = GetString(active.buffer, range); + + Command_EvalLua(active.view, string); + } else if (Ctrl(SDLK_Q)) { + Caret caret = active.view->carets[0]; + Range range = caret.range; + if (GetSize(caret.range) == 0) range = EncloseLoadWord(active.buffer, GetFront(caret)); + String16 string = GetString(active.buffer, range); + + active.window->active_goto_list = active.view->id; + Open(string); + } + + if (Press(SDLK_ESCAPE)) { + if (active.window->deactivate_on_escape) { + ActiveWindow = GetActiveMainSet().window->id; + } else { + active.view->carets.len = 1; + } + } + + MergeCarets(active.view); + IF_DEBUG(AssertRanges(active.view->carets)); +} + +void UpdateScroll(Window *window, bool update_caret_scrolling) { + ProfileFunction(); + BSet set = GetBSet(window); + + // Scrolling with caret + if (update_caret_scrolling) { + Caret c = set.view->carets[0]; + Int front = GetFront(c); + XY xy = PosToXY(set.buffer, front); + + Rect2I visible = GetVisibleCells(window); + Vec2I visible_cells = GetSize(visible); + Vec2I visible_size = visible_cells * Vec2I{FontCharSpacing, FontLineSpacing}; + Vec2I rect_size = GetSize(window->document_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); + set.view->scroll.y = (set_view_at_line * FontLineSpacing) + cut_off_y; + } + + if (xy.line < visible.min.y + 1) { + set.view->scroll.y = xy.line * FontLineSpacing; + } + + 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); + set.view->scroll.x = (set_view_at_line * FontCharSpacing) + cut_off_x; + } + + if (xy.col <= visible.min.x) { + set.view->scroll.x = xy.col * FontCharSpacing; + } + } + + // Clip scroll + { + Int last_line = LastLine(set.buffer); + set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * FontLineSpacing)); + + // @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? + set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0); + } +} \ No newline at end of file diff --git a/src/text_editor/commands_window.cpp b/src/text_editor/commands_window.cpp deleted file mode 100644 index 0343251..0000000 --- a/src/text_editor/commands_window.cpp +++ /dev/null @@ -1,1299 +0,0 @@ - -void Command_ReplaceWithoutMovingCarets(View *view, Range range, String16 string) { - Buffer *buffer = GetBuffer(view->active_buffer); - Array caret_copy = Copy(GetSystemAllocator(), view->carets); - defer { - Dealloc(&view->carets); - view->carets = caret_copy; - }; - - Scratch scratch; - Command_SelectRangeOneCursor(view, range); - Array edits = Command_ReplaceEx(scratch, view, string); - AdjustCarets(edits, &caret_copy); -} - -void Command_Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line) { - Buffer *buffer = GetBuffer(view->active_buffer); - - bool scroll_to_end = false; - if (scroll_to_end_if_cursor_on_last_line) { - Int line = PosToLine(buffer, GetFront(view->carets[0])); - if (line == buffer->line_starts.len - 1) scroll_to_end = true; - } - - Array caret_copy = {}; - if (!scroll_to_end) caret_copy = Copy(GetSystemAllocator(), view->carets); - defer { - if (!scroll_to_end) { - Dealloc(&view->carets); - view->carets = caret_copy; - } - }; - - Command_SelectRangeOneCursor(view, GetEndAsRange(buffer)); - Command_Replace(view, string); - Command_Replace(view, L"\n"); - - if (scroll_to_end) { - view->carets[0] = MakeCaret(GetEndAsRange(buffer).min); - } -} - -void Command_Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line) { - Scratch scratch; - String16 string16 = ToString16(scratch, string); - Command_Append(view, string16, scroll_to_end_if_cursor_on_last_line); -} - -void ReportErrorf(const char *fmt, ...) { - Scratch scratch; - STRING_FORMAT(scratch, fmt, string); - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error!", string.data, NULL); - View *view = GetView(NullViewID); - Command_Append(view, string, true); -} - -void ReportConsolef(const char *fmt, ...) { - Scratch scratch; - STRING_FORMAT(scratch, fmt, string); - View *view = GetView(NullViewID); - Command_Append(view, string, true); -} - -void ReportWarningf(const char *fmt, ...) { - Scratch scratch; - STRING_FORMAT(scratch, fmt, string); - View *null_view = GetView(NullViewID); - Command_Append(null_view, string, true); - - Window *window = GetWindowWithView(null_view->id); - if (!window) window = GetActiveMainSet().window; - CheckpointBeforeGoto(window); - window->active_view = null_view->id; - ActiveWindow = window->id; -} - -void Command_MoveCursorsByPageSize(Window *window, int direction, bool shift = false) { - Assert(direction == DIR_UP || direction == DIR_DOWN); - BSet set = GetBSet(window); - - Rect2I visible_cells_rect = GetVisibleCells(window); - Int y = GetSize(visible_cells_rect).y - 2; - if (direction == DIR_UP) y = -y; - - For(set.view->carets) { - XY xy = PosToXY(set.buffer, GetFront(it)); - if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) { - Range line_range = GetLineRange(set.buffer, xy.line); - xy.col = line_range.max - line_range.min; - } else if (direction == DIR_UP && xy.line == 0) { - xy.col = 0; - } - xy.line += y; - - Int pos = XYToPos(set.buffer, xy); - if (shift) { - it = SetFront(it, pos); - } else { - it = MakeCaret(pos); - } - } -} - -void Command_MoveCursorsToSide(View *view, int direction, bool shift = false) { - Assert(direction == DIR_LEFT || direction == DIR_RIGHT); - Buffer *buffer = GetBuffer(view->active_buffer); - - For(view->carets) { - Int pos = GetFront(it); - if (direction == DIR_RIGHT) { - pos = GetLineEnd(buffer, pos); - } else { - Int indent = GetIndentAtPos(buffer, pos); - Int new_pos = GetLineStart(buffer, pos); - if (new_pos + indent != pos) { - pos = new_pos + indent; - } else { - pos = new_pos; - } - } - - if (shift) { - it = SetFront(it, pos); - } else { - it.range.max = it.range.min = pos; - } - } -} - -Caret MoveCaret(Buffer *buffer, Caret it, int direction, bool ctrl = false, bool shift = false) { - Int front = GetFront(it); - Int range_size = GetSize(it.range); - switch (direction) { - case DIR_UP: { - if (ctrl && shift) { - Int pos = GetPrevEmptyLineStart(buffer, front); - it = SetFront(it, pos); - } else if (ctrl) { - Int pos = GetPrevEmptyLineStart(buffer, it.range.min); - it = MakeCaret(pos); - } else if (shift) { - Int pos = OffsetByLine(buffer, front, -1); - it = SetFront(it, pos); - } else { - if (range_size == 0) { - Int pos = OffsetByLine(buffer, it.range.min, -1); - it = MakeCaret(pos); - } else { - it = MakeCaret(it.range.min); - } - } - } break; - case DIR_DOWN: { - if (ctrl && shift) { - Int pos = GetNextEmptyLineStart(buffer, front); - it = SetFront(it, pos); - } else if (ctrl) { - Int pos = GetNextEmptyLineStart(buffer, it.range.max); - it = MakeCaret(pos); - } else if (shift) { - Int pos = OffsetByLine(buffer, front, 1); - it = SetFront(it, pos); - } else { - if (range_size == 0) { - Int pos = OffsetByLine(buffer, it.range.max, 1); - it = MakeCaret(pos); - } else { - it = MakeCaret(it.range.max); - } - } - } break; - case DIR_LEFT: { - if (ctrl && shift) { - Int pos = GetPrevWordStart(buffer, front); - it = SetFront(it, pos); - } else if (ctrl) { - if (range_size != 0 && front != it.range.min) { - it = MakeCaret(it.range.min); - } else { - Int pos = GetPrevWordStart(buffer, it.range.min); - it = MakeCaret(pos); - } - } else if (shift) { - Int pos = GetPrevChar(buffer, front); - it = SetFront(it, pos); - } else { - if (range_size == 0) { - Int pos = GetPrevChar(buffer, it.range.min); - it = MakeCaret(pos); - } else { - it = MakeCaret(it.range.min); - } - } - } break; - case DIR_RIGHT: { - if (ctrl && shift) { - Int pos = GetNextWordEnd(buffer, front); - it = SetFront(it, pos); - } else if (ctrl) { - if (range_size != 0 && front != it.range.max) { - it = MakeCaret(it.range.max); - } else { - Int pos = GetNextWordEnd(buffer, it.range.max); - it = MakeCaret(pos); - } - } else if (shift) { - Int pos = GetNextChar(buffer, front); - it = SetFront(it, pos); - } else { - if (range_size == 0) { - Int pos = GetNextChar(buffer, it.range.max); - it = MakeCaret(pos); - } else { - it = MakeCaret(it.range.max); - } - } - } break; - } - return it; -} - -void Command_Move(View *view, int direction, bool ctrl = false, bool shift = false) { - Assert(direction < DIR_COUNT); - Buffer *buffer = GetBuffer(view->active_buffer); - For(view->carets) { - it = MoveCaret(buffer, it, direction, ctrl, shift); - } -} - -void Command_MoveLine(View *view, int direction) { - Assert(direction == DIR_DOWN || direction == DIR_UP); - Scratch scratch; - - struct XYPair { - XY front; - XY back; - }; - - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - Array saved_xy = {scratch}; - For(view->carets) { - Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))}); - Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max)}; - - String16 string = GetString(buffer, lines_to_move_range); - string = Copy(scratch, string); - AddEdit(&edits, lines_to_move_range, {}); - - // @todo: GetPrevLine, GetNextLine, GetNextFull - // GetPrevLineStart, GetNextLineEnd - if (direction == DIR_DOWN) { - Int next_line_start = lines_to_move_range.max; - Int next_line_end = GetFullLineEnd(buffer, next_line_start); - AddEdit(&edits, Rng(next_line_end), string); - } else { - Int prev_line_end = lines_to_move_range.min - 1; - Int prev_line_start = GetFullLineStart(buffer, prev_line_end); - AddEdit(&edits, Rng(prev_line_start), string); - } - } - EndEdit(buffer, &edits, &view->carets); - - Int line_offset = direction == DIR_UP ? -1 : +1; - for (Int i = 0; i < saved_xy.len; i += 1) { - Caret &caret = view->carets[i]; - XYPair &xypair = saved_xy[i]; - xypair.front.line += line_offset; - xypair.back.line += line_offset; - Int front = XYToPos(buffer, xypair.front); - Int back = XYToPos(buffer, xypair.back); - - caret = MakeCaret(front, back); - } -} - -Array Command_ReplaceEx(Allocator scratch, View *view, String16 string) { - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - For(view->carets) AddEdit(&edits, it.range, string); - EndEdit(buffer, &edits, &view->carets); - return edits; -} - -void Command_Replace(View *view, String16 string) { - Scratch scratch; - Command_ReplaceEx(scratch, view, string); -} - -void Command_DuplicateLine(View *view, int direction) { - Assert(direction == DIR_UP || direction == DIR_DOWN); - Scratch scratch; - - Buffer *buffer = GetBuffer(view->active_buffer); - BeginEdit(scratch, buffer, view->carets); - For(view->carets) it = MakeCaret(GetFront(it)); - MergeCarets(view); - - Array edits = {scratch}; - For(view->carets) { - Int line = PosToLine(buffer, it.range.min); - Range range = GetLineRange(buffer, line); - String16 string = Copy(scratch, GetString(buffer, range)); - Int pos = direction == DIR_UP ? range.min : range.max; - AddEdit(&edits, Rng(pos), string); - } - EndEdit(buffer, &edits, &view->carets); - - Command_Move(view, direction); -} - -Int FindRangeByPos(Array &ranges, Int pos) { - // binary search - Int low = 0; - Int high = ranges.len - 1; - Int result = -1; - - while (low <= high) { - Int mid = low + (high - low) / 2; - Range range = ranges[mid]; - if (pos >= range.min && pos < range.max) { - result = mid; - break; - } - - if (range.min < pos) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - return result; -} - -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 Command_IndentSelectedLines(View *view, bool shift = false) { - Scratch scratch; - Buffer *buffer = GetBuffer(view->active_buffer); - - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - - Array line_ranges_to_indent = GetSelectedLinesSorted(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); - - if (!shift) { - String16 whitespace_string = {L" ", StyleIndentSize}; - AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min}, whitespace_string); - } else { - String16 string = GetString(buffer, pos_range_of_line); - Int whitespace_len = 0; - for (Int i = 0; i < StyleIndentSize && i < string.len && string.data[i] == ' '; i += 1) { - whitespace_len += 1; - } - - AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min + whitespace_len}, L""); - } - } - } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - view->update_scroll = false; -} - -void Command_TrimTrailingWhitespace(View *view, bool dont_trim_lines_with_cursor) { - Scratch scratch; - Buffer *buffer = GetBuffer(view->active_buffer); - - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - - Array lines_to_skip_triming = {}; - if (dont_trim_lines_with_cursor) lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); - - for (Int i = 0; i < buffer->line_starts.len; i += 1) { - Int range_index = FindRangeByPos(lines_to_skip_triming, i); - if (range_index != -1) continue; - - Range range = GetLineRangeWithoutNL(buffer, i); - Int whitespace_end = range.max; - for (; whitespace_end > range.min; whitespace_end -= 1) { - U16 w = buffer->data[whitespace_end - 1]; - bool is_whitespace = w == ' ' || w == '\t' || w == '\v' || w == '\r'; - if (!is_whitespace) break; - } - - Range whitespace_range = {whitespace_end, range.max}; - AddEdit(&edits, whitespace_range, L""); - } - - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - view->update_scroll = false; -} - -void Command_ConvertLineEndings(View *view, bool dont_trim_lines_with_cursor) { - Scratch scratch; - Buffer *buffer = GetBuffer(view->active_buffer); - - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - - Array lines_to_skip_triming = {}; - if (dont_trim_lines_with_cursor) lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); - - for (Int i = 0; i < buffer->line_starts.len; i += 1) { - Int range_index = FindRangeByPos(lines_to_skip_triming, i); - if (range_index != -1) continue; - - Range range = GetLineRangeWithoutNL(buffer, i); - wchar_t cr = GetChar(buffer, range.max - 1); - wchar_t lf = GetChar(buffer, range.max); - if (cr == L'\r' && lf == L'\n') { - AddEdit(&edits, {range.max - 1, range.max}, L""); - } - } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - view->update_scroll = false; -} - -void SaveBuffer(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - if (StyleTrimWhitespaceOnSave) { - bool dont_trim_lines_with_cursor = true; - Command_TrimTrailingWhitespace(view, dont_trim_lines_with_cursor); - Command_ConvertLineEndings(view, dont_trim_lines_with_cursor); - Assert(view->active_buffer == buffer->id); - } - - // { - // Scratch scratch; - // String string = AllocCharString(scratch, buffer); - // Buffer *temp_buffer = ExecAndWait(scratch, "clang-format", GetDir(buffer), string); - // Command_ReplaceWithoutMovingCarets(view, GetRange(buffer), {temp_buffer->str, temp_buffer->len}); - // } - - Scratch scratch; - String string = AllocCharString(scratch, buffer); - bool success = WriteFile(buffer->name, string); - - if (success) { - buffer->dirty = false; - } else { - ReportWarningf("Failed to save file with name: %.*s", FmtString(buffer->name)); - } -} - -void Command_KillSelectedLines(View *view) { - Scratch scratch; - Buffer *buffer = GetBuffer(view->active_buffer); - - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - - Array lines = GetSelectedLinesSorted(scratch, view); - For(lines) { - - Range add_range = GetLineRange(buffer, it.min); - for (Int i = it.min + 1; i < it.max; i += 1) { - Range line_range = GetLineRange(buffer, i); - add_range.max = Max(line_range.max, add_range.max); - } - AddEdit(&edits, add_range, L""); - } - - For(view->carets) { - Int line = PosToLine(buffer, it.range.min); - Range range = GetLineRange(buffer, line); - it.range.min = it.range.max = range.min; - } - - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); -} - -void Command_Delete(View *view, int direction, bool ctrl = false) { - Assert(direction == DIR_LEFT || direction == DIR_RIGHT); - Scratch scratch; - - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - - // Select things to delete - For(view->carets) { - if (GetSize(it.range)) continue; - if (direction == DIR_LEFT) { - - // Delete indent in multiple of IndentSize - Range indent_range = GetIndentRangeAtPos(buffer, it.range.min); - if (ctrl == false && it.range.min > indent_range.min && it.range.max <= indent_range.max) { - Int offset = it.range.min - indent_range.min; - Int to_delete = (offset % (StyleIndentSize)); - if (to_delete == 0) to_delete = StyleIndentSize; - to_delete = Clamp(to_delete, (Int)1, StyleIndentSize); - for (Int i = 0; i < to_delete; i += 1) { - it = MoveCaret(buffer, it, direction, false, SHIFT_PRESSED); - } - - } else { - it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESSED); - } - } else { - it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESSED); - } - } - - MergeCarets(view); - For(view->carets) AddEdit(&edits, it.range, {}); - EndEdit(buffer, &edits, &view->carets); -} - -void Command_SelectAll(View *view, String16 needle) { - Buffer *buffer = GetBuffer(view->active_buffer); - String16 string_buffer = GetString(buffer); - - for (Int pos = 0;;) { - Int index = 0; - String16 medium = Skip(string_buffer, pos); - if (!Seek(medium, needle, &index)) { - break; - } - - Add(&view->carets, MakeCaret(pos + index + needle.len, pos + index)); - pos += needle.len; - } - MergeCarets(view); -} - -void Command_CreateCursorVertical(View *_view, int direction) { - Assert(direction == DIR_UP || direction == DIR_DOWN); - View &view = *_view; - Buffer *buffer = GetBuffer(view.active_buffer); - - Int line_offset = direction == DIR_UP ? -1 : 1; - - Scratch scratch; - Array arr = {scratch}; - 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); - Add(&arr, MakeCaret(f, b)); - } else { - Int pos = direction == DIR_UP ? it.range.min : it.range.max; - Caret caret = MakeCaret(pos); - caret = MoveCaret(buffer, caret, direction); - Add(&arr, caret); - } - } - For(arr) Add(&view.carets, it); - MergeCarets(_view); -} - -void Command_SelectRangeOneCursor(View *view, Caret caret) { - view->carets.len = 1; - view->carets[0] = caret; -} - -void Command_SelectRangeOneCursor(View *view, Range range) { - Command_SelectRangeOneCursor(view, MakeCaret(range.min, range.max)); -} - -void Command_SelectEntireBuffer(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - Command_SelectRangeOneCursor(view, GetRange(buffer)); -} - -// Merge carets that overlap, this needs to be handled before any edits to -// make sure overlapping edits won't happen. -// -// mouse_selection_anchor is special case for mouse handling ! - -void MergeCarets(View *view) { - ProfileFunction(); - - Buffer *buffer = GetBuffer(view->active_buffer); - For(view->carets) it.range = Clamp(buffer, it.range); - Caret first_caret = view->carets.data[0]; - - Scratch scratch; - Array c1 = TightCopy(scratch, view->carets); - if (view->carets.len > 1) MergeSort(view->carets.len, c1.data, view->carets.data); - view->carets.len = 0; - - Int first_caret_index = 0; - Add(&view->carets, c1[0]); - for (Int i = 1; i < c1.len; i += 1) { - Caret &it = c1[i]; - Caret *last = GetLast(view->carets); - - if (AreOverlapping(*last, it)) { - last->range.max = Max(last->range.max, it.range.max); - } else { - Add(&view->carets, it); - } - - if (AreEqual(it, first_caret)) first_caret_index = view->carets.len - 1; - } - - Swap(&view->carets[first_caret_index], &view->carets[0]); -} - -Caret FindPrev(Buffer *buffer, String16 needle, Caret caret) { - Int pos = GetFront(caret); - String16 medium = GetString(buffer, {0, pos}); - - Caret result = caret; - Int index = 0; - if (Seek(medium, needle, &index, SeekFlag_MatchFindLast)) { - result = MakeCaret(index, index + needle.len); - } else { - medium = GetString(buffer); - if (Seek(medium, needle, &index, SeekFlag_MatchFindLast)) { - result = MakeCaret(index, index + needle.len); - } - } - - return result; -} - -Caret FindNext(Buffer *buffer, String16 needle, Caret caret) { - Int pos = GetFront(caret); - String16 medium = GetString(buffer, {pos, INT64_MAX}); - - Caret result = caret; - Int index = 0; - if (Seek(medium, needle, &index)) { - result = MakeCaret(pos + index + needle.len, pos + index); - } else { - medium = GetString(buffer); - if (Seek(medium, needle, &index)) { - result = MakeCaret(index + needle.len, index); - } - } - - return result; -} - -void Command_IdentedNewLine(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - Scratch scratch; - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(view); - For(view->carets) { - Int front = GetFront(it); - Int line = PosToLine(buffer, front); - Int indent = GetLineIndent(buffer, line); - String string = Format(scratch, "\n%.*s", (int)indent, " "); - String16 string16 = ToString16(scratch, string); - AddEdit(&edits, it.range, string16); - } - EndEdit(buffer, &edits, &view->carets); -} - -void Command_Find(View *seek_view, String16 needle, bool forward = true) { - Buffer *seek_buffer = GetBuffer(seek_view->active_buffer); - Caret caret = seek_view->carets[0]; - if (forward) caret = FindNext(seek_buffer, needle, caret); - if (!forward) caret = FindPrev(seek_buffer, needle, caret); - Command_SelectRangeOneCursor(seek_view, caret); - - IF_DEBUG(AssertRanges(seek_view->carets)); -} - -void Command_GotoNextInList(Window *window, Int line_offset = 1) { - Assert(line_offset == 1 || line_offset == -1); - View *active_view = GetView(window->active_view); - - View *view_goto = GetView(window->active_goto_list); - window->active_view = view_goto->id; - - Buffer *buffer_goto = GetBuffer(view_goto->active_buffer); - int64_t pos = GetFront(view_goto->carets[0]); - Int line = PosToLine(buffer_goto, pos); - - bool opened = false; - for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) { - Range line_range = GetLineRangeWithoutNL(buffer_goto, line + line_offset); - String16 line = GetString(buffer_goto, line_range); - view_goto->carets[0] = MakeCaret(line_range.min); - line = Trim(line); - - MergeCarets(view_goto); - IF_DEBUG(AssertRanges(view_goto->carets)); - if (line.len == 0) continue; - - CheckpointBeforeGoto(window, active_view); - Open(line); - opened = true; - break; - } - - if (!opened) window->active_view = active_view->id; -} - -void Command_FuzzySort(View *view, String16 needle) { - Buffer *buffer = GetBuffer(view->active_buffer); - - Scratch scratch; - Array ratings = FuzzySearchLines(scratch, buffer, 0, buffer->line_starts.len, needle); - - Buffer *temp_buffer = CreateTempBuffer(scratch, buffer->cap); - For(ratings) { - String16 s = GetLineStringWithoutNL(buffer, it.index); - if (s.len == 0) continue; - IKnowWhatImDoing_ReplaceText(temp_buffer, GetEndAsRange(temp_buffer), s); - IKnowWhatImDoing_ReplaceText(temp_buffer, GetEndAsRange(temp_buffer), L"\n"); - } - - Command_SelectEntireBuffer(view); - Command_Replace(view, GetString(temp_buffer)); - Command_SelectRangeOneCursor(view, Rng(0)); -} - -void Command_SelectTitlebarCommand(Window *window, String16 needle) { - BSet title = GetTitleSet(window); - String16 buffer_string = GetString(title.buffer); - ActiveWindow = title.window->id; - - Scratch scratch; - String16 quoted16 = {}; - { - String needle8 = ToString(scratch, needle); - String quoted = Format(scratch, "%.*s\")", FmtString(needle8)); - quoted16 = ToString16(scratch, quoted); - } - - int64_t index = 0; - if (Seek(buffer_string, needle, &index)) { - Command_SelectRangeOneCursor(title.view, Rng(index + needle.len)); - } else { - Command_SelectRangeOneCursor(title.view, GetEndAsRange(title.buffer)); - Command_Replace(title.view, quoted16); - Command_SelectRangeOneCursor(title.view, GetEndAsRange(title.buffer) - 2); - } -} - -void GlobalCommand(Event event) { - ProfileFunction(); - - // - // Window cursor setting - // - Scratch scratch; - Array order = GetWindowZOrder(scratch); - { - static SDL_Cursor *SDL_MouseCursor; - if (SDL_MouseCursor) { - SDL_DestroyCursor(SDL_MouseCursor); - SDL_MouseCursor = NULL; - } - - Vec2I mouse = MouseVec2I(); - For(order) { - Window *window = Windows[it].o; - if (!window->visible) continue; - bool mouse_in_total = CheckCollisionPointRec(mouse, window->total_rect); - bool mouse_in_scrollbar = CheckCollisionPointRec(mouse, window->scrollbar_rect); - - if (!IsDocumentSelectionValid() && (mouse_in_scrollbar || IsScrollbarSelectionValid())) { - SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); - SDL_SetCursor(SDL_MouseCursor); - break; - } else if (mouse_in_total || IsDocumentSelectionValid()) { - SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT); - SDL_SetCursor(SDL_MouseCursor); - break; - } - } - - if (!SDL_MouseCursor) { - SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); - SDL_SetCursor(SDL_MouseCursor); - } - } - - // Handle wheel scrolling - if (event.wheel.x || event.wheel.y) { - Vec2I mouse = MouseVec2I(); - - For(order) { - Window *window = Windows[it].o; - if (!window->visible) continue; - - bool mouse_in_window = CheckCollisionPointRec(mouse, window->total_rect); - if (mouse_in_window) { - View *view = GetView(window->active_view); - view->scroll.y -= (Int)(event.wheel.y * 48); - view->scroll.x += (Int)(event.wheel.x * 48); - break; - } - } - } - - // Handle selected window scrollbar - // @note: the order here assumes that we won't run this code on the - // same event as the scroll was pressed - if (IsScrollbarSelectionValid() && Mouse(LEFT_UP)) { - Assert(DocumentSelected.id == -1); - ScrollbarSelected.id = -1; - } else if (IsScrollbarSelectionValid()) { - // :ScrollbarImprovement - // @todo: it generally works ok but it moves the scrollbar a bit on click - // when mouse is not even moving - Assert(DocumentSelected.id == -1); - Window *window = GetWindow(ScrollbarSelected); - View *view = GetView(window->active_view); - Vec2 mouse_vec2 = MouseVec2(); - Scroller s = ComputeScrollerRect(window); - double size_y = (double)GetSize(window->scrollbar_rect).y; - double p = mouse_vec2.y - window->scrollbar_rect.min.y; - double v = p / size_y; - v = v + (window->mouse_scroller_offset); - view->scroll.y = (Int)(v * (double)s.line_count * (double)FontLineSpacing); - } - - if (DocumentSelected != ActiveWindow) { - DocumentSelected.id = -1; - } else if (IsDocumentSelectionValid() && MouseUp()) { - Assert(ScrollbarSelected.id == -1); - DocumentSelected.id = -1; - } else if (IsDocumentSelectionValid()) { - Assert(ScrollbarSelected.id == -1); - BSet selected = GetBSet(DocumentSelected); - - Vec2I mouse = MouseVec2I(); - // Special case for full-screen where we can have document - // aligned with monitor screen in which case mouse cursor cannot - // be smaller then 0 which means we cannot scroll - if (mouse.y == 0 && selected.window->document_rect.min.y == 0) { - float x, y; - SDL_GetGlobalMouseState(&x, &y); - if (y == 0) { - mouse.y = -10; - } - } - - Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse); - Caret &caret = selected.view->carets[0]; - - caret = SetFrontWithAnchor(caret, DocumentRangeAnchor, p); - } - - // Set active window on click - if (MousePress()) { - Vec2I mouse = MouseVec2I(); - For(order) { - Window *window = Windows[it].o; - if (!window->visible) continue; - bool mouse_in_document = CheckCollisionPointRec(mouse, window->document_rect); - if (mouse_in_document) { - ActiveWindow = window->id; - break; - } - } - } - - if (event.ctrl && event.shift && Mouse(RIGHT)) { - GotoForward(GetActiveMainSet().window); - } else if (event.alt && event.ctrl && Mouse(RIGHT)) { - } else if (event.ctrl && Mouse(RIGHT)) { - GotoBackward(GetActiveMainSet().window); - } else if (event.alt && Mouse(RIGHT)) { - } else if (Mouse(RIGHT)) { - Vec2I mouse = MouseVec2I(); - BSet active = GetActiveSet(); - bool mouse_in_document = CheckCollisionPointRec(mouse, active.window->document_rect); - if (mouse_in_document) { - Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); - Int saved_front = -1; - - IterRemove(active.view->carets) { - IterRemovePrepare(active.view->carets); - if (InBounds(it.range, p)) { - String16 string = GetString(active.buffer, it.range); - SaveStringInClipboard(string); - - remove_item = true; - saved_front = GetFront(it); - } - } - if (active.view->carets.len == 0) Add(&active.view->carets, MakeCaret(saved_front)); - - if (saved_front == -1) { - Int line = PosToLine(active.buffer, p); - Range line_range = GetLineRangeWithoutNL(active.buffer, line); - String16 string = GetString(active.buffer, line_range); - SaveStringInClipboard(string); - } - } - } - - // @todo: maybe move some of this stuff to window command ??? - // for now let's leave it because we are relaying on global state - // - maybe just do the check if active window is matching the DocumentSelected window - // - if scrollbar selected then don't invoke window command - if (event.ctrl && event.shift && Mouse(LEFT)) { - MouseExecWord(event); - } else if (event.ctrl && Mouse(LEFT)) { - MouseLoadWord(event); - } else if (Mouse(LEFT)) { // CTRL SHIFT - Vec2I mouse = MouseVec2I(); - { - Assert(ScrollbarSelected.id == -1); - Assert(DocumentSelected.id == -1); - - BSet active = GetActiveSet(); - bool mouse_in_document = CheckCollisionPointRec(mouse, active.window->document_rect); - bool mouse_in_line_numbers = CheckCollisionPointRec(mouse, active.window->line_numbers_rect); - if (mouse_in_document || mouse_in_line_numbers) { - DocumentSelected = active.window->id; - - Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); - if (event.alt) Insert(&active.view->carets, MakeCaret(p, p), 0); - if (!event.alt && !event.shift) active.view->carets.len = 1; - - Caret &caret = active.view->carets[0]; - if (event.shift) { - if (p <= caret.range.min) { - caret.range.min = p; - caret.ifront = 0; - } else if (p >= caret.range.max) { - caret.range.max = p; - caret.ifront = 1; - } - } else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) { - Range range = EncloseWord(active.buffer, p); - if (event.clicks >= 3) range = EncloseLoadWord(active.buffer, p); - caret = MakeCaret(range.max, range.min); - } else { - caret = MakeCaret(p); - } - MergeCarets(active.view); - DocumentRangeAnchor = caret.range; - } - } - - // Figure out scrollbar click - // :ScrollbarImprovement - // @todo: it generally works ok but it moves the scrollbar a bit on click - // when mouse is not even moving - For(order) { - Window *window = Windows[it].o; - if (!window->visible) continue; - bool mouse_in_scrollbar = CheckCollisionPointRec(mouse, window->scrollbar_rect); - if (mouse_in_scrollbar) { - ScrollbarSelected = window->id; - - View *view = GetView(window->active_view); - Vec2 mouse_vec2 = MouseVec2(); - Scroller s = ComputeScrollerRect(window); - double size_y = (double)GetSize(window->scrollbar_rect).y; - double p = mouse_vec2.y - window->scrollbar_rect.min.y; - if (mouse_vec2.y < s.rect.min.y || mouse_vec2.y > s.rect.max.y) { - view->scroll.y = (Int)(p / size_y * (double)s.line_count * (double)FontLineSpacing); - window->mouse_scroller_offset = -(double)GetSize(s.rect).y / 2.0 / size_y; - } else { - window->mouse_scroller_offset = (s.rect.min.y - p) / size_y; - } - break; - } - } - } - - if (Ctrl(SDLK_P)) { - Command_ListBuffers(); - } - - if (CtrlShift(SDLK_BACKSLASH)) { - AddRowWindow(); - } else if (Ctrl(SDLK_BACKSLASH)) { - AddColumnWindow(); - } - - if (Ctrl(SDLK_0)) { - ToggleVisibility(DebugWindowID); - } - - if (Press(SDLK_F5)) { - AppIsRunning = false; - } - - if (Press(SDLK_F11)) { - ToggleFullscreen(); - } - - if (Ctrl(SDLK_1)) { - Window *window = GetLayoutWindow(0); - if (window) ActiveWindow = window->id; - } - if (Ctrl(SDLK_2)) { - Window *window = GetLayoutWindow(1); - if (window) ActiveWindow = window->id; - } - if (Ctrl(SDLK_3)) { - Window *window = GetLayoutWindow(2); - if (window) ActiveWindow = window->id; - } - - BSet active = GetActiveSet(); - - if (event.kind == EVENT_DROP_FILE) { - WindowOpenBufferView(active.window, event.text); - } - - if (CtrlAlt(SDLK_DOWN)) { - Command_DuplicateLine(active.view, DIR_DOWN); - } else if (AltShift(SDLK_DOWN)) { - Command_CreateCursorVertical(active.view, DIR_DOWN); - } else if (CtrlShift(SDLK_DOWN)) { - Command_Move(active.view, DIR_DOWN, CTRL_PRESSED, SHIFT_PRESSED); - } else if (Alt(SDLK_DOWN)) { - Command_MoveLine(active.view, DIR_DOWN); - } else if (Ctrl(SDLK_DOWN)) { - Command_Move(active.view, DIR_DOWN, CTRL_PRESSED); - } else if (Shift(SDLK_DOWN)) { - Command_Move(active.view, DIR_DOWN, false, SHIFT_PRESSED); - } else if (Press(SDLK_DOWN)) { - Command_Move(active.view, DIR_DOWN); - } - - if (CtrlAlt(SDLK_UP)) { - Command_DuplicateLine(active.view, DIR_UP); - } else if (AltShift(SDLK_UP)) { - Command_CreateCursorVertical(active.view, DIR_UP); - } else if (CtrlShift(SDLK_UP)) { - Command_Move(active.view, DIR_UP, CTRL_PRESSED, SHIFT_PRESSED); - } else if (Alt(SDLK_UP)) { - Command_MoveLine(active.view, DIR_UP); - } else if (Ctrl(SDLK_UP)) { - Command_Move(active.view, DIR_UP, CTRL_PRESSED); - } else if (Shift(SDLK_UP)) { - Command_Move(active.view, DIR_UP, false, SHIFT_PRESSED); - } else if (Press(SDLK_UP)) { - Command_Move(active.view, DIR_UP); - } - - if (CtrlShift(SDLK_LEFT)) { - Command_Move(active.view, DIR_LEFT, CTRL_PRESSED, SHIFT_PRESSED); - } else if (Ctrl(SDLK_LEFT)) { - Command_Move(active.view, DIR_LEFT, CTRL_PRESSED); - } else if (Shift(SDLK_LEFT)) { - Command_Move(active.view, DIR_LEFT, false, SHIFT_PRESSED); - } else if (Press(SDLK_LEFT)) { - Command_Move(active.view, DIR_LEFT); - } - - if (CtrlShift(SDLK_RIGHT)) { - Command_Move(active.view, DIR_RIGHT, CTRL_PRESSED, SHIFT_PRESSED); - } else if (Ctrl(SDLK_RIGHT)) { - Command_Move(active.view, DIR_RIGHT, CTRL_PRESSED); - } else if (Shift(SDLK_RIGHT)) { - Command_Move(active.view, DIR_RIGHT, false, SHIFT_PRESSED); - } else if (Press(SDLK_RIGHT)) { - Command_Move(active.view, DIR_RIGHT); - } - - if (CtrlShift(SDLK_Z)) { - RedoEdit(active.buffer, &active.view->carets); - } else if (Ctrl(SDLK_Z)) { - UndoEdit(active.buffer, &active.view->carets); - } - - if (Ctrl(SDLK_C)) { - Command_Copy(active.view); - } else if (Ctrl(SDLK_V)) { - Command_Paste(active.view); - } else if (Ctrl(SDLK_X)) { - PreBeginEdit_SaveCaretHistory(active.buffer, active.view->carets); - Command_Copy(active.view); - Command_Replace(active.view, L""); - } - - if (Ctrl(SDLK_A)) { - Command_SelectEntireBuffer(active.view); - active.view->update_scroll = false; - } - - if (Shift(SDLK_PAGEUP)) { - Command_MoveCursorsByPageSize(active.window, DIR_UP, SHIFT_PRESSED); - } else if (Press(SDLK_PAGEUP)) { - Command_MoveCursorsByPageSize(active.window, DIR_UP); - } - - if (Shift(SDLK_PAGEDOWN)) { - Command_MoveCursorsByPageSize(active.window, DIR_DOWN, SHIFT_PRESSED); - } else if (Press(SDLK_PAGEDOWN)) { - Command_MoveCursorsByPageSize(active.window, DIR_DOWN); - } - - if (Shift(SDLK_HOME)) { - Command_MoveCursorsToSide(active.view, DIR_LEFT, SHIFT_PRESSED); - } else if (Press(SDLK_HOME)) { - Command_MoveCursorsToSide(active.view, DIR_LEFT); - } - - if (Shift(SDLK_END)) { - Command_MoveCursorsToSide(active.view, DIR_RIGHT, SHIFT_PRESSED); - } else if (Press(SDLK_END)) { - Command_MoveCursorsToSide(active.view, DIR_RIGHT); - } - - if (Shift(SDLK_TAB)) { - Command_IndentSelectedLines(active.view, SHIFT_PRESSED); - } else if (Press(SDLK_TAB)) { - Command_IndentSelectedLines(active.view); - } - - if (CtrlShift(SDLK_K)) { - Command_KillSelectedLines(active.view); - } - - if (Ctrl(SDLK_BACKSPACE)) { - Command_Delete(active.view, DIR_LEFT, CTRL_PRESSED); - } else if (Press(SDLK_BACKSPACE)) { - Command_Delete(active.view, DIR_LEFT); - } - - if (Ctrl(SDLK_DELETE)) { - Command_Delete(active.view, DIR_RIGHT, CTRL_PRESSED); - } else if (Press(SDLK_DELETE)) { - Command_Delete(active.view, DIR_RIGHT); - } - - if (event.kind == EVENT_TEXT_INPUT) { - Scratch scratch; - String string = event.text; - String16 string16 = ToString16(scratch, string); - Command_Replace(active.view, string16); - } - - if (Ctrl(SDLK_D)) { - String16 string = GetString(active.buffer, active.view->carets[0].range); - Caret caret = FindNext(active.buffer, string, active.view->carets[0]); - Insert(&active.view->carets, caret, 0); - MergeCarets(active.view); - } - - if (Shift(SDLK_F3)) { - Scratch scratch; - BSet main = GetActiveMainSet(); - String16 search_string = ToString16(scratch, main.window->search_string); - Caret caret = FindPrev(main.buffer, search_string, main.view->carets[0]); - Command_SelectRangeOneCursor(main.view, caret); - } else if (Press(SDLK_F3)) { - Scratch scratch; - BSet main = GetActiveMainSet(); - String16 search_string = ToString16(scratch, main.window->search_string); - Caret caret = FindNext(main.buffer, search_string, main.view->carets[0]); - Command_SelectRangeOneCursor(main.view, caret); - } - - if (Shift(SDLK_F4)) { - Command_GotoNextInList(active.window, -1); - } else if (Press(SDLK_F4)) { - Command_GotoNextInList(active.window, 1); - } - - if (CtrlShift(SDLK_RETURN)) { - Command_MoveCursorsToSide(active.view, DIR_LEFT); - Command_IdentedNewLine(active.view); - Command_Move(active.view, DIR_UP); - } else if (Ctrl(SDLK_RETURN)) { - Command_MoveCursorsToSide(active.view, DIR_RIGHT); - Command_IdentedNewLine(active.view); - } else if (Press(SDLK_RETURN)) { - Command_IdentedNewLine(active.view); - } - - if (Ctrl(SDLK_F)) { - Command_SelectTitlebarCommand(active.window, L"#Search(\""); - } - - if (Ctrl(SDLK_S)) { - SaveBuffer(active.view); - } - - if (Ctrl(SDLK_PERIOD)) { - BSet main = GetActiveMainSet(); - String name = ChopLastSlash(main.buffer->name); - Open(name); - } - - if (CtrlShift(SDLK_G)) { - } else if (Ctrl(SDLK_G)) { - Command_SelectTitlebarCommand(active.window, L"#FuzzySort(\""); - } - - if (CtrlShift(SDLK_W)) { - GotoForward(GetActiveMainSet().window); - } else if (Ctrl(SDLK_W)) { - GotoBackward(GetActiveMainSet().window); - } - - if (CtrlShift(SDLK_Q)) { - Caret caret = active.view->carets[0]; - Range range = caret.range; - if (GetSize(caret.range) == 0) range = EncloseExecWord(active.buffer, GetFront(caret)); - String16 string = GetString(active.buffer, range); - - Command_EvalLua(active.view, string); - } else if (Ctrl(SDLK_Q)) { - Caret caret = active.view->carets[0]; - Range range = caret.range; - if (GetSize(caret.range) == 0) range = EncloseLoadWord(active.buffer, GetFront(caret)); - String16 string = GetString(active.buffer, range); - - active.window->active_goto_list = active.view->id; - Open(string); - } - - if (Press(SDLK_ESCAPE)) { - if (active.window->deactivate_on_escape) { - ActiveWindow = GetActiveMainSet().window->id; - } else { - active.view->carets.len = 1; - } - } - - MergeCarets(active.view); - IF_DEBUG(AssertRanges(active.view->carets)); -} - -void UpdateScroll(Window *window, bool update_caret_scrolling) { - ProfileFunction(); - BSet set = GetBSet(window); - - // Scrolling with caret - if (update_caret_scrolling) { - Caret c = set.view->carets[0]; - Int front = GetFront(c); - XY xy = PosToXY(set.buffer, front); - - Rect2I visible = GetVisibleCells(window); - Vec2I visible_cells = GetSize(visible); - Vec2I visible_size = visible_cells * Vec2I{FontCharSpacing, FontLineSpacing}; - Vec2I rect_size = GetSize(window->document_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); - set.view->scroll.y = (set_view_at_line * FontLineSpacing) + cut_off_y; - } - - if (xy.line < visible.min.y + 1) { - set.view->scroll.y = xy.line * FontLineSpacing; - } - - 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); - set.view->scroll.x = (set_view_at_line * FontCharSpacing) + cut_off_x; - } - - if (xy.col <= visible.min.x) { - set.view->scroll.x = xy.col * FontCharSpacing; - } - } - - // Clip scroll - { - Int last_line = LastLine(set.buffer); - set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * FontLineSpacing)); - - // @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? - set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0); - } -} \ No newline at end of file diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 7e05cfa..94cecf3 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -35,9 +35,9 @@ int FullScreenPositionX, FullScreenPositionY; #include "management.cpp" #include "window.cpp" #include "process.cpp" -#include "commands_clipboard.cpp" #include "commands.cpp" -#include "commands_window.cpp" +#include "commands_clipboard.cpp" +#include "commands_bindings.cpp" #include "title_bar.cpp" #include "lua.hpp"