Skip to content

Commit e42055f

Browse files
authored
Add diagnostics gathering feature to debugger and UI (#1297)
* Add diagnostics gathering feature to debugger and UI * Improve error messaging in Debugger and optimize token skipping in DiagnosticsDialog * add suggested chaanges * clean up
1 parent f107109 commit e42055f

File tree

4 files changed

+151
-3
lines changed

4 files changed

+151
-3
lines changed

app/src/processing/app/ui/Editor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2946,4 +2946,13 @@ public void show(Component component, int x, int y) {
29462946
super.show(component, x, y);
29472947
}
29482948
}
2949+
2950+
/**
2951+
* Called when clicking on the version number in the footer.
2952+
* Return a string with diagnostic info from the sketch,
2953+
* or empty string (or null) if not implemented/available.
2954+
*/
2955+
public String getSketchDiagnostics() {
2956+
return "";
2957+
}
29492958
}

app/src/processing/app/ui/EditorFooter.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void mousePressed(MouseEvent e) {
109109
Base.DEBUG = !Base.DEBUG;
110110
editor.updateDevelopMenu();
111111
}
112-
copyDebugInformationToClipboard();
112+
copyFullDiagnosticsToClipboard();
113113
}
114114
});
115115

@@ -120,13 +120,23 @@ public void mousePressed(MouseEvent e) {
120120
updateTheme();
121121
}
122122

123-
public static void copyDebugInformationToClipboard() {
124-
var debugInformation = String.join("\n",
123+
public static String getSystemDebugInformation() {
124+
return String.join("\n",
125125
"Version: " + Base.getVersionName(),
126126
"Revision: " + Base.getRevision(),
127127
"OS: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch"),
128128
"Java: " + System.getProperty("java.version") + " " + System.getProperty("java.vendor")
129129
);
130+
}
131+
132+
public static void copyDebugInformationToClipboard() {
133+
var stringSelection = new StringSelection(getSystemDebugInformation());
134+
var clipboard = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
135+
clipboard.setContents(stringSelection, null);
136+
}
137+
138+
public void copyFullDiagnosticsToClipboard() {
139+
var debugInformation = getSystemDebugInformation() + "\n\n" + editor.getSketchDiagnostics();
130140
var stringSelection = new StringSelection(debugInformation);
131141
var clipboard = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
132142
clipboard.setContents(stringSelection, null);

java/src/processing/mode/java/JavaEditor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,4 +2234,14 @@ static private int howManyFloats(List<List<Handle>> handles) {
22342234
}
22352235
return count;
22362236
}
2237+
2238+
@Override
2239+
public String getSketchDiagnostics() {
2240+
if (debugger.isStarted()) {
2241+
return debugger.getDiagnostics();
2242+
} else if (runtime != null) {
2243+
return Debugger.getDiagnostics(runtime);
2244+
}
2245+
return super.getSketchDiagnostics();
2246+
}
22372247
}

java/src/processing/mode/java/debug/Debugger.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.awt.event.KeyEvent;
3232
import java.io.*;
3333
import java.util.ArrayList;
34+
import java.util.Collections;
3435
import java.util.HashMap;
3536
import java.util.HashSet;
3637
import java.util.Iterator;
@@ -458,6 +459,124 @@ public synchronized void stepOut() {
458459
}
459460

460461

