OXYGENHUB
◆ main openmsx / src / imgui / ImGuiSymbols.cc
15.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ImGuiSymbols.hh"
2
3
#include "ImGuiBreakPoints.hh"
4
#include "ImGuiCpp.hh"
5
#include "ImGuiDebugger.hh"
6
#include "ImGuiManager.hh"
7
#include "ImGuiOpenFile.hh"
8
#include "ImGuiUtils.hh"
9
#include "ImGuiWatchExpr.hh"
10
11
#include "CliComm.hh"
12
#include "File.hh"
13
#include "Interpreter.hh"
14
#include "MSXCPUInterface.hh"
15
#include "MSXCliComm.hh"
16
#include "MSXException.hh"
17
#include "MSXMotherBoard.hh"
18
#include "Reactor.hh"
19
20
#include "StringOp.hh"
21
#include "enumerate.hh"
22
#include "stl.hh"
23
#include "strCat.hh"
24
#include "unreachable.hh"
25
#include "xrange.hh"
26
27
#include <imgui.h>
28
#include <imgui_stdlib.h>
29
30
#include <cassert>
31
#include <ranges>
32
33
namespace openmsx {
34
35
ImGuiSymbols::ImGuiSymbols(ImGuiManager& manager_)
36
	: ImGuiPart(manager_)
37
	, symbolManager(manager.getReactor().getSymbolManager())
38
{
39
	symbolManager.setObserver(this);
40
}
41
42
ImGuiSymbols::~ImGuiSymbols()
43
{
44
	symbolManager.setObserver(nullptr);
45
}
46
47
void ImGuiSymbols::save(ImGuiTextBuffer& buf)
48
{
49
	savePersistent(buf, *this, persistentElements);
50
	for (const auto& file : symbolManager.getFiles()) {
51
		buf.appendf("symbolfile=%s\n", file.filename.c_str());
52
		buf.appendf("symbolfiletype=%s\n", SymbolFile::toString(file.type).c_str());
53
		if (file.slot) {
54
			buf.appendf("slotsubslot=%d\n", *file.slot);
55
		}
56
		if (file.segment) {
57
			buf.appendf("segment=%d\n", *file.segment);
58
		}
59
	}
60
	for (const auto& [file, error, type, slot, segment] : fileError) {
61
		buf.appendf("symbolfile=%s\n", file.c_str());
62
		buf.appendf("symbolfiletype=%s\n", SymbolFile::toString(type).c_str());
63
		if (slot) {
64
			buf.appendf("slotsubslot=%d\n", *slot);
65
		}
66
		if (segment) {
67
			buf.appendf("segment=%d\n", *segment);
68
		}
69
	}
70
}
71
72
void ImGuiSymbols::loadStart()
73
{
74
	symbolManager.removeAllFiles();
75
	fileError.clear();
76
}
77
78
void ImGuiSymbols::loadLine(std::string_view name, zstring_view value)
79
{
80
	if (loadOnePersistent(name, value, *this, persistentElements)) {
81
		// already handled
82
	} else if (name == "symbolfile") {
83
		fileError.emplace_back(std::string{value}, // filename
84
		                       std::string{}, // error
85
		                       SymbolFile::Type::AUTO_DETECT, // type
86
				       std::nullopt); // slot-subslot
87
	} else if (name == "symbolfiletype") {
88
		if (!fileError.empty()) {
89
			fileError.back().type =
90
				SymbolFile::parseType(value).value_or(SymbolFile::Type::AUTO_DETECT);
91
		}
92
	} else if (name == "slotsubslot") {
93
		if (!fileError.empty()) {
94
			if (auto slot = StringOp::stringTo<uint8_t>(value)) {
95
				fileError.back().slot = slot;
96
			}
97
		}
98
	} else if (name == "segment") {
99
		if (!fileError.empty()) {
100
			if (auto segment = StringOp::stringTo<uint16_t>(value)) {
101
				fileError.back().segment = segment;
102
			}
103
		}
104
	}
105
}
106
107
void ImGuiSymbols::loadEnd()
108
{
109
	std::vector<FileInfo> tmp;
110
	std::swap(tmp, fileError);
111
	for (const auto& info : tmp) {
112
		loadFile(info.filename, SymbolManager::LoadEmpty::ALLOWED, info.type, info.slot, info.segment);
113
	}
114
}
115
116
void ImGuiSymbols::loadFile(
117
	const std::string& filename, SymbolManager::LoadEmpty loadEmpty, SymbolFile::Type type,
118
	std::optional<uint8_t> slot, std::optional<uint16_t> segment)
119
{
120
	auto& cliComm = manager.getCliComm();
121
	auto it = std::ranges::find(fileError, filename, &FileInfo::filename);
122
	try {
123
		if (!symbolManager.reloadFile(filename, loadEmpty, type, slot, segment)) {
124
			cliComm.printWarning("Symbol file \"", filename,
125
			                     "\" doesn't contain any symbols");
126
		}
127
		if (it != fileError.end()) fileError.erase(it); // clear previous error
128
	} catch (MSXException& e) {
129
		cliComm.printWarning(
130
			"Couldn't load symbol file \"", filename, "\": ", e.getMessage());
131
		if (it != fileError.end()) {
132
			it->error = e.getMessage(); // overwrite previous error
133
			it->type = type;
134
		} else {
135
			fileError.emplace_back(filename, e.getMessage(), type, slot, segment); // set error
136
		}
137
	}
138
}
139
140
static bool isSlotExpanded(MSXMotherBoard* motherBoard, int ps)
141
{
142
	return motherBoard != nullptr ? motherBoard->getCPUInterface().isExpanded(ps) : true;
143
}
144
145
static std::string formatSlot(std::optional<uint8_t> slot, MSXMotherBoard* motherBoard = nullptr)
146
{
147
	if (!slot) return "-";
148
	int ps = (*slot >> 0) & 3;
149
	int ss = (*slot >> 2) & 3;
150
	return isSlotExpanded(motherBoard, ps) ? strCat(ps, '-', ss)
151
	                                       : strCat(ps);
152
}
153
154
void ImGuiSymbols::checkSort()
155
{
156
	auto* sortSpecs = ImGui::TableGetSortSpecs();
157
	if (!sortSpecs->SpecsDirty) return;
158
159
	sortSpecs->SpecsDirty = false;
160
	assert(sortSpecs->SpecsCount == 1);
161
	assert(sortSpecs->Specs);
162
	assert(sortSpecs->Specs->SortOrder == 0);
163
164
	sortState.columnIndex = sortSpecs->Specs[0].ColumnIndex;
165
	sortState.direction = sortSpecs->Specs[0].SortDirection;
166
167
	switch (sortSpecs->Specs->ColumnIndex) {
168
	case 0: // name
169
		sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return sym.name(symbolManager); });
170
		break;
171
	case 1: // value
172
		sortUpDown_T(symbols, sortSpecs, [&](const auto& sym) { return sym.value(symbolManager); });
173
		break;
174
	case 2: // slot
175
		sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return formatSlot(sym.slot(symbolManager)); });
