From 710269a8768f1596b8ed7cd54040b219d68eceb3 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Thu, 22 Jan 2026 10:25:34 +0100 Subject: [PATCH] plugin_file_commands --- src/text_editor/commands.cpp | 226 +---------------------- src/text_editor/fuzzy_search_view.cpp | 2 +- src/text_editor/plugin_build_window.cpp | 2 +- src/text_editor/plugin_file_commands.cpp | 180 ++++++++++++++++++ src/text_editor/plugin_profiler.h | 8 +- src/text_editor/text_editor.cpp | 2 + src/text_editor/ui.cpp | 34 ++++ 7 files changed, 231 insertions(+), 223 deletions(-) create mode 100644 src/text_editor/plugin_file_commands.cpp diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 532978c..e6e87f0 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -182,6 +182,16 @@ void ApplyFormattingTool(Buffer *buffer, String tool) { } } +void SaveAll() { + For(Buffers) { + // NOTE: file_mod_time is only set when buffer got read or written to disk already so should be saved + if (it->file_mod_time && it->dirty) { + SaveBuffer(it); + } + } +} + +// @todo: plugin_languages ? and then branch out language_cpp ... void CMD_FormatSelection() { Scratch scratch; BSet primary = GetBSet(PrimaryWindowID); @@ -206,227 +216,11 @@ void CMD_FormatSelection() { EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION); } RegisterCommand(CMD_FormatSelection, "", ""); -void New(Window *window, String name = "") { - View *view = GetView(window->active_view); - Buffer *buffer = GetBuffer(view->active_buffer); - - Scratch scratch; - String dir = GetDirectory(buffer); - if (name != "") { - if (!IsAbsolute(name)) { - name = Format(scratch, "%S/%S", dir, name); - } - name = GetAbsolutePath(scratch, name); - } else { - name = GetUniqueBufferName(dir, "new"); - } - WindowOpenBufferView(window, name); -} - -void CMD_New() { - BSet main = GetBSet(PrimaryWindowID); - New(main.window, ""); -} RegisterCommand(CMD_New, "ctrl-n", "Open a new buffer with automatically generated name, use :Rename"); - -void CMD_SaveAll() { - For(Buffers) { - // NOTE: file_mod_time is only set when buffer got read or written to disk already so should be saved - if (it->file_mod_time && it->dirty) { - SaveBuffer(it); - } - } -} RegisterCommand(CMD_SaveAll, "ctrl-shift-s", ""); - -void CMD_Save() { - BSet active = GetBSet(PrimaryWindowID); - SaveBuffer(active.buffer); -} RegisterCommand(CMD_Save, "ctrl-s", "Save buffer currently open in the last primary window"); - -void CMD_Reopen() { - BSet main = GetBSet(PrimaryWindowID); - ReopenBuffer(main.buffer); - NextActiveWindowID = main.window->id; -} RegisterCommand(CMD_Reopen, "", "Reads again from disk the current buffer"); - void CMD_KillProcess() { BSet main = GetBSet(PrimaryWindowID); KillProcess(main.view); } RegisterCommand(CMD_KillProcess, "", "Kill process in the last active primary window"); -void CO_Rename(mco_coro *co) { - BSet main = GetBSet(PrimaryWindowID); - Buffer *original_buffer = main.buffer; - JumpTempBuffer(&main); - NextActiveWindowID = main.window->id; - RawAppendf(main.buffer, "Rename and click enter to submit: [%S]\n :Rename :Cancel", original_buffer->name); - - main.view->carets[0] = FindNext(main.buffer, u"]", MakeCaret(0)); - main.view->carets[0].range.max = main.view->carets[0].range.min; - AddUIAction(main.view, "Rename", EnterKey, UIAction_Yes); - AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); - UIAction action = WaitForUIAction(co, main); - if (action != UIAction_Yes) { - return; - } - - Caret a = FindNext(main.buffer, u"[", MakeCaret(-1)); - Caret b = FindNext(main.buffer, u"]", MakeCaret(-1)); - if (a.range.min == -1 || b.range.max == -1) { - ReportErrorf("Failed to extract the name for the Rename operation from the buffer"); - return; - } - String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max}); - String string = ToString(CoCurr->arena, string16); - original_buffer->name = Intern(&GlobalInternTable, string); -} RegisterCoroutineCommand(CO_Rename, "", "Opens a UI asking for a new name of a buffer open in the last active primary window"); - -void CO_Close(mco_coro *co) { - BSet main = GetBSet(PrimaryWindowID); - if (main.buffer->special || main.buffer->temp) { - Close(main.view->id); - return; - } - - bool ref = false; - For (Views) { - if (it->id == main.view->id) { - continue; - } - if (it->active_buffer == main.buffer->id) { - ref = true; - break; - } - } - - if (ref) { - Close(main.view->id); - return; - } - - if (!main.buffer->dirty) { - Close(main.buffer->id); - return; - } - - String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", main.buffer->name); - UIAction ui_action = QueryUserYesNoCancel(co, main, question); - if (ui_action == UIAction_Yes) { - SaveBuffer(main.buffer); - Close(main.buffer->id); - } else if (ui_action == UIAction_No) { - Close(main.buffer->id); - } else if (ui_action == UIAction_Cancel) { - return; - } ElseInvalidCodepath(); -} RegisterCoroutineCommand(CO_Close, "ctrl-w", "Close open view in the last active primary window"); - -void CMD_DeleteFile() { - BSet main = GetBSet(PrimaryWindowID); - String buffer_name = main.buffer->name; - DeleteFile(main.buffer->name); - Close(main.buffer->id); -} RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk"); - -// Considerations with coroutines: -// 1. Does scratch memory leak across Yield boundary? Or interacts badly with Yield stuff in any way? -// 2. Are pointers and globals correct over time? Or might they get deleted etc. -// 3. Imagine a scenario where the coroutine gets deleted before completion, will the memory leak? -UIAction ShowCloseAllUI(mco_coro *co) { - BSet main = GetBSet(PrimaryWindowID); - Array buffers = {CoCurr->arena}; - For (Buffers) Add(&buffers, it->id); - ForItem (id, buffers) { - Buffer *it = GetBuffer(id, NULL); - if (it == NULL || it->special || !it->dirty) { - continue; - } - if (it->temp || it->dont_try_to_save_in_bulk_ops) { - continue; - } - - String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", it->name); - UIAction ui_action = QueryUserYesNoCancel(co, main, question); - it = GetBuffer(id, NULL); - if (it && ui_action == UIAction_Yes) { - SaveBuffer(it); - } else if (ui_action == UIAction_No) { - } else if (ui_action == UIAction_Cancel) { - return UIAction_Cancel; - } ElseInvalidCodepath(); - } - - For(Buffers) { - Close(it->id); - } - return UIAction_Yes; -} - -void CO_CloseAll(mco_coro *co) { - ShowCloseAllUI(co); -} RegisterCoroutineCommand(CO_CloseAll, "", "Ask user which files to save and close all open normal views and buffers"); - -void CO_ReplaceAll(mco_coro *co) { - BSet main = GetBSet(PrimaryWindowID); - String16 string = FetchLoadWord(main.view); - String string8 = ToString(CoCurr->arena, string); - String16 needle = {}; - String16 replace = {}; - - { - JumpTempBuffer(&main); - NextActiveWindowID = main.window->id; - RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to search for::%S", string8); - - Caret field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None); - main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max); - AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes); - AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); - UIAction action = WaitForUIAction(co, main); - if (action == UIAction_Cancel) { - return; - } - - field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None); - Range range = {field_seek.range.max, main.buffer->len}; - needle = GetString(main.buffer, range); - } - - { - JumpTempBuffer(&main); - NextActiveWindowID = main.window->id; - RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to replace with::%S", ToString(CoCurr->arena, needle)); - - Caret field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None); - main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max); - AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes); - AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); - UIAction action = WaitForUIAction(co, main); - if (action == UIAction_Cancel) { - return; - } - - field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None); - Range range = {field_seek.range.max, main.buffer->len}; - replace = GetString(main.buffer, range); - } - - ForItem (buffer, Buffers) { - if (buffer->special || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) { - continue; - } -#if PLUGIN_DIRECTORY_NAVIGATION - if (buffer->is_dir) { - continue; - } -#endif - - View *view = OpenBufferView(buffer->name); - if (SelectAllOccurences(view, needle)) { - Replace(view, replace); - } - } -} RegisterCoroutineCommand(CO_ReplaceAll, "ctrl-shift-r", "Search and replace over the entire project, you need to select a text with format like this 'FindAnd@>ReplaceWith' and executing the command will change all occurences of FindAnd to ReplaceWith"); - void CMD_MakeFontLarger() { FontSize += 1; ReloadFont(PathToFont, (U32)FontSize); diff --git a/src/text_editor/fuzzy_search_view.cpp b/src/text_editor/fuzzy_search_view.cpp index 07fa206..ff75aa6 100644 --- a/src/text_editor/fuzzy_search_view.cpp +++ b/src/text_editor/fuzzy_search_view.cpp @@ -136,7 +136,7 @@ void CMD_OpenForFuzzyView() { BSet active = GetBSet(ActiveWindowID); BSet main = GetBSet(PrimaryWindowID); NextActiveWindowID = main.window->id; - if (active.view->carets.len == 1) { + if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) { String16 string = FetchFuzzyViewLoadLine(active.view); Open(string); } else { diff --git a/src/text_editor/plugin_build_window.cpp b/src/text_editor/plugin_build_window.cpp index aff83a4..32e3a0a 100644 --- a/src/text_editor/plugin_build_window.cpp +++ b/src/text_editor/plugin_build_window.cpp @@ -32,7 +32,7 @@ void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) { } BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectDirectory) { - CMD_SaveAll(); + SaveAll(); BSet build = GetBSet(BuildWindowID); BSet main = GetBSet(PrimaryWindowID); SelectRange(build.view, Range{}); diff --git a/src/text_editor/plugin_file_commands.cpp b/src/text_editor/plugin_file_commands.cpp new file mode 100644 index 0000000..0f07522 --- /dev/null +++ b/src/text_editor/plugin_file_commands.cpp @@ -0,0 +1,180 @@ +#if PLUGIN_FILE_COMMANDS + +void New(Window *window, String name = "") { + View *view = GetView(window->active_view); + Buffer *buffer = GetBuffer(view->active_buffer); + + Scratch scratch; + String dir = GetDirectory(buffer); + if (name != "") { + if (!IsAbsolute(name)) { + name = Format(scratch, "%S/%S", dir, name); + } + name = GetAbsolutePath(scratch, name); + } else { + name = GetUniqueBufferName(dir, "new"); + } + WindowOpenBufferView(window, name); +} + +void CMD_New() { + BSet main = GetBSet(PrimaryWindowID); + New(main.window, ""); +} RegisterCommand(CMD_New, "ctrl-n", "Open a new buffer with automatically generated name, use :Rename"); + +void CMD_SaveAll() { + SaveAll(); +} RegisterCommand(CMD_SaveAll, "ctrl-shift-s", ""); + +void CMD_Save() { + BSet active = GetBSet(PrimaryWindowID); + SaveBuffer(active.buffer); +} RegisterCommand(CMD_Save, "ctrl-s", "Save buffer currently open in the last primary window"); + +void CMD_Reopen() { + BSet main = GetBSet(PrimaryWindowID); + ReopenBuffer(main.buffer); + NextActiveWindowID = main.window->id; +} RegisterCommand(CMD_Reopen, "", "Reads again from disk the current buffer"); + +void CO_Rename(mco_coro *co) { + BSet main = GetBSet(PrimaryWindowID); + Buffer *original_buffer = main.buffer; + JumpTempBuffer(&main); + NextActiveWindowID = main.window->id; + RawAppendf(main.buffer, "Rename and click enter to submit: [%S]\n :Rename :Cancel", original_buffer->name); + + main.view->carets[0] = FindNext(main.buffer, u"]", MakeCaret(0)); + main.view->carets[0].range.max = main.view->carets[0].range.min; + AddUIAction(main.view, "Rename", EnterKey, UIAction_Yes); + AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); + UIAction action = WaitForUIAction(co, main); + if (action != UIAction_Yes) { + return; + } + + Caret a = FindNext(main.buffer, u"[", MakeCaret(-1)); + Caret b = FindNext(main.buffer, u"]", MakeCaret(-1)); + if (a.range.min == -1 || b.range.max == -1) { + ReportErrorf("Failed to extract the name for the Rename operation from the buffer"); + return; + } + String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max}); + String string = ToString(CoCurr->arena, string16); + original_buffer->name = Intern(&GlobalInternTable, string); +} RegisterCoroutineCommand(CO_Rename, "", "Opens a UI asking for a new name of a buffer open in the last active primary window"); + +void CO_Close(mco_coro *co) { + BSet main = GetBSet(PrimaryWindowID); + if (main.buffer->special || main.buffer->temp) { + Close(main.view->id); + return; + } + + bool ref = false; + For (Views) { + if (it->id == main.view->id) { + continue; + } + if (it->active_buffer == main.buffer->id) { + ref = true; + break; + } + } + + if (ref) { + Close(main.view->id); + return; + } + + if (!main.buffer->dirty) { + Close(main.buffer->id); + return; + } + + String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", main.buffer->name); + UIAction ui_action = QueryUserYesNoCancel(co, main, question); + if (ui_action == UIAction_Yes) { + SaveBuffer(main.buffer); + Close(main.buffer->id); + } else if (ui_action == UIAction_No) { + Close(main.buffer->id); + } else if (ui_action == UIAction_Cancel) { + return; + } ElseInvalidCodepath(); +} RegisterCoroutineCommand(CO_Close, "ctrl-w", "Close open view in the last active primary window"); + +void CMD_DeleteFile() { + BSet main = GetBSet(PrimaryWindowID); + String buffer_name = main.buffer->name; + DeleteFile(main.buffer->name); + Close(main.buffer->id); +} RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk"); + +void CO_CloseAll(mco_coro *co) { + ShowCloseAllUI(co); +} RegisterCoroutineCommand(CO_CloseAll, "", "Ask user which files to save and close all open normal views and buffers"); + +void CO_ReplaceAll(mco_coro *co) { + BSet main = GetBSet(PrimaryWindowID); + String16 string = FetchLoadWord(main.view); + String string8 = ToString(CoCurr->arena, string); + String16 needle = {}; + String16 replace = {}; + + { + JumpTempBuffer(&main); + NextActiveWindowID = main.window->id; + RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to search for::%S", string8); + + Caret field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None); + main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max); + AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes); + AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); + UIAction action = WaitForUIAction(co, main); + if (action == UIAction_Cancel) { + return; + } + + field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None); + Range range = {field_seek.range.max, main.buffer->len}; + needle = GetString(main.buffer, range); + } + + { + JumpTempBuffer(&main); + NextActiveWindowID = main.window->id; + RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to replace with::%S", ToString(CoCurr->arena, needle)); + + Caret field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None); + main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max); + AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes); + AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel); + UIAction action = WaitForUIAction(co, main); + if (action == UIAction_Cancel) { + return; + } + + field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None); + Range range = {field_seek.range.max, main.buffer->len}; + replace = GetString(main.buffer, range); + } + + ForItem (buffer, Buffers) { + if (buffer->special || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) { + continue; + } +#if PLUGIN_DIRECTORY_NAVIGATION + if (buffer->is_dir) { + continue; + } +#endif + + View *view = OpenBufferView(buffer->name); + if (SelectAllOccurences(view, needle)) { + Replace(view, replace); + } + } +} RegisterCoroutineCommand(CO_ReplaceAll, "ctrl-shift-r", "Search and replace over the entire project, you need to select a text with format like this 'FindAnd@>ReplaceWith' and executing the command will change all occurences of FindAnd to ReplaceWith"); + +#endif \ No newline at end of file diff --git a/src/text_editor/plugin_profiler.h b/src/text_editor/plugin_profiler.h index 61d14d4..24511a8 100644 --- a/src/text_editor/plugin_profiler.h +++ b/src/text_editor/plugin_profiler.h @@ -355,20 +355,18 @@ SPALL_FN bool spall_buffer_name_process(SpallProfile *ctx, SpallBuffer *wb, cons return true; } - #define BeginProfileScopeEx(name, len) spall_buffer_begin(&spall_ctx, &spall_buffer, (name), (len), GetTimeNanos()) #define BeginProfileScope(name) BeginProfileScopeEx(#name, sizeof(#name) - 1) #define EndProfileScope() spall_buffer_end(&spall_ctx, &spall_buffer, GetTimeNanos()) -#define ProfileScopeEx(name) ProfileScopeClass PROFILE_SCOPE_VAR_((name).data, (int)(name).len) -#define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1) -#define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1) struct ProfileScopeClass { ProfileScopeClass(const char *name, int len) { BeginProfileScopeEx(name, len); } ~ProfileScopeClass() { EndProfileScope(); } }; +#define ProfileScopeEx(name) ProfileScopeClass PROFILE_SCOPE_VAR_((name).data, (int)(name).len) +#define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1) +#define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1) SPALL_FN void BeginProfiler(); SPALL_FN void EndProfiler(); - #else #define ProfileScopeEx(name) #define ProfileScope(name) diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 77683f0..b8cd953 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -35,6 +35,7 @@ #define PLUGIN_DIRECTORY_NAVIGATION 1 #define PLUGIN_LOAD_VCVARS OS_WINDOWS #define PLUGIN_REMEDYBG OS_WINDOWS +#define PLUGIN_FILE_COMMANDS 1 #include "plugin_directory_navigation.h" #include "plugin_search_window.h" @@ -72,6 +73,7 @@ #include "plugin_load_vcvars.cpp" #include "plugin_remedybg.cpp" #include "plugin_profiler.cpp" +#include "plugin_file_commands.cpp" #if OS_WASM EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), { diff --git a/src/text_editor/ui.cpp b/src/text_editor/ui.cpp index 008ba18..2575ad7 100644 --- a/src/text_editor/ui.cpp +++ b/src/text_editor/ui.cpp @@ -113,6 +113,40 @@ String16 QueryUserString(mco_coro *co, String ask) { return GetString(main.buffer, a.range); } +// Considerations with coroutines: +// 1. Does scratch memory leak across Yield boundary? Or interacts badly with Yield stuff in any way? +// 2. Are pointers and globals correct over time? Or might they get deleted etc. +// 3. Imagine a scenario where the coroutine gets deleted before completion, will the memory leak? +UIAction ShowCloseAllUI(mco_coro *co) { + BSet main = GetBSet(PrimaryWindowID); + Array buffers = {CoCurr->arena}; + For (Buffers) Add(&buffers, it->id); + ForItem (id, buffers) { + Buffer *it = GetBuffer(id, NULL); + if (it == NULL || it->special || !it->dirty) { + continue; + } + if (it->temp || it->dont_try_to_save_in_bulk_ops) { + continue; + } + + String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", it->name); + UIAction ui_action = QueryUserYesNoCancel(co, main, question); + it = GetBuffer(id, NULL); + if (it && ui_action == UIAction_Yes) { + SaveBuffer(it); + } else if (ui_action == UIAction_No) { + } else if (ui_action == UIAction_Cancel) { + return UIAction_Cancel; + } ElseInvalidCodepath(); + } + + For(Buffers) { + Close(it->id); + } + return UIAction_Yes; +} + String QueryUserFile(mco_coro *co) { #if PLUGIN_DIRECTORY_NAVIGATION Window *window = GetWindow(PrimaryWindowID);