diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index a24c3de..93e6851 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -108,6 +108,7 @@ #define CMD_DEBUG 152 #define CMD_WIN_CALLSTACK 153 #define CMD_WIN_WATCH 154 +#define CMD_WIN_BREAKPOINTS 155 #define IDE_MAX_IMM 1024 #define IDE_DESIGN_W 400 #define IDE_DESIGN_H 300 @@ -184,11 +185,14 @@ static void debugSetBreakTitles(bool paused); static void debugUpdateWindows(void); static void onBreakpointHit(void *ctx, int32_t line); static void onGutterClick(WidgetT *w, int32_t lineNum); +static void navigateToCodeLine(int32_t fileIdx, int32_t codeLine, const char *procName, bool setDbgLine); static void debugNavigateToLine(int32_t concatLine); static void buildVmBreakpoints(void); +static void showBreakpointWindow(void); static void showCallStackWindow(void); static void showLocalsWindow(void); static void showWatchWindow(void); +static void updateBreakpointWindow(void); static void toggleBreakpointLine(int32_t line); static void updateCallStackWindow(void); static void updateLocalsWindow(void); @@ -318,6 +322,7 @@ typedef struct { int32_t fileIdx; // project file index int32_t codeLine; // line within file's code section (1-based) int32_t procIdx; // procedure index at time of toggle (-1 = general) + char procName[64]; // procedure name at time of toggle } IdeBreakpointT; static IdeDebugStateE sDbgState = DBG_IDLE; @@ -337,6 +342,8 @@ static WindowT *sWatchWin = NULL; // Watch window static WidgetT *sWatchList = NULL; // Watch ListView widget static WidgetT *sWatchInput = NULL; // Watch expression input static char *sWatchExprs[16]; // watch expressions (strdup'd) +static WindowT *sBreakpointWin = NULL; // Breakpoints window +static WidgetT *sBreakpointList = NULL; // Breakpoints ListView widget static int32_t sWatchExprCount = 0; // ============================================================ @@ -715,6 +722,7 @@ static void buildWindow(void) { wmAddMenuItem(winMenu, "&Locals", CMD_WIN_LOCALS); wmAddMenuItem(winMenu, "Call &Stack", CMD_WIN_CALLSTACK); wmAddMenuItem(winMenu, "&Watch", CMD_WIN_WATCH); + wmAddMenuItem(winMenu, "&Breakpoints", CMD_WIN_BREAKPOINTS); wmAddMenuSeparator(winMenu); wmAddMenuItem(winMenu, "&Project Explorer", CMD_WIN_PROJECT); wmAddMenuItem(winMenu, "&Toolbox", CMD_WIN_TOOLBOX); @@ -1122,24 +1130,33 @@ static void toggleBreakpointLine(int32_t editorLine) { wgtInvalidatePaint(sEditor); } + updateBreakpointWindow(); return; } } // Add new breakpoint IdeBreakpointT bp; + memset(&bp, 0, sizeof(bp)); bp.fileIdx = fileIdx; bp.codeLine = codeLine; bp.procIdx = sCurProcIdx; + + if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcTable)) { + snprintf(bp.procName, sizeof(bp.procName), "%s.%s", + sProcTable[sCurProcIdx].objName, sProcTable[sCurProcIdx].evtName); + } else { + snprintf(bp.procName, sizeof(bp.procName), "(General)"); + } + arrput(sBreakpoints, bp); sBreakpointCount = (int32_t)arrlen(sBreakpoints); - dvxLog("[BP] Added: file=%d editorLine=%d codeLine=%d procIdx=%d", - fileIdx, editorLine, codeLine, sCurProcIdx); - if (sEditor) { wgtInvalidatePaint(sEditor); } + + updateBreakpointWindow(); } @@ -1182,6 +1199,87 @@ static uint32_t debugLineDecorator(int32_t lineNum, uint32_t *gutterColor, void } +// ============================================================ +// navigateToCodeLine -- show a specific file/line/proc in the code editor +// ============================================================ +// +// fileIdx: project file index (-1 = current) +// codeLine: 1-based line within the file's code section +// procName: "Obj.Evt" (dot-separated) to match editor proc table, or NULL for General +// setDbgLine: if true, update sDbgCurrentLine for the debug decorator + +static void navigateToCodeLine(int32_t fileIdx, int32_t codeLine, const char *procName, bool setDbgLine) { + // Track whether the file changed so we can force showProc + bool fileChanged = (fileIdx >= 0 && fileIdx != sProject.activeFileIdx); + + // Switch to the correct file if needed + if (fileChanged) { + activateFile(fileIdx, ViewCodeE); + } + + // Ensure code window exists + if (!sCodeWin) { + showCodeWindow(); + updateDropdowns(); + sCurProcIdx = -2; + } + + if (sCodeWin && !sCodeWin->visible) { + dvxShowWindow(sAc, sCodeWin); + } + + if (sCodeWin) { + dvxRaiseWindow(sAc, sCodeWin); + } + + // Find the target procedure in the editor's proc table + int32_t targetProcIdx = -1; + int32_t procCount = (int32_t)arrlen(sProcTable); + + if (procName) { + for (int32_t i = 0; i < procCount; i++) { + char fullName[128]; + snprintf(fullName, sizeof(fullName), "%s.%s", + sProcTable[i].objName, sProcTable[i].evtName); + + if (strcasecmp(fullName, procName) == 0) { + targetProcIdx = i; + break; + } + } + } + + // Switch to the target procedure (always force after a file change) + if (targetProcIdx != sCurProcIdx || fileChanged) { + showProc(targetProcIdx); + } + + // Update dropdowns to match + if (targetProcIdx >= 0 && targetProcIdx < procCount) { + selectDropdowns(sProcTable[targetProcIdx].objName, + sProcTable[targetProcIdx].evtName); + } else if (sObjDropdown) { + wgtDropdownSetSelected(sObjDropdown, 0); + } + + // Compute editor-local line within the current proc + int32_t editorLine = codeLine; + + if (sCurProcIdx >= 0 && sCurProcIdx < procCount) { + editorLine = codeLine - sProcTable[sCurProcIdx].lineNum + 1; + } + + if (setDbgLine) { + sDbgCurrentLine = editorLine; + } + + if (sEditor) { + wgtTextAreaGoToLine(sEditor, editorLine); + wgtInvalidatePaint(sEditor); + } +} + + // ============================================================ // debugNavigateToLine -- map concatenated source line to file and navigate // ============================================================ @@ -1205,59 +1303,29 @@ static void debugNavigateToLine(int32_t concatLine) { } } - // Switch to the correct file if needed - if (fileIdx >= 0 && fileIdx != sProject.activeFileIdx) { - activateFile(fileIdx, ViewCodeE); - } - - // Ensure the code editor is loaded for this file - if (!sEditor && sCodeWin) { - // File was activated but editor might not be set up - } - - // Show code window (recreate if it was closed). - // Don't rebuild proc tables — they were built at session start. - // Just recreate the window; showProc below will load the text. - if (!sCodeWin) { - showCodeWindow(); - updateDropdowns(); - sCurProcIdx = -2; // force showProc to reload even if same proc - } - - if (sCodeWin && !sCodeWin->visible) { - dvxShowWindow(sAc, sCodeWin); - } - - if (sCodeWin) { - dvxRaiseWindow(sAc, sCodeWin); - } - // Find which procedure we're in using the VM's PC and the compiled - // module's proc table. Then match by name to the editor's proc table. - int32_t targetProcIdx = -1; // -1 = General section - int32_t procCount = (int32_t)arrlen(sProcTable); + // module's proc table, then build a dot-separated name for navigateToCodeLine. + const char *procName = NULL; + char procBuf[128]; if (sVm && sDbgModule) { - // Find the proc whose codeAddr is closest to (but not exceeding) the PC - const char *procName = NULL; - int32_t bestAddr = -1; + const char *compiledName = NULL; + int32_t bestAddr = -1; for (int32_t i = 0; i < sDbgModule->procCount; i++) { int32_t addr = sDbgModule->procs[i].codeAddr; if (addr <= sVm->pc && addr > bestAddr) { - bestAddr = addr; - procName = sDbgModule->procs[i].name; + bestAddr = addr; + compiledName = sDbgModule->procs[i].name; } } - dvxLog("[Navigate] PC=%d bestAddr=%d procName=%s localLine=%d", - sVm->pc, bestAddr, procName ? procName : "(null)", localLine); + // Convert compiled name (Obj_Evt) to dot-separated (Obj.Evt) for matching + if (compiledName) { + int32_t procCount = (int32_t)arrlen(sProcTable); - // Match the compiled proc name to the editor's proc table - if (procName) { for (int32_t i = 0; i < procCount; i++) { - // Build the full proc name from obj + "_" + evt char fullName[128]; if (sProcTable[i].objName[0] && @@ -1268,44 +1336,17 @@ static void debugNavigateToLine(int32_t concatLine) { snprintf(fullName, sizeof(fullName), "%s", sProcTable[i].evtName); } - dvxLog("[Navigate] match? '%s' vs '%s'", fullName, procName); - - if (strcasecmp(fullName, procName) == 0) { - targetProcIdx = i; + if (strcasecmp(fullName, compiledName) == 0) { + snprintf(procBuf, sizeof(procBuf), "%s.%s", + sProcTable[i].objName, sProcTable[i].evtName); + procName = procBuf; break; } } } } - // Switch to the target procedure - if (targetProcIdx != sCurProcIdx) { - showProc(targetProcIdx); - } - - // Update dropdowns to match - if (targetProcIdx >= 0 && targetProcIdx < procCount) { - selectDropdowns(sProcTable[targetProcIdx].objName, - sProcTable[targetProcIdx].evtName); - } else if (sObjDropdown) { - wgtDropdownSetSelected(sObjDropdown, 0); - } - - // Compute editor-local line within the current proc - int32_t editorLine = localLine; - - if (sCurProcIdx >= 0 && sCurProcIdx < procCount) { - editorLine = localLine - sProcTable[sCurProcIdx].lineNum + 1; - } - - // Store the editor-local line for the decorator - sDbgCurrentLine = editorLine; - dvxLog("[Navigate] targetProc=%d sCurProc=%d editorLine=%d", targetProcIdx, sCurProcIdx, editorLine); - - if (sEditor) { - wgtTextAreaGoToLine(sEditor, editorLine); - wgtInvalidatePaint(sEditor); - } + navigateToCodeLine(fileIdx, localLine, procName, true); } @@ -1321,9 +1362,6 @@ static void buildVmBreakpoints(void) { arrfree(sVmBreakpoints); sVmBreakpoints = NULL; - dvxLog("[Debug] Building VM breakpoints: IDE=%d sourceMap=%d", - sBreakpointCount, sProject.sourceMapCount); - for (int32_t i = 0; i < sBreakpointCount; i++) { int32_t fileIdx = sBreakpoints[i].fileIdx; int32_t codeLine = sBreakpoints[i].codeLine; @@ -1341,16 +1379,11 @@ static void buildVmBreakpoints(void) { int32_t vmLine = base + codeLine - 1; arrput(sVmBreakpoints, vmLine); - - dvxLog(" BP %d: file=%d codeLine=%d startLine=%d isForm=%d -> vmLine=%d", - i, fileIdx, codeLine, sProject.sourceMap[m].startLine, - sProject.files[fileIdx].isForm, vmLine); break; } } } - dvxLog(" VM breakpoints built: %d", (int32_t)arrlen(sVmBreakpoints)); } @@ -1460,10 +1493,7 @@ static void compileAndRun(void) { // If this is the active form in the designer, use form->code. if (sDesigner.form && i == sProject.activeFileIdx) { fileSrc = sDesigner.form->code; - dvxLog("[Compile] Form %d: using designer code (code=%s)", - i, fileSrc ? "yes" : "NULL"); } else if (sProject.files[i].buffer) { - dvxLog("[Compile] Form %d: extracting code from buffer", i); // Extract code from the stashed .frm text (after "End\n") const char *buf = sProject.files[i].buffer; const char *endTag = strstr(buf, "\nEnd\n"); @@ -1540,23 +1570,9 @@ static void compileAndRun(void) { } if (!fileSrc) { - dvxLog("[Compile] File %d: no source found, skipping", i); continue; } - { - char preview[81]; - int32_t pl = (int32_t)strlen(fileSrc); - if (pl > 80) { pl = 80; } - memcpy(preview, fileSrc, pl); - preview[pl] = '\0'; - // Replace newlines for readability - for (int32_t j = 0; j < pl; j++) { - if (preview[j] == '\n' || preview[j] == '\r') { preview[j] = '|'; } - } - dvxLog("[Compile] File %d: startLine=%d src=\"%s...\"", i, line, preview); - } - int32_t startLine = line; // Inject BEGINFORM directive for .frm code sections @@ -1788,7 +1804,6 @@ static void runModule(BasModuleT *mod) { // Hide designer windows while the program runs. // Keep the code window visible if debugging (breakpoints or step-into). bool debugging = sDbgEnabled; - dvxLog("[Run] debugging=%d sDbgEnabled=%d sCodeWin=%p", debugging, sDbgEnabled, (void *)sCodeWin); bool hadFormWin = sFormWin && sFormWin->visible; bool hadToolbox = sToolboxWin && sToolboxWin->visible; bool hadProps = sPropsWin && sPropsWin->visible; @@ -3048,6 +3063,13 @@ static void onClose(WindowT *win) { sWatchList = NULL; sWatchInput = NULL; + if (sBreakpointWin && sBreakpointWin != win) { + dvxDestroyWindow(sAc, sBreakpointWin); + } + + sBreakpointWin = NULL; + sBreakpointList = NULL; + if (sFormWin) { dvxDestroyWindow(sAc, sFormWin); cleanupFormWin(); @@ -4068,6 +4090,10 @@ static void handleWindowCmd(int32_t cmd) { showWatchWindow(); break; + case CMD_WIN_BREAKPOINTS: + showBreakpointWindow(); + break; + case CMD_WIN_TOOLBOX: if (!sToolboxWin) { sToolboxWin = tbxCreate(sAc, &sDesigner); @@ -5902,6 +5928,7 @@ static void showCodeWindow(void) { // (navigateToEventSub, onPrjFileDblClick, etc.) to prevent false dirty marking. updateProjectMenuState(); + updateDirtyIndicators(); } } @@ -5978,6 +6005,165 @@ static void showImmediateWindow(void) { } +// ============================================================ +// showBreakpointWindow +// ============================================================ + +#define MAX_BP_DISPLAY 64 + +static char sBpFiles[MAX_BP_DISPLAY][DVX_MAX_PATH]; +static char sBpProcs[MAX_BP_DISPLAY][64]; +static char sBpLines[MAX_BP_DISPLAY][12]; +static const char *sBpCells[MAX_BP_DISPLAY * 3]; + +static void onBreakpointWinClose(WindowT *win) { + dvxHideWindow(sAc, win); +} + + +static void navigateToBreakpoint(int32_t bpIdx) { + if (bpIdx < 0 || bpIdx >= sBreakpointCount) { + return; + } + + IdeBreakpointT *bp = &sBreakpoints[bpIdx]; + const char *procName = (bp->procIdx >= 0 && bp->procName[0]) ? bp->procName : NULL; + navigateToCodeLine(bp->fileIdx, bp->codeLine, procName, false); +} + + +static void onBreakpointListDblClick(WidgetT *w) { + (void)w; + + if (!sBreakpointList) { + return; + } + + int32_t sel = wgtListViewGetSelected(sBreakpointList); + navigateToBreakpoint(sel); +} + + +static void onBreakpointListKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { + (void)w; + (void)shift; + + // Delete key + if (keyCode != (0x53 | 0x100) && keyCode != 127) { + return; + } + + if (!sBreakpointList || sBreakpointCount == 0) { + return; + } + + // Remove selected breakpoints in reverse order to preserve indices + for (int32_t i = sBreakpointCount - 1; i >= 0; i--) { + if (wgtListViewIsItemSelected(sBreakpointList, i)) { + arrdel(sBreakpoints, i); + } + } + + sBreakpointCount = (int32_t)arrlen(sBreakpoints); + updateBreakpointWindow(); + + // Repaint editor to remove breakpoint dots + if (sEditor) { + wgtInvalidatePaint(sEditor); + } +} + + +static void showBreakpointWindow(void) { + if (sBreakpointWin) { + dvxShowWindow(sAc, sBreakpointWin); + dvxRaiseWindow(sAc, sBreakpointWin); + updateBreakpointWindow(); + return; + } + + int32_t winW = 320; + int32_t winH = 180; + int32_t winX = sAc->display.width - winW - 10; + int32_t winY = sAc->display.height - winH - 10; + + sBreakpointWin = dvxCreateWindow(sAc, "Breakpoints", winX, winY, winW, winH, true); + + if (sBreakpointWin) { + sBreakpointWin->onClose = onBreakpointWinClose; + sBreakpointWin->onMenu = onMenu; + sBreakpointWin->accelTable = sWin ? sWin->accelTable : NULL; + sBreakpointWin->resizable = true; + + WidgetT *root = wgtInitWindow(sAc, sBreakpointWin); + + if (root) { + sBreakpointList = wgtListView(root); + + if (sBreakpointList) { + sBreakpointList->weight = 100; + sBreakpointList->onKeyDown = onBreakpointListKeyDown; + sBreakpointList->onDblClick = onBreakpointListDblClick; + wgtListViewSetMultiSelect(sBreakpointList, true); + + static const ListViewColT cols[] = { + { "File", wgtChars(12), ListViewAlignLeftE }, + { "Procedure", wgtChars(16), ListViewAlignLeftE }, + { "Line", wgtChars(6), ListViewAlignRightE }, + }; + + wgtListViewSetColumns(sBreakpointList, cols, 3); + } + } + } + + updateBreakpointWindow(); +} + + +// ============================================================ +// updateBreakpointWindow +// ============================================================ + +static void updateBreakpointWindow(void) { + if (!sBreakpointList || !sBreakpointWin || !sBreakpointWin->visible) { + return; + } + + if (sBreakpointCount == 0) { + wgtListViewSetData(sBreakpointList, NULL, 0); + return; + } + + int32_t count = sBreakpointCount; + + if (count > MAX_BP_DISPLAY) { + count = MAX_BP_DISPLAY; + } + + for (int32_t i = 0; i < count; i++) { + // File name + if (sBreakpoints[i].fileIdx >= 0 && sBreakpoints[i].fileIdx < sProject.fileCount) { + snprintf(sBpFiles[i], sizeof(sBpFiles[i]), "%s", sProject.files[sBreakpoints[i].fileIdx].path); + } else { + snprintf(sBpFiles[i], sizeof(sBpFiles[i]), "?"); + } + + // Procedure name + snprintf(sBpProcs[i], sizeof(sBpProcs[i]), "%s", sBreakpoints[i].procName); + + // Line number + snprintf(sBpLines[i], sizeof(sBpLines[i]), "%d", sBreakpoints[i].codeLine); + + sBpCells[i * 3] = sBpFiles[i]; + sBpCells[i * 3 + 1] = sBpProcs[i]; + sBpCells[i * 3 + 2] = sBpLines[i]; + } + + wgtListViewSetData(sBreakpointList, sBpCells, count); +} + + // ============================================================ // showLocalsWindow // ============================================================ diff --git a/apps/dvxbasic/runtime/vm.c b/apps/dvxbasic/runtime/vm.c index bb8f11a..15c6097 100644 --- a/apps/dvxbasic/runtime/vm.c +++ b/apps/dvxbasic/runtime/vm.c @@ -3372,18 +3372,6 @@ BasVmResultE basVmStep(BasVmT *vm) { uint16_t lineNum = readUint16(vm); vm->currentLine = lineNum; - // Debug trace: log first 20 OP_LINE values when breakpoints active - if (vm->breakpointCount > 0) { - static int32_t sLineLogCount = 0; - - if (sLineLogCount < 20) { - extern void dvxLog(const char *fmt, ...); - dvxLog(" OP_LINE %d (bp[0]=%d, bpCount=%d)", - (int)lineNum, vm->breakpoints[0], vm->breakpointCount); - sLineLogCount++; - } - } - // Step into: break at any OP_LINE if (vm->debugBreak) { vm->debugBreak = false; diff --git a/core/dvxApp.c b/core/dvxApp.c index 1f07770..17ee880 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -2405,8 +2405,6 @@ static void pollKeyboard(AppContextT *ctx) { // Ctrl+Esc -- system-wide hotkey (e.g. task manager) if (scancode == 0x01 && ascii == 0x1B && (shiftFlags & KEY_MOD_CTRL)) { - dvxLog("[Key] Ctrl+Esc: onCtrlEsc=%p", (void *)ctx->onCtrlEsc); - if (ctx->onCtrlEsc) { ctx->onCtrlEsc(ctx->ctrlEscCtx); } @@ -2414,11 +2412,6 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Debug: log ESC presses that didn't match Ctrl+Esc - if (scancode == 0x01) { - dvxLog("[Key] ESC: scan=%02x ascii=%02x shift=%04x", scancode, ascii, shiftFlags); - } - // F10 -- activate menu bar if (ascii == 0 && scancode == 0x44) { if (ctx->stack.focusedIdx >= 0) {