openMSX
DebuggableEditor.cc
Go to the documentation of this file.
1#include "DebuggableEditor.hh"
2
3#include "ImGuiCpp.hh"
4#include "ImGuiManager.hh"
5#include "ImGuiSettings.hh"
6#include "ImGuiUtils.hh"
7#include "Shortcuts.hh"
8
9#include "CommandException.hh"
10#include "Debuggable.hh"
11#include "Debugger.hh"
12#include "Interpreter.hh"
13#include "MSXMotherBoard.hh"
14#include "SymbolManager.hh"
15#include "TclObject.hh"
16
17#include "enumerate.hh"
18#include "narrow.hh"
19#include "unreachable.hh"
20
21#include "imgui_stdlib.h"
22
23#include <algorithm>
24#include <array>
25#include <bit>
26#include <cassert>
27#include <cstdint>
28#include <cstdio>
29#include <span>
30
31namespace openmsx {
32
33using namespace std::literals;
34
35static constexpr int MidColsCount = 8; // extra spacing between every mid-cols.
36static constexpr auto HighlightColor = IM_COL32(255, 255, 255, 50); // background color of highlighted bytes.
37static constexpr auto HighlightSymbolColor = IM_COL32(148, 95, 35, 255); // background color of known symbol bytes.
38
39DebuggableEditor::DebuggableEditor(ImGuiManager& manager_, std::string debuggableName_, size_t index)
40 : ImGuiPart(manager_)
41 , symbolManager(manager.getReactor().getSymbolManager())
42 , title(std::move(debuggableName_))
43{
44 debuggableNameSize = title.size();
45 if (index) {
46 strAppend(title, " (", index + 1, ')');
47 }
48}
49
50void DebuggableEditor::save(ImGuiTextBuffer& buf)
51{
52 savePersistent(buf, *this, persistentElements);
53}
54
55void DebuggableEditor::loadLine(std::string_view name, zstring_view value)
56{
57 loadOnePersistent(name, value, *this, persistentElements);
58 parseSearchString(searchString);
59}
60
62{
63 updateAddr = true;
64}
65
66DebuggableEditor::Sizes DebuggableEditor::calcSizes(unsigned memSize) const
67{
68 Sizes s;
69 const auto& style = ImGui::GetStyle();
70
71 s.addrDigitsCount = 0;
72 for (unsigned n = memSize - 1; n > 0; n >>= 4) {
73 ++s.addrDigitsCount;
74 }
75
76 s.lineHeight = ImGui::GetTextLineHeight();
77 s.glyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
78 s.hexCellWidth = truncf(s.glyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
79 s.spacingBetweenMidCols = truncf(s.hexCellWidth * 0.25f); // Every 'MidColsCount' columns we add a bit of extra spacing
80 s.posHexStart = float(s.addrDigitsCount + 2) * s.glyphWidth;
81 auto posHexEnd = s.posHexStart + (s.hexCellWidth * float(columns));
82 s.posAsciiStart = s.posAsciiEnd = posHexEnd;
83 if (showAscii) {
84 int numMacroColumns = (columns + MidColsCount - 1) / MidColsCount;
85 s.posAsciiStart = posHexEnd + s.glyphWidth + float(numMacroColumns) * s.spacingBetweenMidCols;
86 s.posAsciiEnd = s.posAsciiStart + float(columns) * s.glyphWidth;
87 }
88 s.windowWidth = s.posAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.glyphWidth;
89 return s;
90}
91
93{
94 if (!open || !motherBoard) return;
95 auto& debugger = motherBoard->getDebugger();
96 auto* debuggable = debugger.findDebuggable(getDebuggableName());
97 if (!debuggable) return;
98
100
101 unsigned memSize = debuggable->getSize();
102 columns = std::min(columns, narrow<int>(memSize));
103 auto s = calcSizes(memSize);
104 ImGui::SetNextWindowSize(ImVec2(s.windowWidth, s.windowWidth * 0.60f), ImGuiCond_FirstUseEver);
105
106 im::Window(title.c_str(), &open, ImGuiWindowFlags_NoScrollbar, [&]{
107 if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) &&
108 ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
109 ImGui::OpenPopup("context");
110 }
111 drawContents(s, *debuggable, memSize);
112 });
113}
114
115[[nodiscard]] static unsigned DataTypeGetSize(ImGuiDataType dataType)
116{
117 std::array<unsigned, 8> sizes = { 1, 1, 2, 2, 4, 4, 8, 8 };
118 assert(dataType >= 0 && dataType < 8);
119 return sizes[dataType];
120}
121
122[[nodiscard]] static std::optional<int> parseHexDigit(char c)
123{
124 if ('0' <= c && c <= '9') return c - '0';
125 if ('a' <= c && c <= 'f') return c - 'a' + 10;
126 if ('A' <= c && c <= 'F') return c - 'A' + 10;
127 return std::nullopt;
128}
129
130[[nodiscard]] static std::optional<uint8_t> parseDataValue(std::string_view str)
131{
132 if (str.size() == 1) {
133 return parseHexDigit(str[0]);
134 } else if (str.size() == 2) {
135 if (auto digit0 = parseHexDigit(str[0])) {
136 if (auto digit1 = parseHexDigit(str[1])) {
137 return 16 * *digit0 + *digit1;
138 }
139 }
140 }
141 return std::nullopt;
142}
143
144struct ParseAddrResult { // TODO c++23 std::expected might be a good fit here
145 std::string error;
146 unsigned addr = 0;
147};
148[[nodiscard]] static ParseAddrResult parseAddressExpr(
149 std::string_view str, const SymbolManager& symbolManager, Interpreter& interp)
150{
152 if (str.empty()) return r;
153
154 // TODO linear search, probably OK for now, but can be improved if it turns out to be a problem
155 // Note: limited to 16-bit, but larger values trigger an errors and are then handled below, so that's fine
156 if (auto addr = symbolManager.parseSymbolOrValue(str)) {
157 r.addr = *addr;
158 return r;
159 }
160
161 try {
162 r.addr = TclObject(str).eval(interp).getInt(interp);
163 } catch (CommandException& e) {
164 r.error = e.getMessage();
165 }
166 return r;
167}
168
169[[nodiscard]] static std::string formatData(uint8_t val)
170{
171 return strCat(hex_string<2, HexCase::upper>(val));
172}
173
174[[nodiscard]] static char formatAsciiData(uint8_t val)
175{
176 return (val < 32 || val >= 128) ? '.' : char(val);
177}
178
179[[nodiscard]] std::string DebuggableEditor::formatAddr(const Sizes& s, unsigned addr) const
180{
181 return strCat(hex_string<HexCase::upper>(Digits{size_t(s.addrDigitsCount)}, addr));
182}
183void DebuggableEditor::setStrings(const Sizes& s, Debuggable& debuggable)
184{
185 addrStr = strCat("0x", formatAddr(s, currentAddr));
186 auto b = debuggable.read(currentAddr);
187 if (dataEditingActive == HEX ) dataInput = formatData(b);
188 if (dataEditingActive == ASCII) dataInput = std::string(1, formatAsciiData(b));
189}
190bool DebuggableEditor::setAddr(const Sizes& s, Debuggable& debuggable, unsigned memSize, unsigned addr)
191{
192 addr = std::min(addr, memSize - 1);
193 if (currentAddr == addr) return false;
194 currentAddr = addr;
195 setStrings(s, debuggable);
196 return true;
197}
198void DebuggableEditor::scrollAddr(const Sizes& s, Debuggable& debuggable, unsigned memSize, unsigned addr, bool forceScroll)
199{
200 if (setAddr(s, debuggable, memSize, addr) || forceScroll) {
201 im::Child("##scrolling", [&]{
202 int row = narrow<int>(currentAddr) / columns;
203 ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + float(row) * ImGui::GetTextLineHeight());
204 });
205 }
206}
207
208void DebuggableEditor::drawContents(const Sizes& s, Debuggable& debuggable, unsigned memSize)
209{
210 const auto& style = ImGui::GetStyle();
211 if (updateAddr) {
212 updateAddr = false;
213 scrollAddr(s, debuggable, memSize, currentAddr, true);
214 } else {
215 // still clip addr (for the unlikely case that 'memSize' got smaller)
216 setAddr(s, debuggable, memSize, currentAddr);
217 }
218
219 float footerHeight = 0.0f;
220 if (showAddress) {
221 footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
222 }
223 if (showSearch) {
224 footerHeight += style.ItemSpacing.y + 2 * ImGui::GetFrameHeightWithSpacing();
225 }
226 if (showDataPreview) {
227 footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing() + 3 * ImGui::GetTextLineHeightWithSpacing();
228 }
229 if (showSymbolInfo) {
230 footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
231 }
232 // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
233 // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
234 int cFlags = ImGuiWindowFlags_NoMove;
235 // note: with ImGuiWindowFlags_NoNav it happens occasionally that (rapid) cursor-input is passed to the underlying MSX window
236 // without ImGuiWindowFlags_NoNav PgUp/PgDown work, but they are ALSO interpreted as openMSX hotkeys,
237 // though other windows have the same problem.
238 //flags |= ImGuiWindowFlags_NoNav;
239 cFlags |= ImGuiWindowFlags_HorizontalScrollbar;
240 ImGui::BeginChild("##scrolling", ImVec2(0, -footerHeight), ImGuiChildFlags_None, cFlags);
241 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
242 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
243
244 std::optional<unsigned> nextAddr;
245 // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
246 if (addrMode == CURSOR) {
247 const auto& shortcuts = manager.getShortcuts();
248 if ((int(currentAddr) >= columns) &&
249 shortcuts.checkShortcut({.keyChord = ImGuiKey_UpArrow, .repeat = true})) {
250 nextAddr = currentAddr - columns;
251 }
252 if ((int(currentAddr) < int(memSize - columns)) &&
253 shortcuts.checkShortcut({.keyChord = ImGuiKey_DownArrow, .repeat = true})) {
254 nextAddr = currentAddr + columns;
255 }
256 if ((int(currentAddr) > 0) &&
257 shortcuts.checkShortcut({.keyChord = ImGuiKey_LeftArrow, .repeat = true})) {
258 nextAddr = currentAddr - 1;
259 }
260 if ((int(currentAddr) < int(memSize - 1)) &&
261 shortcuts.checkShortcut({.keyChord = ImGuiKey_RightArrow, .repeat = true})) {
262 nextAddr = currentAddr + 1;
263 }
264 }
265
266 // Draw vertical separator
267 auto* drawList = ImGui::GetWindowDrawList();
268 ImVec2 windowPos = ImGui::GetWindowPos();
269 if (showAscii) {
270 drawList->AddLine(ImVec2(windowPos.x + s.posAsciiStart - s.glyphWidth, windowPos.y),
271 ImVec2(windowPos.x + s.posAsciiStart - s.glyphWidth, windowPos.y + 9999),
272 ImGui::GetColorU32(ImGuiCol_Border));
273 }
274
275 auto handleInput = [&](unsigned addr, int width, auto formatData, auto parseData, int extraFlags = 0) {
276 // Display text input on current byte
277 if (dataEditingTakeFocus) {
278 ImGui::SetKeyboardFocusHere();
279 setStrings(s, debuggable);
280 }
281 struct UserData {
282 // TODO: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
283 static int Callback(ImGuiInputTextCallbackData* data) {
284 auto* userData = static_cast<UserData*>(data->UserData);
285 if (!data->HasSelection()) {
286 userData->cursorPos = data->CursorPos;
287 }
288 if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) {
289 // When not editing a byte, always refresh its InputText content pulled from underlying memory data
290 // (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
291 data->DeleteChars(0, data->BufTextLen);
292 userData->format(data);
293 //data->InsertChars(0, ...);
294 //data->SelectionEnd = width;
295 data->SelectionStart = 0;
296 data->CursorPos = 0;
297 }
298 return 0;
299 }
300 std::function<void(ImGuiInputTextCallbackData* data)> format;
301 int cursorPos = -1; // Output
302 };
303 UserData userData;
304 userData.format = formatData;
305 ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue
306 | ImGuiInputTextFlags_AutoSelectAll
307 | ImGuiInputTextFlags_NoHorizontalScroll
308 | ImGuiInputTextFlags_CallbackAlways
309 | ImGuiInputTextFlags_AlwaysOverwrite
310 | extraFlags;
311 ImGui::SetNextItemWidth(s.glyphWidth * float(width));
312 bool dataWrite = false;
313 im::ID(int(addr), [&]{
314 if (ImGui::InputText("##data", &dataInput, flags, UserData::Callback, &userData)) {
315 dataWrite = true;
316 } else if (!ImGui::IsItemActive()) {
317 setStrings(s, debuggable);
318 }
319 });
320 dataEditingTakeFocus = false;
321 dataWrite |= userData.cursorPos >= width;
322 if (nextAddr) dataWrite = false;
323 if (dataWrite) {
324 if (auto value = parseData(dataInput)) {
325 debuggable.write(addr, *value);
326 assert(!nextAddr);
327 nextAddr = currentAddr + 1;
328 }
329 }
330 };
331
332 const auto totalLineCount = int((memSize + columns - 1) / columns);
333 im::ListClipper(totalLineCount, -1, s.lineHeight, [&](int line) {
334 auto addr = unsigned(line) * columns;
335 ImGui::StrCat(formatAddr(s, addr), ':');
336
337 auto previewDataTypeSize = DataTypeGetSize(previewDataType);
338 auto inside = [](unsigned a, unsigned start, unsigned size) {
339 return (start <= a) && (a < (start + size));
340 };
341 auto highLightDataPreview = [&](unsigned a) {
342 return inside(a, currentAddr, previewDataTypeSize);
343 };
344 auto highLightSearch = [&](unsigned a) {
345 if (!searchPattern) return false;
346 auto len = narrow<unsigned>(searchPattern->size());
347 if (searchHighlight == static_cast<int>(SearchHighlight::SINGLE)) {
348 if (searchResult) {
349 return inside(a, *searchResult, len);
350 }
351 } else if (searchHighlight == static_cast<int>(SearchHighlight::ALL)) {
352 int start = std::max(0, int(a - len + 1));
353 for (unsigned i = start; i <= a; ++i) {
354 if (match(debuggable, memSize, i)) return true;
355 }
356 }
357 return false;
358 };
359 auto highLight = [&](unsigned a) {
360 return highLightDataPreview(a) || highLightSearch(a);
361 };
362
363 // Draw Hexadecimal
364 for (int n = 0; n < columns && addr < memSize; ++n, ++addr) {
365 int macroColumn = n / MidColsCount;
366 float bytePosX = s.posHexStart + float(n) * s.hexCellWidth
367 + float(macroColumn) * s.spacingBetweenMidCols;
368 ImGui::SameLine(bytePosX);
369
370 // Draw highlight
371 if (highLight(addr)) {
372 ImVec2 pos = ImGui::GetCursorScreenPos();
373 float highlightWidth = s.glyphWidth * 2;
374 if (highLight(addr + 1)) {
375 highlightWidth = s.hexCellWidth;
376 if (n > 0 && (n + 1) < columns && ((n + 1) % MidColsCount) == 0) {
377 highlightWidth += s.spacingBetweenMidCols;
378 }
379 }
380 drawList->AddRectFilled(pos, ImVec2(pos.x + highlightWidth, pos.y + s.lineHeight), HighlightColor);
381 }
382
383 // Draw symbol highlight
384 if (showSymbolInfo) {
385 auto symbol = symbolManager.lookupValue(narrow_cast<uint16_t>(addr));
386 if (!symbol.empty()) {
387 float highlightWidth = s.glyphWidth * 2;
388 ImVec2 pos = ImGui::GetCursorScreenPos();
389 drawList->AddRectFilled(pos, ImVec2(pos.x + highlightWidth, pos.y + s.lineHeight), HighlightSymbolColor);
390 }
391 }
392
393 if (currentAddr == addr && (dataEditingActive == HEX)) {
394 handleInput(addr, 2,
395 [&](ImGuiInputTextCallbackData* data) { // format
396 auto valStr = formatData(debuggable.read(addr));
397 data->InsertChars(0, valStr.data(), valStr.data() + valStr.size());
398 data->SelectionEnd = 2;
399 },
400 [&](std::string_view data) { // parse
401 return parseDataValue(data);
402 },
403 ImGuiInputTextFlags_CharsHexadecimal);
404 } else {
405 uint8_t b = debuggable.read(addr);
406 im::StyleColor(b == 0 && greyOutZeroes, ImGuiCol_Text, getColor(imColor::TEXT_DISABLED), [&]{
407 ImGui::StrCat(formatData(b), ' ');
408 });
409 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
410 dataEditingActive = HEX;
411 dataEditingTakeFocus = true;
412 nextAddr = addr;
413 }
414 }
415 }
416
417 if (showAscii) {
418 // Draw ASCII values
419 ImGui::SameLine(s.posAsciiStart);
420 gl::vec2 pos = ImGui::GetCursorPos();
421 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
422 addr = unsigned(line) * columns;
423
424 im::ID(line, [&]{
425 // handle via a single full-width button, this ensures we don't miss
426 // clicks because they fall in between two chars
427 if (ImGui::InvisibleButton("ascii", ImVec2(s.posAsciiEnd - s.posAsciiStart, s.lineHeight))) {
428 dataEditingActive = ASCII;
429 dataEditingTakeFocus = true;
430 nextAddr = addr + unsigned((ImGui::GetIO().MousePos.x - scrnPos.x) / s.glyphWidth);
431 }
432 });
433
434 for (int n = 0; n < columns && addr < memSize; ++n, ++addr) {
435 if (highLight(addr)) {
436 auto start = scrnPos + gl::vec2(float(n) * s.glyphWidth, 0.0f);
437 drawList->AddRectFilled(start, start + gl::vec2(s.glyphWidth, s.lineHeight), ImGui::GetColorU32(HighlightColor));
438 }
439
440 ImGui::SetCursorPos(pos);
441 if (currentAddr == addr && (dataEditingActive == ASCII)) {
442 handleInput(addr, 1,
443 [&](ImGuiInputTextCallbackData* data) { // format
444 char valChar = formatAsciiData(debuggable.read(addr));
445 data->InsertChars(0, &valChar, &valChar + 1);
446 data->SelectionEnd = 1;
447 },
448 [&](std::string_view data) -> std::optional<uint8_t> { // parse
449 if (data.empty()) return {};
450 uint8_t b = data[0];
451 if (b < 32 || b >= 128) return {};
452 return b;
453 });
454 } else {
455 uint8_t c = debuggable.read(addr);
456 char display = formatAsciiData(c);
457 im::StyleColor(display != char(c), ImGuiCol_Text, getColor(imColor::TEXT_DISABLED), [&]{
458 ImGui::TextUnformatted(&display, &display + 1);
459 });
460 }
461 pos.x += s.glyphWidth;
462 }
463 }
464 });
465 ImGui::PopStyleVar(2);
466 ImGui::EndChild();
467
468 if (nextAddr) {
469 setAddr(s, debuggable, memSize, *nextAddr);
470 dataEditingTakeFocus = true;
471 addrMode = CURSOR;
472 }
473
474 if (showAddress) {
475 bool forceScroll = ImGui::IsWindowAppearing();
476
477 ImGui::Separator();
478 ImGui::AlignTextToFramePadding();
479 ImGui::TextUnformatted("Address");
480 ImGui::SameLine();
481 ImGui::SetNextItemWidth(2.0f * style.FramePadding.x + ImGui::CalcTextSize("Expression").x + ImGui::GetFrameHeight());
482 if (ImGui::Combo("##mode", &addrMode, "Cursor\0Expression\0Link BC\0Link DE\0Link HL\0")) {
483 dataEditingTakeFocus = true;
484 forceScroll = true;
485 if (addrMode >=2) {
486 static constexpr std::array linkExpr = {
487 "[reg bc]", "[reg de]", "[reg hl]"
488 };
489 addrExpr = linkExpr[addrMode - 2];
490 addrMode = EXPRESSION;
491 }
492 }
493 ImGui::SameLine();
494
495 std::string* as = addrMode == CURSOR ? &addrStr : &addrExpr;
496 auto r = parseAddressExpr(*as, symbolManager, manager.getInterpreter());
497 im::StyleColor(!r.error.empty(), ImGuiCol_Text, getColor(imColor::ERROR), [&] {
498 if (addrMode == EXPRESSION && r.error.empty()) {
499 scrollAddr(s, debuggable, memSize, r.addr, forceScroll);
500 }
501 if (manager.getShortcuts().checkShortcut(Shortcuts::ID::HEX_GOTO_ADDR)) {
502 ImGui::SetKeyboardFocusHere();
503 }
504 ImGui::SetNextItemWidth(15.0f * ImGui::GetFontSize());
505 if (ImGui::InputText("##addr", as, ImGuiInputTextFlags_EnterReturnsTrue)) {
506 auto r2 = parseAddressExpr(addrStr, symbolManager, manager.getInterpreter());
507 if (r2.error.empty()) {
508 scrollAddr(s, debuggable, memSize, r2.addr, forceScroll);
509 dataEditingTakeFocus = true;
510 }
511 }
512 simpleToolTip([&]{
513 return r.error.empty() ? strCat("0x", formatAddr(s, r.addr))
514 : r.error;
515 });
516 });
517 im::Font(manager.fontProp, [&]{
518 HelpMarker("Address-mode:\n"
519 " Cursor: view the cursor position\n"
520 " Expression: continuously re-evaluate an expression and view that address\n"
521 "\n"
522 "Addresses can be entered as:\n"
523 " Decimal or hexadecimal values (e.g. 0x1234)\n"
524 " A calculation like 0x1234 + 7*22\n"
525 " The name of a label (e.g. CHPUT)\n"
526 " A Tcl expression (e.g. [reg hl] to follow the content of register HL)\n"
527 "\n"
528 "Right-click to configure this view.");
529 });
530 }
531 if (showSearch) {
532 ImGui::Separator();
533 drawSearch(s, debuggable, memSize);
534 }
535 if (showDataPreview) {
536 ImGui::Separator();
537 drawPreviewLine(s, debuggable, memSize);
538 }
539 if (showSymbolInfo) {
540 ImGui::Separator();
541 ImGui::AlignTextToFramePadding();
542 auto symbol = symbolManager.lookupValue(narrow_cast<uint16_t>(currentAddr));
543 if (!symbol.empty()) {
544 ImGui::Text("Current symbol: %s", symbol[0]->name.c_str());
545 } else {
546 ImGui::TextUnformatted("No symbol for this address defined"sv);
547 }
548 }
549
550 im::Popup("context", [&]{
551 ImGui::SetNextItemWidth(7.5f * s.glyphWidth + 2.0f * style.FramePadding.x);
552 if (ImGui::InputInt("Columns", &columns, 1, 0)) {
553 columns = std::clamp(columns, 1, MAX_COLUMNS);
554 }
555 ImGui::Checkbox("Show Address bar", &showAddress);
556 ImGui::Checkbox("Show Search pane", &showSearch);
557 ImGui::Checkbox("Show Data Preview", &showDataPreview);
558 ImGui::Checkbox("Show Ascii", &showAscii);
559 ImGui::Checkbox("Show Symbol info", &showSymbolInfo);
560 ImGui::Checkbox("Grey out zeroes", &greyOutZeroes);
561 });
562 im::Popup("NotFound", [&]{
563 ImGui::TextUnformatted("Not found");
564 });
565}
566
567[[nodiscard]] static const char* DataTypeGetDesc(ImGuiDataType dataType)
568{
569 std::array<const char*, 8> desc = {
570 "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64"
571 };
572 assert(dataType >= 0 && dataType < 8);
573 return desc[dataType];
574}
575
576template<typename T>
577[[nodiscard]] static T read(std::span<const uint8_t> buf)
578{
579 assert(buf.size() >= sizeof(T));
580 T t = 0;
581 memcpy(&t, buf.data(), sizeof(T));
582 return t;
583}
584
585static void formatDec(std::span<const uint8_t> buf, ImGuiDataType dataType)
586{
587 switch (dataType) {
588 case ImGuiDataType_S8:
589 ImGui::StrCat(read<int8_t>(buf));
590 break;
591 case ImGuiDataType_U8:
592 ImGui::StrCat(read<uint8_t>(buf));
593 break;
594 case ImGuiDataType_S16:
595 ImGui::StrCat(read<int16_t>(buf));
596 break;
597 case ImGuiDataType_U16:
598 ImGui::StrCat(read<uint16_t>(buf));
599 break;
600 case ImGuiDataType_S32:
601 ImGui::StrCat(read<int32_t>(buf));
602 break;
603 case ImGuiDataType_U32:
604 ImGui::StrCat(read<uint32_t>(buf));
605 break;
606 case ImGuiDataType_S64:
607 ImGui::StrCat(read<int64_t>(buf));
608 break;
609 case ImGuiDataType_U64:
610 ImGui::StrCat(read<uint64_t>(buf));
611 break;
612 default:
614 }
615}
616
617static void formatHex(std::span<const uint8_t> buf, ImGuiDataType data_type)
618{
619 switch (data_type) {
620 case ImGuiDataType_S8:
621 case ImGuiDataType_U8:
622 ImGui::StrCat(hex_string<2>(read<uint8_t>(buf)));
623 break;
624 case ImGuiDataType_S16:
625 case ImGuiDataType_U16:
626 ImGui::StrCat(hex_string<4>(read<uint16_t>(buf)));
627 break;
628 case ImGuiDataType_S32:
629 case ImGuiDataType_U32:
630 ImGui::StrCat(hex_string<8>(read<uint32_t>(buf)));
631 break;
632 case ImGuiDataType_S64:
633 case ImGuiDataType_U64:
634 ImGui::StrCat(hex_string<16>(read<uint64_t>(buf)));
635 break;
636 default:
638 }
639}
640
641static void formatBin(std::span<const uint8_t> buf)
642{
643 for (int i = int(buf.size()) - 1; i >= 0; --i) {
644 ImGui::StrCat(bin_string<8>(buf[i]));
645 if (i != 0) ImGui::SameLine();
646 }
647}
648
649void DebuggableEditor::parseSearchString(std::string_view str)
650{
651 searchPattern.reset();
652 searchResult.reset();
653 std::vector<uint8_t> result;
654
655 if (searchType == static_cast<int>(SearchType::ASCII)) {
656 const auto* begin = std::bit_cast<const uint8_t*>(str.data());
657 const auto* end = begin + str.size();
658 result.assign(begin, end);
659 } else {
660 assert(searchType == static_cast<int>(SearchType::HEX));
661 std::optional<int> partial;
662 for (char c : str) {
663 if (c == ' ') continue; // ignore space characters
664 auto digit = parseHexDigit(c);
665 if (!digit) return; // error: invalid hex digit
666 if (partial) {
667 result.push_back(narrow<uint8_t>(16 * *partial + *digit));
668 partial.reset();
669 } else {
670 partial = *digit;
671 }
672 }
673 if (partial) return; // error: odd number of hex digits
674 }
675
676 searchPattern = std::move(result);
677}
678
679void DebuggableEditor::drawSearch(const Sizes& s, Debuggable& debuggable, unsigned memSize)
680{
681 const auto& style = ImGui::GetStyle();
682
683 bool doSearch = false;
684 auto buttonSize = ImGui::CalcTextSize("Search").x + 2.0f * style.FramePadding.x;
685 ImGui::SetNextItemWidth(-(buttonSize + style.WindowPadding.x));
686 im::StyleColor(!searchPattern, ImGuiCol_Text, getColor(imColor::ERROR), [&] {
687 auto callback = [](ImGuiInputTextCallbackData* data) {
688 if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) {
689 auto& self = *static_cast<DebuggableEditor*>(data->UserData);
690 self.parseSearchString(std::string_view(data->Buf, data->BufTextLen));
691 }
692 return 0;
693 };
694 ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue
695 | ImGuiInputTextFlags_CallbackEdit;
696 if (ImGui::InputText("##search_string", &searchString, flags, callback, this)) {
697 doSearch = true; // pressed enter
698 }
699 });
700 ImGui::SameLine();
701 im::Disabled(!searchPattern, [&]{
702 doSearch |= ImGui::Button("Search");
703 });
704 if (!searchPattern) {
705 simpleToolTip("Must be an even number of hex digits, optionally separated by spaces");
706 }
707 if (searchPattern && doSearch) {
708 search(s, debuggable, memSize);
709 }
710
711 auto arrowSize = ImGui::GetFrameHeight();
712 auto extra = arrowSize + 2.0f * style.FramePadding.x;
713 ImGui::AlignTextToFramePadding();
715 ImGui::SameLine();
716 ImGui::SetNextItemWidth(ImGui::CalcTextSize("Ascii").x + extra);
717 if (ImGui::Combo("##search_type", &searchType, "Hex\0Ascii\0\0")) {
718 parseSearchString(searchString);
719 }
720
721 ImGui::SameLine(0.0f, 2 * ImGui::GetFontSize());
722 ImGui::TextUnformatted("Direction");
723 ImGui::SameLine();
724 ImGui::SetNextItemWidth(ImGui::CalcTextSize("Backwards").x + extra);
725 ImGui::Combo("##search_direction", &searchDirection, "Forwards\0Backwards\0\0");
726
727 ImGui::SameLine(0.0f, 2 * ImGui::GetFontSize());
728 ImGui::TextUnformatted("Highlight");
729 ImGui::SameLine();
730 ImGui::SetNextItemWidth(ImGui::CalcTextSize("Single").x + extra);
731 ImGui::Combo("##search_highlight", &searchHighlight, "None\0Single\0All\0\0");
732}
733
734bool DebuggableEditor::match(Debuggable& debuggable, unsigned memSize, unsigned addr)
735{
736 assert(searchPattern);
737 if ((addr + searchPattern->size()) > memSize) return false;
738 for (auto [i, c] : enumerate(*searchPattern)) {
739 if (debuggable.read(narrow<unsigned>(addr + i)) != c) return false;
740 }
741 return true;
742}
743
744void DebuggableEditor::search(const Sizes& s, Debuggable& debuggable, unsigned memSize)
745{
746 std::optional<unsigned> found;
747 auto test = [&](unsigned addr) {
748 if (match(debuggable, memSize, addr)) {
749 found = addr;
750 return true;
751 }
752 return false;
753 };
754 if (searchDirection == static_cast<int>(SearchDirection::FWD)) {
755 for (unsigned addr = currentAddr + 1; addr < memSize; ++addr) {
756 if (test(addr)) break;
757 }
758 if (!found) {
759 for (unsigned addr = 0; addr <= currentAddr; ++addr) {
760 if (test(addr)) break;
761 }
762 }
763 } else {
764 for (int addr = currentAddr - 1; addr > 0; --addr) {
765 if (test(unsigned(addr))) break;
766 }
767 if (!found) {
768 for (int addr = memSize - 1; addr >= int(currentAddr); --addr) {
769 if (test(unsigned(addr))) break;
770 }
771 }
772 }
773 if (found) {
774 searchResult = *found;
775 scrollAddr(s, debuggable, memSize, *found, false);
776 dataEditingTakeFocus = true;
777 addrMode = CURSOR;
778 } else {
779 searchResult.reset();
780 ImGui::OpenPopup("NotFound");
781 }
782}
783
784void DebuggableEditor::drawPreviewLine(const Sizes& s, Debuggable& debuggable, unsigned memSize)
785{
786 const auto& style = ImGui::GetStyle();
787 ImGui::AlignTextToFramePadding();
788 ImGui::TextUnformatted("Preview as:"sv);
789 ImGui::SameLine();
790 ImGui::SetNextItemWidth((s.glyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
791 if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(previewDataType), ImGuiComboFlags_HeightLargest)) {
792 for (ImGuiDataType n = 0; n < 8; ++n) {
793 if (ImGui::Selectable(DataTypeGetDesc(n), previewDataType == n)) {
794 previewDataType = n;
795 }
796 }
797 ImGui::EndCombo();
798 }
799 ImGui::SameLine();
800 ImGui::SetNextItemWidth((s.glyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
801 ImGui::Combo("##combo_endianess", &previewEndianess, "LE\0BE\0\0");
802
803 std::array<uint8_t, 8> dataBuf = {};
804 auto elemSize = DataTypeGetSize(previewDataType);
805 for (auto i : xrange(elemSize)) {
806 auto addr = currentAddr + i;
807 dataBuf[i] = (addr < memSize) ? debuggable.read(addr) : 0;
808 }
809
810 static constexpr bool nativeIsLittle = std::endian::native == std::endian::little;
811 if (bool previewIsLittle = previewEndianess == LE;
812 nativeIsLittle != previewIsLittle) {
813 std::reverse(dataBuf.begin(), dataBuf.begin() + elemSize);
814 }
815
816 ImGui::TextUnformatted("Dec "sv);
817 ImGui::SameLine();
818 formatDec(dataBuf, previewDataType);
819
820 ImGui::TextUnformatted("Hex "sv);
821 ImGui::SameLine();
822 formatHex(dataBuf, previewDataType);
823
824 ImGui::TextUnformatted("Bin "sv);
825 ImGui::SameLine();
826 formatBin(subspan(dataBuf, 0, elemSize));
827}
828
829} // namespace openmsx
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
TclObject t
void paint(MSXMotherBoard *motherBoard) override
DebuggableEditor(ImGuiManager &manager_, std::string debuggableName, size_t index)
std::string_view getDebuggableName() const
void loadLine(std::string_view name, zstring_view value) override
void save(ImGuiTextBuffer &buf) override
Debuggable * findDebuggable(std::string_view name)
Definition Debugger.cc:66
ImGuiManager & manager
Definition ImGuiPart.hh:30
std::span< Symbol const *const > lookupValue(uint16_t value)
std::optional< uint16_t > parseSymbolOrValue(std::string_view s) const
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:44
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:38
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:25
constexpr double e
Definition Math.hh:21
vecN< 2, float > vec2
Definition gl_vec.hh:382
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void ID(const char *str_id, std::invocable<> auto next)
Definition ImGuiCpp.hh:244
void StyleColor(bool active, Args &&...args)
Definition ImGuiCpp.hh:175
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:110
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:506
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:131
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
Definition ImGuiCpp.hh:538
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:391
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:78
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::optional< bool > match(const BooleanInput &binding, const Event &event, function_ref< int(JoystickId)> getJoyDeadZone)
ImU32 getColor(imColor col)
STL namespace.
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
std::string strCat()
Definition strCat.hh:703
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)