From 266378154a0ee2329ad926075c8245ee07620c14 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sat, 20 Dec 2025 18:17:28 +0100 Subject: [PATCH] Windows refactor and moving things around --- src/backup/todo.txt | 29 +- src/text_editor/buffer.cpp | 14 + src/text_editor/commands.cpp | 636 +----------------- src/text_editor/globals.cpp | 5 +- src/text_editor/lua_api.cpp | 5 - src/text_editor/process.cpp | 7 +- src/text_editor/text_editor.cpp | 60 +- src/text_editor/text_editor.h | 4 + src/text_editor/view.cpp | 491 ++++++++++++++ src/text_editor/view.h | 1 - src/text_editor/window.cpp | 129 +--- src/text_editor/window.h | 15 +- src/text_editor/window_command.cpp | 133 ++++ .../{title_bar.cpp => window_debug.cpp} | 108 +-- src/text_editor/window_search.cpp | 31 + src/text_editor/window_status.cpp | 95 +++ 16 files changed, 865 insertions(+), 898 deletions(-) create mode 100644 src/text_editor/window_command.cpp rename src/text_editor/{title_bar.cpp => window_debug.cpp} (50%) create mode 100644 src/text_editor/window_search.cpp create mode 100644 src/text_editor/window_status.cpp diff --git a/src/backup/todo.txt b/src/backup/todo.txt index ed7dc9a..4ed2b25 100644 --- a/src/backup/todo.txt +++ b/src/backup/todo.txt @@ -1,3 +1,12 @@ +- What precise workflow do I need for me to be viable to use this? +- From a user (novice) point of view, how does it look like? + +- Move things to window_ .cpp files (command, build, search, debug, status .. ) +- Remove LUA and replace with keybindings, config, commands +- "DO YOU REALLY WANT TO QUIT?" popup +- open all in the folder and ctrl + p like in VSCode (without special buffers) +- Guide on the first page for new users with links to configs, tutorials + - Window, View, Buffer + flags design (or is completely new kind based approach needed for Windows/Views?) - How to make non-editable, informative, with different font size, title bar. Which might also contain tabs - How to design clickable tree view in this way? @@ -26,30 +35,14 @@ Commands TODO: backlog -DESIGN try to make console less special, make stuff reusable etc. -DESIGN Config file versions, when loading should be checked, at the top of the file, what to do when old version? ISSUE Ctrl+Alt+Down (DuplicateLine) doesn't work on ubuntu -DESIGN Moving vertically between splits, which keys??? -DESIGN Console, when writing commands and evaling in console it should properly insert a new line after our thing when writing on last line -DESIGN The cursor hopping history needs a bit of fixing, probably needs to be more complicated with collapsing commands -FEATURE KillConsole, or maybe some window targetting but how?? -ISSUE I hit a case where GC tried deleting a buffer which was not attached to the buffer list, it had zeroed next, prev. It happened after iterating through directories using the ctrl + period FEATURE Search whole words, case sensitive etc. FEATURE Select all searched occurences -PLATFORM Fix windows build -ISSUE What to do / how should Reopen work after we delete the file on disk in some other program? -DESIGN Changing window properties by changing the window name? FEATURE commands for scrolling: center, cursor_at_bottom_of_screen, cursor_at_top FEATURE Kill buffer command (it should be marked for deletion and deleted at the end of frame!) -DESIGN Check. Convert more commands to taking buffer instead of view -DESIGN Check. Rewrite more commands to use already implemented commands? -FEATURE Lua namespaces for different kinds of commands (by ids, using main_set, using active_set)? FEATURE Some decl/function indexing in fuzzy format ISSUE? Fix jump scroll, the query ends up the last line on screen, kind of wacky FEATURE dump text editor state to file, restore state -DESIGN ask user if he really wants to quit even though he has an unsaved buffer - popup window | we could just show ForceClose() in the titlebar -FEATURE Find matches using grep, change things in that buffer then apply those changes to all items -FEATURE group history entries so the you can rollback through multiple ones at once and not waste time on skipping whitespace trimming or deleting every character - Search and replace - word complete - Search all buffers in 10X style, incrementally searched results popping up on every key press (maybe we need coroutine library in C so this is easier?) @@ -58,7 +51,6 @@ FEATURE group history entries so the you can rollback through multiple ones at o - code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey! - combine glyph and selection rendering -- expose a coroutine based scripting enviorment where user can execute shell commands wait for them and perform actions in very linear manner - Test stdin writing code - Implement shell interaction (the valid cmd lines should start with '>' or '$', user can add more lines like this to expand the command size maybe?, if we have a case where we have a line with '>' but the last line doesn't have (just a space) then it should execute?) - drop text into window @@ -68,9 +60,6 @@ FEATURE group history entries so the you can rollback through multiple ones at o - I want a way to assign flags to buffers/views/windows from user perspective so that console window concept can be created from user space - font cache and on demand unicode loads - color parens, braces -- redo tree - gap buffer - optimize rendering - command buffer, and vertice buffer instead of vertice buffer with scissor - -!!As little lua code as possible, but lua code should be powerful just in case of quick edits \ No newline at end of file diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 53b8847..228cd82 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1572,3 +1572,17 @@ bool BufferIsReferenced(BufferID buffer_id) { return false; } + +void ReopenBuffer(Buffer *buffer) { + Scratch scratch; + String string = ReadFile(scratch, buffer->name); + if (string.len == 0) { + return; + } + + String16 string16 = ToUnixString16(scratch, string); + ReplaceWithoutMovingCarets(buffer, GetRange(buffer), string16); + buffer->file_mod_time = GetFileModTime(buffer->name); + buffer->changed_on_disk = false; + buffer->dirty = false; +} diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 8130af4..2b1bc50 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -310,309 +310,6 @@ void MoveCursorByPageSize(Window *window, int direction, bool shift = false) { } } -void MoveCursorToSide(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) { - if (PosToLine(buffer, front) == 0) { - it = SetFront(it, 0); - } else { - 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) { - if (LastLine(buffer) == PosToLine(buffer, front)) { - it = SetFront(it, buffer->len); - } else { - 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 MoveCarets(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 MoveCaretsLine(View *view, int direction) { - Assert(direction == DIR_DOWN || direction == DIR_UP); - Scratch scratch; - - // @todo: this doesn't work well at the end of buffer - struct XYPair { - XY front; - XY back; - }; - - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(buffer, &view->carets); - Array saved_xy = {scratch}; - For(view->carets) { - Int eof_current = 0; - Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max, &eof_current)}; - if (lines_to_move_range.min == 0 && direction == DIR_UP) { - continue; - } - - Int eof = 0; - Int next_line_start = lines_to_move_range.max; - Int next_line_end = GetFullLineEnd(buffer, next_line_start, &eof); - Int prev_line_end = lines_to_move_range.min - 1; - Int prev_line_start = GetFullLineStart(buffer, prev_line_end); - - if (direction == DIR_DOWN && eof) { - continue; - } else if (direction == DIR_UP && eof_current) { - continue; - } - - String16 string = Copy16(scratch, GetString(buffer, lines_to_move_range)); - - AddEdit(&edits, lines_to_move_range, {}); - if (direction == DIR_DOWN) { - AddEdit(&edits, MakeRange(next_line_end), string); - } else { - AddEdit(&edits, MakeRange(prev_line_start), string); - } - - Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))}); - } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - - Int line_offset = direction == DIR_UP ? -1 : +1; - 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 ReplaceEx(Allocator scratch, View *view, String16 string) { - Buffer *buffer = GetBuffer(view->active_buffer); - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(buffer, &view->carets); - For(view->carets) AddEdit(&edits, it.range, string); - EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); - return edits; -} - -void Replace(View *view, String16 string) { - Scratch scratch; - ReplaceEx(scratch, view, string); -} - -void 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); - MergeCarets(buffer, &view->carets); - - Array edits = {scratch}; - For(view->carets) { - Int eof = 0; - Range range = {}; - range.max = GetFullLineEnd(buffer, it.range.max, &eof); - range.min = GetFullLineStart(buffer, it.range.min); - range.min -= Clamp(eof, (Int)0, buffer->len); - String16 string = Copy16(scratch, GetString(buffer, range)); - - Int pos = direction == DIR_UP ? range.min : range.max; - AddEdit(&edits, MakeRange(pos), string); - } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - - Int coef = direction == DIR_UP ? -1 : 1; - for (Int i = 0; i < edits.len; i += 1) { - Caret *caret = view->carets.data + i; - - XY xymin = PosToXY(buffer, caret->range.min); - XY xymax = PosToXY(buffer, caret->range.max); - Int line_count = xymax.line - xymin.line + 1; - - xymin.line += coef * line_count; - xymax.line += coef * line_count; - - caret->range.min = XYToPos(buffer, xymin); - caret->range.max = XYToPos(buffer, xymax); - } -} - -Array GetSelectedLinesSorted(Allocator allocator, View *view) { - Scratch scratch(allocator); - Buffer *buffer = GetBuffer(view->active_buffer); - Array caret_copy = TightCopy(scratch, view->carets); - Array temp = TightCopy(scratch, view->carets); - if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data); - - Array result = {allocator}; - For(caret_copy) { - Int min_line = PosToLine(buffer, it.range.min); - Int max_line = PosToLine(buffer, it.range.max); - Range line_range = {min_line, max_line + 1}; - - if (result.len == 0) { - Add(&result, line_range); - continue; - } - - Range *last = GetLast(result); - if (AreOverlapping(*last, line_range)) { - last->max = Max(last->max, line_range.max); - } else { - Add(&result, line_range); - } - } - return result; -} - -void IndentSelectedLines(View *view, bool shift = false) { - Scratch scratch; - Buffer *buffer = GetBuffer(view->active_buffer); - - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(buffer, &view->carets); - - 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 = {u" ", 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}, u""); - } - } - } - EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); - view->update_scroll = false; -} - void TrimTrailingWhitespace(Buffer *buffer, bool trim_lines_with_caret = false) { Scratch scratch; @@ -737,167 +434,6 @@ void EncloseScope(View *view) { } } -void EncloseSpace(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - For (view->carets) { - it.range.min = GetPrevEmptyLineStart(buffer, it.range.min); - it.range.max = GetNextEmptyLineStart(buffer, it.range.max); - } -} - -void 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_PRESS); - } - - } else { - it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESS); - } - } else { - it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESS); - } - } - - MergeCarets(buffer, &view->carets); - For(view->carets) AddEdit(&edits, it.range, {}); - EndEdit(buffer, &edits, &view->carets, true); -} - -void 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(buffer, &view->carets); -} - -void CreateCursorVertical(View *view, int direction) { - Assert(direction == DIR_UP || direction == DIR_DOWN); - 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(buffer, &view->carets); -} - -void SelectRange(View *view, Caret caret) { - view->carets.len = 1; - view->carets[0] = caret; -} - -void SelectRange(View *view, Range range) { - SelectRange(view, MakeCaret(range.min, range.max)); -} - -void SelectEntireBuffer(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - SelectRange(view, GetRange(buffer)); -} - -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 = GetMax(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 IdentedNewLine(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - Scratch scratch; - Array edits = BeginEdit(scratch, buffer, view->carets); - MergeCarets(buffer, &view->carets); - 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, KILL_SELECTION); -} - -void 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); - SelectRange(seek_view, caret); - - IF_DEBUG(AssertRanges(seek_view->carets)); -} - void GotoNextInList(Window *window, Int line_offset = 1) { Assert(line_offset == 1 || line_offset == -1); View *active_view = GetView(window->active_view); @@ -935,39 +471,6 @@ void GotoNextInList(Window *window, Int line_offset = 1) { if (!opened) window->active_view = active_view->id; } -void FuzzySortView(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; - RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), s); - RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), u"\n"); - } - - SelectEntireBuffer(view); - Replace(view, GetString(temp_buffer)); - SelectRange(view, MakeRange(0)); -} - -void ReopenBuffer(Buffer *buffer) { - Scratch scratch; - String string = ReadFile(scratch, buffer->name); - if (string.len == 0) { - return; - } - - String16 string16 = ToUnixString16(scratch, string); - ReplaceWithoutMovingCarets(buffer, GetRange(buffer), string16); - buffer->file_mod_time = GetFileModTime(buffer->name); - buffer->changed_on_disk = false; - buffer->dirty = false; -} - void New(Window *window, String name = "") { View *view = GetView(window->active_view); Buffer *buffer = GetBuffer(view->active_buffer); @@ -1114,14 +617,6 @@ void SetProjectFile(Buffer *buffer) { LuaProjectBuffer->user_change_id = -1; } -String16 FetchLoadWord(BSet set) { - Caret caret = set.view->carets[0]; - Range range = caret.range; - if (GetSize(caret.range) == 0) range = EncloseLoadWord(set.buffer, GetFront(caret)); - String16 string = GetString(set.buffer, range); - return string; -} - void Command_Save() { BSet active = GetBSet(LastActiveLayoutWindowID); SaveBuffer(active.buffer); @@ -1163,43 +658,6 @@ void Command_ToggleFullscreen() { IsInFullscreen = !IsInFullscreen; } RegisterCommand(Command_ToggleFullscreen, "f11"); -void Command_ListCode() { - BSet main = GetBSet(LastActiveLayoutWindowID); - JumpGarbageBuffer(&main); - ListFilesRecursive(main.buffer, WorkDir); - main.view->fuzzy_search = true; - main.view->update_scroll = true; - SelectRange(main.view, GetBufferEndAsRange(main.buffer)); -} RegisterCommand(Command_ListCode, ""); - -void Command_ShowBufferList() { - BSet command_bar = GetBSet(CommandBarWindowID); - command_bar.window->visible = true; - command_bar.window->eval_command = false; - ActiveWindowID = command_bar.window->id; - ResetBuffer(command_bar.buffer); - For(Buffers) { - RawAppendf(command_bar.buffer, "%-80S || %S\n", SkipToLastSlash(it->name), it->name); - } - command_bar.view->update_scroll = true; - SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); -} RegisterCommand(Command_ShowBufferList, "ctrl-p"); - -void Command_ListViews() { - BSet command_bar = GetBSet(CommandBarWindowID); - command_bar.window->visible = true; - command_bar.window->eval_command = false; - ActiveWindowID = command_bar.window->id; - ResetBuffer(command_bar.buffer); - For(Views) { - Buffer *buffer = GetBuffer(it->active_buffer); - Appendf(command_bar.view, "%d %S\n", (int)it->id.id, buffer->name); - } - command_bar.view->fuzzy_search = true; - command_bar.view->update_scroll = true; - SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); -} RegisterCommand(Command_ListViews, ""); - void Command_SetProjectFile() { BSet main = GetBSet(LastActiveLayoutWindowID); SetProjectFile(main.buffer); @@ -1220,11 +678,6 @@ void Command_SetProject() { Command_SetProjectFile(); } RegisterCommand(Command_SetProject, ""); -void Command_ToggleDebug() { - Window *window = GetWindow(DebugWindowID); - window->visible = !window->visible; -} RegisterCommand(Command_ToggleDebug, "ctrl-0"); - void Command_KillProcess() { BSet main = GetBSet(LastActiveLayoutWindowID); KillProcess(main.view); @@ -1235,32 +688,6 @@ void Command_KillWindow() { main.window->kill = true; } RegisterCommand(Command_KillWindow, "ctrl-w"); -void Command_ShowCommands() { - BSet command_bar = GetBSet(CommandBarWindowID); - command_bar.window->visible = true; - command_bar.window->eval_command = true; - ActiveWindowID = command_bar.window->id; - ResetBuffer(command_bar.buffer); - For(CommandFunctions) { - Appendf(command_bar.view, "%S\n", it.name); - } - command_bar.view->update_scroll = true; - SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); -} RegisterCommand(Command_ShowCommands, "ctrl-shift-p"); - -void Command_ShowLuaFunctions() { - BSet command_bar = GetBSet(CommandBarWindowID); - command_bar.window->visible = true; - command_bar.window->eval_command = false; - ActiveWindowID = command_bar.window->id; - ResetBuffer(command_bar.buffer); - For(LuaFunctions) { - Appendf(command_bar.view, "%S()\n ", it.name); - } - command_bar.view->update_scroll = true; - SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); -} RegisterCommand(Command_ShowLuaFunctions, ""); - void Command_GotoBackward() { BSet main = GetBSet(LastActiveLayoutWindowID); GotoBackward(main.window); @@ -1313,56 +740,13 @@ void Command_MakeFontSmaller() { } } RegisterCommand(Command_MakeFontSmaller, "ctrl-minus"); -void Command_Search() { - Window *window = GetWindow(SearchBarWindowID); - window->visible = !window->visible; -} RegisterCommand(Command_Search, "ctrl-f"); - -void EvalCommand(String command) { - For (CommandFunctions) { - if (it.name == command) { - it.function(); - break; - } - } -} - -void EvalCommand(String16 command) { - Scratch scratch; - EvalCommand(ToString(scratch, command)); -} - -void FuzzySearchOpen(BSet active) { - Range range = active.view->carets[0].range; - String16 string = FetchLoadWord(active); - if (GetSize(range) == 0) { - Int line = PosToLine(active.buffer, range.min); - if ((active.buffer->line_starts.len - 1) == line) { - line = ClampBottom(0ll, line - 1ll); - } - - string = GetLineStringWithoutNL(active.buffer, line); - Int idx = 0; - if (Seek(string, u"||", &idx)) { - string = Skip(string, idx + 3); - } - } - if (active.window->eval_command) { - BSet main = GetBSet(LastActiveLayoutWindowID); - ActiveWindowID = main.window->id; - EvalCommand(string); - } else { - Open(string); - } -} - void Command_Open() { BSet active = GetBSet(ActiveWindowID); - if (active.view->fuzzy_search) { - FuzzySearchOpen(active); + if (active.window->id == CommandBarWindowID) { + CommandWindowOpen(active); } else { BSet active = GetBSet(LastActiveLayoutWindowID); - Open(FetchLoadWord(active)); + Open(FetchLoadWord(active.view)); } } RegisterCommand(Command_Open, "ctrl-q"); @@ -1582,17 +966,13 @@ void Command_InsertNewLineDown() { IdentedNewLine(active.view); } RegisterCommand(Command_InsertNewLineDown, "ctrl-enter"); -void Command_SelectFuzzySearchEntry() { - BSet active = GetBSet(ActiveWindowID); - if (active.view->fuzzy_search) { - FuzzySearchOpen(active); - SkipRemainingCommands = true; - } -} RegisterCommand(Command_SelectFuzzySearchEntry, "enter"); - void Command_NewLine() { BSet active = GetBSet(ActiveWindowID); - IdentedNewLine(active.view); + if (active.window->id == CommandBarWindowID) { + CommandWindowOpen(active); + } else { + IdentedNewLine(active.view); + } } RegisterCommand(Command_NewLine, "enter"); void Command_CreateCaretOnNextFind() { diff --git a/src/text_editor/globals.cpp b/src/text_editor/globals.cpp index c7963f6..8fa0b3e 100644 --- a/src/text_editor/globals.cpp +++ b/src/text_editor/globals.cpp @@ -77,12 +77,15 @@ String Intern(InternTable *table, String string) { // optimize worst offenders (like event text) InternTable GlobalInternTable; -bool SkipRemainingCommands; Array CommandFunctions; Array LuaFunctions; Array TestFunctions; Array Variables; +Array ActiveProcesses = {}; +Array ProcessEnviroment = {}; + + /////////////////////////////// // CONFIG Color GruvboxDark0Hard = {0x1d, 0x20, 0x21, 0xff}; diff --git a/src/text_editor/lua_api.cpp b/src/text_editor/lua_api.cpp index 6b06e97..a404c80 100644 --- a/src/text_editor/lua_api.cpp +++ b/src/text_editor/lua_api.cpp @@ -232,11 +232,6 @@ int Lua_Cmd(lua_State *L) { BeginJump(&set); Exec(set.view->id, true, cmd, working_dir); EndJump(set); - } else if (kind == "fuzzy") { - JumpGarbageBuffer(&main); - Exec(main.view->id, true, cmd, working_dir); - main.view->fuzzy_search = true; - ActiveWindowID = main.window->id; } else { JumpGarbageBuffer(&main); main.window->active_goto_list = main.view->id; diff --git a/src/text_editor/process.cpp b/src/text_editor/process.cpp index e2adef7..eccabe2 100644 --- a/src/text_editor/process.cpp +++ b/src/text_editor/process.cpp @@ -1,6 +1,3 @@ -Array ActiveProcesses = {}; -Array Enviroment = {}; - // WARNING: seems that this maybe can't work reliably? // in case of 'git grep a' it's possible that child process spawns it's own // child process and then it won't print anything because it won't have @@ -25,7 +22,7 @@ void UpdateProcesses() { } void Exec(ViewID view, bool scroll_to_end, String cmd, String working_dir) { - Process process = SpawnProcess(cmd, working_dir, {}, Enviroment); + Process process = SpawnProcess(cmd, working_dir, {}, ProcessEnviroment); ReportDebugf("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, cmd, working_dir); process.view_id = view.id; process.scroll_to_end = scroll_to_end; @@ -42,7 +39,7 @@ Buffer *ExecAndWait(Allocator allocator, String cmd, String working_dir, String ReportDebugf("ExecAndWait cmd = %S working_dir = %S stdin_string = %S", cmd, working_dir, stdin_string); Buffer *temp_buffer = CreateTempBuffer(allocator, 4096 * 4); - for (Process process = SpawnProcess(cmd, working_dir, stdin_string, Enviroment); IsValid(&process);) { + for (Process process = SpawnProcess(cmd, working_dir, stdin_string, ProcessEnviroment); IsValid(&process);) { Scratch scratch(allocator); String poll = PollStdout(scratch, &process, true); if (poll.len) RawAppend(temp_buffer, poll); diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 4fcd67b..db07694 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -30,14 +30,19 @@ #include "buffer.cpp" #include "view.cpp" #include "window.cpp" +#include "window_command.cpp" +#include "window_debug.cpp" +#include "window_status.cpp" +#include "window_search.cpp" + #include "process.cpp" #include "event.cpp" #include "parser.cpp" + #include "commands.cpp" #include "lua_api.cpp" #include "commands_clipboard.cpp" -#include "title_bar.cpp" #include "generated_config.cpp" @@ -390,18 +395,11 @@ void OnCommand(Event event) { BSet main = GetBSet(LastActiveLayoutWindowID); BSet active = GetBSet(ActiveWindowID); + Int buffer_change_id = active.buffer->change_id; - // @todo: somehow detect in post command that buffer changed but random buffer can get changed??? Not sure - // maybe we use a timestamo instead ! - Int buffer_change_id = active.buffer->change_id; - - SkipRemainingCommands = false; For (CommandFunctions) { if (it.trigger && MatchEvent(it.trigger, &event)) { it.function(); - if (SkipRemainingCommands) { - break; - } } } @@ -414,42 +412,15 @@ void OnCommand(Event event) { String16 string16 = ToString16(scratch, event.text); Replace(active.view, string16); } - - if (active.view->fuzzy_search) { - if (!ProcessIsActive(active.view->id)) { - Scratch scratch; - String16 last_line_string = GetLineStringWithoutNL(active.buffer, active.buffer->line_starts.len - 1); - if (active.view->prev_search_line != last_line_string) { - active.view->prev_search_line = last_line_string; - Array ratings = FuzzySearchLines(scratch, active.buffer, 0, active.buffer->line_starts.len - 1, last_line_string); - - Buffer *temp_buffer = CreateTempBuffer(scratch, active.buffer->cap); - For(IterateInReverse(&ratings)) { - String16 s = GetLineStringWithoutNL(active.buffer, it.index); - if (s.len == 0) continue; - RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), s); - RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), u"\n"); - } - RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), last_line_string); - - Caret caret = active.view->carets[0]; - SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets); - SelectEntireBuffer(active.view); - Replace(active.view, GetString(temp_buffer)); - active.view->carets[0] = caret; - } - } - } - - MergeCarets(active.buffer, &active.view->carets); - IF_DEBUG(AssertRanges(active.view->carets)); - MergeCarets(main.buffer, &main.view->carets); - IF_DEBUG(AssertRanges(main.view->carets)); } void GarbageCollect() { Allocator sys_allocator = GetSystemAllocator(); + For (Views) { + IF_DEBUG(AssertRanges(it->carets)); + } + For (Windows) { if (it->sync_visibility_with_focus) { if (it->id == ActiveWindowID) { @@ -535,11 +506,12 @@ void Update(Event event) { } OnCommand(event); + StatusWindowUpdate(); + DebugWindowUpdate(); + CommandWindowUpdate(); UpdateProcesses(); CoUpdate(&event); ReloadLuaConfigs(); - StatusBarUpdate(); - UpdateDebugBuffer(); CallLuaOnUpdate(&event); GarbageCollect(); @@ -576,7 +548,7 @@ void Windows_SetupVCVarsall(mco_coro *co) { String s = Trim(it); Array values = Split(scratch, s, "="); if (values.len == 1) continue; - Add(&Enviroment, Copy(GetSystemAllocator(), s)); + Add(&ProcessEnviroment, Copy(GetSystemAllocator(), s)); } } @@ -661,7 +633,7 @@ int main(int argc, char **argv) #if !OS_WINDOWS for (int i = 0; environ[i]; i += 1) { - Add(&Enviroment, Copy(Perm, environ[i])); + Add(&ProcessEnviroment, Copy(Perm, environ[i])); } #endif diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index cfdd6b8..12dd340 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -59,6 +59,9 @@ struct BSet { View *view; Buffer *buffer; }; +BSet GetBSet(struct Window *window); +BSet GetBSet(WindowID window_id); + // @WARNING: be careful about using this, should only be used for debugging // the problem with this is that we want events to be reproducible. @@ -100,6 +103,7 @@ Buffer *CreateBuffer(Allocator allocator, String name, Int size = 4096); View *CreateView(BufferID active_buffer); void ReopenBuffer(Buffer *buffer); inline Buffer *FindBuffer(String name); +bool ProcessIsActive(ViewID view); inline bool operator==(BufferID a, BufferID b) { return a.id == b.id; } inline bool operator==(ViewID a, ViewID b) { return a.id == b.id; } diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp index fc3894c..42726ec 100644 --- a/src/text_editor/view.cpp +++ b/src/text_editor/view.cpp @@ -75,3 +75,494 @@ API bool ViewIsReferenced(ViewID view) { } return false; } + +String16 FetchLoadWord(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + Caret caret = view->carets[0]; + Range range = caret.range; + if (GetSize(caret.range) == 0) range = EncloseLoadWord(buffer, GetFront(caret)); + String16 string = GetString(buffer, range); + return string; +} + +void IdentedNewLine(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + Scratch scratch; + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(buffer, &view->carets); + 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, KILL_SELECTION); +} + +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 = GetMax(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 SelectRange(View *view, Caret caret) { + view->carets.len = 1; + view->carets[0] = caret; +} + +void SelectRange(View *view, Range range) { + SelectRange(view, MakeCaret(range.min, range.max)); +} + +void SelectEntireBuffer(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + SelectRange(view, GetRange(buffer)); +} + +void 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); + SelectRange(seek_view, caret); + IF_DEBUG(AssertRanges(seek_view->carets)); +} + +void FuzzySortView(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; + RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), s); + RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), u"\n"); + } + + SelectEntireBuffer(view); + Replace(view, GetString(temp_buffer)); + SelectRange(view, MakeRange(0)); +} + +void 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(buffer, &view->carets); +} + +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) { + if (PosToLine(buffer, front) == 0) { + it = SetFront(it, 0); + } else { + 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) { + if (LastLine(buffer) == PosToLine(buffer, front)) { + it = SetFront(it, buffer->len); + } else { + 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 MoveCarets(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 MoveCaretsLine(View *view, int direction) { + Assert(direction == DIR_DOWN || direction == DIR_UP); + Scratch scratch; + + // @todo: this doesn't work well at the end of buffer + struct XYPair { + XY front; + XY back; + }; + + Buffer *buffer = GetBuffer(view->active_buffer); + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(buffer, &view->carets); + Array saved_xy = {scratch}; + For(view->carets) { + Int eof_current = 0; + Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max, &eof_current)}; + if (lines_to_move_range.min == 0 && direction == DIR_UP) { + continue; + } + + Int eof = 0; + Int next_line_start = lines_to_move_range.max; + Int next_line_end = GetFullLineEnd(buffer, next_line_start, &eof); + Int prev_line_end = lines_to_move_range.min - 1; + Int prev_line_start = GetFullLineStart(buffer, prev_line_end); + + if (direction == DIR_DOWN && eof) { + continue; + } else if (direction == DIR_UP && eof_current) { + continue; + } + + String16 string = Copy16(scratch, GetString(buffer, lines_to_move_range)); + + AddEdit(&edits, lines_to_move_range, {}); + if (direction == DIR_DOWN) { + AddEdit(&edits, MakeRange(next_line_end), string); + } else { + AddEdit(&edits, MakeRange(prev_line_start), string); + } + + Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))}); + } + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + + Int line_offset = direction == DIR_UP ? -1 : +1; + 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); + } +} + +void CreateCursorVertical(View *view, int direction) { + Assert(direction == DIR_UP || direction == DIR_DOWN); + 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(buffer, &view->carets); +} + +void 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_PRESS); + } + + } else { + it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESS); + } + } else { + it = MoveCaret(buffer, it, direction, ctrl, SHIFT_PRESS); + } + } + + MergeCarets(buffer, &view->carets); + For(view->carets) AddEdit(&edits, it.range, {}); + EndEdit(buffer, &edits, &view->carets, true); +} + +void EncloseSpace(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + For (view->carets) { + it.range.min = GetPrevEmptyLineStart(buffer, it.range.min); + it.range.max = GetNextEmptyLineStart(buffer, it.range.max); + } +} + +Array GetSelectedLinesSorted(Allocator allocator, View *view) { + Scratch scratch(allocator); + Buffer *buffer = GetBuffer(view->active_buffer); + Array caret_copy = TightCopy(scratch, view->carets); + Array temp = TightCopy(scratch, view->carets); + if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data); + + Array result = {allocator}; + For(caret_copy) { + Int min_line = PosToLine(buffer, it.range.min); + Int max_line = PosToLine(buffer, it.range.max); + Range line_range = {min_line, max_line + 1}; + + if (result.len == 0) { + Add(&result, line_range); + continue; + } + + Range *last = GetLast(result); + if (AreOverlapping(*last, line_range)) { + last->max = Max(last->max, line_range.max); + } else { + Add(&result, line_range); + } + } + return result; +} + +void IndentSelectedLines(View *view, bool shift = false) { + Scratch scratch; + Buffer *buffer = GetBuffer(view->active_buffer); + + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(buffer, &view->carets); + + 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 = {u" ", 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}, u""); + } + } + } + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + view->update_scroll = false; +} + +void MoveCursorToSide(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; + } + } +} + +Array ReplaceEx(Allocator scratch, View *view, String16 string) { + Buffer *buffer = GetBuffer(view->active_buffer); + Array edits = BeginEdit(scratch, buffer, view->carets); + MergeCarets(buffer, &view->carets); + For(view->carets) AddEdit(&edits, it.range, string); + EndEdit(buffer, &edits, &view->carets, KILL_SELECTION); + return edits; +} + +void Replace(View *view, String16 string) { + Scratch scratch; + ReplaceEx(scratch, view, string); +} + +void 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); + MergeCarets(buffer, &view->carets); + + Array edits = {scratch}; + For(view->carets) { + Int eof = 0; + Range range = {}; + range.max = GetFullLineEnd(buffer, it.range.max, &eof); + range.min = GetFullLineStart(buffer, it.range.min); + range.min -= Clamp(eof, (Int)0, buffer->len); + String16 string = Copy16(scratch, GetString(buffer, range)); + + Int pos = direction == DIR_UP ? range.min : range.max; + AddEdit(&edits, MakeRange(pos), string); + } + EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); + + Int coef = direction == DIR_UP ? -1 : 1; + for (Int i = 0; i < edits.len; i += 1) { + Caret *caret = view->carets.data + i; + + XY xymin = PosToXY(buffer, caret->range.min); + XY xymax = PosToXY(buffer, caret->range.max); + Int line_count = xymax.line - xymin.line + 1; + + xymin.line += coef * line_count; + xymax.line += coef * line_count; + + caret->range.min = XYToPos(buffer, xymin); + caret->range.max = XYToPos(buffer, xymax); + } +} diff --git a/src/text_editor/view.h b/src/text_editor/view.h index f1447a0..7bc1503 100644 --- a/src/text_editor/view.h +++ b/src/text_editor/view.h @@ -11,7 +11,6 @@ struct View { Caret main_caret_on_begin_frame; bool update_scroll; - bool fuzzy_search; String16 prev_search_line; }; diff --git a/src/text_editor/window.cpp b/src/text_editor/window.cpp index 483f692..d668c2b 100644 --- a/src/text_editor/window.cpp +++ b/src/text_editor/window.cpp @@ -109,80 +109,10 @@ void InitWindows() { Scratch scratch; CreateWind(); - CreateWind(); - - // COMMAND BAR - { - Window *window = CreateWind(); - CommandBarWindowID = window->id; - Buffer *buffer = CreateBuffer(SysAllocator, "command_bar"); - View *view = CreateView(buffer->id); - window->active_view = view->id; - window->draw_line_numbers = false; - window->draw_scrollbar = false; - window->draw_darker = true; - window->draw_line_highlight = false; - window->layout = false; - window->visible = false; - window->sync_visibility_with_focus = true; - window->lose_focus_on_escape = true; - window->jump_history = false; - view->fuzzy_search = true; - } - - // SEARCH BAR - { - Window *window = CreateWind(); - SearchBarWindowID = window->id; - Buffer *buffer = CreateBuffer(SysAllocator, "search_bar"); - SearchBufferID = buffer->id; - View *view = CreateView(buffer->id); - SearchViewID = view->id; - window->active_view = view->id; - window->draw_line_numbers = false; - window->draw_scrollbar = false; - window->draw_darker = true; - window->draw_line_highlight = false; - window->layout = false; - window->visible = false; - window->lose_focus_on_escape = true; - } - - // STATUS BAR at the bottom - { - Window *window = CreateWind(); - StatusBarWindowID = window->id; - Buffer *buffer = CreateBuffer(SysAllocator, "status_bar"); - View *view = CreateView(buffer->id); - window->active_view = view->id; - window->font = &SecondaryFont; - window->draw_line_numbers = false; - window->draw_scrollbar = false; - window->draw_line_highlight = false; - window->draw_darker = true; - window->layout = false; - } - - // DEBUG WINDOW - { - Window *window = CreateWind(); - DebugWindowID = window->id; - window->draw_line_numbers = false; - window->draw_scrollbar = false; - window->visible = false; - window->z = 2; - window->layout = false; - - Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "debug")); - DebugBufferID = buffer->id; - buffer->no_history = true; - - View *view = CreateView(buffer->id); - DebugViewID = view->id; - window->active_view = view->id; - - window->visible = false; - } + CommandWindowInit(); + SearchWindowInit(); + StatusWindowInit(); + DebugWindowInit(); } void CalcNiceties(Window *n) { @@ -208,53 +138,10 @@ double WindowCalcEvenResizerValue(Int screen_size_x, Int *out_count = NULL) { void LayoutWindows(int16_t wx, int16_t wy) { Rect2I screen_rect = RectI0Size(wx, wy); - // Command bar - { - Window *n = GetWindow(CommandBarWindowID); - Rect2I *rect = &screen_rect; - Rect2I copy_rect = screen_rect; - if (!n->visible) { - rect = ©_rect; - } - Int barsize = Clamp((Int)n->font->line_spacing*10, (Int)0, (Int)wx - 100); - n->document_rect = n->total_rect = CutBottom(rect, barsize); - } - - // bar at the bottom - { - Window *n = GetWindow(StatusBarWindowID); - Rect2I *rect = &screen_rect; - Rect2I copy_rect = screen_rect; - if (!n->visible) { - rect = ©_rect; - } - Int barsize = GetExpandingBarSize(n); - n->document_rect = n->total_rect = CutBottom(rect, barsize); - } - - // search bar - { - Window *n = GetWindow(SearchBarWindowID); - Rect2I *rect = &screen_rect; - Rect2I copy_rect = screen_rect; - if (!n->visible) { - rect = ©_rect; - } - Int barsize = GetExpandingBarSize(n); - n->document_rect = n->total_rect = CutBottom(rect, barsize); - } - - // floating debug window - { - Window *n = GetWindow(DebugWindowID); - Rect2 screen_rect = Rect0Size(wx, wy); - Vec2 size = GetSize(screen_rect); - - Rect2 a = CutRight(&screen_rect, 0.3f * size.x); - Rect2 b = CutTop(&a, 0.4f * size.y); - Rect2 c = Shrink(b, 20); - n->document_rect = n->total_rect = ToRect2I(c); - } + CommandWindowLayout(&screen_rect, wx, wy); + SearchWindowLayout(&screen_rect, wx, wy); + StatusWindowLayout(&screen_rect, wx, wy); + DebugWindowLayout(&screen_rect, wx, wy); // Column layout Int c = 0; diff --git a/src/text_editor/window.h b/src/text_editor/window.h index d63cbb2..b5feba5 100644 --- a/src/text_editor/window.h +++ b/src/text_editor/window.h @@ -47,4 +47,17 @@ struct Scroller { inline bool operator==(WindowID a, WindowID b) { return a.id == b.id; } inline bool operator!=(WindowID a, WindowID b) { return a.id != b.id; } -Rect2I GetVisibleCells(Window *window); \ No newline at end of file +Rect2I GetVisibleCells(Window *window); +Window *CreateWind(); + +void CommandWindowLayout(Rect2I *rect, Int wx, Int wy); +void CommandWindowInit(); + +void SearchWindowLayout(Rect2I *rect, Int wx, Int wy); +void SearchWindowInit(); + +void StatusWindowInit(); +void StatusWindowLayout(Rect2I *rect, Int wx, Int wy); + +void DebugWindowInit(); +void DebugWindowLayout(Rect2I *rect, Int wx, Int wy); \ No newline at end of file diff --git a/src/text_editor/window_command.cpp b/src/text_editor/window_command.cpp new file mode 100644 index 0000000..fdbf323 --- /dev/null +++ b/src/text_editor/window_command.cpp @@ -0,0 +1,133 @@ +void CommandWindowInit() { + Window *window = CreateWind(); + CommandBarWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, "command_bar"); + View *view = CreateView(buffer->id); + window->active_view = view->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->draw_darker = true; + window->draw_line_highlight = false; + window->layout = false; + window->visible = false; + window->sync_visibility_with_focus = true; + window->lose_focus_on_escape = true; + window->jump_history = false; +} + +void CommandWindowLayout(Rect2I *rect, Int wx, Int wy) { + Window *n = GetWindow(CommandBarWindowID); + Rect2I copy_rect = *rect; + if (!n->visible) { + rect = ©_rect; + } + Int barsize = Clamp((Int)n->font->line_spacing*10, (Int)0, (Int)wx - 100); + n->document_rect = n->total_rect = CutBottom(rect, barsize); +} + +void CommandWindowUpdate() { + BSet active = GetBSet(ActiveWindowID); + if (active.window->id == CommandBarWindowID) { + if (!ProcessIsActive(active.view->id)) { + Scratch scratch; + String16 last_line_string = GetLineStringWithoutNL(active.buffer, active.buffer->line_starts.len - 1); + if (active.view->prev_search_line != last_line_string) { + active.view->prev_search_line = last_line_string; + Array ratings = FuzzySearchLines(scratch, active.buffer, 0, active.buffer->line_starts.len - 1, last_line_string); + + Buffer *temp_buffer = CreateTempBuffer(scratch, active.buffer->cap); + For(IterateInReverse(&ratings)) { + String16 s = GetLineStringWithoutNL(active.buffer, it.index); + if (s.len == 0) continue; + RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), s); + RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), u"\n"); + } + RawReplaceText(temp_buffer, GetBufferEndAsRange(temp_buffer), last_line_string); + + Caret caret = active.view->carets[0]; + SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets); + SelectEntireBuffer(active.view); + Replace(active.view, GetString(temp_buffer)); + active.view->carets[0] = caret; + } + } + } +} + +void Command_ShowCommands() { + BSet command_bar = GetBSet(CommandBarWindowID); + command_bar.window->visible = true; + command_bar.window->eval_command = true; + ActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For(CommandFunctions) { + Appendf(command_bar.view, "%S\n", it.name); + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); +} RegisterCommand(Command_ShowCommands, "ctrl-shift-p"); + +void Command_ShowLuaFunctions() { + BSet command_bar = GetBSet(CommandBarWindowID); + command_bar.window->visible = true; + command_bar.window->eval_command = false; + ActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For(LuaFunctions) { + Appendf(command_bar.view, "%S()\n ", it.name); + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); +} RegisterCommand(Command_ShowLuaFunctions, ""); + + +void Command_ShowBufferList() { + BSet command_bar = GetBSet(CommandBarWindowID); + command_bar.window->visible = true; + command_bar.window->eval_command = false; + ActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For(Buffers) { + RawAppendf(command_bar.buffer, "%-80S || %S\n", SkipToLastSlash(it->name), it->name); + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferEndAsRange(command_bar.buffer)); +} RegisterCommand(Command_ShowBufferList, "ctrl-p"); + +void EvalCommand(String command) { + For (CommandFunctions) { + if (it.name == command) { + it.function(); + break; + } + } +} + +void EvalCommand(String16 command) { + Scratch scratch; + EvalCommand(ToString(scratch, command)); +} + +void CommandWindowOpen(BSet active) { + Range range = active.view->carets[0].range; + String16 string = FetchLoadWord(active.view); + if (GetSize(range) == 0) { + Int line = PosToLine(active.buffer, range.min); + if ((active.buffer->line_starts.len - 1) == line) { + line = ClampBottom(0ll, line - 1ll); + } + + string = GetLineStringWithoutNL(active.buffer, line); + Int idx = 0; + if (Seek(string, u"||", &idx)) { + string = Skip(string, idx + 3); + } + } + if (active.window->eval_command) { + BSet main = GetBSet(LastActiveLayoutWindowID); + ActiveWindowID = main.window->id; + EvalCommand(string); + } else { + Open(string); + } +} diff --git a/src/text_editor/title_bar.cpp b/src/text_editor/window_debug.cpp similarity index 50% rename from src/text_editor/title_bar.cpp rename to src/text_editor/window_debug.cpp index 6601004..b229bcb 100644 --- a/src/text_editor/title_bar.cpp +++ b/src/text_editor/window_debug.cpp @@ -1,4 +1,35 @@ -void UpdateDebugBuffer() { +void DebugWindowInit() { + Window *window = CreateWind(); + DebugWindowID = window->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->visible = false; + window->z = 2; + window->layout = false; + + Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "debug")); + DebugBufferID = buffer->id; + buffer->no_history = true; + + View *view = CreateView(buffer->id); + DebugViewID = view->id; + window->active_view = view->id; + + window->visible = false; +} + +void DebugWindowLayout(Rect2I *rect, Int wx, Int wy) { + Window *n = GetWindow(DebugWindowID); + Rect2 screen_rect = Rect0Size((float)wx, (float)wy); + Vec2 size = GetSize(screen_rect); + + Rect2 a = CutRight(&screen_rect, 0.3f * size.x); + Rect2 b = CutTop(&a, 0.4f * size.y); + Rect2 c = Shrink(b, 20); + n->document_rect = n->total_rect = ToRect2I(c); +} + +void DebugWindowUpdate() { Buffer *buffer = GetBuffer(DebugBufferID); Window *window = GetActiveWind(); @@ -41,74 +72,7 @@ void UpdateDebugBuffer() { RawAppendf(buffer, "int garbage = %d\n", main.buffer->garbage); } -void StatusBarUpdate() { - Window *status_bar_window = GetWindow(StatusBarWindowID, NULL); - if (status_bar_window == NULL) { - return; - } - - Scratch scratch; - BSet main = GetBSet(LastActiveLayoutWindowID); - BSet title = GetBSet(status_bar_window); - title.view->scroll.y = 0; - - String16 buffer_string = GetString(title.buffer); - Range replace_range = {0, title.buffer->len}; - bool found_separator = Seek(buffer_string, u" |", &replace_range.max); - - // Parse the title and line - if (title.window->id == ActiveWindowID) { - if (title.buffer->change_id == title.window->status_bar_last_buffer_change_id) { - return; - } - String16 buffer_name = GetString(title.buffer, replace_range); - buffer_name = Skip(buffer_name, 1); - buffer_name = Trim(buffer_name); - - Int column = ChopNumber(&buffer_name); - if (column == -1) return; - - Int line = ChopNumber(&buffer_name); - if (line == -1) { - line = column; - column = 0; - } - - Int buffer_pos = XYToPos(main.buffer, {column, line}); - Caret &caret = main.view->carets[0]; - if (GetFront(caret) != buffer_pos) { - caret = MakeCaret(buffer_pos); - } - title.window->status_bar_last_buffer_change_id = title.buffer->change_id; - return; - } - - Caret caret = main.view->carets[0]; - XY xy = PosToXY(main.buffer, GetFront(caret)); - - // add separator at the end of buffer - if (!found_separator) { - SelectRange(title.view, GetBufferEndAsRange(title.buffer)); - Array edits = ReplaceEx(scratch, title.view, u" |"); - } - - - // replace data up to separator with filename and stuff - const char *reopen = main.buffer->changed_on_disk ? " Reopen()" : ""; - String s = Format(scratch, "# %S:%lld:%lld%s", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, reopen); - For (ActiveProcesses) { - if (it.view_id == main.view->id.id) { - s = Format(scratch, "%S %lld Kill()", s, (long long)it.id); - } - } - - String16 string = ToString16(scratch, s); - String16 string_to_replace = GetString(title.buffer, replace_range); - if (string_to_replace != string) { - SelectRange(title.view, replace_range); - Array edits = ReplaceEx(scratch, title.view, string); - } - - SelectRange(title.view, MakeRange(0)); - ResetHistory(title.buffer); -} \ No newline at end of file +void Command_ToggleDebug() { + Window *window = GetWindow(DebugWindowID); + window->visible = !window->visible; +} RegisterCommand(Command_ToggleDebug, "ctrl-0"); \ No newline at end of file diff --git a/src/text_editor/window_search.cpp b/src/text_editor/window_search.cpp new file mode 100644 index 0000000..0e17048 --- /dev/null +++ b/src/text_editor/window_search.cpp @@ -0,0 +1,31 @@ +void SearchWindowInit() { + Window *window = CreateWind(); + SearchBarWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, "search_bar"); + SearchBufferID = buffer->id; + View *view = CreateView(buffer->id); + SearchViewID = view->id; + window->active_view = view->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->draw_darker = true; + window->draw_line_highlight = false; + window->layout = false; + window->visible = false; + window->lose_focus_on_escape = true; +} + +void SearchWindowLayout(Rect2I *rect, Int wx, Int wy) { + Window *n = GetWindow(SearchBarWindowID); + Rect2I copy_rect = *rect; + if (!n->visible) { + rect = ©_rect; + } + Int barsize = GetExpandingBarSize(n); + n->document_rect = n->total_rect = CutBottom(rect, barsize); +} + +void Command_Search() { + Window *window = GetWindow(SearchBarWindowID); + window->visible = !window->visible; +} RegisterCommand(Command_Search, "ctrl-f"); diff --git a/src/text_editor/window_status.cpp b/src/text_editor/window_status.cpp new file mode 100644 index 0000000..84d976a --- /dev/null +++ b/src/text_editor/window_status.cpp @@ -0,0 +1,95 @@ +void StatusWindowInit() { + Window *window = CreateWind(); + StatusBarWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, "status_bar"); + View *view = CreateView(buffer->id); + window->active_view = view->id; + window->font = &SecondaryFont; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->draw_line_highlight = false; + window->draw_darker = true; + window->layout = false; +} + +void StatusWindowLayout(Rect2I *rect, Int wx, Int wy) { + Window *n = GetWindow(StatusBarWindowID); + Rect2I copy_rect = *rect; + if (!n->visible) { + rect = ©_rect; + } + Int barsize = GetExpandingBarSize(n); + n->document_rect = n->total_rect = CutBottom(rect, barsize); +} + +void StatusWindowUpdate() { + Window *status_bar_window = GetWindow(StatusBarWindowID, NULL); + if (status_bar_window == NULL) { + return; + } + + Scratch scratch; + BSet main = GetBSet(LastActiveLayoutWindowID); + BSet title = GetBSet(status_bar_window); + title.view->scroll.y = 0; + + String16 buffer_string = GetString(title.buffer); + Range replace_range = {0, title.buffer->len}; + bool found_separator = Seek(buffer_string, u" |", &replace_range.max); + + // Parse the title and line + if (title.window->id == ActiveWindowID) { + if (title.buffer->change_id == title.window->status_bar_last_buffer_change_id) { + return; + } + String16 buffer_name = GetString(title.buffer, replace_range); + buffer_name = Skip(buffer_name, 1); + buffer_name = Trim(buffer_name); + + Int column = ChopNumber(&buffer_name); + if (column == -1) return; + + Int line = ChopNumber(&buffer_name); + if (line == -1) { + line = column; + column = 0; + } + + Int buffer_pos = XYToPos(main.buffer, {column, line}); + Caret &caret = main.view->carets[0]; + if (GetFront(caret) != buffer_pos) { + caret = MakeCaret(buffer_pos); + } + title.window->status_bar_last_buffer_change_id = title.buffer->change_id; + return; + } + + Caret caret = main.view->carets[0]; + XY xy = PosToXY(main.buffer, GetFront(caret)); + + // add separator at the end of buffer + if (!found_separator) { + SelectRange(title.view, GetBufferEndAsRange(title.buffer)); + Array edits = ReplaceEx(scratch, title.view, u" |"); + } + + + // replace data up to separator with filename and stuff + const char *reopen = main.buffer->changed_on_disk ? " Reopen()" : ""; + String s = Format(scratch, "# %S:%lld:%lld%s", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, reopen); + For (ActiveProcesses) { + if (it.view_id == main.view->id.id) { + s = Format(scratch, "%S %lld Kill()", s, (long long)it.id); + } + } + + String16 string = ToString16(scratch, s); + String16 string_to_replace = GetString(title.buffer, replace_range); + if (string_to_replace != string) { + SelectRange(title.view, replace_range); + Array edits = ReplaceEx(scratch, title.view, string); + } + + SelectRange(title.view, MakeRange(0)); + ResetHistory(title.buffer); +} \ No newline at end of file