OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / src / debug_ui / panels / symbols_panel.cpp
6.9 KiB · C++ · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// Commander X16 Emulator — ImGui "Symbols" panel.
2
//
3
// A filterable, sortable list of the address labels parsed from the loaded cc65
4
// .dbg info (dbg_info's `lab` records). Each row shows the label's address and
5
// its live byte + 16-bit word value. Double-click a row to jump the Disassembly
6
// and Memory views there (via the cross-panel goto service); right-click for
7
// set-breakpoint / run-to. This is the "where is X / what's in X right now" view.
8
#include "imgui.h"
9
#include "debug_ui_panels.h"
10
#include "debug_ui_bridge.h"  // dbg_info_symbol_*, debug_ui_read6502, DEBUG*, request_goto
11
#include "debug_ui_widgets.h" // dbgui_value_lines
12
13
#include <stdint.h>
14
#include <string.h>
15
#include <cctype>
16
#include <vector>
17
#include <algorithm>
18
19
namespace {
20
21
char s_filter[64] = "";
22
23
// Case-insensitive substring test.
24
bool
25
contains_ci(const char *hay, const char *needle)
26
{
27
    if (!needle[0])
28
        return true;
29
    size_t nl = strlen(needle);
30
    for (const char *p = hay; *p; ++p) {
31
        size_t i = 0;
32
        while (i < nl && p[i] &&
33
               tolower((unsigned char)p[i]) == tolower((unsigned char)needle[i]))
34
            i++;
35
        if (i == nl)
36
            return true;
37
    }
38
    return false;
39
}
40
41
// Case-insensitive strcmp (portable; the CRT one differs by platform).
42
int
43
sym_stricmp(const char *a, const char *b)
44
{
45
    for (;; a++, b++) {
46
        int ca = tolower((unsigned char)*a);
47
        int cb = tolower((unsigned char)*b);
48
        if (ca != cb)
49
            return ca - cb;
50
        if (!ca)
51
            return 0;
52
    }
53
}
54
55
// Toggle a breakpoint at a symbol's address (CPU program bank 0).
56
void
57
sym_toggle_bp(uint16_t addr)
58
{
59
    // Resolve the bank first: it keys the remove as well as the add.
60
    int x16 = (addr >= 0xA000)
61
                  ? (addr < 0xC000 ? (int)memory_get_ram_bank() : (int)memory_get_rom_bank())
62
                  : -1;
63
    if (!DEBUGRemoveBreakPoint((int)addr, 0, x16)) {
64
        struct breakpoint bp = {};
65
        bp.pc      = (int)addr;
66
        bp.bank    = 0;
67
        bp.x16Bank = x16;
68
        DEBUGAddBreakPoint(bp);
69
    }
70
}
71
72
struct SymRow {
73
    const char *name;
74
    uint16_t    addr;
75
};
76
77
void
78
symbols_panel_render(bool *p_open)
79
{
80
    if (!ImGui::Begin("Symbols", p_open)) {
81
        dbgui_window_end();
82
        return;
83
    }
84
    dbgui_window_zoom("symbols");
85
86
    const ImVec4 orange(1.0f, 0.78f, 0.35f, 1.0f);
87
88
    if (!dbg_info_is_loaded() || dbg_info_symbol_count() == 0) {
89
        ImGui::TextColored(orange, "No symbols loaded.");
90
        ImGui::Spacing();
91
        ImGui::TextWrapped("Symbols come from cc65 .dbg address labels. Start with "
92
                           "-dbgfile <prog.dbg> (and -srcpath if needed), or LOAD a "
93
                           "program whose matching .dbg auto-loads.");
94
        dbgui_window_end();
95
        return;
96
    }
97
98
    // Toolbar: name filter.
99
    ImGui::SetNextItemWidth(220);
100
    ImGui::InputTextWithHint("##symfilter", "filter by name...", s_filter, sizeof s_filter);
101
    ImGui::SameLine();
102
    ImGui::TextDisabled("%d symbols", dbg_info_symbol_count());
103
    ImGui::SameLine();
104
    ImGui::TextDisabled("(double-click = go to; right-click for more)");
105
106
    // Materialize the filtered rows (symbol counts are modest; rebuild per frame).
107
    static std::vector<SymRow> rows;
108
    rows.clear();
109
    int n = dbg_info_symbol_count();
110
    for (int i = 0; i < n; i++) {
111
        const char *nm = nullptr;
112
        uint16_t    a;
113
        if (debug_ui_symbol_at(i, &nm, &a) && nm && contains_ci(nm, s_filter))
114
            rows.push_back({nm, a});
115
    }
116
117
    ImGuiTableFlags tflags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
118
                             ImGuiTableFlags_ScrollY | ImGuiTableFlags_Sortable |
119
                             ImGuiTableFlags_Resizable;
120
    if (ImGui::BeginTable("symbols", 4, tflags, ImVec2(0, 0))) {
121
        ImGui::TableSetupScrollFreeze(0, 1);
122
        ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_DefaultSort);
123
        ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed);
124
        ImGui::TableSetupColumn("Byte", ImGuiTableColumnFlags_WidthFixed);
125
        ImGui::TableSetupColumn("Word", ImGuiTableColumnFlags_WidthFixed);
126
        ImGui::TableHeadersRow();
127
128
        // Apply the requested sort (default: by name, ascending).
129
        if (ImGuiTableSortSpecs *ss = ImGui::TableGetSortSpecs()) {
130
            if (ss->SpecsCount > 0) {
131
                const ImGuiTableColumnSortSpecs &s = ss->Specs[0];
132
                bool asc = s.SortDirection != ImGuiSortDirection_Descending;
133
                if (s.ColumnIndex == 0) {
134
                    std::sort(rows.begin(), rows.end(), [asc](const SymRow &a, const SymRow &b) {
135
                        int c = sym_stricmp(a.name, b.name);
136
                        return asc ? c < 0 : c > 0;
137
                    });
138
                } else {
139
                    std::sort(rows.begin(), rows.end(), [asc](const SymRow &a, const SymRow &b) {
140
                        return asc ? a.addr < b.addr : a.addr > b.addr;
141
                    });
142
                }
143
            }
144
        }
145
146
        ImGuiListClipper clip;
147
        clip.Begin((int)rows.size());
148
        while (clip.Step()) {
149
            for (int i = clip.DisplayStart; i < clip.DisplayEnd; i++) {
150
                const SymRow &r  = rows[i];
151
                uint8_t       b  = debug_ui_read6502(r.addr, 0, DEBUG_UI_CURRENT_BANK);
152
                uint8_t       b2 = debug_ui_read6502((uint16_t)(r.addr + 1), 0, DEBUG_UI_CURRENT_BANK);
153
                uint16_t      w  = (uint16_t)(b | (b2 << 8));
154
155
                ImGui::TableNextRow();
156
                ImGui::PushID(i);
157
158
                ImGui::TableSetColumnIndex(0);
159
                if (ImGui::Selectable(r.name, false,
160
                                      ImGuiSelectableFlags_SpanAllColumns |
161
                                          ImGuiSelectableFlags_AllowDoubleClick) &&
162
                    ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
163
                    debug_ui_request_goto(r.addr, 0);
164
                }
165
                if (ImGui::BeginPopupContextItem("symctx")) {
166
                    if (ImGui::MenuItem("Go to (disasm + memory)"))
167
                        debug_ui_request_goto(r.addr, 0);
168
                    if (ImGui::MenuItem("Toggle breakpoint"))
169
                        sym_toggle_bp(r.addr);
170
                    ImGui::BeginDisabled(!DEBUGIsPaused());
171
                    if (ImGui::MenuItem("Run to here"))
172
                        DEBUGRunTo(r.addr, 0, DEBUG_OWNER_UI);
173
                    ImGui::EndDisabled();
174
                    ImGui::EndPopup();
175
                }
176
177
                ImGui::TableSetColumnIndex(1);
178
                ImGui::Text("$%04X", r.addr);
179
                ImGui::TableSetColumnIndex(2);
180
                ImGui::Text("$%02X", b);
181
                ImGui::TableSetColumnIndex(3);
182
                ImGui::Text("$%04X", w);
183
184
                ImGui::PopID();
185
            }
186
        }
187
        ImGui::EndTable();
188
    }
189
190
    dbgui_window_end();
191
}
192
193
DebugPanelRegistration s_reg("Symbols", symbols_panel_render, true);
194
195
} // namespace