Skip to content

Commit b38dd00

Browse files
committed
Add "assert else" block and pause on error feature
1 parent eb6747f commit b38dd00

6 files changed

Lines changed: 99 additions & 44 deletions

File tree

addons-l10n/en/debugger.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"debugger/debug": "Debug",
44
"debugger/export": "Export",
55
"debugger/clear": "Clear",
6+
"debugger/pause-on-error": "Pause on error",
67
"debugger/close": "Close",
78
"debugger/step": "Step",
89
"debugger/thread": "Thread {id}",
@@ -12,12 +13,14 @@
1213
"debugger/clone-of": "Clone of {sprite}",
1314
"debugger/icon-warn": "Warning",
1415
"debugger/icon-error": "Error",
16+
"debugger/icon-assertion-error": "Assertion Error",
1517
"debugger/empty-string": "(empty string)",
1618
"debugger/export-desc": "Click while holding Shift to customize export format.",
1719
"debugger/block-breakpoint": "breakpoint",
1820
"debugger/block-log": "log %s",
1921
"debugger/block-warn": "warn %s",
2022
"debugger/block-error": "error %s",
23+
"debugger/block-assert": "assert %b else %s",
2124
"debugger/block-start-timer": "start timer %s",
2225
"debugger/block-stop-timer": "stop timer %s",
2326
"debugger/default-timer-label": "timer1",

