plugin_file_commands

This commit is contained in:
Krzosa Karol
2026-01-22 10:25:34 +01:00
parent 4a410c3c1a
commit 710269a876
7 changed files with 231 additions and 223 deletions

View File

@@ -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() { void CMD_FormatSelection() {
Scratch scratch; Scratch scratch;
BSet primary = GetBSet(PrimaryWindowID); BSet primary = GetBSet(PrimaryWindowID);
@@ -206,227 +216,11 @@ void CMD_FormatSelection() {
EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION); EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION);
} RegisterCommand(CMD_FormatSelection, "", ""); } 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() { void CMD_KillProcess() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
KillProcess(main.view); KillProcess(main.view);
} RegisterCommand(CMD_KillProcess, "", "Kill process in the last active primary window"); } 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<BufferID> 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() { void CMD_MakeFontLarger() {
FontSize += 1; FontSize += 1;
ReloadFont(PathToFont, (U32)FontSize); ReloadFont(PathToFont, (U32)FontSize);

View File

@@ -136,7 +136,7 @@ void CMD_OpenForFuzzyView() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
NextActiveWindowID = main.window->id; 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); String16 string = FetchFuzzyViewLoadLine(active.view);
Open(string); Open(string);
} else { } else {

View File

@@ -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) { BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectDirectory) {
CMD_SaveAll(); SaveAll();
BSet build = GetBSet(BuildWindowID); BSet build = GetBSet(BuildWindowID);
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
SelectRange(build.view, Range{}); SelectRange(build.view, Range{});

View File

@@ -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

View File

@@ -355,20 +355,18 @@ SPALL_FN bool spall_buffer_name_process(SpallProfile *ctx, SpallBuffer *wb, cons
return true; return true;
} }
#define BeginProfileScopeEx(name, len) spall_buffer_begin(&spall_ctx, &spall_buffer, (name), (len), GetTimeNanos()) #define BeginProfileScopeEx(name, len) spall_buffer_begin(&spall_ctx, &spall_buffer, (name), (len), GetTimeNanos())
#define BeginProfileScope(name) BeginProfileScopeEx(#name, sizeof(#name) - 1) #define BeginProfileScope(name) BeginProfileScopeEx(#name, sizeof(#name) - 1)
#define EndProfileScope() spall_buffer_end(&spall_ctx, &spall_buffer, GetTimeNanos()) #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 { struct ProfileScopeClass {
ProfileScopeClass(const char *name, int len) { BeginProfileScopeEx(name, len); } ProfileScopeClass(const char *name, int len) { BeginProfileScopeEx(name, len); }
~ProfileScopeClass() { EndProfileScope(); } ~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 BeginProfiler();
SPALL_FN void EndProfiler(); SPALL_FN void EndProfiler();
#else #else
#define ProfileScopeEx(name) #define ProfileScopeEx(name)
#define ProfileScope(name) #define ProfileScope(name)

View File

@@ -35,6 +35,7 @@
#define PLUGIN_DIRECTORY_NAVIGATION 1 #define PLUGIN_DIRECTORY_NAVIGATION 1
#define PLUGIN_LOAD_VCVARS OS_WINDOWS #define PLUGIN_LOAD_VCVARS OS_WINDOWS
#define PLUGIN_REMEDYBG OS_WINDOWS #define PLUGIN_REMEDYBG OS_WINDOWS
#define PLUGIN_FILE_COMMANDS 1
#include "plugin_directory_navigation.h" #include "plugin_directory_navigation.h"
#include "plugin_search_window.h" #include "plugin_search_window.h"
@@ -72,6 +73,7 @@
#include "plugin_load_vcvars.cpp" #include "plugin_load_vcvars.cpp"
#include "plugin_remedybg.cpp" #include "plugin_remedybg.cpp"
#include "plugin_profiler.cpp" #include "plugin_profiler.cpp"
#include "plugin_file_commands.cpp"
#if OS_WASM #if OS_WASM
EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), { EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), {

View File

@@ -113,6 +113,40 @@ String16 QueryUserString(mco_coro *co, String ask) {
return GetString(main.buffer, a.range); 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<BufferID> 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) { String QueryUserFile(mco_coro *co) {
#if PLUGIN_DIRECTORY_NAVIGATION #if PLUGIN_DIRECTORY_NAVIGATION
Window *window = GetWindow(PrimaryWindowID); Window *window = GetWindow(PrimaryWindowID);