Breakpoint window added.

This commit is contained in:
Scott Duensing 2026-04-06 19:06:49 -05:00
parent 35f877d3e1
commit 5f2358fcf2
3 changed files with 287 additions and 120 deletions

View file

@ -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
// ============================================================

View file

@ -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;

View file

@ -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) {