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