diff --git a/.gitignore b/.gitignore
index e3b98dcf5f..c57a023c7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -92,4 +92,7 @@ bin-test
# VS Code Java project files
.project
-.vscode/
\ No newline at end of file
+.vscode/
+
+# ANTLR intermediate files
+java/src/processing/mode/java/preproc/.antlr
\ No newline at end of file
diff --git a/app/build.xml b/app/build.xml
index c48adeb02d..97b3bb0606 100644
--- a/app/build.xml
+++ b/app/build.xml
@@ -160,7 +160,8 @@
lib/ant-launcher.jar;
lib/flatlaf.jar;
lib/jna.jar;
- lib/jna-platform.jar"
+ lib/jna-platform;
+ lib/build-flexmark-all.jar"
debug="on"
nowarn="true">
diff --git a/app/lib/build-flexmark-all.jar b/app/lib/build-flexmark-all.jar
new file mode 100644
index 0000000000..9e17821c0c
Binary files /dev/null and b/app/lib/build-flexmark-all.jar differ
diff --git a/app/src/processing/app/ui/EditorStatus.java b/app/src/processing/app/ui/EditorStatus.java
index 02a85f10e1..471a3e8bbb 100644
--- a/app/src/processing/app/ui/EditorStatus.java
+++ b/app/src/processing/app/ui/EditorStatus.java
@@ -35,15 +35,30 @@
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
+import java.awt.BorderLayout;
+import java.awt.Desktop;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
+import javax.swing.JScrollPane;
+import javax.swing.event.*;
+import javax.swing.border.Border;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.net.URI;
+
+import java.io.IOException;
import processing.app.Platform;
import processing.app.Preferences;
import processing.core.PApplet;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.data.MutableDataSet;
/**
* Panel just below the editing area that contains status messages.
@@ -68,6 +83,7 @@ public class EditorStatus extends BasicSplitPaneDivider {
int mode;
String message = "";
+ String friendlyMessage = "";
int messageRight;
String url;
@@ -79,6 +95,8 @@ public class EditorStatus extends BasicSplitPaneDivider {
static final int COLLAPSE_PRESSED = 4;
static final int CLIPBOARD_ROLLOVER = 5;
static final int CLIPBOARD_PRESSED = 6;
+ static final int MORE_INFO_ROLLOVER = 7;
+ static final int MORE_INFO_PRESSED = 8;
int mouseState;
Font font;
@@ -91,6 +109,8 @@ public class EditorStatus extends BasicSplitPaneDivider {
ImageIcon[] searchIcon;
ImageIcon[] collapseIcon;
ImageIcon[] expandIcon;
+ ImageIcon[] moreInfoIcon;
+
float btnEnabledAlpha;
float btnRolloverAlpha;
@@ -106,8 +126,115 @@ public class EditorStatus extends BasicSplitPaneDivider {
boolean collapsed = false;
boolean indeterminate;
+ public boolean isFriendly = false;
Thread thread;
+ public class friendlyErrorPopup{
+
+ private String messageText = "It looks like either a class name or semicolon is missing near 'asdfsda' on line 2!
Hint: Either a [class](https://processing.org/reference/class.html) was not given a name or a [semicolon](https://processing.org/reference/semicolon.html) is missing.
Suggestion: If the name was forgotten, add it and ensure that it does not start with a number. Make sure it only includes letters, numbers, dollar signs ($) and underscores (_). Also check that the name is not a keyword that is part of the language like \"true\", \"class\" or \"setup\" . If you are missing a semicolon, check the ends of the [statements](https://processing.org/examples/statementscomments.html) near 'if (' and line '43234234'.
For more: [Variable Examples](https://processing.org/examples/variables.html)";
+ private JFrame popupFrame;
+ final int PROCESSING_SAYS_OFFSET = 5;
+
+ String markdownToHtml(String target) {
+ MutableDataSet options = new MutableDataSet();
+ Parser parser = Parser.builder(options).build();
+ HtmlRenderer renderer = HtmlRenderer.builder(options).build();
+ Node document = parser.parse(target);
+ String html = renderer.render(document);
+ html = "
🔵Processing says:" + html + "
" ;
+ return html;
+ }
+
+ public friendlyErrorPopup(String messageText){
+
+ int firstLineIndex = messageText.indexOf(" ");
+ String firstSentence = messageText.substring(0,firstLineIndex);
+ int lineCounter = 0;
+ int newLineCount = 0;
+ String pureText = messageText;
+
+ Pattern newLineCounter = Pattern.compile(" ");
+ Matcher newLineMatcher = newLineCounter.matcher(pureText);
+
+ Pattern linkSkipper = Pattern.compile("\\[([^\\]]+)\\]\\([^\\)]+\\)");
+ Matcher linkSkipperMatcher = linkSkipper.matcher(pureText);
+
+ // allows for error messages with markdown links in the first line although there currently are none
+ while (linkSkipperMatcher.find()){
+
+ pureText = pureText.replace(linkSkipperMatcher.group(0),linkSkipperMatcher.group(1));
+
+ }
+
+ firstSentence = pureText.substring(0,pureText.indexOf(" "));
+ firstLineIndex = firstSentence.length();
+
+ int index = 0;
+
+ while (index < pureText.length()) {
+ lineCounter++;
+ int nextBreakIndex = pureText.indexOf(" ", index);
+ index = (((nextBreakIndex - index) <= firstLineIndex) && nextBreakIndex != -1) ? nextBreakIndex + 4 : index + firstLineIndex;
+ }
+
+ pureText = pureText.replaceAll(" ","");
+
+
+ messageText = markdownToHtml(messageText);
+
+ JEditorPane errorPane = new JEditorPane();
+ errorPane.setContentType("text/html");
+
+ JScrollPane scrollPane = new JScrollPane(errorPane);
+
+ //not actually necessary but it allows the window resizing to work
+ errorPane.setFont(new Font("Source Code PRO", Font.PLAIN, 15));
+ errorPane.setText(messageText);
+ errorPane.setBackground(Color.decode("#fafcff"));
+ errorPane.setEditable(false);
+ java.awt.FontMetrics fontMetrics = errorPane.getFontMetrics(errorPane.getFont());
+
+ int popupWidth = fontMetrics.stringWidth(firstSentence) - 25;
+ int popupHeight = (lineCounter + PROCESSING_SAYS_OFFSET) * fontMetrics.getHeight();
+
+ errorPane.addHyperlinkListener((event) -> {
+ HyperlinkEvent.EventType eventType = event.getEventType();
+ boolean linkClicked = eventType == HyperlinkEvent.EventType.ACTIVATED;
+ if (linkClicked) {
+ String url = event.getURL().toString();
+ URI targetUri = URI.create(url);
+
+ try {
+ Desktop.getDesktop().browse(targetUri);
+ }
+ catch(IOException e) {
+ }
+ }
+ }
+ );
+
+ JFrame frame = new JFrame("[classname.pde]");
+
+ Border paddingBorder = BorderFactory.createEmptyBorder(0, 20, 0, 0); // Customize the padding as needed
+ Border border = BorderFactory.createLineBorder(java.awt.Color.decode("#58a2d1"), 10); // Customize the border as needed\
+ Border compoundBorder = BorderFactory.createCompoundBorder(border, paddingBorder);
+ errorPane.setBorder(compoundBorder);
+
+ // Set the preferred size for the scroll pane
+ scrollPane.setBorder(null);
+ frame.setSize(popupWidth, popupHeight);
+
+ // Create a container panel to hold the scroll pane
+ JPanel containerPanel = new JPanel(new BorderLayout());
+ containerPanel.add(scrollPane, BorderLayout.CENTER);
+
+ frame.setContentPane(containerPanel);
+
+ frame.setVisible(true);
+
+ }
+
+ }
public EditorStatus(BasicSplitPaneUI ui, Editor editor) {
super(ui);
@@ -222,18 +349,26 @@ void updateMouse(MouseEvent e, boolean pressed) {
} else if (url != null && mouseX > LEFT_MARGIN && mouseX < messageRight) {
mouseState = pressed ? URL_PRESSED : URL_ROLLOVER;
}
+ else if (mouseX > sizeW - (buttonEach * 3) && mouseX < sizeW - (2 * buttonEach)) {
+ mouseState = pressed ? MORE_INFO_PRESSED : MORE_INFO_ROLLOVER;
+ }
}
}
// only change on the rollover, no need to update on press
switch (mouseState) {
case CLIPBOARD_ROLLOVER:
+ setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ break;
case URL_ROLLOVER:
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
break;
case COLLAPSE_ROLLOVER:
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
break;
+ case MORE_INFO_ROLLOVER:
+ setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ break;
case NONE:
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
break;
@@ -268,6 +403,8 @@ protected void updateTheme() {
searchIcon = renderIcons("status/search", stateColors);
collapseIcon = renderIcons("status/console-collapse", stateColors);
expandIcon = renderIcons("status/console-expand", stateColors);
+ moreInfoIcon = renderIcons("status/more-info", stateColors);
+
btnEnabledAlpha = Theme.getInteger("status.button.enabled.alpha") / 100f;
btnRolloverAlpha = Theme.getInteger("status.button.rollover.alpha") / 100f;
@@ -310,12 +447,20 @@ public void empty() {
repaint();
}
+ public void message(String message, int mode) {
+
+ String newMessage = message;
+ int indexOfNewLine = message.indexOf(" ");
+ if (indexOfNewLine != -1) {
+ this.isFriendly = true;
+ this.friendlyMessage = message;
+ newMessage = message.substring(0,indexOfNewLine);
- public void message(String message, int mode) {
- this.message = message;
+ }
+ System.out.println("newMessage: "+newMessage);
+ this.message = newMessage;
this.mode = mode;
-
- url = findURL(message);
+ url = findURL(newMessage);
repaint();
}
@@ -449,8 +594,29 @@ public void paint(Graphics g) {
alpha = btnEnabledAlpha;
}
drawButton(g, 0, glyph, alpha);
- }
+
+ // draw more info button
+
+
+ if (isFriendly) {
+ ImageIcon glyph2;
+ glyph2 = moreInfoIcon[mode];
+ if (mouseState == MORE_INFO_ROLLOVER) {
+ alpha = btnRolloverAlpha;
+ } else if (mouseState == MORE_INFO_PRESSED) {
+ alpha = btnPressedAlpha;
+ friendlyErrorPopup friendlyPopup = new friendlyErrorPopup(friendlyMessage);
+ }
+ else {
+ alpha = btnEnabledAlpha;
+ }
+
+ drawButton(g,2,moreInfoIcon[mode],alpha);
+ }
+
+
+ }
/**
* @param pos A zero-based button index with 0 as the rightmost button
diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties
index fb2042f225..68298480c4 100644
--- a/build/shared/lib/languages/PDE.properties
+++ b/build/shared/lib/languages/PDE.properties
@@ -418,7 +418,7 @@ editor.status.uninitialized_variable = The local variable “%s” may not have
editor.status.no_effect_assignment = The assignment to variable “%s” has no effect
editor.status.hiding_enclosing_type = The class “%s” cannot have the same name as your sketch or its enclosing class
-editor.status.bad.assignment = Possible error on variable assignment near ‘%s’?
+editor.status.bad.assignment = There seems to be some trouble with how a variable is being assigned near line 12!
Hint: There is an issue with how a variable is being set to a certain value. This is also known as variable [assignment](https://processing.org/reference/assign.html). We suspect that the problem is near ‘%s’.
Suggestion: It’s important to double check that the variable is named properly (see the variable example below) and that you have included all of the necessary parts of a variable assignment. You should also make sure that you chose an appropriate [datatype](https://processing.org/reference/#data) for your variable so that it can hold the value that you are assigning to it.
Example:To assign variables you need the type, name, assignment operator (=), the value you want to assign the variable to and a semicolon. Here’s the basic format: [data type] [name] = [value]; Example: > int num = 4;