IDE fixes.

This commit is contained in:
Scott Duensing 2026-03-27 19:40:28 -05:00
parent 51ade9f119
commit 5171753250
2 changed files with 111 additions and 155 deletions

View file

@ -13,10 +13,10 @@
#include "dvxWm.h" #include "dvxWm.h"
#include "shellApp.h" #include "shellApp.h"
#include "widgetBox.h" #include "widgetBox.h"
#include "widgetButton.h"
#include "widgetLabel.h" #include "widgetLabel.h"
#include "widgetTextInput.h" #include "widgetTextInput.h"
#include "widgetDropdown.h" #include "widgetDropdown.h"
#include "widgetSplitter.h"
#include "widgetStatusBar.h" #include "widgetStatusBar.h"
#include "../compiler/parser.h" #include "../compiler/parser.h"
@ -24,6 +24,8 @@
#include "../runtime/vm.h" #include "../runtime/vm.h"
#include "../runtime/values.h" #include "../runtime/values.h"
#include "stb_ds_wrap.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@ -35,10 +37,10 @@
// Constants // Constants
// ============================================================ // ============================================================
#define IDE_WIN_W 560 #define IDE_WIN_X 10
#define IDE_WIN_H 400 #define IDE_WIN_Y 10
#define IDE_BTN_W 80 #define IDE_WIN_W 620
#define IDE_BTN_SPACING 8 #define IDE_WIN_H 460
#define IDE_MAX_SOURCE 65536 #define IDE_MAX_SOURCE 65536
#define IDE_MAX_OUTPUT 32768 #define IDE_MAX_OUTPUT 32768
#define IDE_STEP_SLICE 10000 // VM steps per slice before yielding to DVX #define IDE_STEP_SLICE 10000 // VM steps per slice before yielding to DVX
@ -50,8 +52,6 @@
#define CMD_CLEAR 103 #define CMD_CLEAR 103
#define CMD_EXIT 104 #define CMD_EXIT 104
#define CMD_RUN_NOCMP 105 #define CMD_RUN_NOCMP 105
#define IDE_MAX_PROCS 128
#define IDE_MAX_IMM 1024 #define IDE_MAX_IMM 1024
// ============================================================ // ============================================================
@ -65,10 +65,6 @@ static void compileAndRun(void);
static void loadFile(void); static void loadFile(void);
static void onClose(WindowT *win); static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId); static void onMenu(WindowT *win, int32_t menuId);
static void onOpenClick(WidgetT *w);
static void onRunClick(WidgetT *w);
static void onStopClick(WidgetT *w);
static void onClearClick(WidgetT *w);
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx); static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
static void evaluateImmediate(const char *expr); static void evaluateImmediate(const char *expr);
static void loadFrmFiles(BasFormRtT *rt); static void loadFrmFiles(BasFormRtT *rt);
@ -111,8 +107,9 @@ typedef struct {
int32_t lineNum; int32_t lineNum;
} IdeProcEntryT; } IdeProcEntryT;
static IdeProcEntryT sProcTable[IDE_MAX_PROCS]; static IdeProcEntryT *sProcTable = NULL; // stb_ds dynamic array
static int32_t sProcCount = 0; static const char **sObjItems = NULL; // stb_ds dynamic array
static const char **sEvtItems = NULL; // stb_ds dynamic array
// ============================================================ // ============================================================
// App descriptor // App descriptor
@ -151,10 +148,7 @@ int32_t appMain(DxeAppContextT *ctx) {
// ============================================================ // ============================================================
static void buildWindow(void) { static void buildWindow(void) {
int32_t winX = (sAc->display.width - IDE_WIN_W) / 2; sWin = dvxCreateWindow(sAc, "DVX BASIC", IDE_WIN_X, IDE_WIN_Y, IDE_WIN_W, IDE_WIN_H, true);
int32_t winY = (sAc->display.height - IDE_WIN_H) / 4;
sWin = dvxCreateWindow(sAc, "DVX BASIC", winX, winY, IDE_WIN_W, IDE_WIN_H, true);
if (!sWin) { if (!sWin) {
return; return;
@ -187,9 +181,12 @@ static void buildWindow(void) {
// Widget tree // Widget tree
WidgetT *root = wgtInitWindow(sAc, sWin); WidgetT *root = wgtInitWindow(sAc, sWin);
// Source code editor (top half) // Horizontal splitter: editor on top, output+immediate on bottom
WidgetT *editorFrame = wgtFrame(root, "Source"); WidgetT *splitter = wgtSplitter(root, false);
editorFrame->weight = 100; splitter->weight = 100;
// Top pane: source code editor
WidgetT *editorFrame = wgtFrame(splitter, "Source");
// Object/Event dropdown row // Object/Event dropdown row
WidgetT *dropdownRow = wgtHBox(editorFrame); WidgetT *dropdownRow = wgtHBox(editorFrame);
@ -198,10 +195,12 @@ static void buildWindow(void) {
sObjDropdown = wgtDropdown(dropdownRow); sObjDropdown = wgtDropdown(dropdownRow);
sObjDropdown->weight = 100; sObjDropdown->weight = 100;
sObjDropdown->onChange = onObjDropdownChange; sObjDropdown->onChange = onObjDropdownChange;
wgtDropdownSetItems(sObjDropdown, NULL, 0);
sEvtDropdown = wgtDropdown(dropdownRow); sEvtDropdown = wgtDropdown(dropdownRow);
sEvtDropdown->weight = 100; sEvtDropdown->weight = 100;
sEvtDropdown->onChange = onEvtDropdownChange; sEvtDropdown->onChange = onEvtDropdownChange;
wgtDropdownSetItems(sEvtDropdown, NULL, 0);
sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE); sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE);
sEditor->weight = 100; sEditor->weight = 100;
@ -209,35 +208,16 @@ static void buildWindow(void) {
wgtTextAreaSetShowLineNumbers(sEditor, true); wgtTextAreaSetShowLineNumbers(sEditor, true);
wgtTextAreaSetAutoIndent(sEditor, true); wgtTextAreaSetAutoIndent(sEditor, true);
// Button bar // Bottom pane: output + immediate
WidgetT *btnRow = wgtHBox(root); WidgetT *bottomPane = wgtVBox(splitter);
btnRow->spacing = wgtPixels(IDE_BTN_SPACING);
WidgetT *openBtn = wgtButton(btnRow, "&Open..."); WidgetT *outputFrame = wgtFrame(bottomPane, "Output");
openBtn->onClick = onOpenClick; outputFrame->weight = 70;
openBtn->prefW = wgtPixels(IDE_BTN_W);
WidgetT *runBtn = wgtButton(btnRow, "&Run");
runBtn->onClick = onRunClick;
runBtn->prefW = wgtPixels(IDE_BTN_W);
WidgetT *stopBtn = wgtButton(btnRow, "&Stop");
stopBtn->onClick = onStopClick;
stopBtn->prefW = wgtPixels(IDE_BTN_W);
WidgetT *clearBtn = wgtButton(btnRow, "&Clear");
clearBtn->onClick = onClearClick;
clearBtn->prefW = wgtPixels(IDE_BTN_W);
// Output area
WidgetT *outputFrame = wgtFrame(root, "Output");
outputFrame->weight = 80;
sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT); sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT);
sOutput->weight = 100; sOutput->weight = 100;
sOutput->readOnly = true; sOutput->readOnly = true;
// Immediate window WidgetT *immFrame = wgtFrame(bottomPane, "Immediate");
WidgetT *immFrame = wgtFrame(root, "Immediate");
immFrame->weight = 30; immFrame->weight = 30;
sImmediate = wgtTextArea(immFrame, IDE_MAX_IMM); sImmediate = wgtTextArea(immFrame, IDE_MAX_IMM);
sImmediate->weight = 100; sImmediate->weight = 100;
@ -248,7 +228,6 @@ static void buildWindow(void) {
sStatus = wgtLabel(statusBar, ""); sStatus = wgtLabel(statusBar, "");
sStatus->weight = 100; sStatus->weight = 100;
dvxFitWindow(sAc, sWin);
} }
// ============================================================ // ============================================================
@ -748,6 +727,7 @@ static void loadFile(void) {
snprintf(title, sizeof(title), "DVX BASIC - %s", path); snprintf(title, sizeof(title), "DVX BASIC - %s", path);
dvxSetTitle(sAc, sWin, title); dvxSetTitle(sAc, sWin, title);
updateDropdowns();
setStatus("File loaded."); setStatus("File loaded.");
} }
} }
@ -817,15 +797,7 @@ static void loadFrmFiles(BasFormRtT *rt) {
} }
// ============================================================
// onClearClick
// ============================================================
static void onClearClick(WidgetT *w) {
(void)w;
clearOutput();
setStatus("Output cleared.");
}
// ============================================================ // ============================================================
// onClose // onClose
@ -845,6 +817,13 @@ static void onClose(WindowT *win) {
sCachedModule = NULL; sCachedModule = NULL;
} }
arrfree(sProcTable);
arrfree(sObjItems);
arrfree(sEvtItems);
sProcTable = NULL;
sObjItems = NULL;
sEvtItems = NULL;
dvxDestroyWindow(sAc, win); dvxDestroyWindow(sAc, win);
} }
@ -903,22 +882,18 @@ static void onEvtDropdownChange(WidgetT *w) {
int32_t objIdx = wgtDropdownGetSelected(sObjDropdown); int32_t objIdx = wgtDropdownGetSelected(sObjDropdown);
int32_t evtIdx = wgtDropdownGetSelected(sEvtDropdown); int32_t evtIdx = wgtDropdownGetSelected(sEvtDropdown);
if (objIdx < 0 || evtIdx < 0) { if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems) || evtIdx < 0) {
return; return;
} }
// Build the object name from the dropdown const char *selObj = sObjItems[objIdx];
// Dropdown items are set in updateDropdowns; find matching proc
const char *cur = wgtGetText(sObjDropdown);
if (!cur) { // Find the proc matching the selected object + event
return;
}
// Find the proc entry matching the selected object + event indices
int32_t matchIdx = 0; int32_t matchIdx = 0;
int32_t procCount = (int32_t)arrlen(sProcTable);
for (int32_t i = 0; i < sProcCount; i++) { for (int32_t i = 0; i < procCount; i++) {
if (strcasecmp(sProcTable[i].objName, selObj) == 0) {
if (matchIdx == evtIdx) { if (matchIdx == evtIdx) {
wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum); wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum);
return; return;
@ -927,6 +902,7 @@ static void onEvtDropdownChange(WidgetT *w) {
matchIdx++; matchIdx++;
} }
} }
}
// ============================================================ // ============================================================
@ -996,47 +972,25 @@ static void onObjDropdownChange(WidgetT *w) {
int32_t objIdx = wgtDropdownGetSelected(sObjDropdown); int32_t objIdx = wgtDropdownGetSelected(sObjDropdown);
if (objIdx < 0) { if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems)) {
return; return;
} }
// Collect unique object names to find which one is selected const char *selObj = sObjItems[objIdx];
char objNames[IDE_MAX_PROCS][64];
int32_t objCount = 0;
for (int32_t i = 0; i < sProcCount; i++) {
bool found = false;
for (int32_t j = 0; j < objCount; j++) {
if (strcasecmp(objNames[j], sProcTable[i].objName) == 0) {
found = true;
break;
}
}
if (!found && objCount < IDE_MAX_PROCS) {
memcpy(objNames[objCount], sProcTable[i].objName, 64);
objCount++;
}
}
if (objIdx >= objCount) {
return;
}
const char *selObj = objNames[objIdx];
// Build event list for the selected object // Build event list for the selected object
const char *evtItems[IDE_MAX_PROCS]; arrsetlen(sEvtItems, 0);
int32_t evtCount = 0;
for (int32_t i = 0; i < sProcCount; i++) { int32_t procCount = (int32_t)arrlen(sProcTable);
if (strcasecmp(sProcTable[i].objName, selObj) == 0 && evtCount < IDE_MAX_PROCS) {
evtItems[evtCount++] = sProcTable[i].evtName; for (int32_t i = 0; i < procCount; i++) {
if (strcasecmp(sProcTable[i].objName, selObj) == 0) {
arrput(sEvtItems, sProcTable[i].evtName);
} }
} }
wgtDropdownSetItems(sEvtDropdown, evtItems, evtCount); int32_t evtCount = (int32_t)arrlen(sEvtItems);
wgtDropdownSetItems(sEvtDropdown, sEvtItems, evtCount);
if (evtCount > 0) { if (evtCount > 0) {
wgtDropdownSetSelected(sEvtDropdown, 0); wgtDropdownSetSelected(sEvtDropdown, 0);
@ -1048,35 +1002,6 @@ static void onObjDropdownChange(WidgetT *w) {
// onOpenClick // onOpenClick
// ============================================================ // ============================================================
static void onOpenClick(WidgetT *w) {
(void)w;
loadFile();
}
// ============================================================
// onRunClick
// ============================================================
static void onRunClick(WidgetT *w) {
(void)w;
compileAndRun();
}
// ============================================================
// onStopClick
// ============================================================
static void onStopClick(WidgetT *w) {
(void)w;
if (sVm) {
sVm->running = false;
setStatus("Program stopped.");
}
}
// ============================================================ // ============================================================
// printCallback // printCallback
// ============================================================ // ============================================================
@ -1128,7 +1053,10 @@ static void setStatus(const char *text) {
// '_' into ObjectName and EventName (e.g. "Command1_Click"). // '_' into ObjectName and EventName (e.g. "Command1_Click").
static void updateDropdowns(void) { static void updateDropdowns(void) {
sProcCount = 0; // Reset dynamic arrays
arrsetlen(sProcTable, 0);
arrsetlen(sObjItems, 0);
arrsetlen(sEvtItems, 0);
if (!sEditor || !sObjDropdown || !sEvtDropdown) { if (!sEditor || !sObjDropdown || !sEvtDropdown) {
return; return;
@ -1154,7 +1082,7 @@ static void updateDropdowns(void) {
bool isSub = (strncasecmp(pos, "SUB ", 4) == 0); bool isSub = (strncasecmp(pos, "SUB ", 4) == 0);
bool isFunc = (strncasecmp(pos, "FUNCTION ", 9) == 0); bool isFunc = (strncasecmp(pos, "FUNCTION ", 9) == 0);
if ((isSub || isFunc) && sProcCount < IDE_MAX_PROCS) { if (isSub || isFunc) {
pos += isSub ? 4 : 9; pos += isSub ? 4 : 9;
// Skip whitespace after keyword // Skip whitespace after keyword
@ -1173,6 +1101,10 @@ static void updateDropdowns(void) {
procName[nameLen] = '\0'; procName[nameLen] = '\0';
// Split on '_' into object + event // Split on '_' into object + event
IdeProcEntryT entry;
memset(&entry, 0, sizeof(entry));
entry.lineNum = lineNum;
char *underscore = strchr(procName, '_'); char *underscore = strchr(procName, '_');
if (underscore) { if (underscore) {
@ -1182,16 +1114,15 @@ static void updateDropdowns(void) {
objLen = 63; objLen = 63;
} }
memcpy(sProcTable[sProcCount].objName, procName, objLen); memcpy(entry.objName, procName, objLen);
sProcTable[sProcCount].objName[objLen] = '\0'; entry.objName[objLen] = '\0';
snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", underscore + 1); snprintf(entry.evtName, sizeof(entry.evtName), "%s", underscore + 1);
} else { } else {
snprintf(sProcTable[sProcCount].objName, sizeof(sProcTable[sProcCount].objName), "%s", "(General)"); snprintf(entry.objName, sizeof(entry.objName), "%s", "(General)");
snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", procName); snprintf(entry.evtName, sizeof(entry.evtName), "%s", procName);
} }
sProcTable[sProcCount].lineNum = lineNum; arrput(sProcTable, entry);
sProcCount++;
} }
// Advance to end of line // Advance to end of line
@ -1207,28 +1138,31 @@ static void updateDropdowns(void) {
} }
// Build unique object names for the Object dropdown // Build unique object names for the Object dropdown
const char *objItems[IDE_MAX_PROCS]; int32_t procCount = (int32_t)arrlen(sProcTable);
int32_t objCount = 0;
for (int32_t i = 0; i < sProcCount; i++) { for (int32_t i = 0; i < procCount; i++) {
bool found = false; bool found = false;
int32_t objCount = (int32_t)arrlen(sObjItems);
for (int32_t j = 0; j < objCount; j++) { for (int32_t j = 0; j < objCount; j++) {
if (strcasecmp(objItems[j], sProcTable[i].objName) == 0) { if (strcasecmp(sObjItems[j], sProcTable[i].objName) == 0) {
found = true; found = true;
break; break;
} }
} }
if (!found && objCount < IDE_MAX_PROCS) { if (!found) {
objItems[objCount++] = sProcTable[i].objName; arrput(sObjItems, sProcTable[i].objName);
} }
} }
wgtDropdownSetItems(sObjDropdown, objItems, objCount); int32_t objCount = (int32_t)arrlen(sObjItems);
wgtDropdownSetItems(sObjDropdown, sObjItems, objCount);
if (objCount > 0) { if (objCount > 0) {
wgtDropdownSetSelected(sObjDropdown, 0); wgtDropdownSetSelected(sObjDropdown, 0);
onObjDropdownChange(sObjDropdown); onObjDropdownChange(sObjDropdown);
} else {
wgtDropdownSetItems(sEvtDropdown, NULL, 0);
} }
} }

View file

@ -2516,8 +2516,10 @@ void wgtTextAreaGoToLine(WidgetT *w, int32_t line) {
ta->selAnchor = lineStart; ta->selAnchor = lineStart;
ta->selCursor = lineStart + lineL; ta->selCursor = lineStart + lineL;
// Scroll into view // Scroll to put the target line near the top of the visible area
AppContextT *ctx = wgtGetContext(w); AppContextT *ctx = wgtGetContext(w);
if (ctx) {
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t gutterW = textAreaGutterWidth(w, font); int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW; int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
@ -2528,7 +2530,27 @@ void wgtTextAreaGoToLine(WidgetT *w, int32_t line) {
if (visCols < 1) { visCols = 1; } if (visCols < 1) { visCols = 1; }
if (visRows < 1) { visRows = 1; } if (visRows < 1) { visRows = 1; }
textAreaEnsureVisible(w, visRows, visCols); // Place target line 1/4 from top for context, then clamp
int32_t targetScroll = row - visRows / 4;
if (targetScroll < 0) {
targetScroll = 0;
}
int32_t maxScroll = totalLines - visRows;
if (maxScroll < 0) {
maxScroll = 0;
}
if (targetScroll > maxScroll) {
targetScroll = maxScroll;
}
ta->scrollRow = targetScroll;
ta->scrollCol = 0;
}
wgtInvalidatePaint(w); wgtInvalidatePaint(w);
} }