diff options
| author | hachem <im@hachem.wtf> | 2026-09-04 03:21:45 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-04 03:21:45 +0200 |
| commit | 9023d106c61db0be0de6460a397bbad42bbc3d96 (patch) | |
| tree | c1773245e02b507058d65d031c258c2a24601047 /src/ui/source_view.cpp | |
| parent | 0c3cb5bc5fa54e2d6c49fad746871b0ed0da66af (diff) | |
feat: macro expantion
Diffstat (limited to 'src/ui/source_view.cpp')
| -rw-r--r-- | src/ui/source_view.cpp | 141 |
1 files changed, 136 insertions, 5 deletions
diff --git a/src/ui/source_view.cpp b/src/ui/source_view.cpp index b998e3f..7638f17 100644 --- a/src/ui/source_view.cpp +++ b/src/ui/source_view.cpp @@ -103,7 +103,8 @@ namespace Hsdbg // splits every line into contiguous coloured spans. the whole file is // walked in order so a block comment opened on one line stays open on the // next, which a per-line pass could not know - auto highlight_lines(const std::vector<std::string>& lines, Language language) + auto highlight_lines(const std::vector<std::string>& lines, Language language, + const MacroTable& macros) -> std::vector<std::vector<SourceSpan>> { std::vector<std::vector<SourceSpan>> out(lines.size()); @@ -265,9 +266,17 @@ namespace Hsdbg while (at < size && is_word(line[at])) ++at; - if (const SyntaxKind kind = classify_word( - std::string_view(line).substr(start, at - start), language); - kind != SyntaxKind::Default) + const std::string_view word = + std::string_view(line).substr(start, at - start); + SyntaxKind kind = classify_word(word, language); + + // a plain identifier that names a #define is coloured as a + // macro so it reads as expandable in the source view + if (kind == SyntaxKind::Default && language == Language::Cpp && + macros.find(word) != nullptr) + kind = SyntaxKind::Macro; + + if (kind != SyntaxKind::Default) { flush_default(start); emit(start, at - start, kind); @@ -321,6 +330,46 @@ namespace Hsdbg { return ImGui::CalcTextSize(std::to_string(line_count).c_str()).x; } + + // 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 + // back to the bare name rather than guessing where it closes + auto capture_invocation(const std::string& line, uint32_t start) -> std::string + { + const size_t size = line.size(); + size_t at = start; + + while (at < size && is_word(line[at])) + ++at; + + size_t paren = at; + + while (paren < size && std::isspace(static_cast<unsigned char>(line[paren])) != 0) + ++paren; + + if (paren < size && line[paren] == '(') + { + int depth = 0; + + for (size_t i = paren; i < size; ++i) + { + if (line[i] == '(') + { + ++depth; + } + else if (line[i] == ')') + { + --depth; + + if (depth == 0) + return line.substr(start, i + 1 - start); + } + } + } + + return line.substr(start, at - start); + } } auto SourceView::open(const std::filesystem::path& path) -> Result<void> @@ -345,7 +394,15 @@ namespace Hsdbg m_path = path; m_lines = std::move(lines); m_highlight = language.has_value(); - m_spans = language ? highlight_lines(m_lines, *language) + + // gather the file's #defines (chasing local quote includes) so macro + // names highlight and the macros panel has something to expand + m_macros.clear(); + + if (language == Language::Cpp) + m_macros.build(m_lines, path.parent_path()); + + m_spans = language ? highlight_lines(m_lines, *language, m_macros) : std::vector<std::vector<SourceSpan>>{}; m_path_input = path.string(); m_error.clear(); @@ -361,11 +418,20 @@ namespace Hsdbg m_path.clear(); m_lines.clear(); m_spans.clear(); + m_macros.clear(); + m_macro_request.reset(); m_highlight = false; m_error.clear(); m_highlighted_line = 0; } + auto SourceView::take_macro_request() -> std::optional<std::string> + { + std::optional<std::string> request = std::move(m_macro_request); + m_macro_request.reset(); + return request; + } + auto SourceView::set_highlighted_line(uint32_t line) -> void { m_highlighted_line = line; @@ -541,6 +607,10 @@ namespace Hsdbg { ImGui::TextUnformatted(begin, end); } + else if (span.kind == SyntaxKind::Macro) + { + draw_macro_span(draw_list, text, span); + } else { ImGui::PushStyleColor(ImGuiCol_Text, @@ -557,4 +627,65 @@ namespace Hsdbg ImGui::EndChild(); } + + auto SourceView::draw_macro_span(ImDrawList* draw_list, const std::string& line, + const SourceSpan& span) -> void + { + const char* const begin = line.c_str() + span.start; + const char* const end = begin + span.length; + const ImU32 color = m_syntax_colors[static_cast<size_t>(SyntaxKind::Macro)]; + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(begin, end); + ImGui::PopStyleColor(); + + if (!ImGui::IsItemHovered()) + return; + + // underline on hover so the name reads as a link + const ImVec2 rect_min = ImGui::GetItemRectMin(); + const ImVec2 rect_max = ImGui::GetItemRectMax(); + draw_list->AddLine(ImVec2(rect_min.x, rect_max.y - 1.0f), + ImVec2(rect_max.x, rect_max.y - 1.0f), color); + + const std::string invocation = capture_invocation(line, span.start); + const MacroExpansion expansion = expand_stages(m_macros, invocation); + + ImGui::BeginTooltip(); + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(invocation.c_str()); + ImGui::PopStyleColor(); + + ImGui::Separator(); + + std::string preview = expansion.levels.back(); + + if (constexpr size_t limit = 240; preview.size() > limit) + { + preview.resize(limit); + preview += " ..."; + } + + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 30.0f); + ImGui::TextUnformatted(preview.c_str()); + ImGui::PopTextWrapPos(); + + ImGui::Spacing(); + + const size_t steps = expansion.levels.size() - 1; + + if (steps == 0) + ImGui::TextDisabled("no expansion"); + else if (!expansion.fully_expanded()) + ImGui::TextDisabled("%zu+ levels — click to step through", steps); + else + ImGui::TextDisabled("%zu level%s — click to step through", steps, + steps == 1 ? "" : "s"); + + ImGui::EndTooltip(); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + m_macro_request = invocation; + } } |
