From bfcab9b3c311cf78ba85d01f101a13b2410f0106 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sat, 3 Jan 2026 14:48:27 +0100 Subject: [PATCH] Fix EvalCommandsLineByLine wholly failing on any error, redesign :Set --- src/backup/todo.txt | 5 +- src/basic/basic_string.cpp | 71 ++++++ src/text_editor/buffer.cpp | 4 + src/text_editor/commands.cpp | 367 ++++++++++++----------------- src/text_editor/globals.cpp | 3 +- src/text_editor/text_editor.h | 1 + src/text_editor/window_command.cpp | 10 +- 7 files changed, 235 insertions(+), 226 deletions(-) diff --git a/src/backup/todo.txt b/src/backup/todo.txt index f88f144..4ef25d6 100644 --- a/src/backup/todo.txt +++ b/src/backup/todo.txt @@ -2,10 +2,9 @@ ! From a user (novice) point of view, how does it look like? Use session 4 -- SkipLoadWord -- Delete file command +- Add <> <> template strings to Open (Then remove SEtWorkdirhere) +- :Set Filename to name current buffer ??? :O and others like that!! - :Close Fuzzy search exact match doesn't match with Close -- Maybe search everything window should have a special buffer - Setting variables maybe should create and modify config, commit these changes immediately? So user can change keybindings in command window and commit immediately - Make the special view hooks also available for modification and registered but maybe under different name or something diff --git a/src/basic/basic_string.cpp b/src/basic/basic_string.cpp index 8490544..cd8c27d 100644 --- a/src/basic/basic_string.cpp +++ b/src/basic/basic_string.cpp @@ -339,6 +339,22 @@ API Array Split(Allocator allocator, String string, String delimiter) { return result; } +API Array SplitWhitespace(Allocator allocator, String string) { + Array result = {allocator}; + String it = {string.data, 0}; + for (int64_t i = 0; i < string.len; i += 1) { + if (IsWhitespace(string[i])) { + if (it.len) Add(&result, it); + it.len = 0; + it.data = string.data + (i + 1); + } else { + it.len += 1; + } + } + if (it.len) Add(&result, it); + return result; +} + API String Merge(Allocator allocator, Array list, String separator) { int64_t char_count = 0; For(list) char_count += it.len; @@ -441,3 +457,58 @@ API Int ChopNumber(String *string) { Int result = strtoll(col.data, NULL, 10) - 1; return result; } + +String SkipIdent(String *string) { + String begin = {string->data, 0}; + if (IsIdent(At(*string, 0))) { + for (;string->len;) { + char c = At(*string, 0); + if (!IsIdent(c) && !IsDigit(c)) { + break; + } + *string = Skip(*string, 1); + begin.len += 1; + } + } + return begin; +} + +String SkipString(String *string) { + String saved_string = *string; + char c = At(*string, 0); + String q = {&c, 1}; + if (c == '"' || c == '\'') { + *string = Skip(*string, 1); + String quote = SkipUntil(string, q); + if (At(*string, 0) != c) { + *string = saved_string; + return {}; + } + return quote; + } + return {}; +} + +API String SkipFloatEx(String *string) { + String col = {string->data, 0}; + if (At(*string, 0) == '-') { + col.len += 1; + *string = Skip(*string, 1); + } + for (int64_t i = 0; i < string->len; i += 1) { + if (IsDigit(string->data[i]) || string->data[i] == u'.') { + col.len += 1; + } else { + break; + } + } + *string = Skip(*string, col.len); + return col; +} + +API Float SkipFloat(String *string) { + String col = SkipFloatEx(string); + if (col.len == 0) return 0; + Float result = strtod(string->data, NULL); + return result; +} diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index e7e751e..f75f581 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1399,6 +1399,10 @@ void InitBuffers() { EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "events")); EventBuffer->no_history = true; EventBuffer->special = true; + Buffer *search_project = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "search_project")); + search_project->no_history = true; + search_project->special = true; + SearchProjectBufferID = search_project->id; } Int ConvertUTF8ToUTF16UnixLine(String string, char16_t *buffer, Int buffer_cap) { diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 6b51c2a..e87775f 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -480,6 +480,135 @@ bool IsOpenBoundary(char c) { return result; } +#define ExpectP(x, ...) \ + if (!(x)) { \ + ReportErrorf("Failed to parse '" __FUNCTION__ "' command, " __VA_ARGS__); \ + return; \ + } + +void SetWorkDirHere(String dir) { + WorkDir = Intern(&GlobalInternTable, dir); + Scratch scratch; + For (Buffers) { + if (it->special) { + String name = SkipToLastSlash(it->name); + it->name = Intern(&GlobalInternTable, Format(scratch, "%S/%S", dir, name)); + } + } +} + +void Set(String string) { + String name = SkipIdent(&string); + ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string); + + Variable *var = NULL; + For (Variables) { + if (name == it.name) { + var = ⁢ + break; + } + } + + if (var) { + SkipWhitespace(&string); + if (var->type == VariableType_String) { + char c = At(string, 0); + ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); + string = Skip(string, 1); + String quote = SkipUntil(&string, {&c, 1}); + ExpectP(At(string, 0) == c, ":Set %S , unclosed quote", name); + ReportConsolef(":Set %S %c%S%c", name, c, quote, c); + *var->string = Intern(&GlobalInternTable, quote); + } else if (var->type == VariableType_Int) { + ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string); + Int number = SkipInt(&string); + ReportConsolef(":Set %S %lld", name, number); + *var->i = number; + } else if (var->type == VariableType_Float) { + ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string); + Float number = SkipFloat(&string); + ReportConsolef(":Set %S %f", name, number); + *var->f = number; + } else if (var->type == VariableType_Color) { + ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string); + String begin = {string.data, 0}; + while (IsHexDigit(At(string, 0))) { + string = Skip(string, 1); + begin.len += 1; + } + ReportConsolef(":Set %S %S", name, begin); + var->color->value = (uint32_t)strtoll(begin.data, NULL, 16); + } ElseInvalidCodepath(); + + + if (name == "FontSize" || name == "Font") { + ReloadFont(Font, (U32)FontSize); + } else if (name == "WorkDir") { + SetWorkDirHere(*var->string); + } + + return; + } + + CommandData *cmd = NULL; + For (CommandFunctions) { + if (it.name == name) { + cmd = ⁢ + break; + } + } + + if (cmd) { + SkipWhitespace(&string); + char c = At(string, 0); + ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); + string = Skip(string, 1); + String quote = SkipUntil(&string, {&c, 1}); + ExpectP(At(string, 0) == c, "Failed to parse command, unclose quote"); + quote = Intern(&GlobalInternTable, quote); + ReportConsolef(":Set %S %c%S%c", name, c, quote, c); + Trigger *trigger = ParseKeyCached(quote); + if (trigger) { + cmd->binding = quote; + cmd->trigger = trigger; + } + return; + } + + ReportErrorf("Failed to :Set, no such variable found: %S", name); +} + +void EvalCommandsLineByLine(BSet set) { + WindowID save_last = PrimaryWindowID; + WindowID save_active = ActiveWindowID; + WindowID save_next = NextActiveWindowID; + Caret save_caret = set.view->carets[0]; + ActiveWindowID = set.window->id; + PrimaryWindowID = set.window->id; + NextActiveWindowID = set.window->id; + for (Int i = 0; i < set.buffer->line_starts.len; i += 1) { + Range range = GetLineRangeWithoutNL(set.buffer, i); + String16 string = GetString(set.buffer, range); + string = Trim(string); + if (string.len == 0) { + continue; + } + if (StartsWith(string, u"//")) { + continue; + } + Open(string); + } + set.view->carets[0] = save_caret; + PrimaryWindowID = save_last; + ActiveWindowID = save_active; + NextActiveWindowID = save_next; +} + +void CMD_EvalCommandsLineByLine() { + BSet set = GetBSet(PrimaryWindowID); + EvalCommandsLineByLine(set); +} RegisterCommand(CMD_EvalCommandsLineByLine, "", "Goes line by line over a buffer and evaluates every line as a command, ignores empty or lines starting with '//'"); + ResolvedOpen ResolveOpen(Allocator alo, String path, ResolveOpenMeta meta) { ResolvedOpen result = {}; path = Trim(path); @@ -487,6 +616,12 @@ ResolvedOpen ResolveOpen(Allocator alo, String path, ResolveOpenMeta meta) { // Editor command if(!(ResolveOpenMeta_DontExec & meta)) { if (StartsWith(path, ":")) { + if (StartsWith(path, ":Set ")) { + result.kind = OpenKind_Set; + result.path = Skip(path, 5); + return result; + } + result.kind = OpenKind_Command; path = Skip(path, 1); result.path.data = path.data; @@ -654,6 +789,8 @@ BSet Open(Window *window, String path, ResolveOpenMeta meta, bool set_active = t Exec(NullViewID, false, o.path, GetMainDir()); } else if (o.kind == OpenKind_Command) { EvalCommand(o.path); + } else if (o.kind == OpenKind_Set) { + Set(o.path); } else if (o.kind == OpenKind_Skip) { return {}; } else { @@ -708,45 +845,10 @@ void CMD_ToggleFullscreen() { IsInFullscreen = !IsInFullscreen; } RegisterCommand(CMD_ToggleFullscreen, "f11"); -String16 FetchStringForCommandParsing() { - BSet set = GetBSet(ActiveWindowID); - Range range = set.view->carets[0].range; - range.max = range.min; // We only scan for :Set - if (GetSize(range) == 0) { - range = EncloseLoadWord(set.buffer, range.min); - } - Int line_end = GetLineEnd(set.buffer, range.min); - String16 string = GetString(set.buffer, {range.min, line_end}); - return string; -} - -void SetWorkDir(String string) { - Scratch scratch; - WorkDir = Intern(&GlobalInternTable, string); - For (Buffers) { - if (it->special) { - String name = SkipToLastSlash(it->name); - it->name = Intern(&GlobalInternTable, Format(scratch, "%S/%S", WorkDir, name)); - } - } -} - -void CMD_SetWorkDir() { - Scratch scratch; +void CMD_SetWorkDirHere() { BSet main = GetBSet(PrimaryWindowID); - SetWorkDir(GetDir(main.buffer)); -} RegisterCommand(CMD_SetWorkDir, "", "Sets work directory to the directory of the current buffer, it also renames couple special buffers to make them accomodate the new WorkDir"); - -void CMD_SetWorkDirAt() { - String16 string = FetchStringForCommandParsing(); - string = Skip(string, 1); - SkipIdent(&string); - SkipWhitespace(&string); - Scratch scratch; - String16 arg = SkipString(&string); - String arg8 = ToString(scratch, arg); - SetWorkDir(arg8); -} RegisterCommand(CMD_SetWorkDirAt, "", "Sets work directory using the argument string passed here, it also renames couple special buffers to make them accomodate the new WorkDir"); + SetWorkDirHere(GetDir(main.buffer)); +} RegisterCommand(CMD_SetWorkDirHere, "", "Sets work directory to the directory of the current buffer, it also renames couple special buffers to make them accomodate the new WorkDir"); void Coro_OpenCode(mco_coro *co) { Array patterns = Split(CoCurr->arena, NonCodePatterns_EndsWith, "|"); @@ -790,17 +892,6 @@ void CMD_OpenCode() { OpenCode(WorkDir); } RegisterCommand(CMD_OpenCode, "", "Open all code files in current WorkDir, the code files are determined through NonCodePatterns_EndsWith config variable list"); -void CMD_OpenCodeAt() { - String16 string = FetchStringForCommandParsing(); - string = Skip(string, 1); - SkipIdent(&string); - SkipWhitespace(&string); - Scratch scratch; - String16 arg = SkipString(&string); - String arg8 = ToString(scratch, arg); - OpenCode(arg8); -} RegisterCommand(CMD_OpenCodeAt, "", "Open all code files pointed to by string argument following the command"); - void CMD_KillProcess() { BSet main = GetBSet(PrimaryWindowID); KillProcess(main.view); @@ -936,6 +1027,13 @@ void CMD_Close() { CoResume(data); } RegisterCommand(CMD_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. @@ -1250,10 +1348,10 @@ void CMD_SelectToLineEnd() { MoveCursorToSide(active.view, DIR_RIGHT, SHIFT_PRESS); } RegisterCommand(CMD_SelectToLineEnd, "shift-end"); -void CMD_Delete() { +void CMD_DeleteCharacter() { BSet active = GetBSet(ActiveWindowID); Delete(active.view, DIR_LEFT); -} RegisterCommand(CMD_Delete, "shift-backspace | backspace"); +} RegisterCommand(CMD_DeleteCharacter, "shift-backspace | backspace"); void CMD_DeleteBoundary() { BSet active = GetBSet(ActiveWindowID); @@ -1343,173 +1441,6 @@ void CMD_ClearCarets() { } } RegisterCommand(CMD_ClearCarets, "escape", "Clear all carets and reset to 1 caret, also do some windowing stuff that closes things on escape"); -void Set(String16 string) { - Scratch scratch; - - SkipWhitespace(&string); - if (At(string, 0) != u':') { - ReportErrorf("Expected :Set"); - return; - } - string = Skip(string, 1); - - if (!MatchIdent(&string, u"Set")) { - ReportErrorf("Expected :Set"); - return; - } - - SkipWhitespace(&string); - String16 name = SkipIdent(&string); - if (name.len == 0) { - ReportErrorf("Set command failed to parse at the variable name"); - return; - } - - String name8 = ToString(scratch, name); - Variable *var = NULL; - For (Variables) { - if (name8 == it.name) { - var = ⁢ - break; - } - } - - if (var) { - SkipWhitespace(&string); - if (var->type == VariableType_String) { - char16_t c = At(string, 0); - String16 q = {&c, 1}; - if (c == u'"' || c == u'\'') { - string = Skip(string, 1); - String16 quote = SkipUntil(&string, q); - if (At(string, 0) != c) { - ReportErrorf("Failed to parse :Set %S , unclosed quote", name8); - return; - } - String quote8 = ToString(scratch, quote); - ReportConsolef(":Set %S %c%S%c", name8, c, quote8, c); - *var->string = Intern(&GlobalInternTable, quote8); - } else { - ReportErrorf("Failed to parse :Set %S ", name8); - return; - } - } else if (var->type == VariableType_Int) { - if (IsDigit(At(string, 0))) { - Int number = SkipInt(&string); - ReportConsolef(":Set %S %lld", name8, number); - *var->i = number; - } else { - ReportErrorf("Failed to parse :Set %S ", name8); - return; - } - } else if (var->type == VariableType_Float) { - if (IsDigit(At(string, 0)) || At(string, 0) == '.') { - Float number = SkipFloat(&string); - ReportConsolef(":Set %S %f", name8, number); - *var->f = number; - } else { - ReportErrorf("Failed to parse :Set %S ", name8); - return; - } - } else if (var->type == VariableType_Color) { - if (IsHexDigit(At(string, 0))) { - String16 begin = {string.data, 0}; - while (IsHexDigit(At(string, 0))) { - string = Skip(string, 1); - begin.len += 1; - } - String p = ToString(scratch, begin); - ReportConsolef(":Set %S %S", name8, p); - var->color->value = (uint32_t)strtoll(p.data, NULL, 16); - } else { - ReportErrorf("Failed to parse :Set %S ", name8); - return; - } - } ElseInvalidCodepath(); - - - if (name8 == "FontSize" || name8 == "Font") { - ReloadFont(Font, (U32)FontSize); - } - - return; - } - - CommandData *cmd = NULL; - For (CommandFunctions) { - if (it.name == name8) { - cmd = ⁢ - break; - } - } - - if (cmd) { - SkipWhitespace(&string); - char16_t c = At(string, 0); - String16 q = {&c, 1}; - if (c == u'"' || c == u'\'') { - string = Skip(string, 1); - String16 quote = SkipUntil(&string, q); - if (At(string, 0) != c) { - ReportErrorf("Failed to parse :Set %S , unclosed quote", name8); - return; - } - String quote8 = Intern(&GlobalInternTable, ToString(scratch, quote)); - ReportConsolef(":Set %S %c%S%c", name8, c, quote8, c); - Trigger *trigger = ParseKeyCached(quote8); - if (trigger) { - cmd->binding = quote8; - cmd->trigger = trigger; - } - } else { - ReportErrorf("Failed to parse :Set %S ", name8); - return; - } - - return; - } - - ReportErrorf("Failed to :Set, no such variable found: %S", name8); -} - -void CMD_Set() { - String16 string = FetchStringForCommandParsing(); - Set(string); -} RegisterCommand(CMD_Set, "", "Sets a named editor variable to the text argument following the command, the format is ':Set FormatCode 0'"); - -void EvalCommandsLineByLine(BSet set) { - WindowID save_last = PrimaryWindowID; - WindowID save_active = ActiveWindowID; - WindowID save_next = NextActiveWindowID; - Caret save_caret = set.view->carets[0]; - ActiveWindowID = set.window->id; - PrimaryWindowID = set.window->id; - NextActiveWindowID = set.window->id; - for (Int i = 0; i < set.buffer->line_starts.len; i += 1) { - Int pos = GetLineRangeWithoutNL(set.buffer, i).min; - SelectRange(set.view, MakeRange(pos)); - Range range = EncloseLoadWord(set.buffer, pos); - String16 string = GetString(set.buffer, range); - string = Trim(string); - if (string.len == 0) { - continue; - } - if (StartsWith(string, u"//")) { - continue; - } - Open(string); - } - set.view->carets[0] = save_caret; - PrimaryWindowID = save_last; - ActiveWindowID = save_active; - NextActiveWindowID = save_next; -} - -void CMD_EvalCommandsLineByLine() { - BSet set = GetBSet(PrimaryWindowID); - EvalCommandsLineByLine(set); -} RegisterCommand(CMD_EvalCommandsLineByLine, "", "Goes line by line over a buffer and evaluates every line as a command, ignores empty or lines starting with '//'"); - void GenerateConfig(View *view) { For (Variables) { if (it.type == VariableType_String) { diff --git a/src/text_editor/globals.cpp b/src/text_editor/globals.cpp index 8dd1a8b..8328376 100644 --- a/src/text_editor/globals.cpp +++ b/src/text_editor/globals.cpp @@ -35,6 +35,7 @@ BufferID SearchBufferID; WindowID BuildWindowID; ViewID BuildViewID; BufferID BuildBufferID; +BufferID SearchProjectBufferID; WindowID NextActiveWindowID; WindowID ActiveWindowID; @@ -51,7 +52,6 @@ Buffer *EventBuffer; Buffer *TraceBuffer; View *TraceView; -String WorkDir; RandomSeed UniqueBufferNameSeed = {}; Array EventPlayback; BlockArena Perm; @@ -155,6 +155,7 @@ RegisterVariable(Int, DrawLineNumbers, 1); RegisterVariable(Int, DrawScrollbar, 1); RegisterVariable(Int, IndentSize, 4); RegisterVariable(Int, FontSize, 15); +RegisterVariable(String, WorkDir, ""); RegisterVariable(String, Font, ""); RegisterVariable(String, VCVarsall, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat"); RegisterVariable(Float, UndoMergeTime, 0.3); diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index 05a8a0b..5835303 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -183,6 +183,7 @@ enum OpenKind { OpenKind_BackgroundExec, OpenKind_Goto, OpenKind_Command, + OpenKind_Set, }; typedef uint32_t ResolveOpenMeta; diff --git a/src/text_editor/window_command.cpp b/src/text_editor/window_command.cpp index b386837..1334937 100644 --- a/src/text_editor/window_command.cpp +++ b/src/text_editor/window_command.cpp @@ -245,11 +245,13 @@ void FuzzySearchViewUpdate() { void CMD_SearchProject() { BSet main = GetBSet(PrimaryWindowID); - JumpTempBuffer(&main); NextActiveWindowID = main.window->id; - main.view->kind = ViewKind_ActiveSearch; - AddHook(&main.view->hooks, "Open", "ctrl-q | enter", CMD_CommandWindowOpen); - main.buffer->no_history = true; + Buffer *search_project_buffer = GetBuffer(SearchProjectBufferID); + View *view = WindowOpenBufferView(main.window, search_project_buffer->name); + view->special = true; + view->kind = ViewKind_ActiveSearch; + AddHook(&view->hooks, "Open", "ctrl-q | enter", CMD_CommandWindowOpen); + SelectRange(view, GetLineRangeWithoutNL(search_project_buffer, 0)); } RegisterCommand(CMD_SearchProject, "ctrl-shift-f", "Interactive search over the entire project in a new buffer view"); void SetFuzzy(View *view) {