diff options
Diffstat (limited to 'src/ui/source_view.cpp')
| -rw-r--r-- | src/ui/source_view.cpp | 124 |
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; + } + } } } |