176
		break;
177
	case 3: // segment
178
		sortUpDown_T(symbols, sortSpecs, [&](const auto& sym) { return sym.segment(symbolManager); });
179
		break;
180
	case 4: // file (all symbols)
181
		sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return sym.file(symbolManager); });
182
		break;
183
	default:
184
		UNREACHABLE;
185
	}
186
}
187
188
void ImGuiSymbols::drawContext(MSXMotherBoard* motherBoard, const SymbolRef& sym)
189
{
190
	if (ImGui::MenuItem("Show in Disassembly", "double-click", nullptr, motherBoard != nullptr)) {
191
		manager.debugger->setGotoTarget(sym.value(symbolManager));
192
	}
193
	if (ImGui::MenuItem("Set breakpoint", nullptr, nullptr, motherBoard != nullptr)) {
194
		auto& interp = motherBoard->getReactor().getInterpreter();
195
		BreakPoint newBp;
196
		newBp.setAddress(interp, TclObject(strCat("$sym(", sym.name(symbolManager), ')')));
197
198
		if (auto slot = sym.slot(symbolManager)) {
199
			auto segment = sym.segment(symbolManager);
200
			auto cond = tmpStrCat(
201
				"[pc_in_slot ", *slot & 3, ' ', (*slot >> 2) & 3,
202
				strCat_if(segment, ' ', STRCAT_LAZY(*segment)),
203
				']');
204
			newBp.setCondition(TclObject(cond));
205
		}
206
207
		auto& cpuInterface = motherBoard->getCPUInterface();
208
		cpuInterface.insertBreakPoint(std::move(newBp));
209
	}
210
211
	ImGui::Separator();
212
213
	auto cmdResult = manager.execute(makeTclList("symboltracer", "list", sym.name(symbolManager)));
214
	bool traceAsFunction = cmdResult && !cmdResult->getString().empty();
215
216
	if (ImGui::Checkbox("Toggle trace function", &traceAsFunction)) {
217
		TclObject cmd = makeTclList("symboltracer", traceAsFunction ? "add" : "remove", sym.name(symbolManager));
218
		if (traceAsFunction) {
219
			cmd.addListElement(sym.value(symbolManager));
220
		}
221
		manager.execute(cmd);
222
	}
223
	HelpMarker("Use the console command \"help symboltracer\" for more details on how function traces work.");
224
}
225
226
template<bool FILTER_FILE>
227
void ImGuiSymbols::drawTable(MSXMotherBoard* motherBoard, const std::string& file)
228
{
229
	assert(FILTER_FILE == !file.empty());
230
231
	int flags = ImGuiTableFlags_RowBg |
232
	            ImGuiTableFlags_BordersV |
233
	            ImGuiTableFlags_BordersOuter |
234
	            ImGuiTableFlags_ContextMenuInBody |
235
	            ImGuiTableFlags_Resizable |
236
	            ImGuiTableFlags_Reorderable |
237
	            ImGuiTableFlags_Sortable |
238
	            ImGuiTableFlags_Hideable |
239
	            ImGuiTableFlags_SizingStretchProp |
240
	            (FILTER_FILE ? ImGuiTableFlags_ScrollY : 0);
241
	im::Table(file.c_str(), (FILTER_FILE ? 4 : 5), flags, {0, 100}, [&]{
242
		ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
243
		ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_NoHide);
244
		ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed);
245
		ImGui::TableSetupColumn("slot", ImGuiTableColumnFlags_DefaultHide);
246
		ImGui::TableSetupColumn("segment", ImGuiTableColumnFlags_DefaultHide);
247
		if (!FILTER_FILE) {
248
			ImGui::TableSetupColumn("file");
249
		}
250
251
		// Apply the global sort state to this table's visual indicators
252
		if (sortState.columnIndex >= 0) {
253
			ImGui::TableSetColumnSortDirection(
254
				sortState.columnIndex,
255
				sortState.direction,
256
				false); // don't append, replace existing sort
257
			ImGui::TableGetSortSpecs()->SpecsDirty = false; // prevent re-processing this same frame
258
		}
259
260
		ImGui::TableHeadersRow();
261
		checkSort();
262
263
		static std::vector<int> indices; // static to resuse capacity
264
		indices.clear();
265
		for (auto i : xrange(symbols.size())) {
266
			const auto& sym = symbols[i];
267
			if (!FILTER_FILE || (sym.file(symbolManager) == file)) {
268
				indices.push_back(narrow<int>(i));
269
			}
270
		}
271
		im::ListClipperID(indices.size(), [&](int i) {
272
			const auto& sym = symbols[indices[i]];
273
274
			if (ImGui::TableNextColumn()) { // name
275
				const auto& symName = sym.name(symbolManager);
276
				auto symNameMenu = strCat("symbol-manager##", symName);
277
				{
278
					im::ScopedFont sf(manager.fontMono);
279
					if (ImGui::Selectable(symName.c_str(), false,
280
							ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap |
281
							ImGuiSelectableFlags_AllowDoubleClick)) {
282
						if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
283
							manager.debugger->setGotoTarget(sym.value(symbolManager));
284
						}
285
					}
286
					if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
287
						ImGui::OpenPopup(symNameMenu.c_str());
288
					}
289
				}
290
				im::Popup(symNameMenu.c_str(), [&]{ drawContext(motherBoard, sym); });
291
			}
292
			if (ImGui::TableNextColumn()) { // value
293
				im::ScopedFont sf(manager.fontMono);
294
				ImGui::StrCat(hex_string<4>(sym.value(symbolManager)));
295
			}
296
			if (ImGui::TableNextColumn()) { // slot
297
				im::ScopedFont sf(manager.fontMono);
298
				ImGui::TextUnformatted(formatSlot(sym.slot(symbolManager), motherBoard));
299
			}
300
			if (ImGui::TableNextColumn()) { // segment
301
				im::ScopedFont sf(manager.fontMono);
302
				ImGui::TextUnformatted(sym.segment(symbolManager) ? tmpStrCat(*sym.segment(symbolManager)).c_str() : "-");
303
			}
304
			if (!FILTER_FILE && ImGui::TableNextColumn()) { // file
305
				ImGui::TextUnformatted(sym.file(symbolManager));
306
			}
307
		});
