diff options
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/ui.cpp | 243 | ||||
| -rw-r--r-- | src/ui/ui.h | 4 |
2 files changed, 237 insertions, 10 deletions
diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index b79da17..c3a7fa2 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -26,6 +26,7 @@ namespace Hsdbg constexpr const char* PANEL_BREAKPOINTS = "breakpoints"; constexpr const char* PANEL_CALL_STACK = "call stack"; constexpr const char* PANEL_THREADS = "threads"; + constexpr const char* PANEL_SOURCE_TREE = "source tree"; constexpr const char* PANEL_LOCALS = "locals"; constexpr const char* PANEL_REGISTERS = "registers"; constexpr const char* PANEL_SYMBOLS = "symbols"; @@ -100,6 +101,144 @@ namespace Hsdbg return std::ranges::search(name, filter, equal).begin() != name.end(); } + struct SourceNode + { + std::string name; + std::filesystem::path path; + std::vector<SourceNode> children; + }; + + auto find_or_add_child(std::vector<SourceNode>& nodes, std::string_view name) -> SourceNode& + { + for (SourceNode& node : nodes) + { + if (node.name == name) + return node; + } + + nodes.push_back({ std::string(name), {}, {} }); + return nodes.back(); + } + + auto common_directory(std::span<const std::filesystem::path> files) -> std::filesystem::path + { + if (files.empty()) + return {}; + + std::filesystem::path prefix = files.front().parent_path(); + + for (const std::filesystem::path& file : files) + { + while (true) + { + if (file.parent_path() == prefix) + break; + + std::error_code error; + const std::filesystem::path relative = + std::filesystem::relative(file.parent_path(), prefix, error); + const std::string text = relative.generic_string(); + + if (!error && (text.empty() || text == "." || !text.starts_with(".."))) + break; + + const std::filesystem::path parent = prefix.parent_path(); + + if (parent == prefix) + break; + + prefix = parent; + } + } + + return prefix; + } + + auto build_source_tree(std::span<const std::filesystem::path> files) -> SourceNode + { + SourceNode root; + const std::filesystem::path prefix = common_directory(files); + root.name = prefix.empty() ? "/" : prefix.filename().string(); + + if (root.name.empty()) + root.name = prefix.string(); + + for (const std::filesystem::path& file : files) + { + std::error_code error; + std::filesystem::path relative = std::filesystem::relative(file, prefix, error); + + if (error || relative.empty()) + relative = file.filename(); + + SourceNode* current = &root; + + const std::vector<std::filesystem::path> parts(relative.begin(), relative.end()); + + for (size_t index = 0; index < parts.size(); ++index) + { + SourceNode& child = find_or_add_child(current->children, parts[index].string()); + + if (index + 1 == parts.size()) + child.path = file; + else + current = &child; + } + } + + const auto sort_nodes = [](this const auto& self, std::vector<SourceNode>& nodes) -> void + { + std::ranges::sort(nodes, [](const SourceNode& left, const SourceNode& right) + { + const bool left_directory = left.path.empty(); + const bool right_directory = right.path.empty(); + + if (left_directory != right_directory) + return left_directory; + + return left.name < right.name; + }); + + for (SourceNode& node : nodes) + self(node.children); + }; + + sort_nodes(root.children); + + return root; + } + + auto source_node_matches(const SourceNode& node, std::string_view filter) -> bool + { + if (filter.empty()) + return true; + + if (!node.path.empty()) + return matches_filter(node.name, filter); + + return std::ranges::any_of(node.children, [&](const SourceNode& child) + { + return source_node_matches(child, filter); + }); + } + + auto preferred_source(std::span<const std::filesystem::path> files) -> std::filesystem::path + { + for (const std::filesystem::path& file : files) + { + if (file.stem() == "main" && std::filesystem::exists(file)) + return file; + } + + for (const std::filesystem::path& file : files) + { + if (std::filesystem::exists(file)) + return file; + } + + return {}; + } + auto draw_instruction_table(Debugger& debugger, std::span<const Instruction> instructions, bool scroll_to_current) -> void @@ -353,9 +492,17 @@ namespace Hsdbg return; m_followed_target = debugger.target_path(); - m_focus_symbols = true; m_scroll_to_symbol = true; m_scroll_to_program_counter = true; + + if (m_source_view.path().empty()) + { + if (const std::filesystem::path source = preferred_source(debugger.source_files()); + !source.empty()) + { + open_source(source); + } + } } auto Ui::show_frame(const StackFrame& frame) -> void @@ -408,9 +555,7 @@ namespace Hsdbg { build_default_layout(dockspace_id); m_layout_built = true; - - // otherwise whichever panel happens to be submitted last wins the tab - m_focus_breakpoints = true; + m_select_default_tabs = true; } draw_status_bar(debugger); @@ -421,6 +566,7 @@ namespace Hsdbg draw_source_panel(debugger); draw_breakpoints_panel(debugger); draw_call_stack_panel(debugger); + draw_source_tree_panel(debugger); draw_threads_panel(debugger); draw_locals_panel(debugger); draw_registers_panel(debugger); @@ -433,19 +579,27 @@ namespace Hsdbg // a window claims its tab when it is first submitted, so this can only be // asked for once every panel in the node exists + if (m_select_default_tabs) + { + ImGui::SetWindowFocus(PANEL_LOCALS); + ImGui::SetWindowFocus(PANEL_BREAKPOINTS); + ImGui::SetWindowFocus(PANEL_SOURCE); + ImGui::SetWindowFocus(PANEL_SOURCE_TREE); + m_select_default_tabs = false; + } + if (m_focus_symbols) { ImGui::SetWindowFocus(PANEL_SYMBOLS); m_focus_symbols = false; - m_focus_breakpoints = false; } else if (m_focus_disassembly) { ImGui::SetWindowFocus(PANEL_DISASSEMBLY); m_focus_disassembly = false; - m_focus_breakpoints = false; } - else if (m_focus_breakpoints) + + if (m_focus_breakpoints) { ImGui::SetWindowFocus(PANEL_BREAKPOINTS); m_focus_breakpoints = false; @@ -460,8 +614,8 @@ namespace Hsdbg ImGuiID center_id = dockspace_id; const ImGuiID left_id = ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Left, 0.20f, nullptr, ¢er_id); - const ImGuiID right_id = ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Right, 0.28f, nullptr, ¢er_id); - const ImGuiID bottom_id = ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Down, 0.28f, nullptr, ¢er_id); + const ImGuiID right_id = ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Right, 0.22f, nullptr, ¢er_id); + const ImGuiID bottom_id = ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Down, 0.24f, nullptr, ¢er_id); ImGuiID left_top_id = left_id; const ImGuiID left_bottom_id = ImGui::DockBuilderSplitNode(left_top_id, ImGuiDir_Down, 0.5f, nullptr, &left_top_id); @@ -471,10 +625,11 @@ namespace Hsdbg ImGui::DockBuilderDockWindow(PANEL_SOURCE, center_id); ImGui::DockBuilderDockWindow(PANEL_DISASSEMBLY, center_id); + ImGui::DockBuilderDockWindow(PANEL_SOURCE_TREE, left_top_id); ImGui::DockBuilderDockWindow(PANEL_THREADS, left_top_id); ImGui::DockBuilderDockWindow(PANEL_SYMBOLS, left_bottom_id); - ImGui::DockBuilderDockWindow(PANEL_CALL_STACK, left_bottom_id); ImGui::DockBuilderDockWindow(PANEL_LOCALS, right_top_id); + ImGui::DockBuilderDockWindow(PANEL_CALL_STACK, right_top_id); ImGui::DockBuilderDockWindow(PANEL_REGISTERS, right_bottom_id); ImGui::DockBuilderDockWindow(PANEL_BREAKPOINTS, bottom_id); ImGui::DockBuilderDockWindow(PANEL_CONSOLE, bottom_id); @@ -543,6 +698,7 @@ namespace Hsdbg if (ImGui::BeginMenu("view")) { ImGui::MenuItem(PANEL_SOURCE, nullptr, &m_visible.source); + ImGui::MenuItem(PANEL_SOURCE_TREE, nullptr, &m_visible.source_tree); ImGui::MenuItem(PANEL_BREAKPOINTS, nullptr, &m_visible.breakpoints); ImGui::MenuItem(PANEL_CALL_STACK, nullptr, &m_visible.call_stack); ImGui::MenuItem(PANEL_THREADS, nullptr, &m_visible.threads); @@ -910,6 +1066,73 @@ namespace Hsdbg ImGui::End(); } + auto Ui::draw_source_tree_panel(Debugger& debugger) -> void + { + if (!m_visible.source_tree) + return; + + if (ImGui::Begin(PANEL_SOURCE_TREE, &m_visible.source_tree)) + { + const std::span<const std::filesystem::path> files = debugger.source_files(); + + if (files.empty()) + { + ImGui::TextDisabled("no source files"); + } + else + { + ImGui::SetNextItemWidth(-1.0f); + ImGui::InputTextWithHint("##source_filter", "filter files", &m_source_filter); + + const SourceNode tree = build_source_tree(files); + const std::filesystem::path& open = m_source_view.path(); + + const auto draw_node = [&](this const auto& self, const SourceNode& node) -> void + { + if (!source_node_matches(node, m_source_filter)) + return; + + if (!node.path.empty()) + { + const bool selected = node.path == open; + + if (ImGui::Selectable(node.name.c_str(), selected)) + { + open_source(node.path); + ImGui::SetWindowFocus(PANEL_SOURCE); + } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", node.path.string().c_str()); + + return; + } + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_OpenOnArrow; + + if (m_source_filter.empty()) + flags |= ImGuiTreeNodeFlags_DefaultOpen; + + if (!ImGui::TreeNodeEx(node.name.c_str(), flags)) + return; + + for (const SourceNode& child : node.children) + self(child); + + ImGui::TreePop(); + }; + + if (tree.children.empty()) + ImGui::TextDisabled("no source files"); + else + draw_node(tree); + } + } + + ImGui::End(); + } + auto Ui::draw_locals_panel(Debugger& debugger) -> void { if (!m_visible.locals) diff --git a/src/ui/ui.h b/src/ui/ui.h index 39493dc..8cc573e 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -40,6 +40,7 @@ namespace Hsdbg bool breakpoints = true; bool call_stack = true; bool threads = true; + bool source_tree = true; bool locals = true; bool registers = true; bool symbols = true; @@ -60,6 +61,7 @@ namespace Hsdbg auto draw_breakpoints_panel(Debugger& debugger) -> void; auto draw_call_stack_panel(Debugger& debugger) -> void; auto draw_threads_panel(Debugger& debugger) -> void; + auto draw_source_tree_panel(Debugger& debugger) -> void; auto draw_locals_panel(Debugger& debugger) -> void; auto draw_registers_panel(Debugger& debugger) -> void; auto draw_symbols_panel(Debugger& debugger) -> void; @@ -81,11 +83,13 @@ namespace Hsdbg std::string m_console_input; std::string m_target_input; std::string m_symbol_filter; + std::string m_source_filter; uint64_t m_followed_stop = 0; std::filesystem::path m_followed_target; bool m_layout_built = false; + bool m_select_default_tabs = false; bool m_console_scroll_pending = false; bool m_load_target_pending = false; bool m_focus_breakpoints = false; |
