diff --git a/app/src/processing/app/Problem.java b/app/src/processing/app/Problem.java index cb12ad5e3e..c5f633c678 100644 --- a/app/src/processing/app/Problem.java +++ b/app/src/processing/app/Problem.java @@ -20,16 +20,117 @@ package processing.app; +import java.util.Optional; + +/** + * Structure describing a problem encountered in sketch compilation. + */ public interface Problem { + + /** + * Strategy converting line number in tab to character offset from tab start. + */ + public interface LineToTabOffsetGetter { + + /** + * Convert a line number to the number of characters past tab start. + * + * @param line The line number to convert. + * @return The number of characters past tab start where that line starts. + */ + public int get(int line); + + } + + /** + * Get if the problem is an error that prevented compilation. + * + * @return True if an error such that the sketch did not compile and false + * otherwise. + */ public boolean isError(); + + /** + * Get if the problem is an warning that did not prevent compilation. + * + * @return True if a warning and the sketch compiled and false otherwise. + */ public boolean isWarning(); + /** + * Get which tab (sketch file) the problem was encountered. + * + * @return The index of the tab in which the problem was encountered. + */ public int getTabIndex(); - public int getLineNumber(); // 0-indexed + + /** + * Get at which line the problem was encountered. + * + * @return Zero-indexed line number within the tab at getTabIndex in which + * this problem was encountered. Note that this is not the line in the + * generated Java file. + */ + public int getLineNumber(); + + /** + * Get a human-reabable description of the problem encountered. + * + * @return String describing the error or warning encountered. + */ public String getMessage(); - public int getStartOffset(); - public int getStopOffset(); + /** + * Get the exact character on which this problem starts in code tab relative. + * + * @return Number of characters past the start of the tab if known where the + * code associated with the Problem starts. Returns empty if not provided. + */ + public Optional getTabStartOffset(); + + /** + * Get the exact character on which this problem ends in code tab relative. + * + * @return Number of characters past the start of the tab if known where the + * code associated with the Problem ends. Returns empty if not provided. + */ + public Optional getTabStopOffset(); + + /** + * Get the exact character on which this problem starts in code line relative. + * + * @return Number of characters past the start of the line if known where the + * code associated with the Problem starts. Returns empty if not provided. + */ + public Optional getLineStartOffset(); + + /** + * Get the exact character on which this problem ends in code line relative. + * + * @return Number of characters past the start of the line if known where the + * code associated with the Problem ends. Returns empty if not provided. + */ + public Optional getLineStopOffset(); + + /** + * Get the exact character on which this problem ends in code tab relative. + * + * @param strategy Strategy to convert line to tab start if needed. + * @return Number of characters past the start of the tab if known where the + * code associated with the Problem ends, using the provided conversion + * if needed. Returns line start if character position not given. + */ + public int computeTabStartOffset(LineToTabOffsetGetter strategy); + + /** + * Get the exact character on which this problem ends in code tab relative. + * + * @param strategy Strategy to convert line to tab start if needed. + * @return Number of characters past the start of the tab if known where the + * code associated with the Problem ends, using the provided conversion + * if needed. Returns line start if character position not given. + */ + public int computeTabStopOffset(LineToTabOffsetGetter strategy); } diff --git a/app/src/processing/app/syntax/PdeTextAreaPainter.java b/app/src/processing/app/syntax/PdeTextAreaPainter.java index ef517a9d24..be0d5fdc80 100644 --- a/app/src/processing/app/syntax/PdeTextAreaPainter.java +++ b/app/src/processing/app/syntax/PdeTextAreaPainter.java @@ -46,6 +46,8 @@ public class PdeTextAreaPainter extends TextAreaPainter { protected Color gutterTextInactiveColor; protected Color gutterHighlightColor; + private final Problem.LineToTabOffsetGetter lineToTabOffsetGetter; + public PdeTextAreaPainter(JEditTextArea textArea, TextAreaDefaults defaults) { super(textArea, defaults); @@ -76,6 +78,10 @@ public void mousePressed(MouseEvent event) { } } }); + + lineToTabOffsetGetter = (x) -> { + return textArea.getLineStartOffset(x); + }; } @@ -147,13 +153,14 @@ protected void paintLine(Graphics gfx, int line, int x, TokenMarkerState marker) protected void paintErrorLine(Graphics gfx, int line, int x) { List problems = getEditor().findProblems(line); for (Problem problem : problems) { - int startOffset = problem.getStartOffset(); - int stopOffset = problem.getStopOffset(); + int startOffset = problem.computeTabStartOffset(lineToTabOffsetGetter); + int stopOffset = problem.computeTabStopOffset(lineToTabOffsetGetter); - int lineOffset = textArea.getLineStartOffset(line); + int lineOffsetStart = textArea.getLineStartOffset(line); + int lineOffsetStop = textArea.getLineStopOffset(line); - int wiggleStart = Math.max(startOffset, lineOffset); - int wiggleStop = Math.min(stopOffset, textArea.getLineStopOffset(line)); + int wiggleStart = Math.max(startOffset, lineOffsetStart); + int wiggleStop = Math.min(stopOffset, lineOffsetStop); int y = textArea.lineToY(line) + getLineDisplacement(); @@ -163,7 +170,10 @@ protected void paintErrorLine(Graphics gfx, int line, int x) { try { SyntaxDocument doc = textArea.getDocument(); badCode = doc.getText(wiggleStart, wiggleStop - wiggleStart); - goodCode = doc.getText(lineOffset, wiggleStart - lineOffset); + goodCode = doc.getText( + lineOffsetStart, + wiggleStart - lineOffsetStart + ); //log("paintErrorLine() LineText GC: " + goodCode); //log("paintErrorLine() LineText BC: " + badCode); } catch (BadLocationException bl) { @@ -328,8 +338,8 @@ public String getToolTipText(MouseEvent event) { int lineStart = textArea.getLineStartOffset(line); int lineEnd = textArea.getLineStopOffset(line); - int errorStart = problem.getStartOffset(); - int errorEnd = problem.getStopOffset() + 1; + int errorStart = problem.computeTabStartOffset(lineToTabOffsetGetter); + int errorEnd = problem.computeTabStopOffset(lineToTabOffsetGetter) + 1; int startOffset = Math.max(errorStart, lineStart) - lineStart; int stopOffset = Math.min(errorEnd, lineEnd) - lineStart; diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index b9761aa79e..824f8ac20a 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -2556,8 +2556,15 @@ public void updateErrorTable(List problems) { public void highlight(Problem p) { + Problem.LineToTabOffsetGetter getter = (x) -> { + return textarea.getLineStartOffset(x); + }; + if (p != null) { - highlight(p.getTabIndex(), p.getStartOffset(), p.getStopOffset()); + int tabIndex = p.getTabIndex(); + int tabToStartOffset = p.computeTabStartOffset(getter); + int tabToStopOffset = p.computeTabStopOffset(getter); + highlight(tabIndex, tabToStartOffset, tabToStopOffset); } } @@ -2623,8 +2630,11 @@ public List findProblems(int line) { .filter(p -> p.getTabIndex() == currentTab) .filter(p -> { int pStartLine = p.getLineNumber(); - int pEndOffset = p.getStopOffset(); + int pEndOffset = p.computeTabStopOffset( + (startLine) -> textarea.getLineStartOffset(pStartLine) + ); int pEndLine = textarea.getLineOfOffset(pEndOffset); + return line >= pStartLine && line <= pEndLine; }) .collect(Collectors.toList()); diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index fb2042f225..acfead4bfb 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -420,7 +420,7 @@ editor.status.hiding_enclosing_type = The class “%s” cannot have the same na editor.status.bad.assignment = Possible error on variable assignment near ‘%s’? editor.status.bad.generic = Possibly missing type in generic near ‘%s’? -editor.status.bad.identifier = Bad identifier? Did you forget a variable or start an identifier with digits near ‘%s’? +editor.status.bad.identifier = There's an issue with a "bad identifier" in your code near '%s'. editor.status.bad.parameter = Error on parameter or method declaration near ‘%s’? editor.status.bad.import = Import not allowed here. editor.status.bad.mixed_mode = You may be mixing active and static modes. diff --git a/java/src/processing/mode/java/ErrorChecker.java b/java/src/processing/mode/java/ErrorChecker.java index 015673ed34..1302dd773a 100644 --- a/java/src/processing/mode/java/ErrorChecker.java +++ b/java/src/processing/mode/java/ErrorChecker.java @@ -277,9 +277,16 @@ static private List checkForCurlyQuotes(PreprocSketch ps) { String q = matcher.group(); int tabStart = in.startTabOffset + offset; int tabStop = tabStart + 1; + int line = ps.tabOffsetToTabLine(in.tabIndex, tabStart); + // Prevent duplicate problems - if (problems.stream().noneMatch(p -> p.getStartOffset() == tabStart)) { - int line = ps.tabOffsetToTabLine(in.tabIndex, tabStart); + boolean isDupe = problems.stream() + .filter(p -> p.getTabIndex() == in.tabIndex) + .filter(p -> p.getLineNumber() == line) + .findAny() + .isPresent(); + + if (isDupe) { String message; if (iproblem.getID() == IProblem.UnterminatedString) { message = Language.interpolate("editor.status.unterm_string_curly", q); diff --git a/java/src/processing/mode/java/JavaProblem.java b/java/src/processing/mode/java/JavaProblem.java index 232063d2f0..5233c785cb 100644 --- a/java/src/processing/mode/java/JavaProblem.java +++ b/java/src/processing/mode/java/JavaProblem.java @@ -20,6 +20,8 @@ package processing.mode.java; +import java.util.Optional; + import org.eclipse.jdt.core.compiler.IProblem; import processing.app.Problem; @@ -42,9 +44,9 @@ public class JavaProblem implements Problem { /** Line number (pde code) of the error */ private final int lineNumber; - private int startOffset; + private Optional startOffset; - private int stopOffset; + private Optional stopOffset; /** * If the error is a 'cannot find type' contains the list of suggested imports @@ -60,6 +62,8 @@ public JavaProblem(String message, int type, int tabIndex, int lineNumber) { this.type = type; this.tabIndex = tabIndex; this.lineNumber = lineNumber; + this.startOffset = Optional.empty(); + this.stopOffset = Optional.empty(); } @@ -83,22 +87,31 @@ static public JavaProblem fromIProblem(IProblem iProblem, int tabIndex, public void setPDEOffsets(int startOffset, int stopOffset){ - this.startOffset = startOffset; - this.stopOffset = stopOffset; + this.startOffset = Optional.of(startOffset); + this.stopOffset = Optional.of(stopOffset); } @Override - public int getStartOffset() { + public Optional getTabStartOffset() { return startOffset; } @Override - public int getStopOffset() { + public Optional getTabStopOffset() { return stopOffset; } + @Override + public Optional getLineStartOffset() { + return Optional.empty(); + } + + @Override + public Optional getLineStopOffset() { + return Optional.empty(); + } @Override public boolean isError() { @@ -139,6 +152,9 @@ public void setImportSuggestions(String[] a) { importSuggestions = a; } + public boolean usesLineOffset() { + return false; + } @Override public String toString() { @@ -146,4 +162,37 @@ public String toString() { + startOffset + ",LN STOP OFF: " + stopOffset + ",PROB: " + message; } + + @Override + public int computeTabStartOffset(LineToTabOffsetGetter strategy) { + Optional nativeTabStartOffset = getTabStartOffset(); + if (nativeTabStartOffset.isPresent()) { + return nativeTabStartOffset.get(); + } + + Optional lineStartOffset = getLineStartOffset(); + int lineOffset = strategy.get(getLineNumber()); + if (lineStartOffset.isPresent()) { + return lineOffset + lineStartOffset.get(); + } else { + return lineOffset; + } + } + + @Override + public int computeTabStopOffset(LineToTabOffsetGetter strategy) { + Optional nativeTabStopOffset = getTabStopOffset(); + if (nativeTabStopOffset.isPresent()) { + return nativeTabStopOffset.get(); + } + + Optional lineStopOffset = getLineStopOffset(); + int lineOffset = strategy.get(getLineNumber()); + if (lineStopOffset.isPresent()) { + return lineOffset + lineStopOffset.get(); + } else { + return lineOffset; + } + } + } diff --git a/java/src/processing/mode/java/ProblemFactory.java b/java/src/processing/mode/java/ProblemFactory.java index e7ec067296..ce5cbff63a 100644 --- a/java/src/processing/mode/java/ProblemFactory.java +++ b/java/src/processing/mode/java/ProblemFactory.java @@ -53,7 +53,8 @@ public static Problem build(PdePreprocessIssue pdePreprocessIssue, List localLine, message, lineStart, - lineStop + lineStop, + false ); } @@ -83,8 +84,9 @@ public static Problem build(PdePreprocessIssue pdePreprocessIssue, List tab, localLine, message, - localLine, - localLine + col + 0, + col, + true ); } diff --git a/java/src/processing/mode/java/SyntaxProblem.java b/java/src/processing/mode/java/SyntaxProblem.java index 14b9e92620..bf037b9717 100644 --- a/java/src/processing/mode/java/SyntaxProblem.java +++ b/java/src/processing/mode/java/SyntaxProblem.java @@ -1,5 +1,26 @@ +/* +Part of the Processing project - http://processing.org +Copyright (c) 2012-15 The Processing Foundation + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + package processing.mode.java; +import java.util.Optional; + + /** * Problem identifying a syntax error found in preprocessing. */ @@ -8,8 +29,10 @@ public class SyntaxProblem extends JavaProblem { private final int tabIndex; private final int lineNumber; private final String message; - private final int startOffset; - private final int stopOffset; + private final Optional tabStartOffset; + private final Optional tabStopOffset; + private final Optional lineStartOffset; + private final Optional lineStopOffset; /** * Create a new syntax problem. @@ -18,20 +41,32 @@ public class SyntaxProblem extends JavaProblem { * @param newLineNumber The line number within the tab at which the offending code can be found. * @param newMessage Human readable message describing the issue. * @param newStartOffset The character index at which the issue starts. This is relative to start - * of tab / file not relative to start of line. + * of tab / file not relative to start of line if newUsesLineOffset is true else it is line + * offset. * @param newStopOffset The character index at which the issue ends. This is relative to start - * * of tab / file not relative to start of line. + * of tab / file not relative to start of line if newUsesLineOffset is true else it is line + * offset. */ public SyntaxProblem(int newTabIndex, int newLineNumber, String newMessage, int newStartOffset, - int newStopOffset) { + int newStopOffset, boolean newUsesLineOffset) { super(newMessage, JavaProblem.ERROR, newLineNumber, newLineNumber); tabIndex = newTabIndex; lineNumber = newLineNumber; message = newMessage; - startOffset = newStartOffset; - stopOffset = newStopOffset; + + if (newUsesLineOffset) { + lineStartOffset = Optional.of(newStartOffset); + lineStopOffset = Optional.of(newStopOffset); + tabStartOffset = Optional.empty(); + tabStopOffset = Optional.empty(); + } else { + lineStartOffset = Optional.empty(); + lineStopOffset = Optional.empty(); + tabStartOffset = Optional.of(newStartOffset); + tabStopOffset = Optional.of(newStopOffset); + } } @Override @@ -60,13 +95,23 @@ public String getMessage() { } @Override - public int getStartOffset() { - return startOffset; + public Optional getTabStartOffset() { + return tabStartOffset; + } + + @Override + public Optional getTabStopOffset() { + return tabStopOffset; + } + + @Override + public Optional getLineStartOffset() { + return lineStartOffset; } @Override - public int getStopOffset() { - return stopOffset; + public Optional getLineStopOffset() { + return lineStopOffset; } } diff --git a/java/src/processing/mode/java/debug/VariableNode.java b/java/src/processing/mode/java/debug/VariableNode.java index b8b2684471..7056854b2d 100644 --- a/java/src/processing/mode/java/debug/VariableNode.java +++ b/java/src/processing/mode/java/debug/VariableNode.java @@ -29,6 +29,9 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; @@ -52,13 +55,16 @@ public class VariableNode implements MutableTreeNode { public static final int TYPE_SHORT = 10; public static final int TYPE_VOID = 11; + private static final Pattern ARRAY_REGEX = Pattern.compile( + "^(?[^\\[]+)(?(\\[\\])*)(?(\\[\\d+\\])+)(?[^\\[]*)$" + ); + protected String type; protected String name; protected Value value; protected List children = new ArrayList<>(); protected MutableTreeNode parent; - /** * Construct a {@link VariableNode}. * @param name the name @@ -88,22 +94,21 @@ public Value getValue() { * @return a String representing the value. */ public String getStringValue() { - String str; - if (value != null) { - if (getType() == TYPE_OBJECT) { - str = "instance of " + type; - } else if (getType() == TYPE_ARRAY) { - //instance of int[5] (id=998) --> instance of int[5] - str = value.toString().substring(0, value.toString().lastIndexOf(" ")); - } else if (getType() == TYPE_STRING) { - str = ((StringReference) value).value(); // use original string value (without quotes) - } else { - str = value.toString(); - } + if (value == null) { + return "null"; + } + + int typeDescriptor = getType(); + if (typeDescriptor == TYPE_OBJECT) { + return "instance of " + type; + } else if (typeDescriptor == TYPE_ARRAY) { + return describeArray(value.toString()); + } else if (typeDescriptor == TYPE_STRING) { + // use original string value (without quotes) + return ((StringReference) value).value(); } else { - str = "null"; + return value.toString(); } - return str; } @@ -380,4 +385,27 @@ public int hashCode() { hash = 97 * hash + (this.value != null ? this.value.hashCode() : 0); return hash; } + + + /** + * Describe an array in a human friendly description. + * + * @see Issue #606 + * @param fullDescrition The full description of the array like "instance of + * int[5] (id=998)" or "instance of int[][5] (id=998)" + * @return Human-friendly description like "instance of int[5]" or + * "instance of int[5][]". + */ + private String describeArray(String fullDescription) { + Matcher matcher = ARRAY_REGEX.matcher(fullDescription); + StringJoiner joiner = new StringJoiner(""); + if (!matcher.matches()) { + return fullDescription; + } + + joiner.add(matcher.group("prefix")); // Type without brackets + joiner.add(matcher.group("bounded")); // Brackets with numbers + joiner.add(matcher.group("unbounded")); // Brackets without numbers + return joiner.toString(); + } } diff --git a/java/src/processing/mode/java/lsp/PdeAdapter.java b/java/src/processing/mode/java/lsp/PdeAdapter.java index 24dc2e1d67..83d2b30960 100644 --- a/java/src/processing/mode/java/lsp/PdeAdapter.java +++ b/java/src/processing/mode/java/lsp/PdeAdapter.java @@ -228,18 +228,25 @@ void updateProblems(List problems) { Map> dias = problems.stream() .map(prob -> { SketchCode code = sketch.getCode(prob.getTabIndex()); + + Optional startOffset = prob.getTabStartOffset(); + Optional endOffset = prob.getTabStopOffset(); + + assert startOffset.isPresent(); + assert endOffset.isPresent(); + Diagnostic dia = new Diagnostic( new Range( new Position( prob.getLineNumber(), PdeAdapter - .toLineCol(code.getProgram(), prob.getStartOffset()) + .toLineCol(code.getProgram(), startOffset.get()) .col - 1 ), new Position( prob.getLineNumber(), PdeAdapter - .toLineCol(code.getProgram(), prob.getStopOffset()) + .toLineCol(code.getProgram(), endOffset.get()) .col - 1 ) ), diff --git a/java/src/processing/mode/java/tweak/Handle.java b/java/src/processing/mode/java/tweak/Handle.java index 72dfa5ed31..d1b21a74ca 100644 --- a/java/src/processing/mode/java/tweak/Handle.java +++ b/java/src/processing/mode/java/tweak/Handle.java @@ -85,12 +85,26 @@ public Handle(String t, String n, int vi, String v, int ti, int l, int sc, textFormat = "0x%x"; } else if ("webcolor".equals(type)) { - Long val = Long.parseLong(strValue.substring(1, strValue.length()), 16); + Long val; + String prefix; + if (strValue.length() == 7) { + val = Long.parseLong(strValue.substring(1, strValue.length()), 16); + prefix = ""; + } else { + String valStr = strValue.substring( + strValue.length() - 6, + strValue.length() + ); + val = Long.parseLong(valStr, 16); + prefix = strValue.substring( + 1, + strValue.length() - 6 + ); + } val = val | 0xff000000; value = newValue = val.intValue(); strNewValue = strValue; - textFormat = "#%06x"; - + textFormat = "#" + prefix + "%06x"; } else if ("float".equals(type)) { value = newValue = Float.parseFloat(strValue); strNewValue = strValue; @@ -267,7 +281,19 @@ public void sendNewValue() { } else if ("hex".equals(type)) { tweakClient.sendInt(index, newValue.intValue()); } else if ("webcolor".equals(type)) { - tweakClient.sendInt(index, newValue.intValue()); + // If full opaque color, don't spend the cycles on string processing + // which does appear to matter at high frame rates. Otherwise take the + // hit and parse back from string value with transparency. + if (strNewValue.length() == 7) { + tweakClient.sendInt(index, newValue.intValue()); + } else { + long target = Long.parseLong( + strNewValue.substring(1, strNewValue.length()), + 16 + ); + tweakClient.sendInt(index, (int) target); + } + } else if ("float".equals(type)) { tweakClient.sendFloat(index, newValue.floatValue()); } diff --git a/java/src/processing/mode/java/tweak/SketchParser.java b/java/src/processing/mode/java/tweak/SketchParser.java index 2dfedd7321..40fdffee1e 100644 --- a/java/src/processing/mode/java/tweak/SketchParser.java +++ b/java/src/processing/mode/java/tweak/SketchParser.java @@ -270,7 +270,7 @@ private void addAllHexNumbers() { * list of all hexadecimal numbers in the sketch */ private void addAllWebColorNumbers() { - Pattern p = Pattern.compile("#[A-Fa-f0-9]{6}"); + Pattern p = Pattern.compile("#([A-Fa-f0-9]{2})?[A-Fa-f0-9]{6}"); for (int i=0; i