diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 9ec854a..e5ba151 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -1551,13 +1551,8 @@ void ReopenBuffer(Buffer *buffer) { void SaveBuffer(Buffer *buffer) { ProfileFunction(); - For (GlobalHooks) { - if (it.kind == HookKind_BeforeSavingBuffer) { - ProfileScopeEx(it.name); - HookParam param = {}; - param.buffer = buffer; - it.function(param); - } + if (TrimTrailingWhitespace) { + TrimWhitespace(buffer, false); } Scratch scratch; diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 05f07c4..43c651e 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -291,12 +291,6 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) { view->update_scroll = false; } -void HOOK_TrimTrailingWhitespace(HookParam param) { - if (TrimTrailingWhitespace) { - TrimWhitespace(param.buffer, false); - } -} RegisterHook(HOOK_TrimTrailingWhitespace, HookKind_BeforeSavingBuffer, "", "Cleans trailing whitespace before saving to file"); - void ConvertLineEndingsToLF(Buffer *buffer, bool trim_lines_with_caret = false) { Scratch scratch; @@ -477,17 +471,6 @@ BSet Exec(String cmd, String working_dir, bool set_active = true) { return main; } -BSet ExecBuild(String cmd) { - BSet build = GetBSet(BuildWindowID); - BSet main = GetBSet(PrimaryWindowID); - SelectRange(build.view, Range{}); - ResetBuffer(build.buffer); - Exec(build.view->id, true, cmd, WorkDir); - main.window->active_goto_list = build.view->id; - main.window->goto_list_pos = 0; - return build; -} - void CMD_SaveAll(HookParam param) { For(Buffers) { // NOTE: file_mod_time is only set when buffer got read or written to disk already so should be saved @@ -497,17 +480,6 @@ void CMD_SaveAll(HookParam param) { } } RegisterCommand(CMD_SaveAll, "ctrl-shift-s"); -void CMD_Build(HookParam param) { - CMD_SaveAll({}); -#if OS_WINDOWS - ExecBuild("build.bat"); -#else - ExecBuild("sh build.sh"); -#endif - BSet main = GetBSet(BuildWindowID); - main.window->visible = true; -} RegisterCommand(CMD_Build, "f1", "Run build.sh or build.bat in working directory, output is printed in a popup console and a special build buffer"); - void CMD_GotoNextInList(HookParam param) { BSet main = GetBSet(PrimaryWindowID); GotoNextInList(main.window, 1); @@ -1109,16 +1081,17 @@ String Coro_CloseAllEx(mco_coro *co) { return "Yes"; } +void CMD_QuitWithoutSaving(HookParam param) { +#ifdef PLUGIN_REMEDYBG + QuitDebugger(); +#endif + AppIsRunning = false; +} RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory"); + void Coro_Quit(mco_coro *co) { String res = Coro_CloseAllEx(co); if (res != "Cancel") { - For (GlobalHooks) { - if (it.kind == HookKind_AppQuit) { - ProfileScopeEx(it.name); - it.function({}); - } - } - AppIsRunning = false; + CMD_QuitWithoutSaving({}); } } @@ -1128,16 +1101,6 @@ void CMD_Quit(HookParam param) { CoResume(data); } RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit"); -void CMD_QuitWithoutSaving(HookParam param) { - For (GlobalHooks) { - if (it.kind == HookKind_AppQuit) { - ProfileScopeEx(it.name); - it.function({}); - } - } - AppIsRunning = false; -} RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory"); - void Coro_CloseAll(mco_coro *co) { Coro_CloseAllEx(co); } @@ -1587,8 +1550,44 @@ void CMD_ReplaceAll(HookParam param) { CoResume(data); } RegisterCommand(CMD_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"); +struct SearchProjectParams { + String16 needle; + BufferID buffer; +}; -void UpdateSearchProjectView(HookParam param) { +void Coro_SearchProject(mco_coro *co) { + SearchProjectParams *param = (SearchProjectParams *)CoCurr->user_ctx; + + Array buffers = {CoCurr->arena}; + For (Buffers) { + Add(&buffers, it->id); + } + + ForItem (id, buffers) { + Buffer *it = GetBuffer(id, NULL); + if (it == NULL || 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); + Range range = GetLineRangeWithoutNL(it, line); + Int column = pos - range.min; + String16 line_string = GetString(it, range); + String line_string8 = ToString(scratch, line_string); + RawAppendf(out_buffer, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1); + } + } + CoYield(co); + } +} + +void UpdateSearchProjectView() { Scratch scratch; BSet active = GetBSet(ActiveWindowID); String16 line_string = GetLineStringWithoutNL(active.buffer, 0); @@ -1636,7 +1635,7 @@ void CMD_SearchProject(HookParam param) { main.window->goto_list_pos = active.view->carets[0].range.min; Open(string); }); - AddHook(&view->hooks, HookKind_AppUpdate, "UpdateSearchProjectView", "", UpdateSearchProjectView); + view->update_hook = UpdateSearchProjectView; SelectRange(view, GetLineRangeWithoutNL(search_project_buffer, 0)); if (string.len) { Replace(view, string); diff --git a/src/text_editor/fuzzy_search_view.cpp b/src/text_editor/fuzzy_search_view.cpp new file mode 100644 index 0000000..18468bd --- /dev/null +++ b/src/text_editor/fuzzy_search_view.cpp @@ -0,0 +1,135 @@ +String16 FetchFuzzyViewLoadLine(View *view) { + Buffer *buffer = GetBuffer(view->active_buffer); + Range range = view->carets[0].range; + String16 string = GetString(buffer, range); + if (GetSize(range) == 0) { + Int line = PosToLine(buffer, range.min); + if (line == 0) { + line = ClampTop(1ll, buffer->line_starts.len - 1ll); + } + string = GetLineStringWithoutNL(buffer, line); + + Int idx = 0; + String16 delim = u"||>"; + if (Seek(string, delim, &idx, SeekFlag_None)) { + string = Skip(string, idx + delim.len); + } + } + return string; +} + +void OpenCommand(BSet active) { + String16 string = FetchFuzzyViewLoadLine(active.view); + Open(string); +} + +float NewFuzzyRate(String16 s, String16 p) { + float score = 0; + // try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go + return score; +} + +float FuzzyRate(String16 s, String16 p) { + float score = 0; + for (Int outer_pi = 0; outer_pi < p.len; outer_pi += 1) { + String16 pit = Skip(p, outer_pi); + if (IsWhitespace(At(pit, 0))) { + continue; + } + + float matching = 0; + for (Int outer_si = 0; outer_si < s.len; outer_si += 1) { + String16 sit = Skip(s, outer_si); + if (IsWhitespace(At(sit, 0))) { + continue; + } + + Int si = 0; + Int pi = 0; + for (;si < sit.len && pi < pit.len;) { + while (si < sit.len && IsWhitespace(sit[si])) si += 1; + while (pi < pit.len && IsWhitespace(pit[pi])) pi += 1; + if (pi >= pit.len) break; + if (si >= sit.len) break; + + if (ToLowerCase(sit[si]) == ToLowerCase(pit[pi])) { + matching += 1.0f; + } else { + break; + } + si += 1; + pi += 1; + } + } + score += matching; + } + score = score / (float)s.len; + return score; +} + +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; + if (Seek(s, u"||>", &idx, SeekFlag_None)) { + s = GetPrefix(s, idx); + } else if (Seek(s, u"<||", &idx, SeekFlag_None)) { + s = GetPrefix(s, idx); + } + s = Trim(s); + + float 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 UpdateFuzzySearchView() { + Scratch scratch; + BSet active = GetBSet(ActiveWindowID); + 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 SetFuzzy(View *view) { + AddCommand(&view->hooks, "Open", "ctrl-q | enter | f12", [](HookParam){ + BSet active = GetBSet(ActiveWindowID); + BSet main = GetBSet(PrimaryWindowID); + NextActiveWindowID = main.window->id; + String16 string = FetchFuzzyViewLoadLine(active.view); + Open(string); + }); + view->update_hook = UpdateFuzzySearchView; +} diff --git a/src/text_editor/globals.cpp b/src/text_editor/globals.cpp index 51b5f4a..ecbcc65 100644 --- a/src/text_editor/globals.cpp +++ b/src/text_editor/globals.cpp @@ -33,18 +33,32 @@ BufferID NullBufferID; ViewID NullViewID; WindowID NullWindowID; -// hidden floating window -WindowID DebugWindowID; -ViewID DebugViewID; -BufferID DebugBufferID; -WindowID CommandWindowID; -WindowID StatusBarWindowID; -WindowID SearchWindowID; -ViewID SearchViewID; -BufferID SearchBufferID; -WindowID BuildWindowID; -ViewID BuildViewID; -BufferID BuildBufferID; +#ifdef PLUGIN_DEBUG_WINDOW + WindowID DebugWindowID; + ViewID DebugViewID; + BufferID DebugBufferID; +#endif + +#ifdef PLUGIN_COMMAND_WINDOW + WindowID CommandWindowID; +#endif + +#ifdef PLUGIN_SEARCH_WINDOW + WindowID SearchWindowID; + ViewID SearchViewID; + BufferID SearchBufferID; +#endif + +#ifdef PLUGIN_STATUS_WINDOW + WindowID StatusWindowID; +#endif + +#ifdef PLUGIN_BUILD_WINDOW + WindowID BuildWindowID; + ViewID BuildViewID; + BufferID BuildBufferID; +#endif + BufferID SearchProjectBufferID; BufferID GlobalConfigBufferID; @@ -175,3 +189,8 @@ RegisterVariable(String, InternetBrowser, "firefox"); RegisterVariable(String, OpenCodePatterns, ".c .h .cpp .hpp .cc .cxx .rs .go .zig .py .lua .js .ts .jsx .tsx .java .kt .swift .cs .rb .php .html .css .scss .bat .sh .bash .zsh .sql .asm .s .cmake .make .json .yaml .toml .ini .txt .md .rst .Makefile .Dockerfile .gitignore .bashrc .zshrc"); RegisterVariable(String, OpenCodeExcludePatterns, ""); RegisterVariable(Int, TrimTrailingWhitespace, 1); + +// BEGIN PLUGIN_REMEDYBG +RegisterVariable(String, BinaryUnderDebug, "build/te.exe"); +RegisterVariable(String, RemedyBGPath, "remedybg.exe"); +// END PLUGIN_REMEDYBG \ No newline at end of file diff --git a/src/text_editor/plugin_build_window.cpp b/src/text_editor/plugin_build_window.cpp new file mode 100644 index 0000000..4bff3c1 --- /dev/null +++ b/src/text_editor/plugin_build_window.cpp @@ -0,0 +1,60 @@ +void InitBuildWindow() { + Window *window = CreateWind(); + BuildWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "build")); + buffer->special = true; + buffer->no_history = true; + BuildBufferID = buffer->id; + View *view = CreateView(buffer->id); + view->special = true; + BuildViewID = view->id; + window->active_view = view->id; + window->secondary_window_style = true; + window->draw_line_highlight = true; + window->primary = false; + window->visible = false; + window->lose_visibility_on_escape = true; + window->jump_history = false; +} + +void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) { + Window *window = GetWindow(BuildWindowID); + Rect2I copy_rect = *rect; + if (!window->visible) { + rect = ©_rect; + } + Int barsize = window->font->line_spacing * 10; + window->document_rect = window->total_rect = CutBottom(rect, barsize); +} + +BSet ExecBuild(String cmd) { + BSet build = GetBSet(BuildWindowID); + BSet main = GetBSet(PrimaryWindowID); + SelectRange(build.view, Range{}); + ResetBuffer(build.buffer); + Exec(build.view->id, true, cmd, WorkDir); + main.window->active_goto_list = build.view->id; + main.window->goto_list_pos = 0; + return build; +} + +void CMD_Build(HookParam param) { + CMD_SaveAll({}); +#if OS_WINDOWS + ExecBuild("build.bat"); +#else + ExecBuild("sh build.sh"); +#endif + BSet main = GetBSet(BuildWindowID); + main.window->visible = true; +} RegisterCommand(CMD_Build, "f1", "Run build.sh or build.bat in working directory, output is printed in a popup console and a special build buffer"); + +void CMD_ShowBuildWindow(HookParam param) { + BSet main = GetBSet(BuildWindowID); + if (ActiveWindowID != BuildWindowID) { + main.window->visible = true; + NextActiveWindowID = BuildWindowID; + } else { + main.window->visible = false; + } +} RegisterCommand(CMD_ShowBuildWindow, "ctrl-grave"); diff --git a/src/text_editor/plugin_build_window.h b/src/text_editor/plugin_build_window.h new file mode 100644 index 0000000..438d709 --- /dev/null +++ b/src/text_editor/plugin_build_window.h @@ -0,0 +1,3 @@ +#define PLUGIN_BUILD_WINDOW +void InitBuildWindow(); +void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy); diff --git a/src/text_editor/plugin_command_window.cpp b/src/text_editor/plugin_command_window.cpp new file mode 100644 index 0000000..13c3202 --- /dev/null +++ b/src/text_editor/plugin_command_window.cpp @@ -0,0 +1,98 @@ +void CMD_ShowCommands(HookParam param) { + // @todo: maybe redo this, similar behavior but use View stored information + // if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowCommands) { + // NextActiveWindowID = PrimaryWindowID; + // return; + // } + + BSet command_bar = GetBSet(CommandWindowID); + command_bar.window->visible = true; + NextActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For (GlobalHooks) { + if (it.name == "OpenCommand") { + continue; + } + // RawAppendf(command_bar.buffer, "\n:%-30S <|| :Set %-30S '%-30S'", it.name, it.name, it.binding); + RawAppendf(command_bar.buffer, "\n:%-30S <|| ", it.name); + if (it.docs.len) { + RawAppendf(command_bar.buffer, "%S", it.docs); + } + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); +} RegisterCommand(CMD_ShowCommands, "ctrl-shift-p", "List available commands and their documentation inside the command window"); + +void CMD_ShowDebugBufferList(HookParam param) { + // @todo: maybe redo this, similar behavior but use View stored information + // if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowDebugBufferList) { + // NextActiveWindowID = PrimaryWindowID; + // return; + // } + + BSet command_bar = GetBSet(CommandWindowID); + command_bar.window->visible = true; + NextActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For (Buffers) { + bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops; + if (!is_special) { + continue; + } + RawAppendf(command_bar.buffer, "\n%S", it->name); + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); +} RegisterCommand(CMD_ShowDebugBufferList, "ctrl-shift-alt-p", "Show full list of buffers, including the special ones that normally just clutter list"); + +void CMD_ShowBufferList(HookParam param) { + // @todo: maybe redo this, similar behavior but use View stored information + // if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowBufferList) { + // NextActiveWindowID = PrimaryWindowID; + // return; + // } + BSet command_bar = GetBSet(CommandWindowID); + command_bar.window->visible = true; + NextActiveWindowID = command_bar.window->id; + ResetBuffer(command_bar.buffer); + For (Buffers) { + bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops; + if (is_special) { + continue; + } + RawAppendf(command_bar.buffer, "\n%S", it->name); + } + command_bar.view->update_scroll = true; + SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); +} RegisterCommand(CMD_ShowBufferList, "ctrl-p", "List open buffers inside the command window that you can fuzzy search over"); + +void LayoutCommandWindow(Rect2I *rect, int16_t wx, int16_t wy) { + Window *window = GetWindow(CommandWindowID); + Rect2I copy_rect = *rect; + if (!window->visible) { + rect = ©_rect; + } + Int barsize = Clamp((Int)window->font->line_spacing*10, (Int)0, (Int)wx - 100); + window->document_rect = window->total_rect = CutBottom(rect, barsize); +} + +void InitCommandWindow() { + Window *window = CreateWind(); + 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); + window->active_view = view->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->secondary_window_style = true; + window->draw_line_highlight = true; + window->primary = false; + window->visible = false; + window->sync_visibility_with_focus = true; + window->lose_focus_on_escape = true; + window->jump_history = false; +} diff --git a/src/text_editor/plugin_command_window.h b/src/text_editor/plugin_command_window.h new file mode 100644 index 0000000..b686d47 --- /dev/null +++ b/src/text_editor/plugin_command_window.h @@ -0,0 +1,3 @@ +#define PLUGIN_COMMAND_WINDOW +void InitCommandWindow(); +void LayoutCommandWindow(Rect2I *rect, int16_t wx, int16_t wy); \ No newline at end of file diff --git a/src/text_editor/plugin_debug_window.cpp b/src/text_editor/plugin_debug_window.cpp new file mode 100644 index 0000000..5c29bab --- /dev/null +++ b/src/text_editor/plugin_debug_window.cpp @@ -0,0 +1,85 @@ +void InitDebugWindow() { + Window *window = CreateWind(); + DebugWindowID = window->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->visible = false; + window->z = 2; + window->primary = false; + window->jump_history = false; + + Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "debug")); + DebugBufferID = buffer->id; + buffer->no_history = true; + buffer->special = true; + + View *view = CreateView(buffer->id); + view->special = true; + DebugViewID = view->id; + window->active_view = view->id; + window->visible = false; +} + +void LayoutDebugWindow(Rect2I *rect, int16_t wx, int16_t wy) { + Window *window = GetWindow(DebugWindowID); + Rect2 screen_rect = Rect0Size((float)wx, (float)wy); + Vec2 size = GetSize(screen_rect); + + Rect2 a = CutRight(&screen_rect, 0.3f * size.x); + Rect2 b = CutTop(&a, 0.4f * size.y); + Rect2 c = Shrink(b, 20); + window->document_rect = window->total_rect = ToRect2I(c); +} + +void UpdateDebugWindow() { + ProfileFunction(); + BSet set = GetBSet(DebugWindowID); + if (!set.window->visible) { + return; + } + + BSet active = GetBSet(ActiveWindowID); + if (active.buffer->id.id == set.buffer->id.id) { + return; + } + + BSet main = GetBSet(PrimaryWindowID); + + Scratch scratch; + String s = Format(scratch, "wid: %d\nvid: %d\nbid: %d\nframe: %lld\n", (int)main.window->id.id, (int)main.view->id.id, (int)main.buffer->id.id, (long long)FrameID); + String16 string = ToString16(scratch, s); + RawReplaceText(set.buffer, GetRange(set.buffer), string); + + float xmouse, ymouse; + SDL_GetMouseState(&xmouse, &ymouse); + RawAppendf(set.buffer, "mouse: [%f, %f]\n", roundf(DPIScale * xmouse), roundf(DPIScale * ymouse)); + + RawAppendf(set.buffer, "BufferID id = %d\n", main.buffer->id.id); + RawAppendf(set.buffer, "String name = %S\n", main.buffer->name); + RawAppendf(set.buffer, "Int change_id = %lld\n", (long long)main.buffer->change_id); + RawAppendf(set.buffer, "Int user_change_id = %lld\n", (long long)main.buffer->user_change_id); + RawAppendf(set.buffer, "Int file_mod_time = %lld\n", (long long)main.buffer->file_mod_time); + RawAppendf(set.buffer, "\n"); + + RawAppendf(set.buffer, "U16 *data = %zu\n", main.buffer->data); + RawAppendf(set.buffer, "Int len = %lld\n", (long long)main.buffer->len); + RawAppendf(set.buffer, "Int cap = %lld\n", (long long)main.buffer->cap); + RawAppendf(set.buffer, "Array line_starts = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->line_starts.len, (long long)main.buffer->line_starts.cap, main.buffer->line_starts.data); + RawAppendf(set.buffer, "\n"); + + RawAppendf(set.buffer, "Array undo_stack = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->undo_stack.len, (long long)main.buffer->undo_stack.cap, main.buffer->undo_stack.data); + RawAppendf(set.buffer, "Array redo_stack = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->redo_stack.len, (long long)main.buffer->redo_stack.cap, main.buffer->redo_stack.data); + RawAppendf(set.buffer, "int edit_phase = %d", main.buffer->edit_phase); + RawAppendf(set.buffer, "\n"); + + RawAppendf(set.buffer, "int no_history = %d\n", main.buffer->no_history); + RawAppendf(set.buffer, "int no_line_starts = %d\n", main.buffer->no_line_starts); + RawAppendf(set.buffer, "int dirty = %d\n", main.buffer->dirty); + RawAppendf(set.buffer, "int changed_on_disk = %d\n", main.buffer->changed_on_disk); + RawAppendf(set.buffer, "int temp = %d\n", main.buffer->temp); +} + +void CMD_ToggleDebug(HookParam param) { + Window *window = GetWindow(DebugWindowID); + window->visible = !window->visible; +} RegisterCommand(CMD_ToggleDebug, "ctrl-0", "Open a floating window that might become useful for debugging"); diff --git a/src/text_editor/plugin_debug_window.h b/src/text_editor/plugin_debug_window.h new file mode 100644 index 0000000..6bdc788 --- /dev/null +++ b/src/text_editor/plugin_debug_window.h @@ -0,0 +1,4 @@ +#define PLUGIN_DEBUG_WINDOW +void InitDebugWindow(); +void LayoutDebugWindow(Rect2I *rect, int16_t wx, int16_t wy); +void UpdateDebugWindow(); \ No newline at end of file diff --git a/src/text_editor/plugin_remedybg.cpp b/src/text_editor/plugin_remedybg.cpp new file mode 100644 index 0000000..34baa6e --- /dev/null +++ b/src/text_editor/plugin_remedybg.cpp @@ -0,0 +1,2254 @@ +#define RDBG_MAX_SERVERNAME_LEN 64 + +typedef uint8_t rdbg_Bool; + +// A rolling 32-bit integer is used for any command that takes or returns a UID. +// These UIDs are never persisted and as such, can change between runs of +// RemedyBG. Zero will never be a valid id. +typedef uint32_t rdbg_Id; + +// A string consists of a length followed by an UTF-8 encoded character array of +// 'length' bytes. Strings are never nul-terminated. +#pragma warning(push) +#pragma warning(disable: 4200) +#pragma pack(push, 1) +struct rdbg_String +{ + uint16_t len; + uint8_t data[0]; +}; +#pragma pack(pop) +#pragma warning(pop) + +enum rdbg_CommandResult +{ + RDBG_COMMAND_RESULT_UNKNOWN = 0, + + RDBG_COMMAND_RESULT_OK = 1, + + // Generic failure + RDBG_COMMAND_RESULT_FAIL = 2, + + // Result if the command is aborted due to a specified behavior and + // condition including RDBG_IF_DEBUGGING_TARGET_ABORT_COMMAND or + // RDBG_IF_SESSION_IS_MODIFIED_ABORT_COMMAND. The result can also be returned + // if an unnamed session is saved, prompts for a filename, and the user + // cancels this operation. + RDBG_COMMAND_RESULT_ABORTED = 3, + + // Result if the given command buffer given is less than 2 bytes or if the + // command is not one of the enumerated commands in rdbg_Command. + RDBG_COMMAND_RESULT_INVALID_COMMAND = 4, + + // Result if the response generated is too large to fit in the buffer. + RDBG_COMMAND_RESULT_BUFFER_TOO_SMALL = 5, + + // Result if an opening a file (i.e., a session, text file). + RDBG_COMMAND_RESULT_FAILED_OPENING_FILE = 6, + + // Result if saving a session fails. + RDBG_COMMAND_RESULT_FAILED_SAVING_SESSION = 7, + + // Result if the given ID is invalid. + RDBG_COMMAND_RESULT_INVALID_ID = 8, + + // Result if a command expects the target to be in a particular state (not + // debugging, debugging and suspended, or debugging and executing) and is + // not. + RDBG_COMMAND_RESULT_INVALID_TARGET_STATE = 9, + + // Result if an active configuration does not exist + RDBG_COMMAND_RESULT_FAILED_NO_ACTIVE_CONFIG = 10, + + // Result if the command does not apply to given breakpoint's kind + RDBG_COMMAND_RESULT_INVALID_BREAKPOINT_KIND = 11, +}; + +// Commands that take an rdbg_DebuggingTargetBehavior can specify what should +// happen in the case the target is being debugged. +enum rdbg_DebuggingTargetBehavior +{ + RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING = 1, + RDBG_IF_DEBUGGING_TARGET_ABORT_COMMAND = 2 +}; + +// Commands that take an rdbg_ModifiedSessionBehavior can specify what should +// happen when there is an open, modified session. +enum rdbg_ModifiedSessionBehavior +{ + RDBG_IF_SESSION_IS_MODIFIED_SAVE_AND_CONTINUE = 1, + RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING = 2, + RDBG_IF_SESSION_IS_MODIFIED_ABORT_COMMAND = 3, +}; + +enum rdbg_TargetState +{ + RDBG_TARGET_STATE_NONE = 1, + RDBG_TARGET_STATE_SUSPENDED = 2, + RDBG_TARGET_STATE_EXECUTING = 3, +}; + +enum rdbg_BreakpointKind +{ + RDBG_BREAKPOINT_KIND_FUNCTION_NAME = 1, + RDBG_BREAKPOINT_KIND_FILENAME_LINE = 2, + RDBG_BREAKPOINT_KIND_ADDRESS = 3, + RDBG_BREAKPOINT_KIND_PROCESSOR = 4, +}; + +enum rdbg_ProcessorBreakpointAccessKind +{ + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE = 1, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE = 2, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE = 3, +}; + +enum rdbg_Command +{ + // Bring the RemedyBG window to the foreground and activate it. No additional + // arguments follow the command. Returns RDBG_COMMAND_RESULT_OK or + // RDBG_COMMAND_RESULT_FAIL. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_BRING_DEBUGGER_TO_FOREGROUND = 50, + + // Set the size and position of the RemedyBG window. + // + // [cmd :: rdbg_Command (uint16_t)] + // [x :: int32_t] + // [y :: int32_t] + // [width :: int32_t] + // [height :: int32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_WINDOW_POS = 51, + + // Get the size and position of the RemedyBG window. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [x :: int32_t] + // [y :: int32_t] + // [width :: int32_t] + // [height :: int32_t] + // [is_maximized: rdbg_Bool] + RDBG_COMMAND_GET_WINDOW_POS = 52, + + // Set whether to automatically bring the debugger to the foreground whenever + // the target is suspended (breakpoint hit, exception, single-step complete, + // etc.). Defaults to true if not set. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bring_to_foreground_on_suspended :: rdbg_Bool (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED = 53, + + // Exit the RemedyBG application. + // + // [cmd :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_EXIT_DEBUGGER = 75, + + // Session + // + + // Returns whether the current session is modified, or "dirty". + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [modified :: rdbg_Bool (uint8_t)] + RDBG_COMMAND_GET_IS_SESSION_MODIFIED = 100, + + // Returns the current session's filename. If the filename has not been set + // for the session then the result will be + // RDBG_COMMAND_RESULT_UNNAMED_SESSION and the length of |filename| will be + // zero. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [filename :: rdbg_String] + RDBG_COMMAND_GET_SESSION_FILENAME = 101, + + // Creates a new session. All configurations are cleared and reset. + // + // [cmd :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_NEW_SESSION = 102, + + // Open a session with the given filename. + // + // [command :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // [filename :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_OPEN_SESSION = 103, + + // Save session with its current filename. If the filename is has not been + // specified for the session the user will be prompted. To save with a + // filename see RDBG_COMMAND_SAVE_AS_SESSION, instead. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SAVE_SESSION = 104, + + // Save session with a given filename. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SAVE_AS_SESSION = 105, + + // Retrieve a list of configurations for the current session. + // + // [cmd :: rdbg_Command (uint16_t) + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_configs :: uint16_t] + // .FOR(num_configs) { + // [uid :: rdbg_Id (uint32_t)] + // [command :: rdbg_String] + // [command_args :: rdbg_String] + // [working_dir :: rdbg_String] + // [environment_vars :: rdbg_String] + // [inherit_environment_vars_from_parent :: rdbg_Bool] + // [break_at_nominal_entry_point :: rdbg_Bool] + // [name :: rdbg_String] + // } + RDBG_COMMAND_GET_SESSION_CONFIGS = 106, + + // Add a new session configuration to the current session. All string + // parameters accept zero length strings. Multiple environment variables + // should be newline, '\n', separated. Returns the a unique ID for the + // configuration. + // + // Note that 'name' is currently optional. + // + // [cmd :: rdbg_Command (uint16_t) + // [command :: rdbg_String] + // [command_args :: rdbg_String] + // [working_dir :: rdbg_String] + // [environment_vars :: rdbg_String] + // [inherit_environment_vars_from_parent :: rdbg_Bool] + // [break_at_nominal_entry_point :: rdbg_Bool] + // [name :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [uid :: rdbg_Id] + RDBG_COMMAND_ADD_SESSION_CONFIG = 107, + + // Sets the active configuration for a session by configuration ID. If the + // ID is not valid for the current session + // RDBG_COMMAND_RESULT_INVALID_ID is returned. + // + // [cmd :: rdbg_Command (uint16_t) + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_ACTIVE_SESSION_CONFIG = 108, + + // Deletes a session configuration by ID. If the ID is not valid for the + // current session RDBG_COMMAND_REMOVE_SESSION_CONFIG is returned. + // + // [cmd :: rdbg_Command (uint16_t) + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_SESSION_CONFIG = 109, + + // Deletes all session configurations in the current session. + // + // [cmd :: rdbg_Command (uint16_t) + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_SESSION_CONFIGS = 110, + + // Source Files + // + + // Opens the given file, if not already opened, and navigates to the + // specified line number. The line number is optional and can be elided from + // the command buffer. Returns result along with an ID for the file. + // + // [cmd :: rdbg_Command (uint16_t) + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [id :: rdbg_Id] + RDBG_COMMAND_GOTO_FILE_AT_LINE = 200, + + // Close the file with the given ID. + // + // [cmd :: rdbg_Command (uint16_t)] + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CLOSE_FILE = 201, + + // Close all open files + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CLOSE_ALL_FILES = 202, + + // Returns the current file. If no file is open, returns a zero ID, + // zero-length filename, and zero line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [id :: rdbg_Id] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + RDBG_COMMAND_GET_CURRENT_FILE = 203, + + // Retrieve a list of open files. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_files :: uint16_t] + // .FOR(num_files) { + // [id :: rdbg_Id] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // } + RDBG_COMMAND_GET_OPEN_FILES = 204, + + // + // Debugger Control + + // Returns the target state for the current session. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [staste :: rdbg_TargetState (uint16_t)] + RDBG_COMMAND_GET_TARGET_STATE = 300, + + // If the target is stopped, i.e., not currently being debugged, then start + // debugging the active configuration. Setting break_at_entry to true will + // stop execution at the at entry point specified in the configuration: + // either the nominal entry point, such as "main" or "WinMain" or the entry + // point function as described in the PE header. If the target is already + // being debugged, this will return RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // [break_at_entry_point :: rdbg_Bool (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_START_DEBUGGING = 301, + + // Stop debugging the target. If the target is not executing this will return + // RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STOP_DEBUGGING = 302, + + // Restart debugging if the target is being debugging (either suspended or + // executing) and the target was not attached to a process. Otherwise, + // returns RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_RESTART_DEBUGGING = 303, + + // Attach to a process by the given process-id. The value of + // |continue_execution| indicates whether the process should resume execution + // after attached. The debugger target behavior specifies what should happen + // in the case when the target is being debugged (suspended or executing). + // Can return: RDBG_COMMAND_RESULT_OK, RDBG_COMMAND_RESULT_FAIL, or + // RDBG_COMMAND_RESULT_ABORT. + // + // [cmd :: rdbg_Command (uint16_t)] + // [process_id :: uint32_t] + // [continue_execution :: rdbg_Bool] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ATTACH_TO_PROCESS_BY_PID = 304, + + // Attach to a process by the given name. The first process found, in the + // case there are more than one with the same name, is used. The value of + // |continue_execution| indicates whether the process should resume execution + // after attached. The debugger target behavior specifies what should happen + // in the case when the target is being debugged (suspended or executing). + // Can return: RDBG_COMMAND_RESULT_OK, RDBG_COMMAND_RESULT_FAIL, or + // RDBG_COMMAND_RESULT_ABORT. + // + // [cmd :: rdbg_Command (uint16_t)] + // [process_name :: rdbg_String] + // [continue_execution :: rdbg_Bool] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ATTACH_TO_PROCESS_BY_NAME = 305, + + // Detach from a target that is being debugged. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DETACH_FROM_PROCESS = 306, + + // With the target suspended, step into by line. If a function call occurs, + // this command will enter the function. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_INTO_BY_LINE = 307, + + // With the target suspended, step into by instruction. If a function call + // occurs, this command will enter the function. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_INTO_BY_INSTRUCTION = 308, + + // With the target suspended, step into by line. If a function call occurs, + // this command step over that function and not enter it. Can return + // return RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OVER_BY_LINE = 309, + + // With the target suspended, step into by instruction. If a function call + // occurs, this command will step over that function and not enter it. Can + // return RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OVER_BY_INSTRUCTION = 310, + + // With the target suspended, continue running to the call site of the + // current function, i.e., step out. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OUT = 311, + + // With the target suspended, continue execution. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CONTINUE_EXECUTION = 312, + + // When the target is not being debugged or is suspended, run to the given + // filename and line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_RUN_TO_FILE_AT_LINE = 313, + + // Halt the execution of a target that is in the executing state. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_BREAK_EXECUTION = 314, + + // + // Breakpoints + + // Return the current list of breakpoints. These are the user requested + // breakpoints. Resolved breakpoint locations, if any, for a requested + // breakpoint can be obtained using RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS. + // + // * Presently, module name is not used and will always be a zero length + // string. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_bps :: uint16_t] + // .FOR(num_bps) { + // [uid :: rdbg_Id] + // [enabled :: rdbg_Bool] + // [module_name :: rdbg_String] + // [condition_expr :: rdbg_String] + // [kind :: rdbg_BreakpointKind (uint8_t)] + // .SWITCH(kind) { + // .CASE(BreakpointKind_FunctionName): + // [function_name :: rdbg_String] + // [overload_id :: uint32_t] + // .CASE(BreakpointKind_FilenameLine): + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // .CASE(BreakpointKind_Address): + // [address :: uint64_t] + // .CASE(BreakpointKind_Processor): + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // } + // } + RDBG_COMMAND_GET_BREAKPOINTS = 600, + + // Return the list of resolved locations for a particular breakpoint. If the + // ID is not valid for the current session RDBG_COMMAND_RESULT_INVALID_ID is + // returned. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_locs :: uint16_t] + // .FOR(num_locs) { + // [address :: uint64_t] + // [module_name :: rdbg_String] + // [filename :: rdbg_String] + // [actual_line_num :: uint32_t] + // } + RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS = 601, + + // Return a list of function overloads for the given function name. If the + // target is being debugged (suspended or executing) then returns a list of + // function overloads for the given function name, otherwise + // RDBG_COMMAND_RESULT_INVALID_TARGET_STATE is returned. Note that, + // presently, all modules are searched for the given function. + // + // [cmd :: rdbg_Command (uint16_t)] + // [function_name :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_overloads :: uint8_t] + // .FOR(num_overloads) { + // [overload_id :: rdbg_Id] + // [signature :: rdbg_String] + // } + RDBG_COMMAND_GET_FUNCTION_OVERLOADS = 602, + + // Request a breakpoint at the given function name and overload. Pass an + // overload ID of zero to add requested breakpoints for all functions with + // the given name. + // + // [cmd :: rdbg_Command (uint16_t)] + // [function_name :: rdbg_String] + // [overload_id :: rdbg_Id] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_FUNCTION = 603, + + // Request a breakpoint at the given source file and line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE = 604, + + // Request a breakpoint at the given address. + // + // [cmd :: rdbg_Command (uint16_t)] + // [address :: uint64_t] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_ADDRESS = 605, + + // Add a processor (hardware) breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_PROCESSOR_BREAKPOINT = 606, + + // Sets the conditional expression for the given breakpoint. Can pass in a + // zero-length string for none. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_BREAKPOINT_CONDITION = 607, + + // Given an existing breakpoint of type RDBG_BREAKPOINT_KIND_FILENAME_LINE, + // update its line number to the given one-based value. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_BREAKPOINT_LINE = 608, + + // Enable or disable an existing breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [enable :: rdbg_Bool] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ENABLE_BREAKPOINT = 609, + + // Delete an existing breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_BREAKPOINT = 610, + + // Delete all existing breakpoints. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_BREAKPOINTS = 611, + + // Return information about a specific user requested breakpoint. + // + // * Presently, module name is not used and will always be a zero length + // string. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // => + // [uid :: rdbg_Id] + // [enabled :: rdbg_Bool] + // [module_name :: rdbg_String] + // [condition_expr :: rdbg_String] + // [kind :: rdbg_BreakpointKind (uint8_t)] + // .SWITCH(kind) { + // .CASE(BreakpointKind_FunctionName): + // [function_name :: rdbg_String] + // [overload_id :: uint32_t] + // .CASE(BreakpointKind_FilenameLine): + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // .CASE(BreakpointKind_Address): + // [address :: uint64_t] + // .CASE(BreakpointKind_Processor): + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // } + RDBG_COMMAND_GET_BREAKPOINT = 612, + + // + // Watch Window Expressions + + // Return a list of watch expressions for the given, one-based watch window, + // presently ranging in [1,8]. + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_watches :: uint16_t] + // .FOR(num_watches) { + // [uid :: rdbg_Id] + // [expr :: rdbg_String] + // [comment :: rdbg_String] + // } + RDBG_COMMAND_GET_WATCHES = 700, + + // Add a watch expresion to the given, one-based watch window. Presently, + // only single line comments are supported. Spaces will replace any newlines + // found in a comment. + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // [expr :: rdbg_String] + // [comment :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [uid :: rdbg_Id] + RDBG_COMMAND_ADD_WATCH = 701, + + // Updates the expression for a given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // [expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_WATCH_EXPRESSION = 702, + + // Updates the comment for a given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // [comment :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_WATCH_COMMENT = 703, + + // Delete the given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_WATCH = 704, + + // Delete all watches in the given watch window + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_WATCHES = 705, +}; + +enum rdbg_SourceLocChangedReason +{ + RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED = 0, + + // An open-file from the command-line updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE = 1, + + // A RDBG_COMMAND_GOTO_FILE_AT_LINE from a named-pipes driver updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BY_DRIVER = 2, + + // A selection of a breakpoint in breakpoints pane updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED = 3, + + // The current stack frame was changed in the callstack pane and updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED = 4, + + // The active thread was changed in the threads pane and updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED = 5, + + // + // The process was suspended and updated the source location + // + RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT = 6, + RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT = 7, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER = 8, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN = 9, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT = 10, + RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT = 11, + RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK = 12, +}; + +enum rdbg_DebugEventKind +{ + // A target being debugged has exited. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [exit_code :: uint32_t] + RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS = 100, + + // The target for the active configuration is now being debugged. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_STARTED = 101, + + // The debugger has attached to a target process. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED = 102, + + // The debugger has detached from a target process. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED = 103, + + // The debugger has transitioned from suspended to executing. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED = 104, + + // The source location changed due to an event in the debugger. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // [reason :: rdbg_SourceLocChangedReason (uint16_t) ] + RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED = 200, + + // An external text editor was requested to be launched for the current file + // and line number. The event is sent even if an external text editor is not + // configured and, if configured, even if the external text editor failed to + // launch for any reason. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED = 201, + + // A user breakpoint was hit + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT = 600, + + // The breakpoint with the given ID has been resolved (has a valid location). + // This can happen if the breakpoint was set in module that became loaded, + // for instance. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED = 601, + + // A new user breakpoint was added. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED = 602, + + // A user breakpoint was modified. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED = 603, + + // A user breakpoint was removed. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED = 604, + + // An OutputDebugString was received by the debugger. The given string will + // be UTF-8 encoded. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [str :: rdbg_String] + RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING = 800, +}; + +// Sample usage of driving an instance of RemedyBG using named pipes. +// +// To use: first, start an named instance of RemedyBG using the "--servername" +// switch. +// +// remedybg.exe --servername {name} +// +// After RemedyBG is started, this sample can be run to control and make queries +// to the running instance. +// +// driver {name} +// +// The sample code demonstrates the usage of both these pipes and is written +// using non-overlapped IO for simplicity. Depending on your application, using +// non-blocking IO may be preferable. + + +#define COMMAND_BUF_SIZE 8192 +#define REPLY_BUF_SIZE 8192 +#define ERROR_MSG_LEN 512 + +enum PipeType +{ + DebugControlPipe, + DebugEventsPipe +}; + +struct rdb_Buffer +{ + uint8_t* data; + uint8_t* curr; + uint32_t capacity; + + bool err; // true if overflow (read or write) on the buffer +}; + +static void ReinitBuffer(struct rdb_Buffer* buf) +{ + buf->curr = buf->data; + buf->err = false; +} + +struct ClientContext +{ + HANDLE command_pipe_handle; + + struct rdb_Buffer cmd; + struct rdb_Buffer reply; + + // Stateful behavior so we don't have to pass these to every command that + // needs them. + enum rdbg_DebuggingTargetBehavior dbg_target_behavior; + enum rdbg_ModifiedSessionBehavior mod_session_behavior; + + char last_error[ERROR_MSG_LEN]; +}; + +static void WriteError(int err_msg_len, char* err_msg, char* format, ...) +{ + va_list args; + va_start(args, format); + + int n = vsnprintf(err_msg, err_msg_len, format, args); + if (n > 0) + { + FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError(), 0, err_msg + n, + err_msg_len - n, 0); + } + + va_end(args); +} + +static bool ContextHadError(struct ClientContext* ctx) +{ + return ctx->last_error[0] != 0 || ctx->cmd.err || ctx->reply.err; +} + +// +// Utilities for working with a ClientContext buffers +// +#define SetErrIfOverwrite(buf, sz) \ + (buf)->err = (buf)->err || ((buf)->curr > ((buf)->data + (buf)->capacity) - sz) + +#define PushBuffer(buf, ty, val) \ + SetErrIfOverwrite(buf, sizeof(ty)); \ + if (!(buf)->err) { *(ty*)((buf)->curr) = (val); (buf)->curr += sizeof(ty); } + +#define PushCommand(buf, cmd) PushBuffer(buf, uint16_t, (uint16_t)(cmd)) +#define PushDebuggingTargetBehavior(buf, dtb) \ + PushBuffer(buf, uint8_t, (uint8_t)dtb) +#define PushModifiedSessionBehavior(buf, msb) \ + PushBuffer(buf, uint8_t, (uint8_t)msb) +#define PushBool(buf, val) PushBuffer(buf, uint8_t, (uint8_t)(val)) +#define PushId(buf, val) PushBuffer(buf, rdbg_Id, (rdbg_Id)(val)) +#define PushU8(buf, val) PushBuffer(buf, uint8_t, (uint8_t)(val)) +#define PushU32(buf, val) PushBuffer(buf, uint32_t, (uint32_t)(val)) +#define PushS32(buf, val) PushBuffer(buf, int32_t, (int32_t)(val)) +#define PushU64(buf, val) PushBuffer(buf, uint64_t, (uint64_t)(val)) +#define PushProcessorBreakpointAccessKind(buf, akind) \ + PushBuffer(buf, uint8_t, (uint8_t)akind) + +static void PushStringZ(struct rdb_Buffer* b, char* str) +{ + uint16_t len = str ? (uint16_t)strlen(str) : 0; + PushBuffer(b, uint16_t, len); + SetErrIfOverwrite(b, len); + if (!b->err && len > 0) + { + memcpy(b->curr, str, len); + b->curr += len; + } +} + +#define PopBuffer(buf, ty) ( \ + (buf)->err = (buf)->err || \ + ((buf)->curr > ((buf)->data + (buf)->capacity) - sizeof(ty)), \ + (buf)->curr += ((buf)->err == 0 ? sizeof(ty) : 0), \ + (buf)->err == 0 ? *(ty*)((buf)->curr - sizeof(ty)) : (ty)0 ) + +#define PopBool(buf) (rdbg_Bool)PopBuffer(buf, uint8_t) +#define PopU8(buf) PopBuffer(buf, uint8_t) +#define PopU16(buf) PopBuffer(buf, uint16_t) +#define PopU32(buf) PopBuffer(buf, uint32_t) +#define PopS32(buf) PopBuffer(buf, int32_t) +#define PopU64(buf) PopBuffer(buf, uint64_t) +#define PopId(buf) (rdbg_Id)PopU32(buf) +#define PopCommandResult(buf) (enum rdbg_CommandResult)PopBuffer(buf, uint16_t) +#define PopTargetState(buf) (enum rdbg_TargetState)PopBuffer(buf, uint16_t) +#define PopBreakpointKind(buf) (enum rdbg_BreakpointKind)PopBuffer(buf, uint8_t) +#define PopProcessorBreakpointAccessKind(buf) \ + (enum rdbg_ProcessorBreakpointAccessKind)PopBuffer(buf, uint8_t) +#define PopDebugEventKind(buf) (enum rdbg_DebugEventKind)PopBuffer(buf, uint16_t) + +static void PopString(struct rdb_Buffer* buf, struct rdbg_String** str) +{ + uint16_t len = PopBuffer(buf, uint16_t); + buf->err = buf->err || buf->curr > buf->data + buf->capacity + len; + if (!buf->err) + { + *str = (struct rdbg_String*)(buf->curr - sizeof(uint16_t)); + buf->curr += len; + } + else + { + *str = 0; + } +} + +#define PIPE_NAME_PREFIX "\\\\.\\pipe\\" +#define PIPE_NAME_PREFIX_LEN 9 + +bool InitConnection(char* server_name, enum PipeType pipe_type, + int last_error_len, char* last_error, HANDLE* ret_pipe_handle) +{ + bool result = false; + + unsigned len = (unsigned)strlen(server_name); + if (len <= RDBG_MAX_SERVERNAME_LEN) + { + char pipe_name[256] = PIPE_NAME_PREFIX; + strcat(pipe_name, server_name); + if (pipe_type == DebugEventsPipe) + { + strcat(pipe_name, "-events"); + } + + DWORD flags = pipe_type == DebugControlPipe ? + GENERIC_READ | GENERIC_WRITE : + GENERIC_READ | FILE_WRITE_ATTRIBUTES; + + HANDLE pipe_handle = CreateFile(pipe_name, flags, 0, NULL, OPEN_EXISTING, + 0, NULL); + if (pipe_handle != INVALID_HANDLE_VALUE) + { + DWORD new_mode = PIPE_READMODE_MESSAGE; + if (SetNamedPipeHandleState(pipe_handle, &new_mode, NULL, NULL)) + { + *ret_pipe_handle = pipe_handle; + result = true; + } + else + { + WriteError(last_error_len, last_error, + "SetNamedPipeHandleState failed: "); + CloseHandle(pipe_handle); + } + } + else + { + WriteError(last_error_len, last_error, "CreateFile failed: "); + } + } + else + { + WriteError(last_error_len, last_error, "Server name too long."); + } + + return result; +} + +static void CloseConnection(struct ClientContext* ctx) +{ + CloseHandle(ctx->command_pipe_handle); +} + +static void TransactCommand(struct ClientContext* ctx) +{ + DWORD bytes_read = 0; + BOOL res = 0; + struct rdb_Buffer* reply = &ctx->reply; + + ReinitBuffer(reply); + + if (!ContextHadError(ctx)) + { + uint8_t* write_ptr = reply->data; + + res = TransactNamedPipe(ctx->command_pipe_handle, ctx->cmd.data, + (uint32_t)(ctx->cmd.curr - ctx->cmd.data), write_ptr, + REPLY_BUF_SIZE, &bytes_read, NULL); + write_ptr += bytes_read; + + while (!res && GetLastError() == ERROR_MORE_DATA) + { + int bytes_left = REPLY_BUF_SIZE - (int)(write_ptr - reply->data); + if (bytes_left > 0) + { + res = ReadFile(ctx->command_pipe_handle, write_ptr, bytes_left, + &bytes_read, NULL); + write_ptr += bytes_read; + } + else + break; // reply buffer full + } + if (res) + { + reply->capacity = (uint32_t)(write_ptr - reply->data); + } + else + { + WriteError(sizeof(ctx->last_error), ctx->last_error, + "TransactCommand failed: "); + } + } +} + +#define BeginCommand(ctx, c) \ + ReinitBuffer(&((ctx)->cmd)); \ + PushCommand(&((ctx)->cmd), c) + +#define SendCommand(ctx, c, res) \ + BeginCommand(ctx, c); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BeginCommandWithFlags(ctx, c) \ + BeginCommand(ctx, c); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + PushModifiedSessionBehavior(&((ctx)->cmd), (ctx)->mod_session_behavior) + +#define SendCommandWithFlags(ctx, c, res) \ + BeginCommandWithFlags(ctx, c); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BringDebuggerToForeground(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_BRING_DEBUGGER_TO_FOREGROUND, res) + +#define SetDebuggerWindowPos(ctx, x, y, cx, cy, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_WINDOW_POS); \ + PushS32(&(ctx)->cmd, x); \ + PushS32(&(ctx)->cmd, y); \ + PushS32(&(ctx)->cmd, cx); \ + PushS32(&(ctx)->cmd, cy); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define GetDebuggerWindowPos(ctx, res, x, y, cx, cy, is_maximized) \ + SendCommand(ctx, RDBG_COMMAND_GET_WINDOW_POS, res); \ + *(x) = PopId(&((ctx)->reply)); \ + *(y) = PopId(&((ctx)->reply)); \ + *(cx) = PopId(&((ctx)->reply)); \ + *(cy) = PopId(&((ctx)->reply)); \ + *(is_maximized) = PopBool(&((ctx)->reply)) != 0 + +#define SetBringToForegroundOnSuspended(ctx, btfos, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED); \ + PushBool(&(ctx)->cmd, btfos); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define ExitDebugger(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_EXIT_DEBUGGER, res) + +#define GetIsSessionModified(ctx, res, is_modified) \ + SendCommand(ctx, RDBG_COMMAND_GET_IS_SESSION_MODIFIED, res); \ + *(is_modified) = PopBool(&((ctx)->reply)) != 0 + +#define GetSessionFilename(ctx, res, filename) \ + SendCommand(ctx, RDBG_COMMAND_GET_SESSION_FILENAME, res); \ + PopString(&((ctx)->reply), filename) + +#define NewSession(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_NEW_SESSION, res) + +#define SendCommandWithString(ctx, c, str, res) \ + BeginCommand(ctx, c); \ + PushStringZ(&(ctx)->cmd, str); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define OpenSession(ctx, filename, res) \ + SendCommandWithString(ctx, RDBG_COMMAND_OPEN_SESSION, filename, res) + +#define SaveSession(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_SAVE_SESSION, res) + +#define SaveAsSession(ctx, filename, res) \ + SendCommandWithString(ctx, RDBG_COMMAND_SAVE_AS_SESSION, filename, res) + +#define GetSessionConfigs(ctx, res, cfg_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_SESSION_CONFIGS, res); \ + BufIterator_Init(cfg_it, PopBuffer(&((ctx)->reply), uint16_t), \ + ((ctx)->reply)) + +#define AddSessionConfig(ctx, command, args, wdir, env, inh, brk, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_SESSION_CONFIG); \ + PushStringZ(&(ctx)->cmd, command); \ + PushStringZ(&(ctx)->cmd, args); \ + PushStringZ(&(ctx)->cmd, wdir); \ + PushStringZ(&(ctx)->cmd, env); \ + PushBool(&(ctx)->cmd, inh); \ + PushBool(&(ctx)->cmd, brk); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(id) = PopId(&((ctx)->reply)) + +#define SendCommandWithId(ctx, c, id, res) \ + BeginCommand(ctx, c); \ + PushId(&(ctx)->cmd, id); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define SetActiveSessionConfig(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_SET_ACTIVE_SESSION_CONFIG, id, res) + +#define DeleteSessionConfig(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_DELETE_SESSION_CONFIG, id, res) + +#define DeleteAllSessionConfigs(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DELETE_ALL_SESSION_CONFIGS, res) + +#define GoToFileAtLine(ctx, filename, line, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_GOTO_FILE_AT_LINE); \ + PushStringZ(&(ctx)->cmd, filename); \ + PushBuffer(&(ctx)->cmd, uint32_t, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(id) = PopId(&((ctx)->reply)) + +#define CloseFileById(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_CLOSE_FILE, id, res) + +#define CloseAllFiles(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_CLOSE_ALL_FILES, res) + +#define GetCurrentFile(ctx, res, file_id, filename, line_num) \ + SendCommand(ctx, RDBG_COMMAND_GET_CURRENT_FILE, res); \ + *(file_id) = PopId(&(ctx)->reply); \ + PopString(&(ctx)->reply, filename); \ + *(line_num) = PopBuffer(&(ctx)->reply, uint32_t) + +#define GetOpenFiles(ctx, res, file_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_OPEN_FILES, res); \ + BufIterator_Init(file_it, PopBuffer(&((ctx)->reply), uint16_t), ((ctx)->reply)) + +#define StartDebugging(ctx, break_at_entry_point, res) \ + BeginCommand(ctx, RDBG_COMMAND_START_DEBUGGING); \ + PushBool(&(ctx)->cmd, break_at_entry_point); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define StopDebugging(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STOP_DEBUGGING, res) + +#define RestartDebugging(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_RESTART_DEBUGGING, res) + +#define AttachToProcessById(ctx, pid, cnt, res) \ + BeginCommand(ctx, RDBG_COMMAND_ATTACH_TO_PROCESS_BY_PID); \ + PushBuffer(&(ctx)->cmd, uint32_t, (uint32_t)(pid)); \ + PushBool(&(ctx)->cmd, cnt); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define AttachToProcessByName(ctx, name, cnt, res) \ + BeginCommand(ctx, RDBG_COMMAND_ATTACH_TO_PROCESS_BY_NAME); \ + PushStringZ(&(ctx)->cmd, name); \ + PushBool(&(ctx)->cmd, cnt); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DetachFromProcess(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DETACH_FROM_PROCESS, res) + +#define StepIntoByLine(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_INTO_BY_LINE, res) + +#define StepIntoByInstruction(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_INTO_BY_INSTRUCTION, res) + +#define StepOverByLine(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OVER_BY_LINE, res) + +#define StepOverByInstruction(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OVER_BY_INSTRUCTION, res) + +#define StepOut(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OUT, res) + +#define ContinueExecution(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_CONTINUE_EXECUTION, res) + +#define RunToFileAtLine(ctx, filename, line, res) \ + BeginCommand(ctx, RDBG_COMMAND_RUN_TO_FILE_AT_LINE); \ + PushStringZ(&(ctx)->cmd, filename); \ + PushBuffer(&(ctx)->cmd, uint32_t, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BreakExecution(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_BREAK_EXECUTION, res) + +#define GetTargetState(ctx, res, state) \ + SendCommand(ctx, RDBG_COMMAND_GET_TARGET_STATE, res); \ + *(state) = PopTargetState(&((ctx)->reply)) + +#define GetAllBreakpoints(ctx, res, bp_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_BREAKPOINTS, res); \ + BufIterator_Init(bp_it, PopBuffer(&((ctx)->reply), uint16_t), ((ctx)->reply)) + +#define GetBreakpoint(ctx, id, res, bp) \ + SendCommandWithId(ctx, RDBG_COMMAND_GET_BREAKPOINT, id, res); \ + PopBreakpoint(&((ctx)->reply), bp) + +#define GetBreakpointLocations(ctx, id, res, num_locs) \ + SendCommandWithId(ctx, RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS, id, res); \ + *(num_locs) = PopU16(&((ctx)->reply)) + +#define GetFunctionOverloads(ctx, res, fn_name, fno_it) \ + BeginCommand(ctx, RDBG_COMMAND_GET_FUNCTION_OVERLOADS); \ + PushStringZ(&(ctx)->cmd, fn_name); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + BufIterator_Init(fno_it, PopU8(&((ctx)->reply)), ((ctx)->reply)) + +#define AddBreakpointAtFn(ctx, fn_name, oid, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_FUNCTION); \ + PushStringZ(&(ctx)->cmd, fn_name); \ + PushId(&(ctx)->cmd, oid) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddBreakpointAtFilenameLine(ctx, file, line, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE); \ + PushStringZ(&(ctx)->cmd, file); \ + PushU32(&(ctx)->cmd, line) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddBreakpointAtAddress(ctx, addr, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_ADDRESS); \ + PushU64(&(ctx)->cmd, addr) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddProcessorBreakpoint(ctx, addr_expr, nbytes, akind, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_PROCESSOR_BREAKPOINT); \ + PushStringZ(&(ctx)->cmd, addr_expr); \ + PushU8(&(ctx)->cmd, nbytes); \ + PushProcessorBreakpointAccessKind(&(ctx)->cmd, akind); \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define SetBreakpointCondition(ctx, bp_id, cond, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_BREAKPOINT_CONDITION); \ + PushId(&(ctx)->cmd, bp_id) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define UpdateBreakpointLine(ctx, bp_id, line, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_BREAKPOINT_LINE); \ + PushId(&(ctx)->cmd, bp_id); \ + PushU32(&(ctx)->cmd, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define EnableBreakpoint(ctx, bp_id, enable, res) \ + BeginCommand(ctx, RDBG_COMMAND_ENABLE_BREAKPOINT); \ + PushId(&(ctx)->cmd, bp_id); \ + PushBool(&(ctx)->cmd, enable); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteBreakpoint(ctx, bp_id, res) \ + BeginCommand(ctx, RDBG_COMMAND_DELETE_BREAKPOINT); \ + PushId(&(ctx)->cmd, bp_id); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteAllBreakpoints(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DELETE_ALL_BREAKPOINTS, res) + +#define GetWatches(ctx, window_num, res, it) \ + BeginCommand(ctx, RDBG_COMMAND_GET_WATCHES); \ + PushU8(&(ctx)->cmd, window_num); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + BufIterator_Init(it, PopU16(&((ctx)->reply)), ((ctx)->reply)) + +#define AddWatch(ctx, window_num, expr, comment, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_WATCH); \ + PushU8(&(ctx)->cmd, window_num); \ + PushStringZ(&(ctx)->cmd, expr); \ + PushStringZ(&(ctx)->cmd, comment); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *id = PopId(&((ctx)->reply)) + +#define UpdateWatchExpression(ctx, id, expr, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_WATCH_EXPRESSION); \ + PushId(&(ctx)->cmd, id); \ + PushStringZ(&(ctx)->cmd, expr); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define UpdateWatchComment(ctx, id, comment, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_WATCH_COMMENT); \ + PushId(&(ctx)->cmd, id); \ + PushStringZ(&(ctx)->cmd, comment); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteWatch(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_DELETE_WATCH, id, res) + +#define DeleteAllWatches(ctx, window_num, res) \ + BeginCommand(ctx, RDBG_COMMAND_DELETE_ALL_WATCHES); \ + PushU8(&(ctx)->cmd, window_num); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +struct BufIterator +{ + int n; + int cur_idx; + struct rdb_Buffer buf; +}; + +static void BufIterator_Init(struct BufIterator* it, int n, + struct rdb_Buffer b) +{ + it->n = n; + it->cur_idx = -1; + + // Make a copy of reply buffer so we can make additional calls within loop. + static uint8_t it_buf[REPLY_BUF_SIZE]; + it->buf.data = it_buf; + it->buf.curr = it_buf + (b.curr - b.data); + it->buf.capacity = b.capacity; + it->buf.err = b.err; + if (!b.err) + { + memcpy(it->buf.data, b.data, b.capacity); + } +} + +struct SessionConfig +{ + rdbg_Id id; + struct rdbg_String* command; + struct rdbg_String* command_args; + struct rdbg_String* working_dir; + struct rdbg_String* environment_vars; + rdbg_Bool inherit_environment_vars_from_parent; + rdbg_Bool break_at_nominal_entry_point; +}; + +static bool SessionConfigIt_Next(struct BufIterator* it, + struct SessionConfig* cfg) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + cfg->id = PopId(b); + PopString(b, &cfg->command); + PopString(b, &cfg->command_args); + PopString(b, &cfg->working_dir); + PopString(b, &cfg->environment_vars); + cfg->inherit_environment_vars_from_parent = PopBool(b); + cfg->break_at_nominal_entry_point = PopBool(b); + + result = true; + } + return result; +} + +struct File +{ + rdbg_Id id; + struct rdbg_String* filename; + uint32_t line_num; +}; + +static bool FileIt_Next(struct BufIterator* it, struct File* file) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + file->id = PopId(b); + PopString(b, &file->filename); + file->line_num = PopBuffer(b, uint32_t); + + result = true; + } + return result; +} + +struct Breakpoint +{ + rdbg_Id uid; + rdbg_Bool enabled; + struct rdbg_String* module_name; + struct rdbg_String* condition_expr; + enum rdbg_BreakpointKind kind; + union + { + struct + { + struct rdbg_String* function_name; + rdbg_Id overload_id; + }; + struct + { + struct rdbg_String* filename; + uint32_t line_num; + }; + uint64_t address; + struct + { + struct rdbg_String* expression; + uint8_t num_bytes; + enum rdbg_InternalProcessorBreakpointType access_kind; + }; + }; +}; + +struct BreakpointLocation +{ + uint64_t address; + struct rdbg_String* module_name; + struct rdbg_String* filename; + uint32_t actual_line_num; +}; + +static char* BreakpointKindToString(enum rdbg_BreakpointKind kind) +{ + char* result = ""; + switch (kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + result = "RDBG_BREAKPOINT_KIND_FUNCTION_NAME"; + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + result = "RDBG_BREAKPOINT_KIND_FILENAME_LINE"; + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + result = "RDBG_BREAKPOINT_KIND_ADDRESS"; + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + result = "RDBG_BREAKPOINT_KIND_PROCESSOR"; + break; + } + return result; +} + +char *ProcessorBreakpointAccessKindToString( + enum rdbg_ProcessorBreakpointAccessKind kind) +{ + char* result = ""; + switch (kind) + { + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE"; + break; + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE"; + break; + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE"; + break; + } + return result; +} + +static void PopBreakpoint(struct rdb_Buffer* b, struct Breakpoint* bp) +{ + bp->uid = PopId(b); + bp->enabled = PopBool(b); + PopString(b, &bp->module_name); + PopString(b, &bp->condition_expr); + bp->kind = PopBreakpointKind(b); + switch (bp->kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + PopString(b, &bp->function_name); + bp->overload_id = PopId(b); + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + PopString(b, &bp->filename); + bp->line_num = PopU32(b); + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + bp->address = PopU64(b); + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + PopString(b, &bp->expression); + bp->num_bytes = PopU8(b); + bp->access_kind = (enum rdbg_InternalProcessorBreakpointType)PopProcessorBreakpointAccessKind(b); + break; + } +} + +static bool BreakpointIt_Next(struct BufIterator* it, struct Breakpoint* bp) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + PopBreakpoint(&it->buf, bp); + result = true; + } + return result; +} + +struct FunctionOverload +{ + rdbg_Id id; + struct rdbg_String* fn_sig; +}; + +static bool FunctionOverloadIt_Next(struct BufIterator* it, + struct FunctionOverload* overload) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + overload->id = PopId(b); + PopString(b, &overload->fn_sig); + result = true; + } + return result; +} + +struct Watch +{ + rdbg_Id uid; + struct rdbg_String* expression; + struct rdbg_String* comment; +}; + +static bool WatchExpressionIt_Next(struct BufIterator* it, + struct Watch* watch) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + watch->uid = PopId(b); + PopString(b, &watch->expression); + PopString(b, &watch->comment); + + result = true; + } + return result; +} + +static void MaybePrintField(struct rdbg_String* str, char* field_name) +{ + if (str && str->len) + { + fprintf(stderr, "\t%s: %.*s\n", field_name, str->len, + (char*)str->data); + } +} + +static char* SourceLocChangedReasonToString(enum rdbg_SourceLocChangedReason reason) +{ + char* result = ""; + switch (reason) + { + case RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BY_DRIVER: + result = "RDBG_SOURCE_LOCATION_CHANGED_REASON_BY_DRIVER"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK"; + break; + } + return result; +} + +static void WriteDebugEvent(struct rdb_Buffer* eb) +{ + struct rdbg_String* str; + + enum rdbg_DebugEventKind kind = PopDebugEventKind(eb); + switch (kind) + { + case RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS\n"); + fprintf(stderr, "\texit_code: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_STARTED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_STARTED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED\n"); + break; + case RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED\n"); + PopString(eb, &str); + if (str && str->len) + { + fprintf(stderr, "\tfilename: %.*s\n", str->len, (char*)str->data); + } + fprintf(stderr, "\tline num: %u\n", PopU32(eb)); + fprintf(stderr, "\treason: %s\n", SourceLocChangedReasonToString((rdbg_SourceLocChangedReason)PopU16(eb))); + break; + case RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED\n"); + PopString(eb, &str); + if (str && str->len) + { + fprintf(stderr, "\tfilename: %.*s\n", str->len, (char*)str->data); + } + fprintf(stderr, "\tline num: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING: + PopString(eb, &str); + + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING\n"); + if (str && str->len) + { + fprintf(stderr, "\tstr: %.*s\n", str->len, (char*)str->data); + } + break; + + default: + fprintf(stderr, "warning: unknown debug event kind received\n"); + } +} + +void DebugControlSample(char* server_name) +{ + static uint8_t command_buf[COMMAND_BUF_SIZE]; + static uint8_t reply_buf[REPLY_BUF_SIZE]; + struct ClientContext ctx = {}; + ctx.cmd.data = command_buf; + ctx.cmd.capacity = sizeof(command_buf); + ctx.reply.data = reply_buf; + ctx.reply.capacity = sizeof(reply_buf); + ctx.dbg_target_behavior = RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING; + ctx.mod_session_behavior = RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING; + ctx.last_error[0] = 0; + + if (InitConnection(server_name, DebugControlPipe, sizeof(ctx.last_error), + ctx.last_error, &ctx.command_pipe_handle)) + { + /* Sample calls to each debug control command (commented out). + * + enum rdbg_CommandResult res; + StartDebugging(&ctx, false, &res); + BringDebuggerToForeground(&ctx, &res); + SetDebuggerWindowPos(&ctx, 20, 20, 100, 100, &res); + + SetBringToForegroundOnSuspended(&ctx, false, &res); + int x, y, width, height; + bool is_maximized; + GetDebuggerWindowPos(&ctx, &res, &x, &y, &width, &height, &is_maximized); + printf("Window pos: (%d, %d) %d x %d; is maximized: %s\n", + x, y, width, height, is_maximized ? "true" : "false"); + bool is_modified; + GetIsSessionModified(&ctx, &res, &is_modified); + struct rdbg_String* filename; + GetSessionFilename(&ctx, &res, &filename); + if (filename && filename->len) + { + printf("Session filename: %.*s\n", filename->len, filename->data); + } + NewSession(&ctx, &res); + OpenSession(&ctx, "c:\\path\\to\\session.rdbg", &res); + SaveSession(&ctx, &res); + SaveAsSession(&ctx, "c:\\path\\to\\session.rdbg", &res); + struct BufIterator cfg_it; + struct SessionConfig cfg; + GetSessionConfigs(&ctx, &res, &cfg_it); + while (SessionConfigIt_Next(&cfg_it, &cfg)) + { + fprintf(stderr, "Config #%d\n", cfg_it.cur_idx); + fprintf(stderr, "\tuid: %hu\n", cfg.id); + if (cfg.command && cfg.command->len) + { + fprintf(stderr, "\tcommand: %.*s\n", cfg.command->len, + (char*)cfg.command->data); + } + if (cfg.command_args && cfg.command_args->len) + { + fprintf(stderr, "\tcommand_args: %.*s\n", cfg.command_args->len, + (char*)cfg.command_args->data); + } + if (cfg.working_dir && cfg.working_dir->len) + { + fprintf(stderr, "\tworking_dir: %.*s\n", cfg.working_dir->len, + (char*)cfg.working_dir->data); + } + if (cfg.environment_vars && cfg.environment_vars->len) + { + fprintf(stderr, "\tenvironment_vars:\n%.*s\n", + cfg.environment_vars->len, + (char*)cfg.environment_vars->data); + } + fprintf(stderr, "\tinherit_environment_vars_from_parent: %s\n", + cfg.inherit_environment_vars_from_parent ? "true" : "false"); + fprintf(stderr, "\tbreak_at_nominal_entry_point: %s\n", + cfg.break_at_nominal_entry_point ? "true" : "false"); + } + rdbg_Id cfg_id; + AddSessionConfig(&ctx, + "C:\\windows\\system32\\whoami.exe", "/USER", 0, + "A=1\nBB=2\nCCC=3", true, true, &res, &cfg_id); + fprintf(stderr, "Added session conf (ID: %u).\n", cfg_id); + SetActiveSessionConfig(&ctx, cfg_id, &res); + DeleteSessionConfig(&ctx, cfg.id, &res); + DeleteAllSessionConfigs(&ctx, &res); + rdbg_Id cur_file_id; + struct rdbg_String* cur_filename = 0; + uint32_t line_num; + GetCurrentFile(&ctx, &res, &cur_file_id, &cur_filename, &line_num); + if (cur_filename && cur_filename->len) + { + fprintf(stderr, "Topmost file: (%u) %.*s Ln %u\n", + cur_file_id, cur_filename->len, (char*)cur_filename->data, + line_num); + } + rdbg_Id file_id; + GoToFileAtLine(&ctx, "C:\\full\\path\\to\\README.txt", 121, &res, &file_id); + CloseFileById(&ctx, file_id, &res); + CloseAllFiles(&ctx, &res); + struct BufIterator file_it; + struct File file; + GetOpenFiles(&ctx, &res, &file_it); + while (FileIt_Next(&file_it, &file)) + { + fprintf(stderr, "File #%d\n", file_it.cur_idx); + fprintf(stderr, "\tId: %u\n", file.id); + if (file.filename && file.filename->len) + { + fprintf(stderr, "\tfilename: %.*s\n", file.filename->len, + (char*)file.filename->data); + } + fprintf(stderr, "\tLn: %u\n", file.line_num); + } + StopDebugging(&ctx, &res); + RestartDebugging(&ctx, &res); + AttachToProcessById(&ctx, 14368, true, &res); + NewSession(&ctx, &res); + AttachToProcessByName(&ctx, "Calculator.exe", true, &res); + DetachFromProcess(&ctx, &res); + StepIntoByLine(&ctx, &res); + StepIntoByInstruction(&ctx, &res); + StepOverByLine(&ctx, &res); + StepOverByInstruction(&ctx, &res); + StepOut(&ctx, &res); + ContinueExecution(&ctx, &res); + RunToFileAtLine(&ctx, "C:\\full\\path\\to\\test.cpp", 13, &res); + ContinueExecution(&ctx, &res); + BreakExecution(&ctx, &res); + enum rdbg_TargetState state; + GetTargetState(&ctx, &res, &state); + fprintf(stderr, "target state: %hu\n", state); + struct BufIterator bp_it; + struct Breakpoint bp; + GetAllBreakpoints(&ctx, &res, &bp_it); + int idx = 0; + while (BreakpointIt_Next(&bp_it, &bp)) + { + fprintf(stderr, "Breakpoint # %d\n", ++idx); + fprintf(stderr, "\tUID: %u\n", bp.uid); + fprintf(stderr, "\tEnabled: %s\n", bp.enabled ? "true" : "false"); + MaybePrintField(bp.module_name, "Module"); + MaybePrintField(bp.condition_expr, "Condition"); + fprintf(stderr, "\tKind: %s\n", BreakpointKindToString(bp.kind)); + switch (bp.kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + MaybePrintField(bp.function_name, "Function"); + fprintf(stderr, "\tOverload: %u\n", bp.overload_id); + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + MaybePrintField(bp.filename, "Filename"); + fprintf(stderr, "\tLine: %u\n", bp.line_num); + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + fprintf(stderr, "\tAddress: 0x%llx\n", bp.address); + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + MaybePrintField(bp.expression, "Expression"); + fprintf(stderr, "\tBytes: %hhu\n", bp.num_bytes); + fprintf(stderr, "\tAccess kind: %s\n", + ProcessorBreakpointAccessKindToString(bp.access_kind)); + break; + } + // Test for call to get information on a single user breakpoint + struct Breakpoint _bp; + GetBreakpoint(&ctx, bp.uid, &res, &_bp); + // See if the breakpoint has been resolved. Not using an iterator + // here, at the moment, because we can only get back one or zero. + uint16_t num_locs; + GetBreakpointLocations(&ctx, bp.uid, &res, &num_locs); + if (num_locs == 1) + { + struct BreakpointLocation loc; + loc.address = PopU64(&ctx.reply); + PopString(&ctx.reply, &loc.module_name); + PopString(&ctx.reply, &loc.filename); + loc.actual_line_num = PopU32(&ctx.reply); + fprintf(stderr, "\t---------\n"); + fprintf(stderr, "\tResolved address: 0x%llx\n", loc.address); + MaybePrintField(loc.module_name, "Module"); + MaybePrintField(loc.module_name, "Filename"); + fprintf(stderr, "\tActual line number: %u\n", + loc.actual_line_num); + } + else + { + fprintf(stderr, "\tUnresolved\n"); + } + } + struct BufIterator fn_overload_it; + struct FunctionOverload overload; + GetFunctionOverloads(&ctx, &res, "SomeFunction", &fn_overload_it); + while (FunctionOverloadIt_Next(&fn_overload_it, &overload)) + { + fprintf(stderr, "Overload %u; sig: ", overload.id); + if (overload.fn_sig && overload.fn_sig->len) + { + fprintf(stderr, "%.*s\n", overload.fn_sig->len, + (char*)overload.fn_sig->data); + } + } + rdbg_Id bp_id; + AddBreakpointAtFn(&ctx, "SomeFunction", 0, "", &res, &bp_id); + AddBreakpointAtFilenameLine(&ctx, "C:\\path\\to\\fn_overloads.cpp", 21, + "", &res, &bp_id); + AddBreakpointAtAddress(&ctx, 0x7FF7F592B703, "", &res, &bp_id); + AddProcessorBreakpoint(&ctx, "&var", 4, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE, "", &res, &bp_id); + SetBreakpointCondition(&ctx, bp_id, "$rax == 0", &res); + UpdateBreakpointLine(&ctx, bp_id, 22, &res); + EnableBreakpoint(&ctx, bp_id, false, &res); + EnableBreakpoint(&ctx, bp_id, true, &res); + DeleteBreakpoint(&ctx, bp_id, &res); + DeleteAllBreakpoints(&ctx, &res); + rdbg_Id watch_id; + AddWatch(&ctx, 1, "0xf0c / 0xa", "testing\nblah", &res, &watch_id); + struct BufIterator watch_it; + GetWatches(&ctx, 1, &res, &watch_it); + struct Watch watch; + while (WatchExpressionIt_Next(&watch_it, &watch)) + { + fprintf(stderr, "Watch %hu\n", watch.uid); + if (watch.expression && watch.expression->len) + { + fprintf(stderr, "\tWatch expression: '%.*s'\n", + watch.expression->len, + (char*)watch.expression->data); + } + if (watch.comment && watch.comment->len) + { + fprintf(stderr, "\tWatch comment: '%.*s'\n", + watch.comment->len, + (char*)watch.comment->data); + } + } + UpdateWatchExpression(&ctx, 37, "expr * expr", &res); + UpdateWatchComment(&ctx, 37, "something left after", &res); + DeleteWatch(&ctx, 42, &res); + DeleteAllWatches(&ctx, 1, &res); + ExitDebugger(&ctx, &res); + */ + + if (ContextHadError(&ctx)) + { + fprintf(stderr, "[ERROR] %s (cmd-err:%s)(reply-err:%s)\n", + ctx.last_error, + ctx.cmd.err ? "true" : "false", + ctx.reply.err ? "true" : "false"); + } + CloseConnection(&ctx); + } + else + { + fprintf(stderr, ctx.last_error); + } +} + +void DebugEventsSample(char* server_name) +{ + HANDLE pipe_handle; + char last_error[ERROR_MSG_LEN]; + last_error[0] = 0; + + if (InitConnection(server_name, DebugEventsPipe, sizeof(last_error), + last_error, &pipe_handle)) + { + // blocking read for testing events + while (1) + { + static uint8_t dbg_evt_buf[REPLY_BUF_SIZE]; + struct rdb_Buffer dbg_evt = {}; + dbg_evt.data = dbg_evt_buf; + dbg_evt.capacity = sizeof(dbg_evt_buf); + + DWORD bytes_read = 0; + if (ReadFile(pipe_handle, dbg_evt.data, REPLY_BUF_SIZE, &bytes_read, + NULL)) + { + dbg_evt.curr = dbg_evt.data; + WriteDebugEvent(&dbg_evt); + } + else + { + fprintf(stderr, "ReadFile FAIL: err=%u\n", GetLastError()); + } + } + } + else + { + fprintf(stderr, last_error); + } +} + +///////////////////////////////////////////////////////////////// + +uint8_t command_buf[COMMAND_BUF_SIZE]; +uint8_t reply_buf[REPLY_BUF_SIZE]; +ClientContext RDBG_Ctx; + +bool RDBG_InitConnection() { + enum rdbg_CommandResult res; + if (RDBG_Ctx.command_pipe_handle != NULL) { + enum rdbg_TargetState state; + GetTargetState(&RDBG_Ctx, &res, &state); + if (!ContextHadError(&RDBG_Ctx)) { + return true; + } + } + Scratch scratch; + String session_name = Format(scratch, "te%llu", GetTimeNanos()); + String remedy_string = Format(scratch, "%S --servername %S", RemedyBGPath, session_name); + Exec(NullViewID, true, remedy_string, GetMainDir()); + MemoryZero(&RDBG_Ctx, sizeof(RDBG_Ctx)); + RDBG_Ctx.cmd.data = command_buf; + RDBG_Ctx.cmd.capacity = sizeof(command_buf); + RDBG_Ctx.reply.data = reply_buf; + RDBG_Ctx.reply.capacity = sizeof(reply_buf); + RDBG_Ctx.dbg_target_behavior = RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING; + RDBG_Ctx.mod_session_behavior = RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING; + RDBG_Ctx.last_error[0] = 0; + + bool result = false; + for (int i = 0; i < 64; i += 1) { + Sleep(10); + result = InitConnection(session_name.data, DebugControlPipe, sizeof(RDBG_Ctx.last_error), RDBG_Ctx.last_error, &RDBG_Ctx.command_pipe_handle); + if (result) { + break; + } + } + + if (result == false) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + RDBG_Ctx = {}; + return false; + } + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + + rdbg_Id cfg_id; + char *exe = Format(scratch, "%S/%S", WorkDir, BinaryUnderDebug).data; + char *args = NULL; + char *work_dir = WorkDir.data; + char *env = NULL; + AddSessionConfig(&RDBG_Ctx, exe, args, work_dir, env, true, true, &res, &cfg_id); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return true; + } + + SetActiveSessionConfig(&RDBG_Ctx, cfg_id, &res); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return true; + } + + return true; +} + +void CMD_StartDebugging(HookParam param) { + bool conn = RDBG_InitConnection(); + if (!conn) { + return; + } + + enum rdbg_CommandResult res; + enum rdbg_TargetState state; + GetTargetState(&RDBG_Ctx, &res, &state); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } + + if (state == RDBG_TARGET_STATE_NONE) { + StartDebugging(&RDBG_Ctx, false, &res); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } + } else if (state == RDBG_TARGET_STATE_SUSPENDED) { + ContinueExecution(&RDBG_Ctx, &res); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } + } + BringDebuggerToForeground(&RDBG_Ctx, &res); + if (ContextHadError(&RDBG_Ctx)) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_StartDebugging, "f5", "Start debugging, if debugger not active it starts it, uses BinaryUnderDebug"); + +void CMD_RunToLineInDebugger(HookParam param) { + bool conn = RDBG_InitConnection(); + if (!conn) { + return; + } + + BSet prim = GetBSet(PrimaryWindowID); + enum rdbg_CommandResult res; + Int line = PosToLine(prim.buffer, GetFront(prim.view->carets[0])); + RunToFileAtLine(&RDBG_Ctx, prim.buffer->name.data, (uint32_t)line + 1, &res); + if (res != RDBG_COMMAND_RESULT_OK) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_RunToLineInDebugger, "ctrl-f10", "Instruct debugger to execute until line and column under caret"); + +void CMD_StopDebugging(HookParam param) { + bool conn = RDBG_InitConnection(); + if (!conn) { + return; + } + + enum rdbg_CommandResult res; + StopDebugging(&RDBG_Ctx, &res); + if (res != RDBG_COMMAND_RESULT_OK) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_StopDebugging, "shift-f5", "Stop debugging"); + +void CMD_AddBreakpoint(HookParam param) { + if (!RDBG_InitConnection()) { + return; + } + + BSet prim = GetBSet(PrimaryWindowID); + Int line = PosToLine(prim.buffer, GetFront(prim.view->carets[0])); + enum rdbg_CommandResult res; + rdbg_Id bp_id; + AddBreakpointAtFilenameLine(&RDBG_Ctx, prim.buffer->name.data, (uint32_t)line + 1, "", &res, &bp_id); + if (res != RDBG_COMMAND_RESULT_OK) { + ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); + MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_AddBreakpoint, "f9", "Add a breakpoint at filename + line"); + +void QuitDebugger() { + if (RDBG_Ctx.command_pipe_handle == NULL) { + return; + } + + enum rdbg_CommandResult res; + ExitDebugger(&RDBG_Ctx, &res); + CloseConnection(&RDBG_Ctx); +} diff --git a/src/text_editor/plugin_remedybg.h b/src/text_editor/plugin_remedybg.h new file mode 100644 index 0000000..3b0209b --- /dev/null +++ b/src/text_editor/plugin_remedybg.h @@ -0,0 +1,2 @@ +#define PLUGIN_REMEDYBG +void QuitDebugger(); \ No newline at end of file diff --git a/src/text_editor/plugin_search_window.cpp b/src/text_editor/plugin_search_window.cpp new file mode 100644 index 0000000..6f0e021 --- /dev/null +++ b/src/text_editor/plugin_search_window.cpp @@ -0,0 +1,103 @@ +void CMD_Search(HookParam param) { + BSet main = GetBSet(ActiveWindowID); + String16 string = {}; + if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) { + string = GetString(main.buffer, main.view->carets[0].range); + } + BSet set = GetBSet(SearchWindowID); + set.window->visible = true; + NextActiveWindowID = SearchWindowID; + SelectEntireBuffer(set.view); + if (string.len > 0) { + Replace(set.view, string); + SelectEntireBuffer(set.view); + } +} RegisterCommand(CMD_Search, "ctrl-f", "Open up a search window"); + +void SearchWindowFindNext(bool forward = true) { + BSet main = GetBSet(PrimaryWindowID); + BSet set = GetBSet(SearchWindowID); + String16 seek = GetString(set.buffer, GetRange(set.buffer)); + Find(main.view, seek, forward); + main.window->search_bar_anchor = main.view->carets[0]; + CenterView(PrimaryWindowID); +} + +void CMD_SearchNextInSearch(HookParam param) { + SearchWindowFindNext(true); +} + +void CMD_SearchPrevInSearch(HookParam param) { + SearchWindowFindNext(false); +} + +void CMD_SearchNext(HookParam param) { + SearchWindowFindNext(true); +} RegisterCommand(CMD_SearchNext, "f3", "Go to the next occurence of the search window needle"); + +void CMD_SearchPrev(HookParam param) { + SearchWindowFindNext(false); +} RegisterCommand(CMD_SearchPrev, "shift-f3", "Go to the previous occurence of the search window needle"); + +void CMD_SearchAll(HookParam param) { + BSet main = GetBSet(PrimaryWindowID); + BSet set = GetBSet(SearchWindowID); + String16 needle = GetString(set.buffer, GetRange(set.buffer)); + SelectAllOccurences(main.view, needle); + set.window->visible = false; +} RegisterCommand(CMD_SearchAll, "alt-f3", "Use the search window needle and seek all the possible occurences in current buffer"); + +void CMD_ToggleCaseSensitiveSearch(HookParam param) { + SearchCaseSensitive = !SearchCaseSensitive; +} RegisterCommand(CMD_ToggleCaseSensitiveSearch, "alt-c", "Text editor wide search toggle, should apply to most search things"); + +void CMD_ToggleSearchWordBoundary(HookParam param) { + SearchWordBoundary = !SearchWordBoundary; +} RegisterCommand(CMD_ToggleSearchWordBoundary, "alt-w", "Text editor wide search toggle, should apply to most search things"); + +void InitSearchWindow() { + Window *window = CreateWind(); + SearchWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "search")); + buffer->special = true; + SearchBufferID = buffer->id; + View *view = CreateView(buffer->id); + view->special = true; + SearchViewID = view->id; + window->active_view = view->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->secondary_window_style = true; + window->draw_line_highlight = false; + window->primary = false; + window->visible = false; + window->lose_visibility_on_escape = true; + window->jump_history = false; + AddCommand(&view->hooks, "SearchAll", "alt-enter", CMD_SearchAll); + AddCommand(&view->hooks, "SearchPrevInSearch", "shift-enter", CMD_SearchPrevInSearch); + AddCommand(&view->hooks, "SearchNextInSearch", "enter", CMD_SearchNextInSearch); +} + +void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) { + Window *window = GetWindow(SearchWindowID); + Rect2I copy_rect = *rect; + if (!window->visible) { + rect = ©_rect; + } + Int barsize = GetExpandingBarSize(window); + window->document_rect = window->total_rect = CutBottom(rect, barsize); + window->line_numbers_rect = CutLeft(&window->document_rect, window->font->char_spacing * 6); +} + +void UpdateSearchWindow() { + if (ActiveWindowID == SearchWindowID) { + BSet active = GetBSet(ActiveWindowID); + if (active.buffer->begin_frame_change_id != active.buffer->change_id) { + BSet main = GetBSet(PrimaryWindowID); + main.view->carets[0] = main.window->search_bar_anchor; + String16 seek = GetString(active.buffer, GetRange(active.buffer)); + Find(main.view, seek, true); + CenterView(main.window->id); + } + } +} diff --git a/src/text_editor/plugin_search_window.h b/src/text_editor/plugin_search_window.h new file mode 100644 index 0000000..cb458be --- /dev/null +++ b/src/text_editor/plugin_search_window.h @@ -0,0 +1,4 @@ +#define PLUGIN_SEARCH_WINDOW +void InitSearchWindow(); +void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy); +void UpdateSearchWindow(); \ No newline at end of file diff --git a/src/text_editor/plugin_status_window.cpp b/src/text_editor/plugin_status_window.cpp new file mode 100644 index 0000000..98c69b9 --- /dev/null +++ b/src/text_editor/plugin_status_window.cpp @@ -0,0 +1,97 @@ +void InitStatusWindow() { + Window *window = CreateWind(); + StatusWindowID = window->id; + Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "status_bar")); + buffer->special = true; + View *view = CreateView(buffer->id); + view->special = true; + window->active_view = view->id; + window->draw_line_numbers = false; + window->draw_scrollbar = false; + window->draw_line_highlight = true; + window->secondary_window_style = true; + window->primary = false; + window->jump_history = false; + window->lose_focus_on_escape = true; +} + +void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy) { + Window *window = GetWindow(StatusWindowID); + Rect2I copy_rect = *rect; + if (!window->visible) { + rect = ©_rect; + } + Int barsize = GetExpandingBarSize(window); + window->document_rect = window->total_rect = CutBottom(rect, barsize); +} + +void UpdateStatusWindow() { + ProfileFunction(); + Window *status_bar_window = GetWindow(StatusWindowID, NULL); + Scratch scratch; + BSet main = GetBSet(PrimaryWindowID); + BSet title = GetBSet(status_bar_window); + title.view->scroll.y = 0; + + String16 buffer_string = GetString(title.buffer); + Range replace_range = {0, title.buffer->len}; + bool found_separator = Seek(buffer_string, u" |", &replace_range.max); + + // Parse the title and line + if (title.window->id == ActiveWindowID) { + if (title.buffer->change_id == title.buffer->begin_frame_change_id) { + return; + } + String16 buffer_name = GetString(title.buffer, replace_range); + buffer_name = Trim(buffer_name); + + Int column = ChopNumber(&buffer_name); + if (column == -1) return; + + Chop(&buffer_name, u":"); + Int line = ChopNumber(&buffer_name); + if (line == -1) { + line = column; + column = 0; + } + Chop(&buffer_name, u":"); + + Int buffer_pos = XYToPos(main.buffer, {column, line}); + Caret &caret = main.view->carets[0]; + if (GetFront(caret) != buffer_pos) { + caret = MakeCaret(buffer_pos); + } + return; + } + + Caret caret = main.view->carets[0]; + XY xy = PosToXY(main.buffer, GetFront(caret)); + + // add separator at the end of buffer + if (!found_separator) { + SelectRange(title.view, GetBufferEndAsRange(title.buffer)); + ReplaceEx(scratch, title.view, u" | :Prev :Next :Close"); + } + + // replace data up to separator with filename and stuff + const char *reopen = main.buffer->changed_on_disk ? " :Reopen" : ""; + const char *case_sens = SearchCaseSensitive ? " C" : ""; + const char *word_bound = SearchWordBoundary ? " W" : ""; + const char *dirty = main.buffer->dirty ? " !" : ""; + String s = Format(scratch, "%S:%lld:%lld%s%s%s", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, dirty, case_sens, word_bound, reopen); + For (ActiveProcesses) { + if (it.view_id == main.view->id.id) { + s = Format(scratch, "%S %lld :KillProcess", s, (long long)it.id); + } + } + + String16 string = ToString16(scratch, s); + String16 string_to_replace = GetString(title.buffer, replace_range); + if (string_to_replace != string) { + SelectRange(title.view, replace_range); + ReplaceEx(scratch, title.view, string); + } + + SelectRange(title.view, MakeRange(0)); + ResetHistory(title.buffer); +} diff --git a/src/text_editor/plugin_status_window.h b/src/text_editor/plugin_status_window.h new file mode 100644 index 0000000..95bbc6f --- /dev/null +++ b/src/text_editor/plugin_status_window.h @@ -0,0 +1,4 @@ +#define PLUGIN_STATUS_WINDOW +void InitStatusWindow(); +void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy); +void UpdateStatusWindow(); \ No newline at end of file diff --git a/src/text_editor/process.cpp b/src/text_editor/process.cpp index b7a9e4a..688f124 100644 --- a/src/text_editor/process.cpp +++ b/src/text_editor/process.cpp @@ -4,7 +4,7 @@ // the appropriate handles. This happens in this case when git grep calls // 'less' program which errors out and doesn't print anything // @todo: maybe I should ask someone smarter about this! -void UpdateProcesses(HookParam param) { +void UpdateProcesses() { IterRemove(ActiveProcesses) { IterRemovePrepare(ActiveProcesses); Scratch scratch; @@ -19,14 +19,16 @@ void UpdateProcesses(HookParam param) { remove_item = true; } } -} RegisterHook(UpdateProcesses, HookKind_AppUpdate, "", "Polls all the processes and outputs the results to corresponding views"); +} void Exec(ViewID view, bool scroll_to_end, String cmd, String working_dir) { Process process = SpawnProcess(cmd, working_dir, {}, ProcessEnviroment); ReportDebugf("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, cmd, working_dir); process.view_id = view.id; process.scroll_to_end = scroll_to_end; - if (process.is_valid) Add(&ActiveProcesses, process); + if (process.is_valid) { + Add(&ActiveProcesses, process); + } } void Exec(ViewID view, bool scroll_to_end, String16 cmd16, String working_dir) { diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 71fabdf..31cfaac 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -15,16 +15,20 @@ #include "render/font.cpp" #include "render/opengl.cpp" #include "text_editor.h" +#include "plugin_command_window.h" +#include "plugin_search_window.h" +#include "plugin_debug_window.h" +#include "plugin_status_window.h" +#include "plugin_build_window.h" +#if OS_WINDOWS +#include "plugin_remedybg.h" +#endif #include "globals.cpp" #include "coroutines.cpp" #include "buffer.cpp" #include "view.cpp" #include "window.cpp" -#include "window_status.cpp" -#include "window_command.cpp" -#include "window_debug.cpp" -#include "window_search.cpp" -#include "window_build.cpp" +#include "fuzzy_search_view.cpp" #include "process.cpp" #include "event.cpp" #include "config.cpp" @@ -34,7 +38,14 @@ #include "draw.cpp" #include "test/tests.cpp" -#include "remedybg_plugin.cpp" +#include "plugin_command_window.cpp" +#include "plugin_search_window.cpp" +#include "plugin_status_window.cpp" +#include "plugin_build_window.cpp" +#include "plugin_debug_window.cpp" +#if OS_WINDOWS +#include "plugin_remedybg.cpp" +#endif #if OS_WASM EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), { @@ -581,25 +592,30 @@ void Update(Event event) { OnCommand(event); +#ifdef PLUGIN_DEBUG_WINDOW + UpdateDebugWindow(); +#endif + +#ifdef PLUGIN_STATUS_WINDOW + UpdateStatusWindow(); +#endif + +#ifdef PLUGIN_SEARCH_WINDOW + UpdateSearchWindow(); +#endif + + // ActiveViewHook { BSet set = GetBSet(ActiveWindowID); - For (set.view->hooks) { - if (it.kind == HookKind_AppUpdate) { - ProfileScopeEx(it.name); - it.function(HookParam{}); - } - } - - For (GlobalHooks) { - if (it.kind == HookKind_AppUpdate) { - ProfileScopeEx(it.name); - it.function(HookParam{}); - } + if (set.view->update_hook) { + set.view->update_hook(); } } + UpdateProcesses(); CoUpdate(&event); + For(IterateInReverse(&order)) { if (!it->visible) continue; View *view = GetView(it->active_view); @@ -664,8 +680,19 @@ void Update(Event event) { // Behavior where these windows cannot be visible at the same time { - WindowID id[] = {BuildWindowID, CommandWindowID, SearchWindowID}; - for (int i = 0; i < Lengthof(id); i += 1) { + WindowID id[] = { + {-1}, // This is just so that we have a valid array if all windows are disabled +#ifdef PLUGIN_BUILD_WINDOW + BuildWindowID, +#endif +#ifdef PLUGIN_COMMAND_WINDOW + CommandWindowID, +#endif +#ifdef PLUGIN_SEARCH_WINDOW + SearchWindowID, +#endif + }; + for (int i = 1; i < Lengthof(id); i += 1) { if (ActiveWindowID == id[i]) { for (int j = 0; j < Lengthof(id); j += 1) { if (i == j) continue; @@ -921,14 +948,25 @@ int main(int argc, char **argv) CoResume(co_data); #endif - { - For (GlobalHooks) { - if (it.kind == HookKind_AppInit) { - ProfileScopeEx(it.name); - it.function({}); - } - } - } +#ifdef PLUGIN_STATUS_WINDOW + InitStatusWindow(); +#endif + +#ifdef PLUGIN_COMMAND_WINDOW + InitCommandWindow(); +#endif + +#ifdef PLUGIN_DEBUG_WINDOW + InitDebugWindow(); +#endif + +#ifdef PLUGIN_BUILD_WINDOW + InitBuildWindow(); +#endif + +#ifdef PLUGIN_SEARCH_WINDOW + InitSearchWindow(); +#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 6b12f57..8da9310 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -27,38 +27,7 @@ struct HistoryEntry { enum HookKind { HookKind_Invalid, - - HookKind_AppInit, - HookKind_AppUpdate, - HookKind_AppQuit, - - // Should we have commands like PostCommand, PreCommand? what would be the purpose? HookKind_Command, - - // Currently we are only basically allowing control over non-layouted windows. - // How can this be expanded? - // - Changing the layout algorithm: this seems like a decent one - // - What beside that? - HookKind_HandleWindowLayout, - - // Move the special window rendering to hooks? - // HookKind_RenderWindow, - // HookKind_CreateWindow, - - // How to handle formatters for multiple languages and other saving rules??? - HookKind_BeforeSavingBuffer, - HookKind_CreateBuffer, - // HookKind_BufferSave, - // HookKind_AfterBufferSave, - - // HookKind_ResolveOpen, - - // HookKind_BeforeBufferOpen, - // HookKind_BufferOpen, - // HookKind_AfterBufferOpen, - - HookKind_CreateView, - }; // Global hooks, per (window, view, buffer) hooks @@ -106,15 +75,15 @@ struct Buffer { int edit_phase; Array hooks; struct { - unsigned no_history : 1; - unsigned no_line_starts : 1; - unsigned dirty : 1; - unsigned changed_on_disk : 1; - unsigned temp : 1; - unsigned dont_try_to_save_in_bulk_ops : 1; - unsigned close : 1; - unsigned special : 1; - unsigned is_dir : 1; + uint32_t no_history : 1; + uint32_t no_line_starts : 1; + uint32_t dirty : 1; + uint32_t changed_on_disk : 1; + uint32_t temp : 1; + uint32_t dont_try_to_save_in_bulk_ops : 1; + uint32_t close : 1; + uint32_t special : 1; + uint32_t is_dir : 1; }; }; @@ -130,10 +99,11 @@ struct View { String hook_cmd; Array hooks; + Function *update_hook; uint64_t prev_search_line_hash; struct { - unsigned close : 1; - unsigned special : 1; + uint32_t close : 1; + uint32_t special : 1; }; }; diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp index bfbc0d6..ab9aaca 100644 --- a/src/text_editor/view.cpp +++ b/src/text_editor/view.cpp @@ -115,26 +115,6 @@ String16 FetchLoadWord(View *view) { return string; } -String16 FetchFuzzyViewLoadLine(View *view) { - Buffer *buffer = GetBuffer(view->active_buffer); - Range range = view->carets[0].range; - String16 string = GetString(buffer, range); - if (GetSize(range) == 0) { - Int line = PosToLine(buffer, range.min); - if (line == 0) { - line = ClampTop(1ll, buffer->line_starts.len - 1ll); - } - string = GetLineStringWithoutNL(buffer, line); - - Int idx = 0; - String16 delim = u"||>"; - if (Seek(string, delim, &idx, SeekFlag_None)) { - string = Skip(string, idx + delim.len); - } - } - return string; -} - char16_t GetIndentChar() { char16_t c = u' '; if (IndentKindWhichIsTabsOrSpaces == "spaces") { diff --git a/src/text_editor/window.cpp b/src/text_editor/window.cpp index 125e8cd..47930b4 100644 --- a/src/text_editor/window.cpp +++ b/src/text_editor/window.cpp @@ -138,19 +138,25 @@ void LayoutWindows(int16_t wx, int16_t wy) { ProfileFunction(); Rect2I screen_rect = RectI0Size(wx, wy); - ForItem (n, Windows) { - ForItem (hook, n->hooks) { - if (hook.kind == HookKind_HandleWindowLayout) { - ProfileScopeEx(it.name); - HookParam param = {}; - param.layout.window = n; - param.layout.rect = &screen_rect; - param.layout.wx = wx; - param.layout.wy = wy; - hook.function(param); - } - } - } +#ifdef PLUGIN_STATUS_WINDOW + LayoutStatusWindow(&screen_rect, wx, wy); +#endif + +#ifdef PLUGIN_SEARCH_WINDOW + LayoutSearchWindow(&screen_rect, wx, wy); +#endif + +#ifdef PLUGIN_COMMAND_WINDOW + LayoutCommandWindow(&screen_rect, wx, wy); +#endif + +#ifdef PLUGIN_DEBUG_WINDOW + LayoutDebugWindow(&screen_rect, wx, wy); +#endif + +#ifdef PLUGIN_BUILD_WINDOW + LayoutBuildWindow(&screen_rect, wx, wy); +#endif // Column layout Int c = 0;