aboutsummaryrefslogtreecommitdiff
path: root/src/ui/source_view.cpp
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-04 19:30:33 +0200
committerhachem <im@hachem.wtf>2026-09-04 19:30:33 +0200
commit3f33c536c06535052d029c7270023a3dae351696 (patch)
treeb072f2135bfc3f7d996aff9c21299dfcd8affaf3 /src/ui/source_view.cpp
parentdcb23f8b4b889061e96d43949d570581469cab84 (diff)
refactor: overhaul the ui
the old ui interface was very disconnected, each tool was its own panel, without much interaction. this commit is an attempt to fix this.
Diffstat (limited to 'src/ui/source_view.cpp')
-rw-r--r--src/ui/source_view.cpp124
1 files changed, 118 insertions, 6 deletions
diff --git a/src/ui/source_view.cpp b/src/ui/source_view.cpp
index 0a6a230..3c15c08 100644
--- a/src/ui/source_view.cpp
+++ b/src/ui/source_view.cpp
@@ -274,12 +274,12 @@ namespace Hsdbg
macros.find(word) != nullptr)
kind = SyntaxKind::Macro;
- if (kind != SyntaxKind::Default)
- {
- flush_default(start);
- emit(start, at - start, kind);
- run = at;
- }
+ // every word gets its own span, including plain identifiers:
+ // isolating them lets the source view hit-test a name under
+ // the cursor and show its live value
+ flush_default(start);
+ emit(start, at - start, kind);
+ run = at;
continue;
}
@@ -324,11 +324,64 @@ namespace Hsdbg
const ImU32 BREAKPOINT_DISABLED_COLOR = IM_COL32(120, 90, 90, 255);
const ImU32 BREAKPOINT_HOVER_COLOR = IM_COL32(226, 84, 84, 90);
+ // muted blue-grey for the live values shown after a line, distinct from the
+ // green of comments so they do not read as part of the code
+ const ImU32 INLINE_VALUE_COLOR = IM_COL32(122, 140, 170, 235);
+
auto line_number_width(size_t line_count) -> float
{
return ImGui::CalcTextSize(std::to_string(line_count).c_str()).x;
}
+ // the frame local named exactly `word`, or null; only names with a value to
+ // show qualify, so pure aggregates without a summary are skipped
+ auto find_local(std::span<const Variable> locals, std::string_view word) -> const Variable*
+ {
+ const auto match = std::ranges::find_if(locals, [&](const Variable& local)
+ {
+ return !local.value.empty() && local.name == word;
+ });
+
+ return match != locals.end() ? &*match : nullptr;
+ }
+
+ // whether `name` appears in `line` as a whole identifier rather than as a
+ // fragment of a longer word
+ auto contains_word(const std::string& line, const std::string& name) -> bool
+ {
+ if (name.empty())
+ return false;
+
+ for (size_t at = line.find(name); at != std::string::npos; at = line.find(name, at + 1))
+ {
+ const bool left = at > 0 && is_word(line[at - 1]);
+ const bool right = at + name.size() < line.size() && is_word(line[at + name.size()]);
+
+ if (!left && !right)
+ return true;
+ }
+
+ return false;
+ }
+
+ // a single-line, length-capped rendering of a value for the inline annotation
+ auto inline_value(const Variable& local) -> std::string
+ {
+ std::string text = local.name + " = " + local.value;
+
+ if (const auto newline = text.find('\n'); newline != std::string::npos)
+ text.resize(newline);
+
+ constexpr size_t cap = 48;
+ if (text.size() > cap)
+ {
+ text.resize(cap - 3);
+ text += "...";
+ }
+
+ return text;
+ }
+
// grab a macro invocation out of a line starting at the name: the name
// alone for an object-like use, or the name plus a balanced argument list
// for a function-like one. a call that runs off the end of the line falls
@@ -430,6 +483,13 @@ namespace Hsdbg
return request;
}
+ auto SourceView::take_watch_request() -> std::optional<std::string>
+ {
+ std::optional<std::string> request = std::move(m_watch_request);
+ m_watch_request.reset();
+ return request;
+ }
+
auto SourceView::set_highlighted_line(uint32_t line) -> void
{
m_highlighted_line = line;
@@ -495,6 +555,13 @@ namespace Hsdbg
m_scroll_to_highlight = false;
}
+ // the selected frame's locals, but only when we are stopped in this very
+ // file (a non-zero highlight means show_frame put us here); used both to
+ // annotate lines and to answer a hover over a name
+ const std::span<const Variable> locals =
+ (debugger.is_stopped() && m_highlighted_line != 0) ? debugger.locals()
+ : std::span<const Variable>{};
+
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(m_lines.size()), text_height);
@@ -596,6 +663,23 @@ namespace Hsdbg
if (span.kind == SyntaxKind::Default)
{
ImGui::TextUnformatted(begin, end);
+
+ const std::string_view word(begin, static_cast<size_t>(end - begin));
+ const bool is_name = !word.empty() && is_word_start(word.front());
+
+ // hovering a name that is a live local shows its value
+ if (!locals.empty() && ImGui::IsItemHovered())
+ {
+ if (const Variable* local = find_local(locals, word))
+ ImGui::SetTooltip("%s = %s%s%s", local->name.c_str(),
+ local->value.c_str(),
+ local->type.empty() ? "" : "\n",
+ local->type.c_str());
+ }
+
+ // right-click any name to add it to the watch list
+ if (is_name && ImGui::IsItemClicked(ImGuiMouseButton_Right))
+ m_watch_request = std::string(word);
}
else if (span.kind == SyntaxKind::Macro)
{
@@ -610,6 +694,34 @@ namespace Hsdbg
}
}
}
+
+ // trailing live values: the locals that appear on this line, shown
+ // only up to the line the pc sits on, since anything past it has
+ // not run yet and would read as a stale or unset value
+ if (!locals.empty() && line_number <= m_highlighted_line)
+ {
+ int shown = 0;
+
+ for (const Variable& local : locals)
+ {
+ if (shown >= 4)
+ break;
+
+ if (local.value.empty() || !contains_word(text, local.name))
+ continue;
+
+ ImGui::SameLine(0.0f, shown == 0 ? GUTTER_MARGIN * 3.0f : GUTTER_MARGIN);
+ ImGui::PushStyleColor(ImGuiCol_Text, INLINE_VALUE_COLOR);
+ ImGui::TextUnformatted(inline_value(local).c_str());
+ ImGui::PopStyleColor();
+
+ if (ImGui::IsItemHovered())
+ ImGui::SetTooltip("%s = %s%s%s", local.name.c_str(), local.value.c_str(),
+ local.type.empty() ? "" : "\n", local.type.c_str());
+
+ ++shown;
+ }
+ }
}
}