From 2acf2c189c05cfd891797bc884333af24a2a0d2e Mon Sep 17 00:00:00 2001 From: krzosa Date: Sun, 28 Dec 2025 16:12:22 +0100 Subject: [PATCH] First prototype of coroutine based UI --- src/backup/todo.txt | 5 +- src/basic/basic_head.h | 2 +- src/render/opengl.cpp | 1 + src/test/tests.cpp | 3 +- src/text_editor/buffer.cpp | 11 ++- src/text_editor/commands.cpp | 122 ++++++++++++------------- src/text_editor/commands_clipboard.cpp | 2 +- src/text_editor/config.cpp | 3 + src/text_editor/coroutines.cpp | 8 +- src/text_editor/globals.cpp | 4 +- src/text_editor/text_editor.cpp | 29 +++++- src/text_editor/view.h | 1 + src/text_editor/window.cpp | 34 +++++-- src/text_editor/window_command.cpp | 2 +- 14 files changed, 138 insertions(+), 89 deletions(-) diff --git a/src/backup/todo.txt b/src/backup/todo.txt index 8aaff3c..b641460 100644 --- a/src/backup/todo.txt +++ b/src/backup/todo.txt @@ -5,13 +5,14 @@ Use session 1: - OpenCommand in command window freezes the app - SDL popups are not working on linux ... - CloseAll idea: should create a buffer interface with list of buffers, user would be able to select which buffers to save and which not, then button UICloseAll -- Creating files more efficiently +- Creating files more efficiently, renaming - Dialog popup on save? Or ctrl-shift-s? - Maybe rename in bar and do :Rename New UI Session - Cleanup String16/String with Open and EvalCommands after lua refactor -- Rename GotoBackward and others to Jump +- Uneditable buffers ? +- Maybe marked allocations??? So that we can associate allocations with a buffer or view and then dealloc all at the same time - Open with seek string (open at pattern) filename:32 filename:/^Window$/ - build console window diff --git a/src/basic/basic_head.h b/src/basic/basic_head.h index f66e0a4..5f5ad4d 100644 --- a/src/basic/basic_head.h +++ b/src/basic/basic_head.h @@ -94,7 +94,7 @@ EM_JS(void, JS_Breakpoint, (), { BREAK(); \ } #define InvalidCodepath() Assert(!"invalid codepath") -#define ElseInvalidCodepath() else {InvalidCodepath()} +#define ElseInvalidCodepath() else {InvalidCodepath();} #define KiB(x) ((x##ull) * 1024ull) #define MiB(x) (KiB(x) * 1024ull) diff --git a/src/render/opengl.cpp b/src/render/opengl.cpp index bde680b..e6f1bf3 100644 --- a/src/render/opengl.cpp +++ b/src/render/opengl.cpp @@ -186,6 +186,7 @@ void BeginFrameRender(float wx, float wy) { // ---------- EndFrameRender for ES3 ---------- void EndFrameRender(float wx, float wy, Color color) { + ProfileFunction(); glEnable(GL_BLEND); glEnable(GL_SCISSOR_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/test/tests.cpp b/src/test/tests.cpp index 14c7509..213f015 100644 --- a/src/test/tests.cpp +++ b/src/test/tests.cpp @@ -152,5 +152,6 @@ void Test(mco_coro *co) { void InitTests() { ConfigWaitForEvents = false; TestDir = Format(TestArena, "%S/test_env", GetExeDir(TestArena)); - CoAdd(Test); + CoData *data = CoAdd(Test); + data->dont_wait_until_resolved = true; } diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 2963fee..19563f6 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1506,9 +1506,9 @@ String16 ToUnixString16(Allocator allocator, String string_) { Buffer *BufferOpenFile(String path) { ProfileFunction(); Allocator sys_allocator = GetSystemAllocator(); - Scratch scratch; + Scratch scratch; - path = GetAbsolutePath(scratch, path); + path = GetAbsolutePath(scratch, path); Buffer *buffer = GetBuffer(path); if (!IsNull(buffer) || (IsNull(buffer) && buffer->name == path)) { return buffer; @@ -1519,10 +1519,11 @@ Buffer *BufferOpenFile(String path) { } else if (IsDir(path)) { buffer = CreateBuffer(sys_allocator, path); buffer->is_dir = true; + buffer->garbage = true; } else { - String string = ReadFile(scratch, path); - buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096); - buffer->len = ConvertUTF8ToUTF16UnixLine(string, buffer->str, buffer->cap); + String string = ReadFile(scratch, path); + buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096); + buffer->len = ConvertUTF8ToUTF16UnixLine(string, buffer->str, buffer->cap); buffer->file_mod_time = GetFileModTime(path); UpdateLines(buffer, {}, String16{(char16_t *)buffer->data, buffer->len}); } diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 0007e71..7100e1e 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -730,7 +730,8 @@ void Coro_OpenCode(mco_coro *co) { void OpenCode(String dir) { Coro_OpenCodeDir = dir; - CoAdd(Coro_OpenCode); + CoData *data = CoAdd(Coro_OpenCode); + data->dont_wait_until_resolved = true; } void Command_OpenCode() { @@ -747,7 +748,7 @@ void Command_CloseWindow() { } RegisterCommand(Command_CloseWindow, ""); SaveResult TrySavingBuffer(Buffer *buffer) { - if (buffer->special || buffer->is_dir || buffer->garbage) { + if (buffer->special || buffer->garbage) { return SAVE_NO; } if (buffer->dirty) { @@ -781,15 +782,13 @@ SaveResult TrySavingAllBuffers() { return SAVE_YES; } -void Command_Close() { +void Coro_Close(mco_coro *co) { BSet main = GetBSet(LastActiveLayoutWindowID); - if (TrySavingBuffer(main.buffer) == SAVE_CANCEL) { + if (main.buffer->special || main.buffer->garbage) { + Close(main.view->id); return; } - main.window->active_view = FindInactiveView(); - Close(main.view->id); - bool ref = false; For (Views) { if (it->id == main.view->id) { @@ -801,9 +800,56 @@ void Command_Close() { } } - if (!ref) { - Close(main.buffer->id); + if (ref) { + Close(main.view->id); + return; } + + if (!main.buffer->dirty) { + Close(main.buffer->id); + return; + } + + Buffer *buffer = main.buffer; + JumpGarbageBuffer(&main); + NextActiveWindowID = main.window->id; + + RawAppendf(main.buffer, R"==( +Do you want to save [%S] before closing? + + :Yes :No :Cancel +)==", buffer->name); + main.view->carets[0] = FindNext(main.buffer, u":Yes", MakeCaret(0)); + main.view->carets[0].range.min = main.view->carets[0].range.max; + Add(&main.view->hooks, {"Yes", "enter", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Yes";}, ParseKeyCached("enter")}); + Add(&main.view->hooks, {"No", "", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "No";}, ParseKeyCached("")}); + Add(&main.view->hooks, {"Cancel", "escape", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Cancel";}, ParseKeyCached("escape")}); + for (;;) { + if (main.window->active_view != main.view->id || main.window->close) { + Close(main.buffer->id); + return; + } + + if (main.view->hook_cmd != "") { + break; + } + + CoYield(co); + } + + Close(main.buffer->id); + if (main.view->hook_cmd == "Yes") { + SaveBuffer(buffer); + Close(buffer->id); + } else if (main.view->hook_cmd == "No") { + Close(buffer->id); + } else if (main.view->hook_cmd == "Cancel") { + return; + } ElseInvalidCodepath(); +} + +void Command_Close() { + CoAdd(Coro_Close); } RegisterCommand(Command_Close, "ctrl-w"); void Command_Quit() { @@ -829,63 +875,17 @@ void Command_CloseAll() { } } RegisterCommand(Command_CloseAll, ""); -void Command_ForceClose() { - BSet active = GetBSet(ActiveWindowID); - Close(active.buffer->id); - Close(active.view->id); -} - -// @todo: make these uneditable? -// @todo: maybe turn this into a coroutine??? -String BufferIDea = R"==( -Do you want to save buffer? [C:/Programming/text_editor/build.sh] - - :Yes :No -)=="; -void Command_TestUIIdea() { - BSet main = GetBSet(LastActiveLayoutWindowID); - JumpGarbageBuffer(&main, "ui"); - NextActiveWindowID = main.window->id; - - Add(&main.view->hooks, {"Yes", "enter", [](){ - BSet active = GetBSet(ActiveWindowID); - Caret a = FindNext(active.buffer, u"[", MakeCaret(0)); - Caret b = FindNext(active.buffer, u"]", MakeCaret(0)); - if (GetSize(a) == 0 || GetSize(b) == 0) { - ReportWarningf("Failed to save, '[' or ']' not found"); - return; - } - - Scratch scratch; - String16 string16 = GetString(active.buffer, {a.range.max, b.range.min}); - String string = ToString(scratch, string16); - Buffer *buffer = GetBuffer(string, NULL); - if (buffer) { - SaveBuffer(buffer); - } else { - ReportWarningf("Failed to save, buffer not found: %S", string); - } - - Close(active.buffer->id); - Close(active.view->id); - }, ParseKey(Perm, "enter", "Command_TestUIIdea_Yes")}); // @todo: fix memory leak - Add(&main.view->hooks, {"No", "escape", Command_ForceClose, ParseKeyCached("escape")}); - RawAppend(main.buffer, BufferIDea); - main.view->carets[0] = FindNext(main.buffer, u":Yes", MakeCaret(0)); - main.view->carets[0].range.min = main.view->carets[0].range.max; -} RegisterCommand(Command_TestUIIdea, ""); - -void Command_GotoBackward() { +void Command_JumpBack() { BSet main = GetBSet(LastActiveLayoutWindowID); main.window->skip_checkpoint = true; - GotoBackward(main.window); -} RegisterCommand(Command_GotoBackward, "alt-q | mousex1"); + JumpBack(main.window); +} RegisterCommand(Command_JumpBack, "alt-q | mousex1"); -void Command_GotoForward() { +void Command_JumpForward() { BSet main = GetBSet(LastActiveLayoutWindowID); main.window->skip_checkpoint = true; - GotoForward(main.window); -} RegisterCommand(Command_GotoForward, "alt-shift-q | mousex2"); + JumpForward(main.window); +} RegisterCommand(Command_JumpForward, "alt-shift-q | mousex2"); void Command_OpenUpFolder() { BSet main = GetBSet(LastActiveLayoutWindowID); diff --git a/src/text_editor/commands_clipboard.cpp b/src/text_editor/commands_clipboard.cpp index aec1130..e0bf565 100644 --- a/src/text_editor/commands_clipboard.cpp +++ b/src/text_editor/commands_clipboard.cpp @@ -102,4 +102,4 @@ void Command_Cut() { SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets); ClipboardCopy(active.view); Replace(active.view, u""); -} RegisterCommand(Command_Cut, "ctrl-x"); \ No newline at end of file +} RegisterCommand(Command_Cut, "ctrl-x"); diff --git a/src/text_editor/config.cpp b/src/text_editor/config.cpp index 4e906ac..4d36a55 100644 --- a/src/text_editor/config.cpp +++ b/src/text_editor/config.cpp @@ -164,6 +164,9 @@ struct CachedTrigger { Array CachedTriggers; Trigger *ParseKeyCached(String key) { + if (key.len == 0) { + return NULL; + } For (CachedTriggers) { if (it.key == key) { return it.trigger; diff --git a/src/text_editor/coroutines.cpp b/src/text_editor/coroutines.cpp index b43b7fd..0714f61 100644 --- a/src/text_editor/coroutines.cpp +++ b/src/text_editor/coroutines.cpp @@ -11,7 +11,7 @@ void CoRemove(String name) { } #define CoAdd(x) CoAddEx(x, #x) -mco_coro *CoAddEx(CoroutineProc *proc, String name) { +CoData *CoAddEx(CoroutineProc *proc, String name) { CoRemove(name); mco_desc desc = mco_desc_init(proc, 0); @@ -24,7 +24,7 @@ mco_coro *CoAddEx(CoroutineProc *proc, String name) { mco_resume(coro); Add(&ActiveCoroutines, {coro, name}); - return coro; + return GetLast(ActiveCoroutines); } void CoUpdate(Event *event) { @@ -46,6 +46,8 @@ void CoUpdate(Event *event) { ReportWarningf("failed to resume coroutine %d", ok); mco_destroy(it.co); remove_item = true; + } else { + it.dont_wait_until_resolved = true; } } } @@ -62,7 +64,7 @@ Event *CoYield(mco_coro *co) { Assert(ok == MCO_SUCCESS); Event *event = NULL; - ok = mco_pop(co, &event, sizeof(Event *)); + ok = mco_pop(co, &event, sizeof(Event *)); Assert(ok == MCO_SUCCESS); return event; diff --git a/src/text_editor/globals.cpp b/src/text_editor/globals.cpp index e7f020b..feb14ee 100644 --- a/src/text_editor/globals.cpp +++ b/src/text_editor/globals.cpp @@ -87,7 +87,7 @@ Array Variables; Array ActiveProcesses = {}; Array ProcessEnviroment = {}; -struct CoData { mco_coro *co; String name; }; +struct CoData { mco_coro *co; String name; bool dont_wait_until_resolved; }; Array ActiveCoroutines; @@ -162,5 +162,5 @@ RegisterVariable(String, ConfigFont, "/home/krz/text_editor/package/CascadiaMono RegisterVariable(String, ConfigVCVarsall, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat"); RegisterVariable(Float, ConfigUndoMergeTimeWindow, 0.3); RegisterVariable(Float, ConfigJumpHistoryMergeTimeWindow, 0.3); -RegisterVariable(Int, ConfigJumpHistorySize, 512); +RegisterVariable(Int, ConfigJumpHistorySize, 4096); RegisterVariable(String, ConfigInternetBrowser, "firefox"); diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index bacd6f7..f34cf9c 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -78,6 +78,7 @@ void SetMouseCursor(SDL_SystemCursor id) { #endif void SetMouseCursor(Event event) { + ProfileFunction(); Scratch scratch; Array order = GetWindowZOrder(scratch); Vec2I mouse = MouseVec2I(); @@ -448,7 +449,6 @@ void EvalCommand(String16 command) { EvalCommand(ToString(scratch, command)); } - void GarbageCollect() { if (RunGCThisFrame == false) { return; @@ -475,7 +475,11 @@ void GarbageCollect() { InvalidCodepath(); } - Buffer *buffer = GetBuffer(it->active_buffer); + Buffer *buffer = GetBuffer(it->active_buffer, NULL); + if (buffer == NULL || buffer->close) { + it->close = true; + } + if (!it->close) { if (!buffer->garbage) { continue; @@ -487,7 +491,7 @@ void GarbageCollect() { } } - RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer->name); + RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"}); remove_item = true; Dealloc(it); } @@ -527,6 +531,11 @@ void GarbageCollect() { Dealloc(&it->goto_redo); Dealloc(sys_allocator, it); remove_item = true; + } else { + View *view = FindView(it->active_view, NULL); + if (!view) { + JumpToLastValidView(it); + } } } } @@ -673,8 +682,17 @@ void MainLoop() { Update(it); } + + bool dont_wait_until_resolved = false; + For (ActiveCoroutines) { + if (it.dont_wait_until_resolved) { + dont_wait_until_resolved = true; + break; + } + } + WaitForEvents = ConfigWaitForEvents; - if (IsDocumentSelectionValid() || IsScrollbarSelectionValid() || ActiveProcesses.len || ActiveCoroutines.len) { + if (IsDocumentSelectionValid() || IsScrollbarSelectionValid() || ActiveProcesses.len || dont_wait_until_resolved) { WaitForEvents = false; } @@ -847,7 +865,8 @@ int main(int argc, char **argv) ReportConsolef("WorkDir = %S", WorkDir); if (Testing) InitTests(); #if OS_WINDOWS - CoAdd(Windows_SetupVCVarsall); + CoData *co_data = CoAdd(Windows_SetupVCVarsall); + co_data->dont_wait_until_resolved = true; #endif #if OS_WASM emscripten_set_main_loop(MainLoop, 0, 1); diff --git a/src/text_editor/view.h b/src/text_editor/view.h index 878e4c9..31dbfea 100644 --- a/src/text_editor/view.h +++ b/src/text_editor/view.h @@ -15,6 +15,7 @@ struct View { Caret main_caret_on_begin_frame; bool update_scroll; + String hook_cmd; Array hooks; String16 prev_search_line; struct { diff --git a/src/text_editor/window.cpp b/src/text_editor/window.cpp index b77cf41..757af79 100644 --- a/src/text_editor/window.cpp +++ b/src/text_editor/window.cpp @@ -235,7 +235,7 @@ Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *b return result; } -GotoCrumb GetCrumb(Array *cr) { +GotoCrumb PopCrumb(Array *cr) { for (; cr->len;) { GotoCrumb c = Pop(cr); View *view = FindView(c.view_id, NULL); @@ -246,13 +246,33 @@ GotoCrumb GetCrumb(Array *cr) { return {}; } -void GotoBackward(Window *window) { +View *GetLastValidView(Window *window) { + For (IterateInReverse(&window->goto_redo)) { + if (it.view_id == window->active_view) continue; + View *view = FindView(it.view_id, NULL); + if (view) return view; + } + For (IterateInReverse(&window->goto_history)) { + if (it.view_id == window->active_view) continue; + View *view = FindView(it.view_id, NULL); + if (view) return view; + } + return Views[0]; +} + +View *JumpToLastValidView(Window *window) { + View *view = GetLastValidView(window); + window->active_view = view->id; + return view; +} + +void JumpBack(Window *window) { if (window->jump_history == false) return; if (window->goto_history.len <= 0) return; BSet set = GetBSet(window); Add(&window->goto_redo, {set.view->id, set.view->carets[0], GetTimeSeconds()}); - GotoCrumb c = GetCrumb(&window->goto_history); + GotoCrumb c = PopCrumb(&window->goto_history); window->active_view = c.view_id; View *view = GetView(c.view_id); view->carets[0] = c.caret; @@ -261,18 +281,18 @@ void GotoBackward(Window *window) { if (window->goto_history.len) { GotoCrumb *next = GetLast(window->goto_history); if (c.view_id == next->view_id && c.time - next->time <= ConfigJumpHistoryMergeTimeWindow) { - GotoBackward(window); + JumpBack(window); } } } -void GotoForward(Window *window) { +void JumpForward(Window *window) { if (window->jump_history == false) return; if (window->goto_redo.len <= 0) return; BSet set = GetBSet(window); Add(&window->goto_history, {set.view->id, set.view->carets[0], GetTimeSeconds()}); - GotoCrumb c = GetCrumb(&window->goto_redo); + GotoCrumb c = PopCrumb(&window->goto_redo); window->active_view = c.view_id; View *view = GetView(c.view_id); view->carets[0] = c.caret; @@ -281,7 +301,7 @@ void GotoForward(Window *window) { if (window->goto_redo.len) { GotoCrumb *next = GetLast(window->goto_redo); if (c.view_id == next->view_id && next->time - c.time <= ConfigJumpHistoryMergeTimeWindow) { - GotoForward(window); + JumpForward(window); } } } diff --git a/src/text_editor/window_command.cpp b/src/text_editor/window_command.cpp index e658e2a..a674c9f 100644 --- a/src/text_editor/window_command.cpp +++ b/src/text_editor/window_command.cpp @@ -160,7 +160,7 @@ void Command_ShowBufferList() { NextActiveWindowID = command_bar.window->id; ResetBuffer(command_bar.buffer); For (Buffers) { - if (it->special || it->garbage || it->is_dir) { + if (it->special || it->garbage) { continue; } RawAppendf(command_bar.buffer, "\n%S", it->name);