462+
463+
/**
464+
* Get diagnostics from the sketch, whether paused or running.
465+
* If running, it will temporarily suspend the VM.
466+
*/
467+
public String getDiagnostics() {
468+
return getDiagnostics(runtime);
469+
}
470+
471+
472+
/**
473+
* Static helper to fetch diagnostics from a Runner, even if not debugging.
474+
* Uses field reads instead of method invocations to avoid thread state issues.
475+
*/
476+
public static String getDiagnostics(Runner targetRuntime) {
477+
if (targetRuntime == null) return "";
478+
VirtualMachine targetVM = targetRuntime.vm();
479+
if (targetVM == null) return "";
480+
481+
targetVM.suspend();
482+
try {
483+
// Find the PApplet subclass
484+
List<ReferenceType> pAppletClasses = targetVM.classesByName("processing.core.PApplet");
485+
if (pAppletClasses.isEmpty()) {
486+
return "processing.core.PApplet not found in VM";
487+
}
488+
ClassType pAppletBase = (ClassType) pAppletClasses.get(0);
489+
490+
ClassType sketchClass = null;
491+
for (ReferenceType type : targetVM.allClasses()) {
492+
if (type instanceof ClassType) {
493+
ClassType ct = (ClassType) type;
494+
ClassType superclass = ct.superclass();
495+
while (superclass != null) {
496+
if (superclass.equals(pAppletBase)) {
497+
sketchClass = ct;
498+
break;
499+
}
500+
superclass = superclass.superclass();
501+
}
502+
if (sketchClass != null) break;
503+
}
504+
}
505+
506+
if (sketchClass == null) {
507+
return "Could not find sketch class extending PApplet";
508+
}
509+
510+
// Find instance
511+
List<ObjectReference> instances = sketchClass.instances(1);
512+
if (instances.isEmpty()) {
513+
return "No instance of " + sketchClass.name() + " found";
514+
}
515+
ObjectReference appletInstance = instances.get(0);
516+
517+
// Build diagnostics by reading fields directly (no thread required)
518+
StringBuilder diag = new StringBuilder();
519+
diag.append("Sketch Diagnostics:\n");
520+
diag.append(" Class: ").append(sketchClass.name()).append("\n");
521+
522+
// Read PApplet fields
523+
appendField(diag, appletInstance, pAppletBase, "width");
524+
appendField(diag, appletInstance, pAppletBase, "height");
525+
appendField(diag, appletInstance, pAppletBase, "pixelDensity");
526+
appendField(diag, appletInstance, pAppletBase, "frameCount");
527+
appendField(diag, appletInstance, pAppletBase, "frameRate");
528+
appendField(diag, appletInstance, pAppletBase, "focused");
529+
530+
// Try to get renderer class name from 'g' field (PGraphics)
531+
try {
532+
Field gField = pAppletBase.fieldByName("g");
533+
if (gField != null) {
534+
Value gValue = appletInstance.getValue(gField);
535+
if (gValue instanceof ObjectReference) {
536+
ObjectReference graphics = (ObjectReference) gValue;
537+
diag.append(" renderer: ").append(graphics.referenceType().name()).append("\n");
538+
}
539+
}
540+
} catch (Exception e) {
541+
diag.append(" renderer: (unavailable)\n");
542+
}
543+
544+
return diag.toString();
545+
546+
} catch (Exception e) {
547+
return "Error gathering diagnostics: " + e.toString();
548+
} finally {
549+
targetVM.resume();
550+
}
551+
}
552+
553+
/**
554+
* Helper to append a field value to the diagnostics string.
555+
*/
556+
private static void appendField(StringBuilder sb, ObjectReference obj, ClassType type, String fieldName) {
557+
try {
558+
Field field = type.fieldByName(fieldName);
559+
if (field != null) {
560+
Value value = obj.getValue(field);
561+
sb.append(" ").append(fieldName).append(": ");
562+
if (value == null) {
563+
sb.append("null");
564+
} else if (value instanceof com.sun.jdi.PrimitiveValue) {
565+
sb.append(value.toString());
566+
} else if (value instanceof StringReference) {
567+
sb.append(((StringReference) value).value());
568+
} else {
569+
sb.append(value.toString());
570+
}
571+
sb.append("\n");
572+
}
573+
} catch (Exception e) {
574+
sb.append(" ").append(fieldName).append(": (error: ").append(e.getMessage()).append(")\n");
575+
}
576+
}
577+
578+
579+
461580
// /** Print the current stack trace. */
462581
// public synchronized void printStackTrace() {
463582
// if (isStarted()) {

0 commit comments

Comments
 (0)