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