From 7c76e3054394ae57f75a28f44c66cb5fca992dce Mon Sep 17 00:00:00 2001 From: Jakub Valtar Date: Wed, 27 Apr 2016 15:52:17 +0200 Subject: [PATCH] ECS + ASTGen: offset mapping and error check overhaul --- .../mode/java/pdex/ASTGenerator.java | 95 ++--- .../mode/java/pdex/ErrorCheckerService.java | 348 +++++------------- .../mode/java/pdex/PreprocessedSketch.java | 140 ++++--- 3 files changed, 214 insertions(+), 369 deletions(-) diff --git a/java/src/processing/mode/java/pdex/ASTGenerator.java b/java/src/processing/mode/java/pdex/ASTGenerator.java index e1add78a0..f6b8acda7 100644 --- a/java/src/processing/mode/java/pdex/ASTGenerator.java +++ b/java/src/processing/mode/java/pdex/ASTGenerator.java @@ -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> mappedNodes = occurrences.stream() - .map(node -> ShowUsageTreeNode.fromSimpleName(ps, node)) - .collect(Collectors.groupingBy(node -> node.tabIndex)); + Map> 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 nodes = entry.getValue(); + List 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 occurrences = findAllOccurrences(ps.compilationUnit, bindingKey); if (occurrences == null) return; + List 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 occurrences) { + public void handleShowUsage(IBinding binding, List occurrences) { showUsageBinding = binding; PreprocessedSketch ps = astGen.errorCheckerService.latestResult; @@ -2822,11 +2821,12 @@ public class ASTGenerator { new DefaultMutableTreeNode(bindingType + ": " + binding.getName()); Map> 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 = "" + (tabLine + 1) + " " + line + ""; diff --git a/java/src/processing/mode/java/pdex/ErrorCheckerService.java b/java/src/processing/mode/java/pdex/ErrorCheckerService.java index 833a4195e..bd665dcf4 100644 --- a/java/src/processing/mode/java/pdex/ErrorCheckerService.java +++ b/java/src/processing/mode/java/pdex/ErrorCheckerService.java @@ -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 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 syntaxProblems = Arrays.asList(compilableCU.getProblems()); + problems.addAll(syntaxProblems); + result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError); - // Get syntax problems - List 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 bindingsProblems = Arrays.asList(bindingsCU.getProblems()); + problems.addAll(bindingsProblems); + result.hasCompilationErrors = bindingsProblems.stream().anyMatch(IProblem::isError); + } - {{ // COMPILATION CHECK - // Compile it - List 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 mappedCompilationProblems = - mapProblems(compilationProblems, result.tabStarts, result.pdeCode, - result.offsetMapper); + // Build it + PreprocessedSketch ps = result.build(); + + { // Process problems + List 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> undefinedTypeProblems = mappedCompilationProblems.stream() @@ -462,19 +478,16 @@ public class ErrorCheckerService { undefinedTypeProblems.entrySet().stream() .forEach(entry -> { String missingClass = entry.getKey(); - List problems = entry.getValue(); + List 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 mapProblems(List 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 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 compileAndReturnProblems(String sourceName, - char[] source, - Map options, - URLClassLoader classLoader, - ClassPath classPath) { - final List 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; - } - } - } } diff --git a/java/src/processing/mode/java/pdex/PreprocessedSketch.java b/java/src/processing/mode/java/pdex/PreprocessedSketch.java index 55d811058..9c8f90254 100644 --- a/java/src/processing/mode/java/pdex/PreprocessedSketch.java +++ b/java/src/processing/mode/java/pdex/PreprocessedSketch.java @@ -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 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 problems = new ArrayList<>(); - public final List programImports = new ArrayList<>(); public final List coreAndDefaultImports = new ArrayList<>(); public final List 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);