hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-16 12:25:58 +0200
committerhachem <im@hachem.wtf>2026-09-16 12:25:58 +0200
commitabf4747f6c83350ec1c57db53cdda82b7aee5ec8 (patch)
tree7559becc40bcb2349b2960725311bb1fe27b2d0e
parent61ef54dd242d26f3668e306750e623d337978307 (diff)
feat: improve layout and themes
-rw-r--r--src/debugger/debugger.cpp39
-rw-r--r--src/debugger/debugger.h2
-rw-r--r--src/debugger/types.h5
-rw-r--r--src/ui/ui.cpp747
-rw-r--r--src/ui/ui.h19
5 files changed, 493 insertions, 319 deletions
diff --git a/src/debugger/debugger.cpp b/src/debugger/debugger.cpp
index 7091c57..b7ccfa6 100644
--- a/src/debugger/debugger.cpp
+++ b/src/debugger/debugger.cpp
@@ -534,6 +534,18 @@ namespace Hsdbg
lldb::SBDebugger::Terminate();
}
+ auto Debugger::backend_version() -> std::string
+ {
+ std::string banner = lldb::SBDebugger::GetVersionString();
+ if (const size_t cut = banner.find_first_of("(\n"); cut != std::string::npos)
+ banner.resize(cut);
+ if (banner.starts_with("lldb version "))
+ banner.erase(0, 13);
+ while (!banner.empty() && banner.back() == ' ')
+ banner.pop_back();
+ return banner;
+ }
+
auto Debugger::load_target(const std::filesystem::path& executable) -> Result<void>
{
std::error_code error;
@@ -2054,6 +2066,17 @@ namespace Hsdbg
frame.line = entry.GetLine();
}
+ if (lldb::SBFunction function = source.GetFunction(); function.IsValid())
+ {
+ if (lldb::SBLineEntry first = function.GetStartAddress().GetLineEntry(); first.IsValid())
+ frame.function_first_line = first.GetLine();
+
+ const lldb::addr_t end = function.GetEndAddress().GetLoadAddress(m_session->target);
+ lldb::SBAddress tail = m_session->target.ResolveLoadAddress(end - 1);
+ if (lldb::SBLineEntry last = tail.GetLineEntry(); last.IsValid())
+ frame.function_last_line = std::max(last.GetLine(), frame.line);
+ }
+
m_call_stack.push_back(std::move(frame));
}
}
@@ -2074,13 +2097,25 @@ namespace Hsdbg
return;
lldb::SBValueList variables = frame.GetVariables(true, true, false, true);
+ const std::filesystem::path frame_file = path_of(frame.GetLineEntry().GetFileSpec());
for (uint32_t index = 0; index < variables.GetSize(); ++index)
{
lldb::SBValue value = variables.GetValueAtIndex(index);
- if (value.IsValid())
- m_locals.push_back(to_variable(value, 0));
+ if (!value.IsValid())
+ continue;
+
+ Variable variable = to_variable(value, 0);
+
+ if (lldb::SBDeclaration declaration = value.GetDeclaration();
+ declaration.IsValid() && !frame_file.empty() &&
+ path_of(declaration.GetFileSpec()) == frame_file)
+ {
+ variable.declaration_line = declaration.GetLine();
+ }
+
+ m_locals.push_back(std::move(variable));
}
lldb::SBValueList sets = frame.GetRegisters();
diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h
index d487712..d7885b5 100644
--- a/src/debugger/debugger.h
+++ b/src/debugger/debugger.h
@@ -31,6 +31,8 @@ namespace Hsdbg
auto operator=(const Debugger&) -> Debugger& = delete;
auto operator=(Debugger&&) -> Debugger& = delete;
+ static auto backend_version() -> std::string;
+
auto load_target(const std::filesystem::path& executable) -> Result<void>;
auto unload_target() -> void;
auto launch(const LaunchSpec& spec) -> Result<void>;
diff --git a/src/debugger/types.h b/src/debugger/types.h
index 6f16562..7086384 100644
--- a/src/debugger/types.h
+++ b/src/debugger/types.h
@@ -104,6 +104,9 @@ namespace Hsdbg
std::string function;
std::filesystem::path file;
uint32_t line = 0;
+
+ uint32_t function_first_line = 0;
+ uint32_t function_last_line = 0;
};
struct Thread
@@ -119,6 +122,8 @@ namespace Hsdbg
std::string type;
std::string value;
std::vector<Variable> children;
+
+ uint32_t declaration_line = 0;
};
struct Register
diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp
index fa9c77c..48b8852 100644
--- a/src/ui/ui.cpp
+++ b/src/ui/ui.cpp
@@ -16,6 +16,8 @@
#include <algorithm>
#include <cctype>
#include <cfloat>
+#include <charconv>
+#include <cstdlib>
#include <filesystem>
#include <format>
#include <optional>
@@ -53,8 +55,6 @@ namespace Hsdbg
const ImVec2 ROOT_PADDING(8.0f, 6.0f);
const ImVec2 TOOLBAR_PADDING(4.0f, 4.0f);
- const ImU32 CURRENT_INSTRUCTION_COLOR = IM_COL32(58, 72, 46, 255);
-
constexpr float BREAKPOINT_RADIUS = 5.0f;
constexpr float DISASSEMBLY_GUTTER_WIDTH = 22.0f;
@@ -62,8 +62,19 @@ namespace Hsdbg
const ImU32 BREAKPOINT_DISABLED_COLOR = IM_COL32(120, 90, 90, 255);
const ImU32 BREAKPOINT_HOVER_COLOR = IM_COL32(226, 84, 84, 90);
- const ImU32 SOURCE_FOLDER_COLOR = IM_COL32(150, 178, 214, 255);
- const ImU32 SOURCE_OPEN_FILE_COLOR = IM_COL32(126, 194, 126, 255);
+ auto source_folder_color() -> ImU32
+ {
+ const ImVec4 text = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ const ImVec4 accent = Widgets::accent();
+ return ImGui::GetColorU32(ImVec4(text.x + (accent.x - text.x) * 0.55f,
+ text.y + (accent.y - text.y) * 0.55f,
+ text.z + (accent.z - text.z) * 0.55f, 1.0f));
+ }
+
+ auto source_open_file_color() -> ImU32
+ {
+ return ImGui::GetColorU32(Widgets::accent());
+ }
const ImU32 SOURCE_TREE_LINE_COLOR = IM_COL32(110, 110, 122, 160);
@@ -427,7 +438,7 @@ namespace Hsdbg
auto draw_instruction_table(Debugger& debugger,
std::span<const Instruction> instructions,
- bool scroll_to_current) -> void
+ bool scroll_to_current, ImU32 current_row_color) -> void
{
constexpr ImGuiTableFlags flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
@@ -480,9 +491,7 @@ namespace Hsdbg
ImGui::PushID(index);
if (instruction.current)
- {
- ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, CURRENT_INSTRUCTION_COLOR);
- }
+ ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, current_row_color);
ImGui::TableNextColumn();
@@ -491,6 +500,11 @@ namespace Hsdbg
if (ImGui::InvisibleButton("##gutter", ImVec2(DISASSEMBLY_GUTTER_WIDTH, row_height)))
toggle_instruction_breakpoint(debugger, instruction);
+ if (instruction.current)
+ draw_list->AddRectFilled(gutter_min,
+ ImVec2(gutter_min.x + 3.0f, gutter_min.y + row_height),
+ ImGui::ColorConvertFloat4ToU32(Widgets::accent()));
+
const bool gutter_hovered = ImGui::IsItemHovered();
const ImVec2 marker_center(gutter_min.x + DISASSEMBLY_GUTTER_WIDTH * 0.5f,
gutter_min.y + row_height * 0.5f);
@@ -544,40 +558,127 @@ namespace Hsdbg
}
}
- auto draw_variable(const Variable& variable) -> void
+ auto pack_color(const float (&c)[3]) -> ImU32
{
- ImGui::TableNextRow();
- ImGui::TableNextColumn();
+ return IM_COL32(static_cast<int>(c[0] * 255.0f), static_cast<int>(c[1] * 255.0f),
+ static_cast<int>(c[2] * 255.0f), 255);
+ }
- if (variable.children.empty())
- {
- ImGui::TreeNodeEx(variable.name.c_str(),
- ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
- ImGuiTreeNodeFlags_SpanFullWidth);
+ struct ValueStyle
+ {
+ ImU32 number = 0;
+ ImU32 string = 0;
+ ImU32 keyword = 0;
+ ImU32 changed = 0;
+ };
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(variable.type.c_str());
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(variable.value.c_str());
+ auto value_style(const Preferences& preferences) -> ValueStyle
+ {
+ return ValueStyle{ pack_color(preferences.color_number), pack_color(preferences.color_string),
+ pack_color(preferences.color_keyword),
+ ImGui::ColorConvertFloat4ToU32(Widgets::accent()) };
+ }
- return;
+ auto value_color(std::string_view value, const ValueStyle& style) -> std::optional<ImU32>
+ {
+ if (value.empty())
+ return std::nullopt;
+
+ const char first = value.front();
+ if (first == '"' || first == '\'')
+ return style.string;
+
+ if (value == "true" || value == "false" || value == "nullptr" || value == "NULL")
+ return style.keyword;
+
+ const bool digit = std::isdigit(static_cast<unsigned char>(first)) != 0;
+ const bool signed_digit = (first == '-' || first == '.') && value.size() > 1 &&
+ std::isdigit(static_cast<unsigned char>(value[1])) != 0;
+ if (digit || signed_digit)
+ return style.number;
+
+ return std::nullopt;
+ }
+
+ auto draw_value(const std::string& value, const ValueStyle& style, bool changed) -> void
+ {
+ const std::optional<ImU32> colour = changed ? std::optional<ImU32>(style.changed)
+ : value_color(value, style);
+ if (colour)
+ ImGui::PushStyleColor(ImGuiCol_Text, *colour);
+ ImGui::TextUnformatted(value.c_str());
+ if (colour)
+ ImGui::PopStyleColor();
+ }
+
+ using ValueSnapshot = std::unordered_map<std::string, std::string>;
+
+ auto flatten_variables(std::span<const Variable> variables, const std::string& prefix,
+ ValueSnapshot& out) -> void
+ {
+ for (const Variable& variable : variables)
+ {
+ const std::string path = prefix.empty() ? variable.name : prefix + "." + variable.name;
+ out[path] = variable.value;
+ flatten_variables(variable.children, path, out);
}
+ }
+
+ auto draw_variable(const Variable& variable, const std::string& path, const ValueStyle& style,
+ const ValueSnapshot* previous) -> void
+ {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
- const bool open = ImGui::TreeNodeEx(variable.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
+ const bool leaf = variable.children.empty();
+ const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth |
+ (leaf ? ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen : 0);
+ const bool open = ImGui::TreeNodeEx(variable.name.c_str(), flags);
ImGui::TableNextColumn();
- ImGui::TextUnformatted(variable.type.c_str());
+ ImGui::TextDisabled("%s", variable.type.c_str());
+
ImGui::TableNextColumn();
- ImGui::TextUnformatted(variable.value.c_str());
+ const std::string* before = nullptr;
+ if (previous != nullptr)
+ {
+ if (const auto found = previous->find(path); found != previous->end())
+ before = &found->second;
+ }
+ const bool changed = before != nullptr && *before != variable.value;
+ draw_value(variable.value, style, changed);
- if (!open)
+ if (changed && ImGui::IsItemHovered())
+ ImGui::SetTooltip("Was %s", before->c_str());
+
+ if (leaf || !open)
return;
for (const Variable& child : variable.children)
- draw_variable(child);
+ draw_variable(child, path + "." + child.name, style, previous);
ImGui::TreePop();
}
+
+ auto file_icon(const std::filesystem::path& path) -> const char*
+ {
+ std::string ext = path.extension().string();
+ std::ranges::transform(ext, ext.begin(), [](unsigned char c)
+ {
+ return static_cast<char>(std::tolower(c));
+ });
+
+ if (ext == ".c")
+ return ICON_PH_FILE_C;
+ if (ext == ".rs")
+ return ICON_PH_FILE_RS;
+ if (ext == ".cpp" || ext == ".cc" || ext == ".cxx" || ext == ".c++" || ext == ".h" ||
+ ext == ".hpp" || ext == ".hh" || ext == ".hxx" || ext == ".inl" || ext == ".ipp" ||
+ ext == ".m" || ext == ".mm")
+ return ICON_PH_FILE_CPP;
+
+ return ICON_PH_FILE;
+ }
}
Ui::Ui(Window& window)
@@ -593,6 +694,7 @@ namespace Hsdbg
load_fonts();
m_source_view.set_mono_font(m_font_mono);
+ m_source_view.set_changed_locals(&m_changed_locals);
m_layout_built = io.IniFilename != nullptr && std::filesystem::exists(io.IniFilename);
@@ -723,6 +825,48 @@ namespace Hsdbg
}
}
+ auto Ui::snapshot_values(const Debugger& debugger) -> void
+ {
+ if (!debugger.is_stopped())
+ return;
+
+ const bool same_stop = debugger.stop_count() == m_snapshot_stop;
+ const bool same_place = debugger.selected_thread() == m_snapshot_thread &&
+ debugger.selected_frame() == m_snapshot_frame;
+ if (same_stop && same_place)
+ return;
+
+ const std::span<const StackFrame> stack = debugger.call_stack();
+ const auto frame = std::ranges::find(stack, debugger.selected_frame(), &StackFrame::index);
+ std::string function = frame != stack.end() ? frame->function : std::string{};
+
+ m_snapshot_comparable = !same_stop && same_place && function == m_snapshot_function;
+ m_snapshot_stop = debugger.stop_count();
+ m_snapshot_thread = debugger.selected_thread();
+ m_snapshot_frame = debugger.selected_frame();
+ m_snapshot_function = std::move(function);
+
+ m_prev_locals = std::move(m_curr_locals);
+ m_curr_locals.clear();
+ flatten_variables(debugger.locals(), "", m_curr_locals);
+
+ m_changed_locals.clear();
+ if (m_snapshot_comparable)
+ {
+ for (const auto& [name, value] : m_curr_locals)
+ {
+ const auto before = m_prev_locals.find(name);
+ if (before != m_prev_locals.end() && before->second != value)
+ m_changed_locals.insert(name);
+ }
+ }
+
+ m_prev_registers = std::move(m_curr_registers);
+ m_curr_registers.clear();
+ for (const Register& entry : debugger.registers())
+ m_curr_registers[entry.name] = entry.value;
+ }
+
auto Ui::show_frame(const StackFrame& frame) -> void
{
if (frame.file.empty() || frame.line == 0)
@@ -732,6 +876,7 @@ namespace Hsdbg
open_source(frame.file);
m_source_view.set_highlighted_line(frame.line);
+ m_source_view.set_frame_extent(frame.function_first_line, frame.function_last_line);
}
auto Ui::draw(Debugger& debugger) -> void
@@ -765,11 +910,24 @@ namespace Hsdbg
ImGui::PopStyleColor();
ImGui::PopStyleVar(3);
+ static const char* dev_open = std::getenv("HSDBG_OPEN");
+ if (dev_open != nullptr && ImGui::GetFrameCount() == 2)
+ {
+ const std::string_view which(dev_open);
+ m_palette_request = which == "palette";
+ m_show_preferences = which.starts_with("preferences");
+ m_load_target_pending = which == "target";
+ if (const size_t colon = which.find(':'); colon != std::string_view::npos)
+ std::from_chars(which.data() + colon + 1, which.data() + which.size(), m_preferences_tab);
+ }
+
draw_menu_bar(debugger);
draw_toolbar(debugger);
+ handle_shortcuts(debugger);
follow_stop(debugger);
follow_target(debugger);
+ snapshot_values(debugger);
const ImGuiID dockspace_id = ImGui::GetID("##hsdbg_dockspace");
const float status_bar_height = ImGui::GetTextLineHeight() +
@@ -778,7 +936,8 @@ namespace Hsdbg
ImGui::DockSpace(dockspace_id,
ImVec2(0.0f, ImGui::GetContentRegionAvail().y - status_bar_height),
ImGuiDockNodeFlags_PassthruCentralNode |
- static_cast<int>(ImGuiDockNodeFlags_NoWindowMenuButton));
+ static_cast<int>(ImGuiDockNodeFlags_NoWindowMenuButton) |
+ static_cast<int>(ImGuiDockNodeFlags_NoCloseButton));
if (!m_layout_built)
{
@@ -794,6 +953,12 @@ namespace Hsdbg
draw_source_panel(debugger);
+ if (m_source_view.font_size() != m_preferences.source_font_size)
+ {
+ m_preferences.source_font_size = m_source_view.font_size();
+ save_preferences(m_preferences_path, m_preferences);
+ }
+
if (std::optional<std::string> request = m_source_view.take_macro_request())
{
m_macro_input = std::move(*request);
@@ -1050,37 +1215,49 @@ namespace Hsdbg
const auto gap = [] { ImGui::SameLine(0.0f, 14.0f); };
- if (Widgets::icon_button("run", ICON_PH_PLAY, has_target && !running, "Run",
- !has_target || running, control))
- {
- LaunchSpec spec;
- spec.executable = debugger.target_path();
- spec.stop_at_entry = m_preferences.stop_at_entry;
+ const bool stopped = debugger.is_stopped();
+ const bool alive = running || stopped;
+ const ImVec4 go = state_color(TargetState::Running);
+ const ImVec4 hold = state_color(TargetState::Stopped);
+ const ImVec4 kill = state_color(TargetState::Crashed);
- report(debugger.launch(spec), "run");
+ if (Widgets::icon_button("run", ICON_PH_PLAY, false, stopped ? "Continue (F5)" : "Run (F5)",
+ !has_target || running, control, go))
+ {
+ if (stopped)
+ {
+ report(debugger.resume(), "continue");
+ }
+ else
+ {
+ LaunchSpec spec;
+ spec.executable = debugger.target_path();
+ spec.stop_at_entry = m_preferences.stop_at_entry;
+ report(debugger.launch(spec), "run");
+ }
}
ImGui::SameLine();
- if (Widgets::icon_button("pause", ICON_PH_PAUSE, false, "Pause", !running, control))
+ if (Widgets::icon_button("pause", ICON_PH_PAUSE, false, "Pause", !running, control, hold))
report(debugger.pause(), "pause");
ImGui::SameLine();
- if (Widgets::icon_button("stop", ICON_PH_STOP, false, "Stop", !has_target, control))
+ if (Widgets::icon_button("stop", ICON_PH_STOP, false, "Stop", !alive, control, kill))
report(debugger.terminate(), "stop");
gap();
- if (Widgets::icon_button("step_over", ICON_PH_STEP_OVER, false, "Step Over",
- !has_target, control))
+ if (Widgets::icon_button("step_over", ICON_PH_STEP_OVER, false, "Step Over (F10)",
+ !stopped, control))
report(debugger.step_over(step_mode), "Step Over");
ImGui::SameLine();
- if (Widgets::icon_button("step_into", ICON_PH_STEP_INTO, false, "Step Into",
- !has_target, control))
+ if (Widgets::icon_button("step_into", ICON_PH_STEP_INTO, false, "Step Into (F11)",
+ !stopped, control))
report(debugger.step_into(step_mode), "Step Into");
ImGui::SameLine();
- if (Widgets::icon_button("step_out", ICON_PH_STEP_OUT, false, "Step Out",
- !has_target, control))
+ if (Widgets::icon_button("step_out", ICON_PH_STEP_OUT, false, "Step Out (Shift+F11)",
+ !stopped, control))
report(debugger.step_out(), "Step Out");
gap();
@@ -1123,27 +1300,117 @@ namespace Hsdbg
ImGui::PopStyleVar(2);
}
+ auto Ui::handle_shortcuts(Debugger& debugger) -> void
+ {
+ if (!debugger.has_target())
+ return;
+
+ const StepMode step_mode = m_preferences.step_by_instruction ? StepMode::Instruction : StepMode::Line;
+
+ if (ImGui::Shortcut(ImGuiKey_F5, ImGuiInputFlags_RouteGlobal))
+ {
+ if (debugger.is_stopped())
+ {
+ report(debugger.resume(), "continue");
+ }
+ else if (!debugger.is_running())
+ {
+ LaunchSpec spec;
+ spec.executable = debugger.target_path();
+ spec.stop_at_entry = m_preferences.stop_at_entry;
+ report(debugger.launch(spec), "run");
+ }
+ }
+
+ if (!debugger.is_stopped())
+ return;
+
+ if (ImGui::Shortcut(ImGuiKey_F10, ImGuiInputFlags_RouteGlobal))
+ report(debugger.step_over(step_mode), "Step Over");
+ if (ImGui::Shortcut(ImGuiKey_F11, ImGuiInputFlags_RouteGlobal))
+ report(debugger.step_into(step_mode), "Step Into");
+ if (ImGui::Shortcut(ImGuiMod_Shift | ImGuiKey_F11, ImGuiInputFlags_RouteGlobal))
+ report(debugger.step_out(), "Step Out");
+ }
+
auto Ui::draw_status_bar(const Debugger& debugger) -> void
{
ImGui::Separator();
+ const auto sep = []
+ {
+ ImGui::SameLine(0.0f, 10.0f);
+ ImGui::TextDisabled("ยท");
+ ImGui::SameLine(0.0f, 10.0f);
+ };
+
+ const auto item = [](const char* icon, const std::string& text)
+ {
+ ImGui::TextDisabled("%s", icon);
+ ImGui::SameLine(0.0f, 5.0f);
+ ImGui::TextUnformatted(text.c_str());
+ };
+
ImGui::AlignTextToFramePadding();
- ImGui::TextColored(state_color(debugger.state()), "%s", to_string(debugger.state()).data());
- ImGui::SameLine();
- ImGui::TextDisabled("|");
- ImGui::SameLine();
+ {
+ const ImVec4 colour = state_color(debugger.state());
+ const ImVec2 p = ImGui::GetCursorScreenPos();
+ const float cy = p.y + ImGui::GetStyle().FramePadding.y + ImGui::GetTextLineHeight() * 0.5f;
+ ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(p.x + 4.0f, cy), 3.5f,
+ ImGui::ColorConvertFloat4ToU32(colour));
+ ImGui::Dummy(ImVec2(8.0f, ImGui::GetTextLineHeight()));
+ ImGui::SameLine(0.0f, 6.0f);
+ ImGui::TextColored(colour, "%s", to_string(debugger.state()).data());
+
+ if (debugger.is_stopped() && debugger.stop_reason() != StopReason::None)
+ {
+ ImGui::SameLine(0.0f, 5.0f);
+ ImGui::TextDisabled("(%s)", to_string(debugger.stop_reason()).data());
+ }
+ }
if (debugger.has_target())
- ImGui::Text("%s", debugger.target_path().filename().string().c_str());
- else
- ImGui::TextDisabled("No target");
+ {
+ sep();
+ item(ICON_PH_TARGET, debugger.target_path().filename().string());
- ImGui::SameLine();
- ImGui::TextDisabled("|");
- ImGui::SameLine();
+ if (debugger.process_id() != 0 && (debugger.is_running() || debugger.is_stopped()))
+ {
+ ImGui::SameLine(0.0f, 8.0f);
+ ImGui::TextDisabled(ICON_PH_HASH " %llu", static_cast<unsigned long long>(debugger.process_id()));
+ }
+ }
- ImGui::Text("%zu breakpoints", debugger.breakpoints().size());
+ if (debugger.is_stopped())
+ {
+ const std::span<const StackFrame> stack = debugger.call_stack();
+ const auto frame = std::ranges::find(stack, debugger.selected_frame(), &StackFrame::index);
+
+ if (frame != stack.end())
+ {
+ sep();
+ item(ICON_PH_MAP_PIN, frame->function);
+
+ if (frame->line != 0)
+ {
+ ImGui::SameLine(0.0f, 8.0f);
+ ImGui::PushFont(m_font_mono, 0.0f);
+ ImGui::TextDisabled("%s:%u", frame->file.filename().string().c_str(), frame->line);
+ ImGui::PopFont();
+ }
+ }
+
+ if (const size_t threads = debugger.threads().size(); threads > 1)
+ {
+ sep();
+ item(ICON_PH_BRANCH, std::format("{} threads", threads));
+ }
+ }
+
+ sep();
+ const size_t breakpoints = debugger.breakpoints().size();
+ item(ICON_PH_CIRCLE, std::format("{} breakpoint{}", breakpoints, breakpoints == 1 ? "" : "s"));
if (m_preferences.show_fps)
{
@@ -1170,16 +1437,23 @@ namespace Hsdbg
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
- if (!ImGui::BeginPopupModal(LOAD_TARGET_POPUP, nullptr, ImGuiWindowFlags_AlwaysAutoResize))
+ if (!ImGui::BeginPopupModal(LOAD_TARGET_POPUP, nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar))
return;
+ ImGui::PushFont(m_font_strong, 0.0f);
+ ImGui::TextUnformatted(ICON_PH_TARGET " Load target");
+ ImGui::PopFont();
ImGui::TextDisabled("Path to an executable to debug");
+ ImGui::Spacing();
- ImGui::SetNextItemWidth(420.0f);
+ ImGui::SetNextItemWidth(460.0f);
+ if (ImGui::IsWindowAppearing())
+ ImGui::SetKeyboardFocusHere();
- const bool submitted = ImGui::InputText("##target_path",
- &m_target_input,
- ImGuiInputTextFlags_EnterReturnsTrue);
+ const bool submitted = Widgets::input_with_icon("##target_path", ICON_PH_FILE, "/path/to/a.out",
+ &m_target_input,
+ ImGuiInputTextFlags_EnterReturnsTrue);
if (ImGui::Button("Load") || submitted)
{
@@ -1217,6 +1491,7 @@ namespace Hsdbg
m_source_view.set_highlighting(m_preferences.syntax_highlighting);
m_source_view.set_line_numbers(m_preferences.show_line_numbers);
m_source_view.set_highlight_current_line(m_preferences.highlight_current_line);
+ m_source_view.set_font_size(m_preferences.source_font_size);
const auto pack = [](const float c[3]) {
return IM_COL32(static_cast<int>(c[0] * 255.0f), static_cast<int>(c[1] * 255.0f),
@@ -1237,223 +1512,6 @@ namespace Hsdbg
}
}
- auto Ui::draw_preferences_window() -> void
- {
- if (!m_show_preferences)
- {
- m_prefs_open_prev = false;
- return;
- }
-
- if (!m_prefs_open_prev)
- m_themes = list_themes(themes_directory());
- m_prefs_open_prev = true;
-
- const ImGuiViewport* viewport = ImGui::GetMainViewport();
- const ImVec2 center(viewport->WorkPos.x + viewport->WorkSize.x * 0.5f,
- viewport->WorkPos.y + viewport->WorkSize.y * 0.5f);
-
- ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
- ImGui::SetNextWindowSize(ImVec2(560.0f, 380.0f), ImGuiCond_Appearing);
- ImGui::SetNextWindowSizeConstraints(ImVec2(460.0f, 300.0f), ImVec2(FLT_MAX, FLT_MAX));
-
- if (!ImGui::Begin("Preferences###preferences", &m_show_preferences, ImGuiWindowFlags_NoDocking))
- {
- ImGui::End();
- return;
- }
-
- static constexpr const char* CATEGORIES[] = { "Appearance", "Editor", "Debugger" };
- static constexpr const char* CATEGORY_ICONS[] = {
- ICON_PH_SLIDERS, ICON_PH_FILE_CODE, ICON_PH_BUG
- };
- bool changed = false;
- bool restyle = false;
-
- const float footer = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y;
-
- ImGui::BeginChild("##pref_categories", ImVec2(158.0f, -footer), ImGuiChildFlags_Borders);
- for (int index = 0; index < IM_ARRAYSIZE(CATEGORIES); ++index)
- {
- const std::string label = std::string(CATEGORY_ICONS[index]) + " " + CATEGORIES[index];
- if (Widgets::selectable_row(label.c_str(), m_preferences_tab == index))
- m_preferences_tab = index;
- }
- ImGui::EndChild();
-
- ImGui::SameLine();
-
- ImGui::BeginChild("##pref_content", ImVec2(0.0f, -footer));
- ImGui::PushItemWidth(-150.0f);
-
- const auto help = [](const char* text) {
- ImGui::SameLine();
- ImGui::TextDisabled("(?)");
- if (ImGui::BeginItemTooltip())
- {
- ImGui::PushTextWrapPos(ImGui::GetFontSize() * 20.0f);
- ImGui::TextUnformatted(text);
- ImGui::PopTextWrapPos();
- ImGui::EndTooltip();
- }
- };
-
- const auto accent_swatch = [&](const char* label, float (&value)[3]) {
- if (ImGui::ColorEdit3(label, value,
- ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha))
- changed = true;
- };
-
- const auto toggle_row = [&](const char* label, bool* value, const char* help_text = nullptr) {
- const float toggle_h = ImGui::GetFrameHeight() * 0.78f;
- const float toggle_w = toggle_h * 1.8f;
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted(label);
- if (help_text != nullptr)
- help(help_text);
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - toggle_w);
- ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::GetFrameHeight() - toggle_h) * 0.5f);
- if (Widgets::toggle(label, value))
- changed = true;
- };
-
- if (m_preferences_tab == 0)
- {
- Widgets::section_header("theme");
-
- const auto swatch = [](ImVec4 colour) {
- ImGui::ColorButton("##sw", colour,
- ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop,
- ImVec2(14.0f, 14.0f));
- ImGui::SameLine(0.0f, 4.0f);
- };
- swatch(m_theme.bg);
- swatch(m_theme.bg_high);
- swatch(m_theme.surface);
- swatch(m_theme.text);
- swatch(ImVec4(m_preferences.accent[0], m_preferences.accent[1],
- m_preferences.accent[2], 1.0f));
- ImGui::NewLine();
-
- const char* current = m_preferences.theme.c_str();
- const auto named = std::ranges::find(m_themes, m_preferences.theme, &ThemeEntry::id);
- if (named != m_themes.end())
- current = named->name.c_str();
-
- if (ImGui::BeginCombo("Theme", current))
- {
- for (const auto& entry : m_themes)
- {
- const bool selected = entry.id == m_preferences.theme;
- if (ImGui::Selectable(entry.name.c_str(), selected))
- select_theme(entry);
- if (selected)
- ImGui::SetItemDefaultFocus();
- }
- ImGui::EndCombo();
- }
- help("Themes live in assets/themes as .toml files. drop a new one in, reopen this "
- "window, and it appears here. picking one resets the tweaks below to its values.");
-
- Widgets::section_header("interface");
- changed |= Widgets::slider_float("UI scale", &m_preferences.ui_scale, 0.75f, 2.0f, "%.2fx");
- help("Scales every font. the text stays crisp because it is re-rasterized, not stretched.");
-
- if (ImGui::ColorEdit3("Accent colour", m_preferences.accent,
- ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha))
- {
- changed = true;
- restyle = true;
- }
-
- if (Widgets::slider_float("Corner rounding", &m_preferences.rounding, 0.0f, 12.0f, "%.0f px"))
- {
- changed = true;
- restyle = true;
- }
-
- toggle_row("Show FPS in the status bar", &m_preferences.show_fps);
-
- Widgets::section_header("layout");
- if (ImGui::Button("Reset window layout"))
- m_layout_built = false;
- help("Restores the default arrangement of all the docked panels.");
-
- Widgets::section_header("mascot");
- toggle_row("Show the crying pepe", &m_preferences.show_mascot);
-
- ImGui::BeginDisabled(!m_preferences.show_mascot);
- changed |= Widgets::slider_float("Pepe size", &m_preferences.mascot_scale, 1.0f, 3.0f, "%.1fx");
- ImGui::EndDisabled();
- }
- else if (m_preferences_tab == 1)
- {
- Widgets::section_header("source view");
- toggle_row("Syntax highlighting", &m_preferences.syntax_highlighting);
- toggle_row("Show line numbers", &m_preferences.show_line_numbers);
- toggle_row("Highlight the current line", &m_preferences.highlight_current_line);
-
- Widgets::section_header("colours");
- ImGui::BeginDisabled(!m_preferences.syntax_highlighting);
- accent_swatch("Keyword", m_preferences.color_keyword);
- accent_swatch("Type", m_preferences.color_type);
- accent_swatch("String", m_preferences.color_string);
- accent_swatch("Number", m_preferences.color_number);
- accent_swatch("Comment", m_preferences.color_comment);
- accent_swatch("Preprocessor", m_preferences.color_preprocessor);
- ImGui::EndDisabled();
- accent_swatch("Current line", m_preferences.color_current_line);
- }
- else if (m_preferences_tab == 2)
- {
- Widgets::section_header("launching");
- toggle_row("Break at entry point on launch", &m_preferences.stop_at_entry,
- "stop on the very first instruction instead of running to your breakpoints.");
-
- Widgets::section_header("stepping");
- {
- static const char* const step_modes[] = { "Line", "Instruction" };
- const int current_mode = m_preferences.step_by_instruction ? 1 : 0;
- const int next_mode = Widgets::segmented("##step_mode", step_modes, 2, current_mode);
- ImGui::SameLine();
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted("Step granularity");
- help("The step over/into buttons advance a whole source line, or a single "
- "machine instruction.");
- if (next_mode != current_mode)
- {
- m_preferences.step_by_instruction = (next_mode == 1);
- changed = true;
- }
- }
- }
-
- ImGui::PopItemWidth();
- ImGui::EndChild();
-
- ImGui::Separator();
-
- if (ImGui::Button("Reset to defaults"))
- {
- m_preferences = Preferences{};
- load_selected_theme(false);
- changed = true;
- restyle = true;
- }
-
- ImGui::SameLine();
- ImGui::TextDisabled("Saved to %s", m_preferences_path.filename().string().c_str());
-
- if (changed)
- save_preferences(m_preferences_path, m_preferences);
-
- if (restyle)
- m_restyle_pending = true;
-
- ImGui::End();
- }
-
auto Ui::draw_source_panel(Debugger& debugger) -> void
{
if (!m_visible.source)
@@ -1504,7 +1562,8 @@ namespace Hsdbg
ImGui::TableNextColumn();
bool enabled = breakpoint.enabled;
- if (ImGui::Checkbox("##enabled", &enabled))
+ if (Widgets::dot_toggle("##enabled", &enabled, BREAKPOINT_COLOR,
+ BREAKPOINT_DISABLED_COLOR, !breakpoint.resolved))
debugger.set_breakpoint_enabled(breakpoint.id, enabled);
ImGui::TableNextColumn();
@@ -1521,12 +1580,7 @@ namespace Hsdbg
const std::string location = std::format(
"{}:{}", breakpoint.file.filename().string(), breakpoint.line);
- ImGui::TextUnformatted(location.c_str());
-
- if (ImGui::IsItemHovered())
- ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
-
- if (ImGui::IsItemClicked() && !breakpoint.file.empty())
+ if (Widgets::link_text(location.c_str()) && !breakpoint.file.empty())
{
open_source(breakpoint.file);
m_source_view.set_highlighted_line(breakpoint.line);
@@ -1692,7 +1746,7 @@ namespace Hsdbg
else
{
ImGui::SetNextItemWidth(-1.0f);
- ImGui::InputTextWithHint("##source_filter", "Filter files", &m_source_filter);
+ Widgets::input_with_icon("##source_filter", ICON_PH_SEARCH, "Filter files", &m_source_filter);
const SourceNode tree = build_source_tree(files);
const std::filesystem::path& open = m_source_view.path();
@@ -1711,10 +1765,10 @@ namespace Hsdbg
const bool selected = node.path == open;
if (selected)
- ImGui::PushStyleColor(ImGuiCol_Text, SOURCE_OPEN_FILE_COLOR);
+ ImGui::PushStyleColor(ImGuiCol_Text, source_open_file_color());
const bool clicked = Widgets::selectable_row(
- (std::string(ICON_PH_FILE " ") + node.name).c_str(), selected);
+ (std::string(file_icon(node.path)) + " " + node.name).c_str(), selected);
if (selected)
ImGui::PopStyleColor();
@@ -1740,7 +1794,7 @@ namespace Hsdbg
else if (reveal && node_contains(node, open))
ImGui::SetNextItemOpen(true, ImGuiCond_Always);
- ImGui::PushStyleColor(ImGuiCol_Text, SOURCE_FOLDER_COLOR);
+ ImGui::PushStyleColor(ImGuiCol_Text, source_folder_color());
const bool open_node = ImGui::TreeNodeEx(
(std::string(ICON_PH_FOLDER " ") + node.name).c_str(), flags);
ImGui::PopStyleColor();
@@ -1812,9 +1866,12 @@ namespace Hsdbg
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
+ const ValueStyle style = value_style(m_preferences);
+ const ValueSnapshot* previous = m_snapshot_comparable ? &m_prev_locals : nullptr;
+
ImGui::PushFont(m_font_mono, 0.0f);
for (const Variable& variable : debugger.locals())
- draw_variable(variable);
+ draw_variable(variable, variable.name, style, previous);
ImGui::PopFont();
ImGui::EndTable();
@@ -1878,6 +1935,9 @@ namespace Hsdbg
for (Watch& watch : m_watches)
{
+ const bool was_ok = watch.ok;
+ const std::string before = watch.value;
+
if (const auto result = debugger.evaluate(watch.expression))
{
watch.value = *result;
@@ -1888,6 +1948,8 @@ namespace Hsdbg
watch.value = result.error();
watch.ok = false;
}
+
+ watch.changed = was_ok && watch.ok && before != watch.value;
}
}
@@ -1895,8 +1957,8 @@ namespace Hsdbg
m_watch_evaluated = false;
ImGui::SetNextItemWidth(-60.0f);
- const bool submitted = ImGui::InputTextWithHint("##watch_input", "Expression to watch",
- &m_watch_input,
+ const bool submitted = Widgets::input_with_icon("##watch_input", ICON_PH_PLUS,
+ "Expression to watch", &m_watch_input,
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::SameLine();
@@ -1910,7 +1972,7 @@ namespace Hsdbg
if (m_watches.empty())
{
- ImGui::TextDisabled("Watch an expression; it re-evaluates on every stop");
+ Widgets::empty_state(ICON_PH_EYE, "Watch an expression; it re-evaluates on every stop");
}
else
{
@@ -1925,6 +1987,8 @@ namespace Hsdbg
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
+ const ValueStyle style = value_style(m_preferences);
+
ImGui::PushFont(m_font_mono, 0.0f);
size_t remove_index = m_watches.size();
@@ -1942,7 +2006,7 @@ namespace Hsdbg
if (!m_watch_evaluated && !stopped)
ImGui::TextDisabled("-");
else if (watch.ok)
- ImGui::TextUnformatted(watch.value.c_str());
+ draw_value(watch.value, style, watch.changed);
else
ImGui::TextColored(ImVec4(0.85f, 0.45f, 0.45f, 1.0f), "%s",
watch.value.c_str());
@@ -1989,6 +2053,8 @@ namespace Hsdbg
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
+ const ImU32 changed_colour = ImGui::ColorConvertFloat4ToU32(Widgets::accent());
+
ImGui::PushFont(m_font_mono, 0.0f);
for (const Register& entry : debugger.registers())
{
@@ -1996,7 +2062,19 @@ namespace Hsdbg
ImGui::TableNextColumn();
ImGui::TextUnformatted(entry.name.c_str());
ImGui::TableNextColumn();
- ImGui::Text("0x%016llx", static_cast<unsigned long long>(entry.value));
+
+ const auto before = m_prev_registers.find(entry.name);
+ const bool changed = m_snapshot_comparable && before != m_prev_registers.end() &&
+ before->second != entry.value;
+
+ if (changed)
+ ImGui::PushStyleColor(ImGuiCol_Text, changed_colour);
+ Widgets::hex_value(entry.value, 16);
+ if (changed)
+ ImGui::PopStyleColor();
+
+ if (changed && ImGui::IsItemHovered())
+ ImGui::SetTooltip("Was 0x%016llx", static_cast<unsigned long long>(before->second));
}
ImGui::PopFont();
@@ -2024,7 +2102,7 @@ namespace Hsdbg
else
{
ImGui::SetNextItemWidth(-1.0f);
- ImGui::InputTextWithHint("##symbol_filter", "Filter symbols", &m_symbol_filter);
+ Widgets::input_with_icon("##symbol_filter", ICON_PH_SEARCH, "Filter symbols", &m_symbol_filter);
std::vector<size_t> visible;
@@ -2125,10 +2203,17 @@ namespace Hsdbg
else
{
if (!debugger.disassembly_name().empty())
- ImGui::TextUnformatted(debugger.disassembly_name().data());
+ {
+ ImGui::PushFont(m_font_strong, 0.0f);
+ ImGui::Text(ICON_PH_FUNCTION " %s", debugger.disassembly_name().data());
+ ImGui::PopFont();
+ ImGui::SameLine();
+ ImGui::TextDisabled("%zu instructions", instructions.size());
+ }
ImGui::PushFont(m_font_mono, 0.0f);
- draw_instruction_table(debugger, instructions, m_scroll_to_program_counter);
+ draw_instruction_table(debugger, instructions, m_scroll_to_program_counter,
+ pack_color(m_preferences.color_current_line));
ImGui::PopFont();
m_scroll_to_program_counter = false;
}
@@ -2148,12 +2233,25 @@ namespace Hsdbg
if (ImGui::BeginChild("##console_output", ImVec2(0.0f, -input_height)))
{
+ if (debugger.console_output().empty() && m_console_lines.empty())
+ Widgets::empty_state(ICON_PH_TERMINAL, "Target output lands here; type C or C++ below to run it in the target");
+
ImGui::PushFont(m_font_mono, 0.0f);
for (const std::string& line : debugger.console_output())
draw_ansi_line(line);
+ const ImU32 echo_colour = ImGui::ColorConvertFloat4ToU32(Widgets::accent());
for (const std::string& line : m_console_lines)
+ {
+ const bool echo = line.starts_with("> ");
+ const bool failure = line.starts_with("error") || line.contains(" failed: ");
+
+ if (echo || failure)
+ ImGui::PushStyleColor(ImGuiCol_Text, echo ? echo_colour : IM_COL32(232, 106, 100, 255));
draw_ansi_line(line);
+ if (echo || failure)
+ ImGui::PopStyleColor();
+ }
if (m_console_scroll_pending)
{
@@ -2167,8 +2265,8 @@ namespace Hsdbg
ImGui::SetNextItemWidth(-1.0f);
- const bool submitted = ImGui::InputTextWithHint("##console_input",
- "Expression to evaluate",
+ const bool submitted = Widgets::input_with_icon("##console_input", ICON_PH_CARET_RIGHT,
+ "Expression to evaluate in the target",
&m_console_input,
ImGuiInputTextFlags_EnterReturnsTrue);
@@ -2206,10 +2304,12 @@ namespace Hsdbg
else
{
bool sampling = debugger.sampling_enabled();
- if (ImGui::Checkbox("sample while running", &sampling))
+ if (Widgets::toggle("##sampling", &sampling))
debugger.set_sampling_enabled(sampling);
ImGui::SameLine();
+ ImGui::TextUnformatted("Sample while running");
+ ImGui::SameLine();
ImGui::TextDisabled("(any binary, approximate)");
}
@@ -2220,11 +2320,11 @@ namespace Hsdbg
ImGui::EndChild();
- if (ImGui::CollapsingHeader("function timings", ImGuiTreeNodeFlags_DefaultOpen))
+ if (ImGui::CollapsingHeader(ICON_PH_TIMER " Function timings", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::SetNextItemWidth(-70.0f);
- const bool submitted = ImGui::InputTextWithHint("##trace_input", "Function to time",
- &m_trace_input,
+ const bool submitted = Widgets::input_with_icon("##trace_input", ICON_PH_PLUS,
+ "Function to time", &m_trace_input,
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::SameLine();
@@ -2301,7 +2401,7 @@ namespace Hsdbg
}
if (debugger.has_target() &&
- ImGui::CollapsingHeader("resident memory", ImGuiTreeNodeFlags_DefaultOpen))
+ ImGui::CollapsingHeader(ICON_PH_MEMORY " Resident memory", ImGuiTreeNodeFlags_DefaultOpen))
{
const TimeSeries& memory = m_profiler.target_memory_mb();
@@ -2313,13 +2413,16 @@ namespace Hsdbg
nullptr, 0.0f, FLT_MAX, ImVec2(-1.0f, 60.0f));
}
- if (ImGui::CollapsingHeader("hsdbg self-timing"))
+ if (ImGui::CollapsingHeader(ICON_PH_PULSE " hsdbg self-timing"))
{
bool paused = m_profiler.paused();
- if (ImGui::Checkbox("pause graphs", &paused))
+ if (Widgets::toggle("##pause_graphs", &paused))
m_profiler.set_paused(paused);
ImGui::SameLine();
+ ImGui::TextUnformatted("Pause graphs");
+
+ ImGui::SameLine();
if (ImGui::SmallButton("Reset"))
m_profiler.reset();
@@ -2408,7 +2511,7 @@ namespace Hsdbg
const float y1 = baseline - static_cast<float>(span.depth) * row_height;
const float y0 = y1 - (row_height - 2.0f);
- const ImU32 fill = ImColor::HSV(span.trace_id * 0.13f, 0.55f, 0.78f);
+ const ImU32 fill = ImColor::HSV(static_cast<float>(span.trace_id) * 0.13f, 0.55f, 0.78f);
draw_list->AddRectFilled(ImVec2(x0, y0), ImVec2(x0 + width, y1), fill, 2.0f);
draw_list->AddRect(ImVec2(x0, y0), ImVec2(x0 + width, y1), IM_COL32(0, 0, 0, 90), 2.0f);
@@ -2581,7 +2684,11 @@ namespace Hsdbg
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
- if (!ImGui::BeginPopup("##command_palette", flags))
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f));
+ const bool open = ImGui::BeginPopup("##command_palette", flags);
+ ImGui::PopStyleVar();
+
+ if (!open)
{
m_palette_open = false;
return;
@@ -2615,14 +2722,14 @@ namespace Hsdbg
entries.push_back({ label, "command", Kind::Command, command, {}, 0 });
};
- add_command(has_target && !running, Run, "run");
- add_command(stopped, Continue, "continue");
- add_command(running, Pause, "pause");
- add_command(has_target, Stop, "stop");
+ add_command(has_target && !running, Run, "Run");
+ add_command(stopped, Continue, "Continue");
+ add_command(running, Pause, "Pause");
+ add_command(has_target, Stop, "Stop");
add_command(stopped, StepOver, "Step Over");
add_command(stopped, StepInto, "Step Into");
add_command(stopped, StepOut, "Step Out");
- add_command(true, ToggleProfiler, m_visible.profiler ? "hide profiler" : "show profiler");
+ add_command(true, ToggleProfiler, m_visible.profiler ? "Hide Profiler" : "Show Profiler");
if (!m_palette_query.empty())
{
@@ -2713,7 +2820,7 @@ namespace Hsdbg
}
ImGui::SetNextItemWidth(-FLT_MIN);
- const bool submitted = ImGui::InputTextWithHint("##palette_query",
+ const bool submitted = Widgets::input_with_icon("##palette_query", ICON_PH_SEARCH,
"Jump to a file or symbol, or run a command",
&m_palette_query,
ImGuiInputTextFlags_EnterReturnsTrue);
@@ -2744,18 +2851,20 @@ namespace Hsdbg
ImGui::PushID(index);
- if (ImGui::Selectable("##row", selected, ImGuiSelectableFlags_SpanAllColumns))
+ const char* icon = entry.kind == Kind::File ? ICON_PH_FILE_CODE
+ : entry.kind == Kind::Symbol ? ICON_PH_FUNCTION
+ : ICON_PH_PLAY;
+ const std::string label = std::format("{} {}##row", icon, entry.label);
+
+ if (Widgets::selectable_row(label.c_str(), selected))
activate(entry);
if (selected && (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsWindowAppearing()))
ImGui::SetScrollHereY(0.5f);
- ImGui::SameLine(0.0f, 0.0f);
- ImGui::TextUnformatted(entry.label.c_str());
-
const float hint_width = ImGui::CalcTextSize(entry.hint).x;
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - hint_width);
+ ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - hint_width - 8.0f);
ImGui::TextDisabled("%s", entry.hint);
ImGui::PopID();
@@ -2916,6 +3025,9 @@ namespace Hsdbg
style.ScrollbarSize = 11.0f;
style.GrabMinSize = 10.0f;
style.TabBarBorderSize = 0.0f;
+ style.TabBarOverlineSize = 2.0f;
+ style.TabCloseButtonMinWidthSelected = 0.0f;
+ style.TabCloseButtonMinWidthUnselected = FLT_MAX;
style.DockingSeparatorSize = 7.0f;
style.WindowTitleAlign = ImVec2(0.0f, 0.5f);
style.SeparatorTextBorderSize = 1.0f;
@@ -2992,6 +3104,7 @@ namespace Hsdbg
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = fade(text, 0.03f);
colors[ImGuiCol_TextSelectedBg] = fade(accent, 0.35f);
+ colors[ImGuiCol_TextLink] = accent;
colors[ImGuiCol_NavCursor] = accent;
}
}
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 251a2b4..c5ecf56 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -12,6 +12,8 @@
#include <filesystem>
#include <string>
#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
#include <vector>
namespace Hsdbg
@@ -71,6 +73,8 @@ namespace Hsdbg
auto draw_menu_bar(Debugger& debugger) -> void;
auto draw_toolbar(Debugger& debugger) -> void;
+
+ auto handle_shortcuts(Debugger& debugger) -> void;
auto draw_status_bar(const Debugger& debugger) -> void;
auto draw_load_target_popup(Debugger& debugger) -> void;
auto draw_preferences_window() -> void;
@@ -100,6 +104,8 @@ namespace Hsdbg
auto follow_stop(Debugger& debugger) -> void;
auto follow_target(Debugger& debugger) -> void;
+ auto snapshot_values(const Debugger& debugger) -> void;
+
auto follow_selected_frame(Debugger& debugger) -> void;
auto show_frame(const StackFrame& frame) -> void;
@@ -116,6 +122,7 @@ namespace Hsdbg
Theme m_theme;
std::vector<ThemeEntry> m_themes;
+ std::vector<Theme> m_theme_previews;
bool m_prefs_open_prev = false;
ImFont* m_font_ui = nullptr;
@@ -145,6 +152,7 @@ namespace Hsdbg
std::string expression;
std::string value;
bool ok = false;
+ bool changed = false;
};
std::vector<Watch> m_watches;
@@ -168,6 +176,17 @@ namespace Hsdbg
uint64_t m_followed_stop = 0;
std::filesystem::path m_followed_target;
+ std::unordered_map<std::string, std::string> m_prev_locals;
+ std::unordered_map<std::string, std::string> m_curr_locals;
+ std::unordered_map<std::string, uint64_t> m_prev_registers;
+ std::unordered_map<std::string, uint64_t> m_curr_registers;
+ std::unordered_set<std::string> m_changed_locals;
+ std::string m_snapshot_function;
+ uint64_t m_snapshot_stop = 0;
+ uint64_t m_snapshot_thread = 0;
+ uint32_t m_snapshot_frame = 0;
+ bool m_snapshot_comparable = false;
+
std::filesystem::path m_revealed_source;
bool m_profiler_revealed = false;