addons/debugger/addon.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Debugger",
3-
"description": "Adds a new \"debugger\" window to the editor. Allows for logging into the \"Logs\" tab of the debugger window using the \"log\", \"warn\" and \"error\" blocks. The \"breakpoint\" block will pause the project when executed. All running stacks of blocks can be viewed in the \"Threads\" tab of the debugger window, and when paused the \"Step\" button can be used to execute the next block. A graph of frames per second and number of clones can be viewed in the \"Performance\" tab. Allows for timing sections of your code with the \"start timing\" and \"stop timing\" block. Timing results and more are accessible from the \"Timing\" tab.",
3+
"description": "Adds a new \"debugger\" window to the editor. Allows for logging into the \"Logs\" tab of the debugger window using the \"log\", \"warn\" and \"error\" blocks. The \"assert else\" block logs an error if the specified condition is not met. The \"breakpoint\" block will pause the project when executed. All running stacks of blocks can be viewed in the \"Threads\" tab of the debugger window, and when paused the \"Step\" button can be used to execute the next block. A graph of frames per second and number of clones can be viewed in the \"Performance\" tab. Allows for timing sections of your code with the \"start timing\" and \"stop timing\" block. Timing results and more are accessible from the \"Timing\" tab.",
44
"credits": [
55
{
66
"name": "Tacodiva",
@@ -21,6 +21,10 @@
2121
{
2222
"name": "Chrome_Cat",
2323
"link": "https://scratch.mit.edu/users/Chrome_Cat/"
24+
},
25+
{
26+
"name": "Valmontechno",
27+
"link": "https://scratch.mit.edu/users/valmontechno/"
2428
}
2529
],
2630
"userscripts": [
@@ -103,10 +107,8 @@
103107
"relatedAddons": ["fps", "clones"],
104108
"versionAdded": "1.16.0",
105109
"latestUpdate": {
106-
"version": "1.44.0",
107-
"temporaryNotice": "New timing tab to help you with micro-optimization of your project and the debugger window is now resizable.",
108-
"newSettings": ["auto_stop_timing", "show_ratio_time"],
109-
"isMajor": true
110+
"version": "1.45.0",
111+
"temporaryNotice": "New \"assert else\" block to help you to find bugs in your code, and new feature in the logs tab for automatic pause the project when an error is logged there."
110112
},
111113
"libraries": ["chartjs", "scratch-gui", "scratch-vm"]
112114
}

addons/debugger/logs.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default async function createLogsTab({ debug, addon, console, msg }) {
3434

3535
const icon = document.createElement("div");
3636
icon.className = "sa-debugger-log-icon";
37-
if (row.type === "warn" || row.type === "error") {
37+
if (row.type === "warn" || row.type === "error" || row.type === "assertion-error") {
3838
icon.title = msg("icon-" + row.type);
3939
}
4040
root.appendChild(icon);
@@ -124,6 +124,14 @@ export default async function createLogsTab({ debug, addon, console, msg }) {
124124
clearLogs();
125125
});
126126

127+
const pauseOnErrorButton = debug.createIconCheckbox({
128+
text: msg("pause-on-error"),
129+
checked: debug.pauseOnError.enabled
130+
});
131+
pauseOnErrorButton.checkbox.addEventListener("change", () => {
132+
debug.pauseOnError.enabled = pauseOnErrorButton.checkbox.checked;
133+
});
134+
127135
const areLogsEqual = (a, b) =>
128136
a.text === b.text &&
129137
a.type === b.type &&
@@ -182,7 +190,7 @@ export default async function createLogsTab({ debug, addon, console, msg }) {
182190
return {
183191
tab,
184192
content: logView.outerElement,
185-
buttons: [exportButton, trashButton],
193+
buttons: [exportButton, trashButton, pauseOnErrorButton],
186194
show,
187195
hide,
188196
addLog,

addons/debugger/style.css

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
color: hsl(38deg 100% 18%);
232232
background-color: hsl(38deg 100% 95%);
233233
}
234-
.sa-debugger-log[data-type="error"] {
234+
.sa-debugger-log[data-type*="error"] {
235235
border-color: hsl(20deg 100% 85%);
236236
color: hsl(20deg 100% 45%);
237237
background-color: hsl(20deg 100% 95%);
@@ -253,7 +253,7 @@
253253
[data-type="warn"] .sa-debugger-log-icon {
254254
background-image: url(./icons/warning.svg);
255255
}
256-
[data-type="error"] .sa-debugger-log-icon {
256+
[data-type*="error"] .sa-debugger-log-icon {
257257
background-image: url(./icons/error.svg);
258258
}
259259
.sa-debugger-threads .sa-debugger-log-icon {
@@ -460,7 +460,7 @@
460460

461461
/* Shared checkbox styles for timing controls */
462462

463-
.sa-timing-checkbox {
463+
.sa-debugger-checkbox {
464464
margin-right: 4px;
465465
cursor: pointer;
466466
transform: scale(1.2);
@@ -474,20 +474,20 @@
474474
}
475475

476476
/* Make checkbox buttons same height as icon buttons */
477-
.sa-timing-profiling-toggle {
477+
.sa-debugger-profiling-toggle {
478478
height: 16px;
479479
}
480480

481481
.sa-timing-heatmap-content-wrapper {
482482
height: 16px;
483483
}
484484

485-
.sa-timing-checkbox:checked {
485+
.sa-debugger-checkbox:checked {
486486
background-color: white;
487487
position: relative;
488488
}
489489

490-
.sa-timing-checkbox:checked::after {
490+
.sa-debugger-checkbox:checked::after {
491491
content: "";
492492
position: absolute;
493493
left: 3.75px;

addons/debugger/timing/createTimingTab.js

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,12 @@ export default async function createTimingTab({ debug, addon, console, msg }) {
1919
}
2020

2121
function createLineByLineButton() {
22-
const lineByLineButton = debug.createIconButton({
22+
const lineByLineButton = debug.createIconCheckbox({
2323
text: msg("timing-profiling"),
2424
});
2525

26-
// Add checkbox to the left side of the button
27-
const checkbox = Object.assign(document.createElement("input"), {
28-
type: "checkbox",
29-
className: "sa-timing-checkbox",
30-
});
31-
32-
// Add specific class
33-
lineByLineButton.element.classList.add("sa-timing-profiling-toggle");
34-
35-
// Prepend checkbox to button (left side)
36-
lineByLineButton.element.insertBefore(checkbox, lineByLineButton.element.firstChild);
37-
38-
// Make entire button clickable to toggle checkbox
39-
lineByLineButton.element.addEventListener("click", (e) => {
40-
// Don't double-toggle if clicking directly on checkbox
41-
if (e.target !== checkbox) {
42-
checkbox.checked = !checkbox.checked;
43-
checkbox.dispatchEvent(new Event("change"));
44-
}
45-
});
46-
47-
// Handle checkbox change
48-
checkbox.addEventListener("change", () => {
49-
config.showLineByLine = checkbox.checked;
26+
lineByLineButton.checkbox.addEventListener("change", () => {
27+
config.showLineByLine = lineByLineButton.checkbox.checked;
5028
if (config.showLineByLine && !config.isStepThreadPolluted) {
5129
polluteStepThread();
5230
} else if (!config.showLineByLine && config.isStepThreadPolluted) {
@@ -65,7 +43,7 @@ export default async function createTimingTab({ debug, addon, console, msg }) {
6543
// Add checkbox to the left side of the button
6644
const checkbox = Object.assign(document.createElement("input"), {
6745
type: "checkbox",
68-
className: "sa-timing-checkbox",
46+
className: "sa-debugger-checkbox",
6947
});
7048

7149
// Create slider container (initially hidden)

addons/debugger/userscript.js

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ const removeAllChildren = (element) => {
1515
export default async function ({ addon, console, msg }) {
1616
setup(addon);
1717

18+
const pauseOnError = {
19+
_enabled: localStorage.getItem("sa-debugger-pauseOnError") === "true",
20+
get enabled() {
21+
return this._enabled;
22+
},
23+
set enabled(value) {
24+
this._enabled = value;
25+
localStorage.setItem("sa-debugger-pauseOnError", value);
26+
}
27+
}
28+
1829
let logsTab;
1930
let timingTab;
2031
const messagesLoggedBeforeLogsTabLoaded = [];
@@ -68,6 +79,24 @@ export default async function ({ addon, console, msg }) {
6879
displayName: msg("block-error"),
6980
callback: ({ content }, thread) => {
7081
logMessage(content, thread, "error");
82+
if (pauseOnError.enabled) {
83+
pause();
84+
}
85+
},
86+
});
87+
addon.tab.addBlock("\u200B\u200Bassert\u200B\u200B %b else %s", {
88+
args: [
89+
{name: "assertion"},
90+
{name: "message", default: "Assertion failed"}
91+
],
92+
displayName: msg("block-assert"),
93+
callback: ({ assertion, message }, thread) => {
94+
if (!assertion) {
95+
logMessage(message, thread, "assertion-error");
96+
if (pauseOnError.enabled) {
97+
pause();
98+
}
99+
}
71100
},
72101
});
73102
addon.tab.addBlock("\u200B\u200Bstart timer\u200B\u200B %s", {
@@ -133,14 +162,15 @@ export default async function ({ addon, console, msg }) {
133162
className: "sa-debugger-footer-buttons",
134163
});
135164

165+
let activeTab; // activeTab must be initialized before its first access
136166
let isInterfaceVisible = false;
137167
const setInterfaceVisible = (_isVisible) => {
138168
isInterfaceVisible = _isVisible;
139169
interfaceContainer.style.display = isInterfaceVisible ? "flex" : "";
140170
if (isInterfaceVisible) {
141-
activeTab.show();
171+
activeTab?.show();
142172
} else {
143-
activeTab.hide();
173+
activeTab?.hide();
144174
}
145175
};
146176

@@ -210,10 +240,43 @@ export default async function ({ addon, console, msg }) {
210240
return {
211241
element: button,
212242
image: imageElement,
213-
text: textElement,
243+
text: textElement
214244
};
215245
};
216246

247+
const createIconCheckbox = ({ text, icon, description, checked=false }) => {
248+
const button = createIconButton({ text, icon, description });
249+
250+
// Add checkbox to the left side of the button
251+
const checkbox = Object.assign(document.createElement("input"), {
252+
type: "checkbox",
253+
checked,
254+
className: "sa-debugger-checkbox",
255+
});
256+
257+
// Add specific class
258+
button.element.classList.add("sa-debugger-profiling-toggle");
259+
260+
// Prepend checkbox to button (left side)
261+
button.element.insertBefore(checkbox, button.element.firstChild);
262+
263+
// Make entire button clickable to toggle checkbox
264+
button.element.addEventListener("click", (e) => {
265+
// Don't double-toggle if clicking directly on checkbox
266+
if (e.target !== checkbox) {
267+
checkbox.checked = !checkbox.checked;
268+
checkbox.dispatchEvent(new Event("change"));
269+
}
270+
});
271+
272+
return {
273+
element: button.element,
274+
image: button.image,
275+
text: button.text,
276+
checkbox
277+
};
278+
}
279+
217280
const createHeaderTab = ({ text, icon }) => {
218281
const tab = document.createElement("li");
219282
const imageElement = Object.assign(document.createElement("img"), {
@@ -526,14 +589,16 @@ export default async function ({ addon, console, msg }) {
526589

527590
const api = {
528591
debug: {
529-
createIconButton: createIconButton,
592+
createIconButton,
593+
createIconCheckbox,
530594
createHeaderTab,
531595
setHasUnreadMessage,
532596
addAfterStepCallback,
533597
getBlock,
534598
getTargetInfoById,
535599
createBlockLink,
536600
createBlockPreview,
601+
pauseOnError
537602
},
538603
addon,
539604
msg,
@@ -562,7 +627,6 @@ export default async function ({ addon, console, msg }) {
562627
}
563628
}
564629

565-
let activeTab;
566630
const setActiveTab = (tab) => {
567631
if (tab === activeTab) return;
568632
const selectedClass = "sa-debugger-tab-selected";

0 commit comments

Comments
 (0)