308
	});
309
}
310
311
void ImGuiSymbols::paint(MSXMotherBoard* motherBoard)
312
{
313
	if (!show) return;
314
315
	const auto& style = ImGui::GetStyle();
316
	ImGui::SetNextWindowSize(gl::vec2{24, 18} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
317
	im::Window("Symbol Manager", &show, [&]{
318
		if (ImGui::Button("Load symbol file...")) {
319
			manager.openFile->selectFile(
320
				"Select symbol file",
321
				SymbolManager::getFileFilters(),
322
				[this](const std::string& filename) {
323
					auto type = SymbolManager::getTypeForFilter(ImGuiOpenFile::getLastFilter());
324
					loadFile(filename, SymbolManager::LoadEmpty::NOT_ALLOWED, type, {}, {});
325
				});
326
		}
327
328
		im::TreeNode("Symbols per file", ImGuiTreeNodeFlags_DefaultOpen, [&]{
329
			std::optional<FileInfo> reloadAction;
330
			std::string removeAction;
331
			auto drawFile = [&](const std::string& filename, std::string_view error, SymbolFile::Type type,
332
				            std::optional<int> slot, std::optional<uint16_t> segment) {
333
				bool hasError = !error.empty();
334
				auto* file = symbolManager.findFile(filename);
335
				assert((file != nullptr) ^ hasError); // not both
336
				im::StyleColor(hasError, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
337
					auto title = tmpStrCat("File: ", filename);
338
					im::TreeNode(title.c_str(), [&]{
339
						if (hasError) {
340
							ImGui::TextUnformatted(error);
341
						}
342
						im::StyleColor(ImGuiCol_Text, getColor(imColor::TEXT), [&]{
343
							if (!hasError) {
344
								// slot
345
								auto arrowSize = ImGui::GetFrameHeight();
346
								auto extra = arrowSize + 2.0f * style.FramePadding.x;
347
								ImGui::SetNextItemWidth(ImGui::CalcTextSize("3-3").x + extra);
348
								ImGui::AlignTextToFramePadding();
349
350
								auto preview = formatSlot(file->slot, motherBoard);
351
								im::Combo(tmpStrCat("Slot##", filename).data(), preview.c_str(), [&]{
352
									// Set slot and all the symbols in it
353
									auto setSlot = [&](std::optional<uint8_t> newSlot) {
354
										file->slot = newSlot;
355
										for (auto& symbol : file->getSymbols()) {
356
											symbol.slot = newSlot;
357
										}
358
									};
359
									// initial state
360
									if (ImGui::Selectable("-", !file->slot)) {
361
										setSlot({});
362
									}
363
364
									for (uint8_t ps = 0; ps < 4; ++ps) {
365
										if (isSlotExpanded(motherBoard, ps)) {
366
											for (uint8_t ss = 0; ss < 4; ++ss) {
367
												auto slotInfo = tmpStrCat(ps, "-", ss);
368
												int psSs = (ss << 2) + ps;
369
												if (ImGui::Selectable(slotInfo.c_str(), psSs == file->slot)) {
370
													setSlot(psSs);
371
												}
372
											}
373
										} else {
374
											if (ImGui::Selectable(tmpStrCat(ps).c_str(), file->slot && ps == file->slot)) {
375
												setSlot(ps);
376
											}
377
										}
378
									}
379
								});
380
								ImGui::SameLine();
381
382
								// segment
383
								if (!file->hasSegmentInfo) {
384
									ImGui::SetNextItemWidth(ImGui::CalcTextSize("88888").x + 2.0f * style.FramePadding.x);
385
									bool valid = (file->segmentStr == "-") || StringOp::stringTo<uint16_t>(file->segmentStr);
386
									im::StyleColor(!valid, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
387
										if (ImGui::InputText("segment", &file->segmentStr)) {
388
											auto seg = StringOp::stringTo<uint16_t>(file->segmentStr);
389
											file->segment = seg;
390
											for (auto& symbol : file->getSymbols()) {
391
												symbol.segment = seg;
392
											}
393
										}
394
									});
395
									ImGui::SameLine();
396
								}
397
							}
398
399
							if (ImGui::Button("Reload")) {
400
								reloadAction.emplace(filename, std::string{}, type, slot, segment);
401
							}
402
							ImGui::SameLine();
403
							if (ImGui::Button("Remove")) {
404
								removeAction = filename;
405
							}
406
							if (!hasError) {
407
								drawTable<true>(motherBoard, filename);
408
							}
409
						});
410
					});
411
				});
412
			};
413
414
			for (const auto& file : symbolManager.getFiles()) {
415
				drawFile(file.filename, {}, file.type, file.slot, file.segment);
416
			}
417
			for (const auto& info : fileError) {
418
				drawFile(info.filename, info.error, info.type, info.slot, info.segment);
419
			}
420
421
			// only make changes after the above loops (don't loop over changing collection)
422
			if (reloadAction) {
423
				loadFile(reloadAction->filename, SymbolManager::LoadEmpty::NOT_ALLOWED,
424
				         reloadAction->type, reloadAction->slot, reloadAction->segment);
425
			}
426
			if (!removeAction.empty()) {
427
				symbolManager.removeFile(removeAction);
428
				if (auto it = std::ranges::find(fileError, removeAction, &FileInfo::filename);
429
					it != fileError.end()) {
430
					fileError.erase(it);
431
				}
432
			}
433
		});
434
		im::TreeNode("All symbols", [&]{
435
			if (ImGui::Button("Reload all")) {
436
				auto tmp = to_vector(std::views::transform(symbolManager.getFiles(), [&](const auto& file) {
437
					return FileInfo{file.filename, std::string{}, file.type, file.slot, file.segment};
438
				}));
439
				append(tmp, std::move(fileError));
440
				fileError.clear();
441
				for (const auto& info : tmp) {
442
					loadFile(info.filename, SymbolManager::LoadEmpty::NOT_ALLOWED, info.type, info.slot, info.segment);
443
				}
444
			}
445
			ImGui::SameLine();
446
			if (ImGui::Button("Remove all")) {
447
				symbolManager.removeAllFiles();
448
				fileError.clear();
449
			}
450
			drawTable<false>(motherBoard);
451
		});
452
	});
453
}
454
455
void ImGuiSymbols::notifySymbolsChanged()
456
{
457
	symbols.clear();
458
	for (const auto& [fileIdx, file] : enumerate(symbolManager.getFiles())) {
459
		for (auto symbolIdx : xrange(file.symbols.size())) {
460
			symbols.emplace_back(narrow<unsigned>(fileIdx),
461
			                     narrow<unsigned>(symbolIdx));
462
		}
463
	}
464
465
	manager.breakPoints->refreshSymbols();
466
	manager.watchExpr->refreshSymbols();
467
}
468
469
} // namespace openmsx