mirror of
https://github.com/processing/processing4.git
synced 2026-01-30 03:41:15 +01:00
ECS + ASTGen: offset mapping and error check overhaul
This commit is contained in:
@@ -113,6 +113,7 @@ import processing.app.Sketch;
|
||||
import processing.app.ui.EditorStatus;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
@@ -966,18 +967,6 @@ public class ASTGenerator {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a word(identifier) in pde code, finds its location in the ASTNode
|
||||
* @param lineNumber
|
||||
* @param name
|
||||
* @param offset - line start nonwhitespace offset
|
||||
* @param scrollOnly
|
||||
* @return
|
||||
*/
|
||||
public SimpleName getSimpleNameAt(int javaOffset) {
|
||||
return getSimpleNameAt(javaOffset, javaOffset);
|
||||
}
|
||||
|
||||
public SimpleName getSimpleNameAt(int startJavaOffset, int stopJavaOffset) {
|
||||
Messages.log("* getSimpleNameAt");
|
||||
|
||||
@@ -1168,10 +1157,9 @@ public class ASTGenerator {
|
||||
.forEach(occurrences::add);
|
||||
}
|
||||
|
||||
// TODO: extract mapping from ShowUsageTreeNode to use here
|
||||
Map<Integer, List<ShowUsageTreeNode>> mappedNodes = occurrences.stream()
|
||||
.map(node -> ShowUsageTreeNode.fromSimpleName(ps, node))
|
||||
.collect(Collectors.groupingBy(node -> node.tabIndex));
|
||||
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
|
||||
.map(ps::mapJavaToSketch)
|
||||
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
|
||||
|
||||
Sketch sketch = ps.sketch;
|
||||
|
||||
@@ -1183,9 +1171,10 @@ public class ASTGenerator {
|
||||
int tabIndex = entry.getKey();
|
||||
sketch.setCurrentCode(tabIndex);
|
||||
|
||||
List<ShowUsageTreeNode> nodes = entry.getValue();
|
||||
List<SketchInterval> nodes = entry.getValue();
|
||||
nodes.stream()
|
||||
.sorted(Comparator.comparing((ShowUsageTreeNode n) -> n.startTabOffset).reversed())
|
||||
// Replace from the end so all unprocess offsets stay valid
|
||||
.sorted(Comparator.comparing((SketchInterval n) -> n.startTabOffset).reversed())
|
||||
.forEach(n -> {
|
||||
// Make sure offsets are in bounds
|
||||
int length = editor.getTextArea().getDocumentLength();
|
||||
@@ -1200,16 +1189,21 @@ public class ASTGenerator {
|
||||
sketch.setModified(true);
|
||||
});
|
||||
|
||||
int precedingNodes = (int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList()).stream()
|
||||
.filter(node -> node.stopTabOffset < currentOffset)
|
||||
.count();
|
||||
int offsetDiff = precedingNodes * (newName.length() - (binding.getName().length()));
|
||||
int precedingIntervals =
|
||||
(int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList())
|
||||
.stream()
|
||||
.filter(interval -> interval.stopTabOffset < currentOffset)
|
||||
.count();
|
||||
int intervalLengthDiff = newName.length() - binding.getName().length();
|
||||
int offsetDiff = precedingIntervals * intervalLengthDiff;
|
||||
|
||||
sketch.setCurrentCode(currentTabIndex);
|
||||
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
|
||||
|
||||
editor.stopCompoundEdit();
|
||||
|
||||
// TODO: update Show Usage window if shown
|
||||
|
||||
errorCheckerService.request();
|
||||
}
|
||||
|
||||
@@ -1246,8 +1240,12 @@ public class ASTGenerator {
|
||||
List<SimpleName> occurrences = findAllOccurrences(ps.compilationUnit, bindingKey);
|
||||
if (occurrences == null) return;
|
||||
|
||||
List<SketchInterval> occurrenceIntervals = occurrences.stream()
|
||||
.map(ps::mapJavaToSketch)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Send to gui
|
||||
EventQueue.invokeLater(() -> gui.handleShowUsage(binding, occurrences));
|
||||
EventQueue.invokeLater(() -> gui.handleShowUsage(binding, occurrenceIntervals));
|
||||
}
|
||||
|
||||
|
||||
@@ -2147,7 +2145,8 @@ public class ASTGenerator {
|
||||
|
||||
// Adjust line number for tabbed sketches
|
||||
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
|
||||
int lineNumber = ps.tabLineToJavaLine(codeIndex, line);
|
||||
int lineStartOffset = editor.getTextArea().getLineStartOffset(line);
|
||||
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
|
||||
|
||||
// Ensure that we're not inside a comment. TODO: Binary search
|
||||
|
||||
@@ -2572,7 +2571,7 @@ public class ASTGenerator {
|
||||
|
||||
int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset);
|
||||
|
||||
SimpleName simpleName = getSimpleNameAt(javaOffset);
|
||||
SimpleName simpleName = getSimpleNameAt(javaOffset, javaOffset);
|
||||
|
||||
if (simpleName == null) {
|
||||
Messages.log("nothing found");
|
||||
@@ -2608,7 +2607,7 @@ public class ASTGenerator {
|
||||
handleShowUsage(binding);
|
||||
} else {
|
||||
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
|
||||
errorCheckerService.highlightJavaRange(declName.getStartPosition(), declName.getLength());
|
||||
errorCheckerService.highlightNode(declName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2796,7 +2795,7 @@ public class ASTGenerator {
|
||||
}
|
||||
|
||||
|
||||
public void handleShowUsage(IBinding binding, List<SimpleName> occurrences) {
|
||||
public void handleShowUsage(IBinding binding, List<SketchInterval> occurrences) {
|
||||
showUsageBinding = binding;
|
||||
|
||||
PreprocessedSketch ps = astGen.errorCheckerService.latestResult;
|
||||
@@ -2822,11 +2821,12 @@ public class ASTGenerator {
|
||||
new DefaultMutableTreeNode(bindingType + ": " + binding.getName());
|
||||
|
||||
Map<Integer, List<ShowUsageTreeNode>> tabGroupedTreeNodes = occurrences.stream()
|
||||
// Convert to TreeNodes
|
||||
.map(name -> ShowUsageTreeNode.fromSimpleName(ps, name))
|
||||
// TODO: this has to be fixed with better token mapping
|
||||
// remove occurrences which fall into generated header
|
||||
.filter(node -> node.tabIndex != 0 || (node.startTabOffset >= 0 && node.stopTabOffset > 0))
|
||||
.filter(in -> in.tabIndex != 0 ||
|
||||
(in.startTabOffset >= 0 && in.stopTabOffset > 0))
|
||||
// Convert to TreeNodes
|
||||
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
|
||||
// Group by tab
|
||||
.collect(Collectors.groupingBy(node -> node.tabIndex));
|
||||
|
||||
@@ -2971,9 +2971,7 @@ public class ASTGenerator {
|
||||
(DefaultMutableTreeNode) debugTree.getLastSelectedPathComponent();
|
||||
if (tnode.getUserObject() instanceof ASTNode) {
|
||||
ASTNode node = (ASTNode) tnode.getUserObject();
|
||||
int startOffset = node.getStartPosition();
|
||||
int length = node.getLength();
|
||||
astGen.errorCheckerService.highlightJavaRange(startOffset, length);
|
||||
astGen.errorCheckerService.highlightNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2985,31 +2983,19 @@ public class ASTGenerator {
|
||||
protected static class ShowUsageTreeNode {
|
||||
|
||||
int tabIndex;
|
||||
int tabLine;
|
||||
int startTabOffset;
|
||||
int stopTabOffset;
|
||||
|
||||
String text;
|
||||
|
||||
public static ShowUsageTreeNode fromSimpleName(PreprocessedSketch ps, SimpleName name) {
|
||||
int startJavaOffset = name.getStartPosition();
|
||||
int stopJavaOffset = startJavaOffset + name.getLength();
|
||||
public static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) {
|
||||
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1;
|
||||
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset);
|
||||
|
||||
int startPdeOffset = ps.javaOffsetToPdeOffset(startJavaOffset);
|
||||
int stopPdeOffset = ps.javaOffsetToPdeOffset(stopJavaOffset);
|
||||
int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset;
|
||||
int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset;
|
||||
|
||||
int tabIndex = ps.pdeOffsetToTabIndex(startPdeOffset);
|
||||
|
||||
int startTabOffset = ps.pdeOffsetToTabOffset(tabIndex, startPdeOffset);
|
||||
int stopTabOffset = ps.pdeOffsetToTabOffset(tabIndex, stopPdeOffset);
|
||||
|
||||
int tabLine = ps.tabOffsetToTabLine(tabIndex, startTabOffset);
|
||||
|
||||
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', startPdeOffset) + 1;
|
||||
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', stopPdeOffset);
|
||||
|
||||
int highlightStartOffset = startPdeOffset - lineStartPdeOffset;
|
||||
int highlightStopOffset = stopPdeOffset - lineStartPdeOffset;
|
||||
int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
|
||||
|
||||
// TODO: what a mess
|
||||
String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset);
|
||||
@@ -3023,10 +3009,9 @@ public class ASTGenerator {
|
||||
line = line.trim();
|
||||
|
||||
ShowUsageTreeNode node = new ShowUsageTreeNode();
|
||||
node.tabIndex = tabIndex;
|
||||
node.tabLine = tabLine;
|
||||
node.startTabOffset = startTabOffset;
|
||||
node.stopTabOffset = stopTabOffset;
|
||||
node.tabIndex = in.tabIndex;
|
||||
node.startTabOffset = in.startTabOffset;
|
||||
node.stopTabOffset = in.stopTabOffset;
|
||||
|
||||
node.text = "<html><font color=#bbbbbb>" +
|
||||
(tabLine + 1) + "</font> <font color=#777777>" + line + "</font></html>";
|
||||
|
||||
@@ -25,10 +25,7 @@ import com.google.classpath.ClassPathFactory;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
@@ -38,7 +35,6 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -56,21 +52,11 @@ import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.compiler.CharOperation;
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.core.dom.AST;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTParser;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.internal.compiler.Compiler;
|
||||
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
|
||||
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
|
||||
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
|
||||
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
|
||||
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
|
||||
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
|
||||
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
|
||||
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
|
||||
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
|
||||
|
||||
import processing.app.Library;
|
||||
import processing.app.Messages;
|
||||
@@ -86,6 +72,7 @@ import processing.data.IntList;
|
||||
import processing.data.StringList;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
import processing.mode.java.preproc.PdePreprocessor.Mode;
|
||||
@@ -324,10 +311,10 @@ public class ErrorCheckerService {
|
||||
workBuffer.append('\n');
|
||||
}
|
||||
}
|
||||
result.tabStarts = tabStartsList.array();
|
||||
result.tabStartOffsets = tabStartsList.array();
|
||||
|
||||
String pdeStage = result.pdeCode = workBuffer.toString();
|
||||
char[] javaStageChars;
|
||||
char[] compilableStageChars;
|
||||
|
||||
{ // Prepare core and default imports
|
||||
// TODO: do this only once
|
||||
@@ -344,6 +331,7 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
// Prepare code folder imports
|
||||
// TODO: do this only when code folder changes
|
||||
if (sketch.hasCodeFolder()) {
|
||||
File codeFolder = sketch.getCodeFolder();
|
||||
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
|
||||
@@ -355,21 +343,22 @@ public class ErrorCheckerService {
|
||||
|
||||
// TODO: convert unicode escapes to chars
|
||||
|
||||
{{ // SYNTAX CHECK
|
||||
List<IProblem> problems = new ArrayList<>();
|
||||
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
|
||||
Mode mode = PdePreprocessor.parseMode(workBuffer);
|
||||
Mode mode = PdePreprocessor.parseMode(workBuffer);
|
||||
|
||||
// Prepare transforms
|
||||
TextTransform toParsable = new TextTransform(pdeStage);
|
||||
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
|
||||
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
|
||||
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
|
||||
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
|
||||
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
|
||||
toParsable.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
|
||||
// Prepare transforms to convert pde code into parsable code
|
||||
TextTransform toParsable = new TextTransform(pdeStage);
|
||||
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
|
||||
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
|
||||
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
|
||||
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
|
||||
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
|
||||
toParsable.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
|
||||
|
||||
{ // Refresh sketch classloader and classpath if imports changed
|
||||
boolean importsChanged = prevResult == null ||
|
||||
prevResult.classPath == null || prevResult.classLoader == null ||
|
||||
checkIfImportsChanged(programImports, prevResult.programImports) ||
|
||||
@@ -396,52 +385,79 @@ public class ErrorCheckerService {
|
||||
result.classPath = prevResult.classPath;
|
||||
result.classPathArray = prevResult.classPathArray;
|
||||
}
|
||||
}
|
||||
|
||||
// Transform code
|
||||
String parsableStage = toParsable.apply();
|
||||
OffsetMapper parsableMapper = toParsable.getMapper();
|
||||
// Transform code to parsable state
|
||||
String parsableStage = toParsable.apply();
|
||||
OffsetMapper parsableMapper = toParsable.getMapper();
|
||||
|
||||
// Create AST
|
||||
CompilationUnit parsableCU =
|
||||
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
|
||||
// Create intermediate AST for advanced preprocessing
|
||||
CompilationUnit parsableCU =
|
||||
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
|
||||
|
||||
// Prepare transforms
|
||||
TextTransform toCompilable = new TextTransform(parsableStage);
|
||||
toCompilable.addAll(SourceUtils.addPublicToTopLevelMethods(parsableCU));
|
||||
toCompilable.addAll(SourceUtils.replaceColorAndFixFloats(parsableCU));
|
||||
// Prepare advanced transforms which operate on AST
|
||||
TextTransform toCompilable = new TextTransform(parsableStage);
|
||||
toCompilable.addAll(SourceUtils.addPublicToTopLevelMethods(parsableCU));
|
||||
toCompilable.addAll(SourceUtils.replaceColorAndFixFloats(parsableCU));
|
||||
|
||||
// Transform code
|
||||
String compilableStage = toCompilable.apply();
|
||||
OffsetMapper compilableMapper = toCompilable.getMapper();
|
||||
javaStageChars = compilableStage.toCharArray();
|
||||
// Transform code to compilable state
|
||||
String compilableStage = toCompilable.apply();
|
||||
OffsetMapper compilableMapper = toCompilable.getMapper();
|
||||
compilableStageChars = compilableStage.toCharArray();
|
||||
|
||||
// Create AST
|
||||
CompilationUnit compilableCU =
|
||||
makeASTWithBindings(parser, javaStageChars, COMPILER_OPTIONS,
|
||||
className, result.classPathArray);
|
||||
// Create compilable AST to get syntax problems
|
||||
CompilationUnit compilableCU =
|
||||
makeAST(parser, compilableStageChars, COMPILER_OPTIONS);
|
||||
|
||||
// Update result
|
||||
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
|
||||
result.javaCode = compilableStage;
|
||||
result.compilationUnit = compilableCU;
|
||||
// Get syntax problems from compilable AST
|
||||
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
|
||||
problems.addAll(syntaxProblems);
|
||||
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
|
||||
|
||||
// Get syntax problems
|
||||
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
|
||||
// Generate bindings after getting problems - avoids
|
||||
// 'missing type' errors when there are syntax problems
|
||||
CompilationUnit bindingsCU =
|
||||
makeASTWithBindings(parser, compilableStageChars, COMPILER_OPTIONS,
|
||||
className, result.classPathArray);
|
||||
|
||||
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
|
||||
}}
|
||||
// Show compilation problems only when there are no syntax problems
|
||||
if (!result.hasSyntaxErrors) {
|
||||
problems.clear(); // clear warnings, they will be generated again
|
||||
List<IProblem> bindingsProblems = Arrays.asList(bindingsCU.getProblems());
|
||||
problems.addAll(bindingsProblems);
|
||||
result.hasCompilationErrors = bindingsProblems.stream().anyMatch(IProblem::isError);
|
||||
}
|
||||
|
||||
{{ // COMPILATION CHECK
|
||||
// Compile it
|
||||
List<IProblem> compilationProblems =
|
||||
compileAndReturnProblems(className, javaStageChars,
|
||||
COMPILER_OPTIONS, result.classLoader,
|
||||
result.classPath);
|
||||
// Update builder
|
||||
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
|
||||
result.javaCode = compilableStage;
|
||||
result.compilationUnit = bindingsCU;
|
||||
|
||||
// TODO: handle error stuff *after* building PreprocessedSketch
|
||||
List<Problem> mappedCompilationProblems =
|
||||
mapProblems(compilationProblems, result.tabStarts, result.pdeCode,
|
||||
result.offsetMapper);
|
||||
// Build it
|
||||
PreprocessedSketch ps = result.build();
|
||||
|
||||
{ // Process problems
|
||||
List<Problem> mappedCompilationProblems = problems.stream()
|
||||
// Filter Warnings if they are not enabled
|
||||
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
|
||||
// Hide a useless error which is produced when a line ends with
|
||||
// an identifier without a semicolon. "Missing a semicolon" is
|
||||
// also produced and is preferred over this one.
|
||||
// (Syntax error, insert ":: IdentifierOrNew" to complete Expression)
|
||||
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780
|
||||
.filter(iproblem -> !iproblem.getMessage()
|
||||
.contains("Syntax error, insert \":: IdentifierOrNew\""))
|
||||
// Transform into our Problems
|
||||
.map(iproblem -> {
|
||||
int start = iproblem.getSourceStart();
|
||||
int stop = iproblem.getSourceEnd() + 1; // make it exclusive
|
||||
SketchInterval in = ps.mapJavaToSketch(start, stop);
|
||||
int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
|
||||
Problem p = new Problem(iproblem, in.tabIndex, line);
|
||||
p.setPDEOffsets(in.startTabOffset, in.stopTabOffset);
|
||||
return p;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF)) {
|
||||
Map<String, List<Problem>> undefinedTypeProblems = mappedCompilationProblems.stream()
|
||||
@@ -462,19 +478,16 @@ public class ErrorCheckerService {
|
||||
undefinedTypeProblems.entrySet().stream()
|
||||
.forEach(entry -> {
|
||||
String missingClass = entry.getKey();
|
||||
List<Problem> problems = entry.getValue();
|
||||
List<Problem> affectedProblems = entry.getValue();
|
||||
String[] suggestions = getImportSuggestions(cp, missingClass);
|
||||
problems.forEach(p -> p.setImportSuggestions(suggestions));
|
||||
affectedProblems.forEach(p -> p.setImportSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
|
||||
result.problems.addAll(mappedCompilationProblems);
|
||||
ps.problems.addAll(mappedCompilationProblems);
|
||||
}
|
||||
|
||||
result.hasCompilationErrors = mappedCompilationProblems.stream()
|
||||
.anyMatch(Problem::isError);
|
||||
}}
|
||||
|
||||
return result.build();
|
||||
return ps;
|
||||
}
|
||||
|
||||
|
||||
@@ -556,57 +569,6 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
protected static List<Problem> mapProblems(List<IProblem> problems,
|
||||
int[] tabStarts, String pdeCode,
|
||||
OffsetMapper mapper) {
|
||||
return problems.stream()
|
||||
// Filter Warnings if they are not enabled
|
||||
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
|
||||
// Hide a useless error which is produced when a line ends with
|
||||
// an identifier without a semicolon. "Missing a semicolon" is
|
||||
// also produced and is preferred over this one.
|
||||
// (Syntax error, insert ":: IdentifierOrNew" to complete Expression)
|
||||
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780
|
||||
.filter(iproblem -> !iproblem.getMessage()
|
||||
.contains("Syntax error, insert \":: IdentifierOrNew\""))
|
||||
// Transform into our Problems
|
||||
.map(iproblem -> {
|
||||
int start = iproblem.getSourceStart();
|
||||
int stop = iproblem.getSourceEnd(); // inclusive
|
||||
|
||||
// Apply mapping
|
||||
start = mapper.getInputOffset(start);
|
||||
stop = mapper.getInputOffset(stop);
|
||||
|
||||
if (stop < start) {
|
||||
// Should not happen, just to be sure
|
||||
int temp = start;
|
||||
start = stop;
|
||||
stop = temp;
|
||||
}
|
||||
|
||||
int pdeStart = PApplet.constrain(start, 0, pdeCode.length()-1);
|
||||
int pdeStop = PApplet.constrain(stop + 1, 1, pdeCode.length()); // +1 for exclusive end
|
||||
|
||||
int tab = Arrays.binarySearch(tabStarts, pdeStart);
|
||||
if (tab < 0) {
|
||||
tab = -(tab + 1) - 1;
|
||||
}
|
||||
|
||||
int tabStart = tabStarts[tab];
|
||||
|
||||
// TODO: quick hack; make it smart, fast & beautiful later
|
||||
int line = Util.countLines(pdeCode.substring(tabStart, pdeStart)) - 1;
|
||||
|
||||
Problem problem = new Problem(iproblem, tab, line);
|
||||
problem.setPDEOffsets(pdeStart - tabStart, pdeStop - tabStart);
|
||||
|
||||
return problem;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
protected static CompilationUnit makeAST(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options) {
|
||||
@@ -635,50 +597,6 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs compiler error check.
|
||||
* @param sourceName - name of the class
|
||||
* @param source - source code
|
||||
* @param options - compiler options
|
||||
* @param classLoader - custom classloader which can load all dependencies
|
||||
* @return list of compiler errors and warnings
|
||||
*/
|
||||
static public List<IProblem> compileAndReturnProblems(String sourceName,
|
||||
char[] source,
|
||||
Map<String, String> options,
|
||||
URLClassLoader classLoader,
|
||||
ClassPath classPath) {
|
||||
final List<IProblem> problems = new ArrayList<>();
|
||||
|
||||
ICompilerRequestor requestor = cr -> {
|
||||
if (cr.hasProblems()) Collections.addAll(problems, cr.getProblems());
|
||||
};
|
||||
|
||||
final char[] contents = source;
|
||||
final char[][] packageName = new char[][]{};
|
||||
final char[] mainTypeName = sourceName.toCharArray();
|
||||
final char[] fileName = (sourceName + ".java").toCharArray();
|
||||
|
||||
ICompilationUnit unit = new ICompilationUnit() {
|
||||
@Override public char[] getContents() { return contents; }
|
||||
@Override public char[][] getPackageName() { return packageName; }
|
||||
@Override public char[] getMainTypeName() { return mainTypeName; }
|
||||
@Override public char[] getFileName() { return fileName; }
|
||||
@Override public boolean ignoreOptionalProblems() { return false; }
|
||||
};
|
||||
|
||||
org.eclipse.jdt.internal.compiler.Compiler compiler =
|
||||
new Compiler(new NameEnvironmentImpl(classLoader, classPath),
|
||||
DefaultErrorHandlingPolicies.proceedWithAllProblems(),
|
||||
new CompilerOptions(options),
|
||||
requestor,
|
||||
new DefaultProblemFactory(Locale.getDefault()));
|
||||
|
||||
compiler.compile(new ICompilationUnit[]{unit});
|
||||
return problems;
|
||||
}
|
||||
|
||||
|
||||
public CompilationUnit getLatestCU() {
|
||||
return latestResult.compilationUnit;
|
||||
}
|
||||
@@ -932,32 +850,12 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
public void highlightJavaRange(int startJavaOffset, int javaLength) {
|
||||
public void highlightNode(ASTNode node) {
|
||||
PreprocessedSketch ps = latestResult;
|
||||
|
||||
int stopJavaOffset = startJavaOffset + javaLength;
|
||||
|
||||
int startPdeOffset = ps.javaOffsetToPdeOffset(startJavaOffset);
|
||||
|
||||
int stopPdeOffset;
|
||||
|
||||
if (javaLength == 0) {
|
||||
stopPdeOffset = startPdeOffset;
|
||||
} else {
|
||||
// Subtract one for inclusive end
|
||||
stopPdeOffset = ps.javaOffsetToPdeOffset(stopJavaOffset - 1);
|
||||
if (javaLength == 1 || stopPdeOffset > startPdeOffset) {
|
||||
// Add one back for exclusive end
|
||||
stopPdeOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
int tabIndex = ps.pdeOffsetToTabIndex(startPdeOffset);
|
||||
|
||||
int startTabOffset = ps.pdeOffsetToTabOffset(tabIndex, startPdeOffset);
|
||||
int stopTabOffset = ps.pdeOffsetToTabOffset(tabIndex, stopPdeOffset);
|
||||
|
||||
EventQueue.invokeLater(() -> highlightTabRange(tabIndex, startTabOffset, stopTabOffset));
|
||||
SketchInterval si = ps.mapJavaToSketch(node);
|
||||
EventQueue.invokeLater(() -> {
|
||||
highlightTabRange(si.tabIndex, si.startTabOffset, si.stopTabOffset);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -996,70 +894,4 @@ public class ErrorCheckerService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NameEnvironmentImpl implements INameEnvironment {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
private final ClassPath classPath;
|
||||
|
||||
NameEnvironmentImpl(ClassLoader classLoader, ClassPath classPath) {
|
||||
this.classLoader = classLoader;
|
||||
this.classPath = classPath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
|
||||
return readClassFile(CharOperation.toString(compoundTypeName), classLoader);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
|
||||
String fullName = CharOperation.toString(packageName);
|
||||
if (typeName != null) {
|
||||
if (fullName.length() > 0) fullName += ".";
|
||||
fullName += new String(typeName);
|
||||
}
|
||||
return readClassFile(fullName, classLoader);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPackage(char[][] parentPackageName, char[] packageName) {
|
||||
String fullName = CharOperation.toString(parentPackageName);
|
||||
if (packageName != null) {
|
||||
if (fullName.length() > 0) fullName += ".";
|
||||
fullName += new String(packageName);
|
||||
}
|
||||
return classPath.isPackage(fullName.replace('.', '/'));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cleanup() { }
|
||||
|
||||
|
||||
private static NameEnvironmentAnswer readClassFile(String fullName, ClassLoader classLoader) {
|
||||
String classFileName = fullName.replace('.', '/') + ".class";
|
||||
|
||||
InputStream is = classLoader.getResourceAsStream(classFileName);
|
||||
if (is == null) return null;
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(buffer.length);
|
||||
try {
|
||||
int bytes;
|
||||
while ((bytes = is.read(buffer, 0, buffer.length)) > 0) {
|
||||
os.write(buffer, 0, bytes);
|
||||
}
|
||||
os.flush();
|
||||
ClassFileReader classFileReader =
|
||||
new ClassFileReader(os.toByteArray(), fullName.toCharArray(), true);
|
||||
return new NameEnvironmentAnswer(classFileReader, null);
|
||||
} catch (IOException | ClassFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
@@ -23,7 +24,7 @@ public class PreprocessedSketch {
|
||||
public final ClassPath classPath;
|
||||
public final URLClassLoader classLoader;
|
||||
|
||||
public final int[] tabStarts;
|
||||
public final int[] tabStartOffsets;
|
||||
|
||||
public final String pdeCode;
|
||||
public final String javaCode;
|
||||
@@ -40,67 +41,58 @@ public class PreprocessedSketch {
|
||||
public final List<ImportStatement> codeFolderImports;
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
public static int lineToOffset(String text, int line) {
|
||||
int lineOffset = 0;
|
||||
for (int i = 0; i < line && lineOffset >= 0; i++) {
|
||||
lineOffset = text.indexOf('\n', lineOffset) + 1;
|
||||
|
||||
/// JAVA -> SKETCH -----------------------------------------------------------
|
||||
|
||||
|
||||
public static class SketchInterval {
|
||||
private SketchInterval(int tabIndex,
|
||||
int startTabOffset, int stopTabOffset,
|
||||
int startPdeOffset, int stopPdeOffset) {
|
||||
this.tabIndex = tabIndex;
|
||||
this.startTabOffset = startTabOffset;
|
||||
this.stopTabOffset = stopTabOffset;
|
||||
this.startPdeOffset = startPdeOffset;
|
||||
this.stopPdeOffset = stopPdeOffset;
|
||||
}
|
||||
return lineOffset;
|
||||
|
||||
final int tabIndex;
|
||||
final int startTabOffset;
|
||||
final int stopTabOffset;
|
||||
|
||||
final int startPdeOffset;
|
||||
final int stopPdeOffset;
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
public static int offsetToLine(String text, int start, int offset) {
|
||||
int line = 0;
|
||||
while (offset >= start) {
|
||||
offset = text.lastIndexOf('\n', offset-1);
|
||||
line++;
|
||||
}
|
||||
return line - 1;
|
||||
public SketchInterval mapJavaToSketch(ASTNode node) {
|
||||
return mapJavaToSketch(node.getStartPosition(),
|
||||
node.getStartPosition() + node.getLength());
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
public static int offsetToLine(String text, int offset) {
|
||||
return offsetToLine(text, 0, offset);
|
||||
public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) {
|
||||
boolean zeroLength = stopJavaOffset == startJavaOffset;
|
||||
int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset);
|
||||
int stopPdeOffset = zeroLength ?
|
||||
javaOffsetToPdeOffset(stopJavaOffset) :
|
||||
javaOffsetToPdeOffset(stopJavaOffset-1)+1;
|
||||
int tabIndex = pdeOffsetToTabIndex(startPdeOffset);
|
||||
|
||||
return new SketchInterval(tabIndex,
|
||||
pdeOffsetToTabOffset(tabIndex, startPdeOffset),
|
||||
pdeOffsetToTabOffset(tabIndex, stopPdeOffset),
|
||||
startPdeOffset, stopPdeOffset);
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize, build lookup together with tabStarts
|
||||
public int tabIndexToTabStartLine(int tabIndex) {
|
||||
int pdeLineNumber = 0;
|
||||
for (int i = 0; i < tabIndex; i++) {
|
||||
pdeLineNumber += sketch.getCode(i).getLineCount();
|
||||
}
|
||||
return pdeLineNumber;
|
||||
}
|
||||
|
||||
|
||||
public int tabLineToJavaLine(int tabIndex, int tabLine) {
|
||||
int tabStartLine = tabIndexToTabStartLine(tabIndex);
|
||||
int pdeLine = tabStartLine + tabLine;
|
||||
int pdeLineOffset = lineToOffset(pdeCode, pdeLine);
|
||||
int javaLineOffset = offsetMapper.getOutputOffset(pdeLineOffset);
|
||||
return offsetToLine(javaCode, javaLineOffset);
|
||||
}
|
||||
|
||||
|
||||
public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) {
|
||||
int tabStartLine = tabIndexToTabStartLine(tabIndex);
|
||||
int tabStartOffset = lineToOffset(pdeCode, tabStartLine);
|
||||
int pdeOffset = tabStartOffset + tabOffset;
|
||||
return offsetMapper.getOutputOffset(pdeOffset);
|
||||
}
|
||||
|
||||
|
||||
public int javaOffsetToPdeOffset(int javaOffset) {
|
||||
private int javaOffsetToPdeOffset(int javaOffset) {
|
||||
return offsetMapper.getInputOffset(javaOffset);
|
||||
}
|
||||
|
||||
|
||||
public int pdeOffsetToTabIndex(int pdeOffset) {
|
||||
int tab = Arrays.binarySearch(tabStarts, pdeOffset);
|
||||
private int pdeOffsetToTabIndex(int pdeOffset) {
|
||||
int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset);
|
||||
if (tab < 0) {
|
||||
tab = -(tab + 1) - 1;
|
||||
}
|
||||
@@ -108,18 +100,56 @@ public class PreprocessedSketch {
|
||||
}
|
||||
|
||||
|
||||
public int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) {
|
||||
int tabStartOffset = tabStarts[tabIndex];
|
||||
private int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) {
|
||||
int tabStartOffset = tabStartOffsets[tabIndex];
|
||||
return pdeOffset - tabStartOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// SKETCH -> JAVA -----------------------------------------------------------
|
||||
|
||||
|
||||
public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) {
|
||||
int tabStartOffset = tabStartOffsets[tabIndex];
|
||||
int pdeOffset = tabStartOffset + tabOffset;
|
||||
return offsetMapper.getOutputOffset(pdeOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// LINE NUMBERS -------------------------------------------------------------
|
||||
|
||||
|
||||
public int tabOffsetToJavaLine(int tabIndex, int tabOffset) {
|
||||
int javaOffset = tabOffsetToJavaOffset(tabIndex, tabOffset);
|
||||
return offsetToLine(javaCode, javaOffset);
|
||||
}
|
||||
|
||||
|
||||
public int tabOffsetToTabLine(int tabIndex, int tabOffset) {
|
||||
int tabStartOffset = tabStarts[tabIndex];
|
||||
int tabStartOffset = tabStartOffsets[tabIndex];
|
||||
return offsetToLine(pdeCode, tabStartOffset, tabStartOffset + tabOffset);
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
private static int offsetToLine(String text, int offset) {
|
||||
return offsetToLine(text, 0, offset);
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
private static int offsetToLine(String text, int start, int offset) {
|
||||
int line = 0;
|
||||
while (offset >= start) {
|
||||
offset = text.lastIndexOf('\n', offset-1);
|
||||
line++;
|
||||
}
|
||||
return line - 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// BUILDER BUSINESS /////////////////////////////////////////////////////////
|
||||
|
||||
@@ -138,7 +168,7 @@ public class PreprocessedSketch {
|
||||
public ClassPath classPath;
|
||||
public URLClassLoader classLoader;
|
||||
|
||||
public int[] tabStarts = new int[0];
|
||||
public int[] tabStartOffsets = new int[0];
|
||||
|
||||
public String pdeCode;
|
||||
public String javaCode;
|
||||
@@ -148,8 +178,6 @@ public class PreprocessedSketch {
|
||||
public boolean hasSyntaxErrors;
|
||||
public boolean hasCompilationErrors;
|
||||
|
||||
public final List<Problem> problems = new ArrayList<>();
|
||||
|
||||
public final List<ImportStatement> programImports = new ArrayList<>();
|
||||
public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
|
||||
public final List<ImportStatement> codeFolderImports = new ArrayList<>();
|
||||
@@ -172,7 +200,7 @@ public class PreprocessedSketch {
|
||||
classPath = b.classPath;
|
||||
classLoader = b.classLoader;
|
||||
|
||||
tabStarts = b.tabStarts;
|
||||
tabStartOffsets = b.tabStartOffsets;
|
||||
|
||||
pdeCode = b.pdeCode;
|
||||
javaCode = b.javaCode;
|
||||
@@ -182,7 +210,7 @@ public class PreprocessedSketch {
|
||||
hasSyntaxErrors = b.hasSyntaxErrors;
|
||||
hasCompilationErrors = b.hasCompilationErrors;
|
||||
|
||||
problems = Collections.unmodifiableList(b.problems);
|
||||
problems = new ArrayList<>();
|
||||
|
||||
programImports = Collections.unmodifiableList(b.programImports);
|
||||
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);
|
||||
|
||||
Reference in New Issue
Block a user