From bedc2b15e214883f175fb371886ac9509fa11c0c Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 2 Jan 2026 09:08:34 +0100 Subject: [PATCH] ActiveSearch/SearchAll command and beginnings of ReplaceAll command, coroutine interface change --- src/backup/todo.txt | 2 + src/basic/basic_head.h | 4 +- src/test/tests.cpp | 3 + src/text_editor/buffer.cpp | 2 +- src/text_editor/commands.cpp | 28 +++- src/text_editor/coroutines.cpp | 11 +- src/text_editor/text_editor.cpp | 2 + src/text_editor/text_editor.h | 3 +- src/text_editor/view.cpp | 40 ++--- src/text_editor/view.h | 10 +- src/text_editor/window_command.cpp | 242 ++++++++++++++++++++--------- src/text_editor/window_search.cpp | 17 +- 12 files changed, 240 insertions(+), 124 deletions(-) diff --git a/src/backup/todo.txt b/src/backup/todo.txt index 6675650..d4c30bf 100644 --- a/src/backup/todo.txt +++ b/src/backup/todo.txt @@ -2,10 +2,12 @@ ! From a user (novice) point of view, how does it look like? - Make a fuzzy command !> grep and fuzzy over it??? (doesn't seem very useful for grep) +- Make the equivalent of SearchProject but for cmds like !@git grep -n "@>" - Add Bool variable - Initialize all keybindings at the start and refer using global variables? - RegisterCommand should_appear_in_listing variable +- RegisterCommand docs - Maybe one list for all variables including the commands etc? Use session 3: diff --git a/src/basic/basic_head.h b/src/basic/basic_head.h index 5f5ad4d..a7c4026 100644 --- a/src/basic/basic_head.h +++ b/src/basic/basic_head.h @@ -162,10 +162,10 @@ inline size_t WrapAroundPowerOf2(size_t x, size_t pow2) { return result; } -inline uint64_t HashBytes(void *data, unsigned size) { +inline uint64_t HashBytes(void *data, int64_t size) { uint8_t *data8 = (uint8_t *)data; uint64_t hash = (uint64_t)14695981039346656037ULL; - for (unsigned i = 0; i < size; i++) { + for (int64_t i = 0; i < size; i++) { hash = hash ^ (uint64_t)(data8[i]); hash = hash * (uint64_t)1099511628211ULL; } diff --git a/src/test/tests.cpp b/src/test/tests.cpp index 3c59e4b..99f3d5a 100644 --- a/src/test/tests.cpp +++ b/src/test/tests.cpp @@ -152,6 +152,9 @@ void Test(mco_coro *co) { void InitTests() { WaitForEvents = false; TestDir = Format(TestArena, "%S/test_env", GetExeDir(TestArena)); + + CoRemove("Test"); CoData *data = CoAdd(Test); data->dont_wait_until_resolved = true; + CoResume(data); } diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index f61c765..e7e751e 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1385,7 +1385,7 @@ String GetUniqueBufferName(String working_dir, String prepend_name, String exten void InitBuffers() { Allocator sys_allocator = GetSystemAllocator(); Scratch scratch; - Buffer *null_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "console")); + Buffer *null_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "logs", "")); null_buffer->special = true; View *null_view = CreateView(null_buffer->id); null_view->special = true; diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index a1a4caf..4946edf 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -33,7 +33,7 @@ String GetMainDir() { return GetDir(main.buffer); } -void JumpTempBuffer(BSet *set, String buffer_name = "") { +void JumpTempBuffer(BSet *set, String buffer_name) { if (buffer_name.len == 0) { buffer_name = GetUniqueBufferName(GetDir(set->buffer), "temp"); } @@ -730,8 +730,10 @@ void Coro_OpenCode(mco_coro *co) { void OpenCode(String dir) { Coro_OpenCodeDir = dir; + CoRemove("Coro_OpenCode"); CoData *data = CoAdd(Coro_OpenCode); data->dont_wait_until_resolved = true; + CoResume(data); } void CMD_OpenCode() { @@ -790,8 +792,11 @@ void Coro_Rename(mco_coro *co) { buffer->name = Intern(&GlobalInternTable, string); } } + void CMD_Rename() { - CoAdd(Coro_Rename); + CoRemove("Coro_Rename"); + CoData *data = CoAdd(Coro_Rename); + CoResume(data); } RegisterCommand(CMD_Rename, ""); String Coro_YesNoCancel(mco_coro *co, BSet main, String question) { @@ -865,7 +870,10 @@ void Coro_Close(mco_coro *co) { } void CMD_Close() { - CoAdd(Coro_Close); + CoRemove("Coro_Close"); + CoData *data = CoAdd(Coro_Close); + CoResume(data); + } RegisterCommand(CMD_Close, "ctrl-w"); // Considerations with coroutines: @@ -910,15 +918,23 @@ void Coro_Quit(mco_coro *co) { } void CMD_Quit() { - CoAdd(Coro_Quit); + CoRemove("Coro_Quit"); + CoData *data = CoAdd(Coro_Quit); + CoResume(data); } RegisterCommand(CMD_Quit, ""); +void CMD_QuitWithoutSaving() { + AppIsRunning = false; +} RegisterCommand(CMD_QuitWithoutSaving, ""); + void Coro_CloseAll(mco_coro *co) { Coro_CloseAllEx(co); } void CMD_CloseAll() { - CoAdd(Coro_CloseAll); + CoRemove("Coro_CloseAll"); + CoData *data = CoAdd(Coro_CloseAll); + CoResume(data); } RegisterCommand(CMD_CloseAll, ""); void CMD_JumpPrev() { @@ -1451,4 +1467,4 @@ void GenerateConfig(Buffer *buffer) { For (CommandFunctions) { RawAppendf(buffer, "// :Set %S '%S'\n", it.name, it.binding); } -} \ No newline at end of file +} diff --git a/src/text_editor/coroutines.cpp b/src/text_editor/coroutines.cpp index bff67d4..aa93f81 100644 --- a/src/text_editor/coroutines.cpp +++ b/src/text_editor/coroutines.cpp @@ -19,9 +19,7 @@ void CoRemove(String name) { #define CoAdd(x) CoAddEx(x, #x) CoData *CoAddEx(CoroutineProc *proc, String name) { - CoRemove(name); mco_desc desc = mco_desc_init(proc, 0); - mco_coro *coro = NULL; mco_result ok = mco_create(&coro, &desc); if (ok != MCO_SUCCESS) { @@ -30,12 +28,15 @@ CoData *CoAddEx(CoroutineProc *proc, String name) { } Add(&ActiveCoroutines, {coro, name}); + return GetLast(ActiveCoroutines); +} + +void CoResume(CoData *dat) { CoData *prev_curr = CoCurr; - CoCurr = GetLast(ActiveCoroutines); - ok = mco_resume(coro); + CoCurr = dat; + mco_result ok = mco_resume(dat->co); Assert(ok == MCO_SUCCESS); CoCurr = prev_curr; - return GetLast(ActiveCoroutines); } void CoUpdate(Event *event) { diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 221f6c3..9df0f3a 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -915,8 +915,10 @@ int main(int argc, char **argv) ReportConsolef("WorkDir = %S", WorkDir); if (Testing) InitTests(); #if OS_WINDOWS + CoRemove("Windows_SetupVCVarsall"); CoData *co_data = CoAdd(Windows_SetupVCVarsall); co_data->dont_wait_until_resolved = true; + CoResume(co_data); #endif #if OS_WASM emscripten_set_main_loop(MainLoop, 0, 1); diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index 4e300a7..b18e559 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -196,4 +196,5 @@ struct ResolvedOpen { ResolvedOpen ResolveOpen(Allocator scratch, String path, String meta); void CenterView(WindowID window); void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret = false); -void ApplyFormattingTool(Buffer *buffer, String tool); \ No newline at end of file +void ApplyFormattingTool(Buffer *buffer, String tool); +void JumpTempBuffer(BSet *set, String buffer_name = ""); \ No newline at end of file diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp index 8bf4c73..0aba4da 100644 --- a/src/text_editor/view.cpp +++ b/src/text_editor/view.cpp @@ -174,8 +174,7 @@ Caret FindNext(Buffer *buffer, String16 needle, Caret caret) { return result; } -Array FindAll(Allocator allocator, Buffer *buffer, String16 needle) { - Array result = {allocator}; +void FindAllEx(Array *n, Buffer *buffer, String16 needle) { String16 string = GetString(buffer); String16 start = string; SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase; @@ -187,12 +186,17 @@ Array FindAll(Allocator allocator, Buffer *buffer, String16 needle) { if (Seek(string, needle, &index, flag)) { Int back = index + (Int)(string.data - start.data); Int front = back + needle.len; - Add(&result, MakeCaret(front, back)); + Add(n, MakeCaret(front, back)); string = Skip(string, index + needle.len); } else { break; } } +} + +Array FindAll(Allocator allocator, Buffer *buffer, String16 needle) { + Array result = {allocator}; + FindAllEx(&result, buffer, needle); return result; } @@ -219,23 +223,6 @@ void Find(View *seek_view, String16 needle, bool forward = true) { IF_DEBUG(AssertRanges(seek_view->carets)); } -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); @@ -638,3 +625,16 @@ void EncloseScope(View *view) { it.range.max = Clamp(buffer, it.range.max - 1); } } + +void SelectAllOccurences(View *view, String16 needle) { + Buffer *buffer = GetBuffer(view->active_buffer); + Scratch scratch; + Array all = FindAll(scratch, buffer, needle); + if (all.len > 0) { + view->carets.len = 0; + For (all) { + Add(&view->carets, it); + } + MergeCarets(buffer, &view->carets); + } +} diff --git a/src/text_editor/view.h b/src/text_editor/view.h index 4bf7600..4a22dea 100644 --- a/src/text_editor/view.h +++ b/src/text_editor/view.h @@ -5,9 +5,16 @@ typedef void Function(); struct FunctionData { String name; Function *function; }; struct CommandData { String name; String binding; Function *function; struct Trigger *trigger; }; +enum ViewKind { + ViewKind_Normal, + ViewKind_FuzzySearch, + ViewKind_ActiveSearch, +}; + struct View { ViewID id; BufferID active_buffer; + ViewKind kind; Vec2I scroll; Array carets; @@ -17,11 +24,10 @@ struct View { String hook_cmd; Array hooks; - String16 prev_search_line; + uint64_t prev_search_line_hash; struct { unsigned close : 1; unsigned special : 1; - unsigned fuzzy : 1; }; }; diff --git a/src/text_editor/window_command.cpp b/src/text_editor/window_command.cpp index 32fa513..20d6423 100644 --- a/src/text_editor/window_command.cpp +++ b/src/text_editor/window_command.cpp @@ -1,74 +1,3 @@ -int32_t FuzzyRate(String16 string, String16 with) { - ProfileFunction(); - if (with.len == 0) return 0; - int32_t points = 0; - int32_t consecutive = 0; - int32_t with_i = 0; - for (int32_t i = 0; i < string.len; i++) { - if (ToLowerCase(string.data[i]) == ToLowerCase(with[with_i])) { - consecutive += 1; - with_i += 1; - } else { - with_i = 0; - points += consecutive * consecutive; - consecutive = 0; - } - - if (with_i >= with.len) with_i = 0; - } - points += consecutive * consecutive; - return points; -} - -inline bool MergeSortCompare(FuzzyPair *a, FuzzyPair *b) { - bool result = a->rating > b->rating; - return result; -} - -Array FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle) { - ProfileFunction(); - if (line_min < 0 || line_min >= buffer->line_starts.len) return {}; - if (line_max < 0 || line_min > buffer->line_starts.len) return {}; - Array ratings = {allocator}; - Reserve(&ratings, line_max - line_min + 4); - for (Int i = line_min; i < line_max; i += 1) { - String16 s = GetLineStringWithoutNL(buffer, i); - int32_t rating = FuzzyRate(s, needle); - Add(&ratings, {(int32_t)i, rating}); - } - Array temp = Copy(allocator, ratings); - MergeSort(ratings.len, ratings.data, temp.data); - return ratings; -} - -void FuzzySearchViewUpdate() { - ProfileFunction(); - BSet active = GetBSet(ActiveWindowID); - if (active.view->fuzzy) { - Scratch scratch; - String16 line_string = GetLineStringWithoutNL(active.buffer, 0); - if (active.view->prev_search_line != line_string) { - active.view->prev_search_line = line_string; - Array ratings = FuzzySearchLines(scratch, active.buffer, 1, active.buffer->line_starts.len, line_string); - - Buffer *scratch_buff = CreateScratchBuffer(scratch, active.buffer->cap); - RawAppend(scratch_buff, line_string); - For(IterateInReverse(&ratings)) { - String16 s = GetLineStringWithoutNL(active.buffer, it.index); - if (s.len == 0) continue; - RawAppend(scratch_buff, u"\n"); - RawAppend(scratch_buff, s); - } - - Caret caret = active.view->carets[0]; - SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets); - SelectEntireBuffer(active.view); - Replace(active.view, GetString(scratch_buff)); - active.view->carets[0] = caret; - } - } -} - void CMD_ShowCommands() { if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowCommands) { NextActiveWindowID = LastActiveLayoutWindowID; @@ -140,6 +69,12 @@ void OpenCommand(BSet active) { line = ClampTop(1ll, active.buffer->line_starts.len - 1ll); } string = GetLineStringWithoutNL(active.buffer, line); + + Int idx = 0; + String16 delim = u"|::|"; + if (Seek(string, delim, &idx, SeekFlag_None)) { + string = Skip(string, idx + delim.len); + } } Open(string); @@ -162,8 +97,170 @@ void CommandWindowLayout(Rect2I *rect, Int wx, Int wy) { n->document_rect = n->total_rect = CutBottom(rect, barsize); } +int32_t FuzzyRate(String16 string, String16 with) { + ProfileFunction(); + if (with.len == 0) return 0; + int32_t points = 0; + int32_t consecutive = 0; + int32_t with_i = 0; + for (int32_t i = 0; i < string.len; i++) { + if (ToLowerCase(string.data[i]) == ToLowerCase(with[with_i])) { + consecutive += 1; + with_i += 1; + } else { + with_i = 0; + points += consecutive * consecutive; + consecutive = 0; + } + + if (with_i >= with.len) with_i = 0; + } + points += consecutive * consecutive; + return points; +} + +inline bool MergeSortCompare(FuzzyPair *a, FuzzyPair *b) { + bool result = a->rating > b->rating; + return result; +} + +Array FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle) { + ProfileFunction(); + if (line_min < 0 || line_min >= buffer->line_starts.len) return {}; + if (line_max < 0 || line_min > buffer->line_starts.len) return {}; + Array ratings = {allocator}; + Reserve(&ratings, line_max - line_min + 4); + for (Int i = line_min; i < line_max; i += 1) { + String16 s = GetLineStringWithoutNL(buffer, i); + + Int idx = 0; + String16 delim = u"|::|"; + if (Seek(s, delim, &idx, SeekFlag_None)) { + s = GetPrefix(s, idx); + } + s = Trim(s); + + int32_t rating = FuzzyRate(s, needle); + Add(&ratings, {(int32_t)i, rating}); + } + Array temp = Copy(allocator, ratings); + MergeSort(ratings.len, ratings.data, temp.data); + return ratings; +} + +struct SearchProjectParams { + String16 needle; + BufferID buffer; +}; + +void Coro_SearchProject(mco_coro *co) { + SearchProjectParams *param = (SearchProjectParams *)CoCurr->user_ctx; + For (Buffers) { + if (it->special || it->is_dir || it->temp || it->dont_try_to_save_in_bulk_ops) { + continue; + } + + { + Scratch scratch; + Array occurences = FindAll(scratch, it, param->needle); + Buffer *out_buffer = GetBuffer(param->buffer); + ForItem (caret, occurences) { + Int pos = caret.range.min; + Int line = PosToLine(it, pos); + String16 line_string = GetLineStringWithoutNL(it, line); + String line_string8 = ToString(scratch, line_string); + RawAppendf(out_buffer, "%-150S |::| %S:%lld\n", line_string8, it->name, (long long)line + 1); + } + } + CoYield(co); + } +} + +void CMD_ReplaceAll() { + BSet main = GetBSet(LastActiveLayoutWindowID); + String16 string = FetchLoadWord(main.view); + Scratch scratch; + Array parts = Split(scratch, string, u"@>"); + if (parts.len != 2) { + ReportErrorf("Invalid ReplaceAll pattern"); + return; + } + + String16 needle = parts[0]; + String16 replace = parts[1]; + ForItem (buffer, Buffers) { + if (buffer->special || buffer->is_dir || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) { + continue; + } + + View *view = OpenBufferView(buffer->name); + SelectAllOccurences(view, needle); + Replace(view, replace); + } +} RegisterCommand(CMD_ReplaceAll, ""); + +void FuzzySearchViewUpdate() { + ProfileFunction(); + BSet active = GetBSet(ActiveWindowID); + if (active.view->kind == ViewKind_ActiveSearch) { + Scratch scratch; + String16 line_string = GetLineStringWithoutNL(active.buffer, 0); + uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t)); + if (active.view->prev_search_line_hash != hash) { + active.view->prev_search_line_hash = hash; + if (line_string.len > 0) { + // @todo: do we reintroduce history here? + SelectEntireBuffer(active.view); + Replace(active.view, line_string); + Append(active.view, "\n", false); + + CoRemove("Coro_SearchProject"); + CoData *dat = CoAdd(Coro_SearchProject); + SearchProjectParams *param = AllocType(dat->arena, SearchProjectParams); + param->needle = Copy16(dat->arena, line_string); + param->buffer = active.buffer->id; + dat->user_ctx = param; + dat->dont_wait_until_resolved = true; + CoResume(dat); + } + } + } else if (active.view->kind == ViewKind_FuzzySearch) { + Scratch scratch; + String16 line_string = GetLineStringWithoutNL(active.buffer, 0); + uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t)); + if (active.view->prev_search_line_hash != hash) { + active.view->prev_search_line_hash = hash; + Array ratings = FuzzySearchLines(scratch, active.buffer, 1, active.buffer->line_starts.len, line_string); + + Buffer *scratch_buff = CreateScratchBuffer(scratch, active.buffer->cap); + RawAppend(scratch_buff, line_string); + For(IterateInReverse(&ratings)) { + String16 s = GetLineStringWithoutNL(active.buffer, it.index); + if (s.len == 0) continue; + RawAppend(scratch_buff, u"\n"); + RawAppend(scratch_buff, s); + } + + Caret caret = active.view->carets[0]; + SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets); + SelectEntireBuffer(active.view); + Replace(active.view, GetString(scratch_buff)); + active.view->carets[0] = caret; + } + } +} + +void CMD_SearchProject() { + BSet main = GetBSet(LastActiveLayoutWindowID); + 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; +} RegisterCommand(CMD_SearchProject, "ctrl-shift-f"); + void SetFuzzy(View *view) { - view->fuzzy = true; + view->kind = ViewKind_FuzzySearch; AddHook(&view->hooks, "Open", "ctrl-q | enter", CMD_CommandWindowOpen); } @@ -172,6 +269,7 @@ void CommandWindowInit() { CommandWindowID = window->id; Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "command_bar")); buffer->special = true; + buffer->no_history = true; View *view = CreateView(buffer->id); view->special = true; SetFuzzy(view); diff --git a/src/text_editor/window_search.cpp b/src/text_editor/window_search.cpp index 62660ac..ef6fe56 100644 --- a/src/text_editor/window_search.cpp +++ b/src/text_editor/window_search.cpp @@ -39,24 +39,11 @@ void CMD_SearchPrev() { SearchWindowFindNext(false); } RegisterCommand(CMD_SearchPrev, "shift-f3"); -void SearchAll() { - Scratch scratch; +void CMD_SearchAll() { BSet main = GetBSet(LastActiveLayoutWindowID); BSet set = GetBSet(SearchWindowID); String16 needle = GetString(set.buffer, GetRange(set.buffer)); - Array all = FindAll(scratch, main.buffer, needle); - if (all.len > 0) { - main.view->carets.len = 0; - For (all) { - Add(&main.view->carets, it); - } - MergeCarets(main.buffer, &main.view->carets); - } -} - -void CMD_SearchAll() { - SearchAll(); - BSet set = GetBSet(SearchWindowID); + SelectAllOccurences(main.view, needle); set.window->visible = false; } RegisterCommand(CMD_SearchAll, "alt-f3");