mirror of
https://github.com/processing/processing4.git
synced 2026-01-30 03:41:15 +01:00
ECS + ASTGen: threading
- when continuous error checking is disabled, the sketch is preprocessed on demand - disable whole infrastructure when there are java tabs - error checker now accepts callbacks which run after error check finishes - preprocessed sketch can be no longer obtained directly via public field; ASTGenerator was refactored to allow passing the preprocessed sketch down through the hierarchy (should be refactored later) - rename, go to declaration and show usage now run in background thread
This commit is contained in:
@@ -177,11 +177,11 @@ public class JavaEditor extends Editor {
|
||||
for (SketchCode code : getSketch().getCode()) {
|
||||
Document document = code.getDocument();
|
||||
if (document != null) {
|
||||
errorCheckerService.addListener(document);
|
||||
errorCheckerService.addDocumentListener(document);
|
||||
}
|
||||
}
|
||||
errorCheckerService.start();
|
||||
errorCheckerService.request();
|
||||
errorCheckerService.notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -2339,7 +2339,7 @@ public class JavaEditor extends Editor {
|
||||
|
||||
Document newDoc = code.getDocument();
|
||||
if (oldDoc != newDoc && errorCheckerService != null) {
|
||||
errorCheckerService.addListener(newDoc);
|
||||
errorCheckerService.addDocumentListener(newDoc);
|
||||
}
|
||||
|
||||
// set line background colors for tab
|
||||
@@ -2366,12 +2366,8 @@ public class JavaEditor extends Editor {
|
||||
if (getDebugger() != null && getDebugger().isStarted()) {
|
||||
getDebugger().startTrackingLineChanges();
|
||||
}
|
||||
if (errorCheckerService != null) {
|
||||
if (errorColumn != null) {
|
||||
getErrorPoints().clear();
|
||||
statusEmpty();
|
||||
}
|
||||
errorCheckerService.request();
|
||||
if (errorColumn != null) {
|
||||
errorColumn.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2517,7 +2513,9 @@ public class JavaEditor extends Editor {
|
||||
public LineMarker findError(int line) {
|
||||
List<LineMarker> errorPoints = getErrorPoints();
|
||||
JavaTextArea textArea = getJavaTextArea();
|
||||
int currentTab = getSketch().getCurrentCodeIndex();
|
||||
for (LineMarker emarker : errorPoints) {
|
||||
if (emarker.getProblem().getTabIndex() != currentTab) continue;
|
||||
Problem p = emarker.getProblem();
|
||||
int pStartLine = p.getLineNumber();
|
||||
int pEndOffset = p.getStopOffset();
|
||||
@@ -2694,9 +2692,8 @@ public class JavaEditor extends Editor {
|
||||
int startOffset = getSelectionStart();
|
||||
int stopOffset = getSelectionStop();
|
||||
int tabIndex = sketch.getCurrentCodeIndex();
|
||||
synchronized (astGenerator) {
|
||||
astGenerator.handleRename(tabIndex, startOffset, stopOffset);
|
||||
}
|
||||
|
||||
astGenerator.handleRename(tabIndex, startOffset, stopOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -2707,9 +2704,8 @@ public class JavaEditor extends Editor {
|
||||
int startOffset = getSelectionStart();
|
||||
int stopOffset = getSelectionStop();
|
||||
int tabIndex = sketch.getCurrentCodeIndex();
|
||||
synchronized (astGenerator) {
|
||||
astGenerator.handleShowUsage(tabIndex, startOffset, stopOffset);
|
||||
}
|
||||
|
||||
astGenerator.handleShowUsage(tabIndex, startOffset, stopOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,12 +31,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.Mode;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchCode;
|
||||
import processing.app.Util;
|
||||
import processing.app.ui.Editor;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.java.pdex.LineMarker;
|
||||
@@ -96,7 +94,10 @@ public class MarkerColumn extends JPanel {
|
||||
g.drawImage(editor.getJavaTextArea().getGutterGradient(),
|
||||
0, 0, getWidth(), getHeight(), this);
|
||||
|
||||
int currentTabIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
|
||||
for (LineMarker m : errorPoints) {
|
||||
if (m.getProblem().getTabIndex() != currentTabIndex) continue;
|
||||
if (m.getType() == LineMarker.ERROR) {
|
||||
g.setColor(errorColor);
|
||||
} else {
|
||||
@@ -113,17 +114,11 @@ public class MarkerColumn extends JPanel {
|
||||
|
||||
|
||||
public void updateErrorPoints(final List<Problem> problems) {
|
||||
// NOTE: ErrorMarkers are calculated for the present tab only Error Marker
|
||||
// index in the arraylist is LOCALIZED for current tab.
|
||||
Sketch sketch = editor.getSketch();
|
||||
int currentTab = sketch.getCurrentCodeIndex();
|
||||
errorPoints.clear();
|
||||
// Each problem.getSourceLine() will have an extra line added because
|
||||
// of class declaration in the beginning as well as default imports
|
||||
for (Problem problem : problems) {
|
||||
if (problem.getTabIndex() == currentTab) {
|
||||
errorPoints.add(new LineMarker(problem, problem.isError()));
|
||||
}
|
||||
errorPoints.add(new LineMarker(problem, problem.isError()));
|
||||
}
|
||||
repaint();
|
||||
editor.getErrorChecker().updateEditorStatus();
|
||||
@@ -175,22 +170,21 @@ public class MarkerColumn extends JPanel {
|
||||
if (errorPoints != null && errorPoints.size() > 0) {
|
||||
Sketch sketch = editor.getSketch();
|
||||
SketchCode code = sketch.getCurrentCode();
|
||||
int totalLines;
|
||||
try {
|
||||
totalLines = Util.countLines(code.getDocumentText());
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
totalLines = 1; // do not divide by zero
|
||||
}
|
||||
int currentTab = sketch.getCurrentCodeIndex();
|
||||
int totalLines = PApplet.max(1, code.getLineCount()); // do not divide by zero
|
||||
int visibleLines = editor.getTextArea().getVisibleLines();
|
||||
totalLines = PApplet.max(totalLines, visibleLines);
|
||||
|
||||
int topMargin = 20; // top scroll button
|
||||
int bottomMargin = 40; // bottom scroll button and horizontal scrollbar
|
||||
int height = getHeight() - topMargin - bottomMargin;
|
||||
|
||||
for (LineMarker m : errorPoints) {
|
||||
if (m.getProblem().getTabIndex() != currentTab) continue;
|
||||
// Ratio of error line to total lines
|
||||
float y = (m.getLineNumber() + 1) / ((float) totalLines);
|
||||
float ratio = (m.getLineNumber() + 1) / ((float) totalLines);
|
||||
// Ratio multiplied by height of the error bar
|
||||
y *= getHeight();
|
||||
y -= 15; // -15 is just a vertical offset
|
||||
float y = topMargin + ratio * height;
|
||||
|
||||
m.setY((int) y);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,18 +30,21 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -70,8 +73,8 @@ import processing.app.ui.ErrorTable;
|
||||
import processing.core.PApplet;
|
||||
import processing.data.IntList;
|
||||
import processing.data.StringList;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
@@ -102,112 +105,69 @@ public class ErrorCheckerService {
|
||||
*/
|
||||
protected final ASTGenerator astGenerator;
|
||||
|
||||
public ErrorCheckerService(JavaEditor editor) {
|
||||
this.editor = editor;
|
||||
astGenerator = new ASTGenerator(editor, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Error checking doesn't happen before this interval has ellapsed since the
|
||||
* last request() call.
|
||||
*/
|
||||
private final static long errorCheckInterval = 650;
|
||||
|
||||
protected volatile PreprocessedSketch latestResult = PreprocessedSketch.empty();
|
||||
|
||||
private Thread errorCheckerThread;
|
||||
private final BlockingQueue<Boolean> requestQueue = new ArrayBlockingQueue<>(1);
|
||||
private ScheduledExecutorService scheduler;
|
||||
private volatile ScheduledFuture<?> scheduledUiUpdate = null;
|
||||
private volatile long nextUiUpdate = 0;
|
||||
|
||||
private final Object requestLock = new Object();
|
||||
private final Object serialCallbackLock = new Object();
|
||||
private boolean needsCheck = false;
|
||||
|
||||
private CompletableFuture<PreprocessedSketch> preprocessingTask =
|
||||
new CompletableFuture<PreprocessedSketch>() {{
|
||||
complete(PreprocessedSketch.empty()); // initialization block
|
||||
}};
|
||||
|
||||
private CompletableFuture<?> lastErrorCheckTask =
|
||||
new CompletableFuture() {{
|
||||
complete(null); // initalization block
|
||||
}};
|
||||
|
||||
private CompletableFuture<?> lastCallback =
|
||||
new CompletableFuture() {{
|
||||
complete(null); // initialization block
|
||||
}};
|
||||
|
||||
|
||||
public ErrorCheckerService(JavaEditor editor) {
|
||||
this.editor = editor;
|
||||
astGenerator = new ASTGenerator(editor, this);
|
||||
}
|
||||
|
||||
|
||||
private final Runnable mainLoop = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
|
||||
latestResult = checkCode();
|
||||
|
||||
if (!latestResult.hasSyntaxErrors && !latestResult.hasCompilationErrors) {
|
||||
// editor.showProblemListView(Language.text("editor.footer.console"));
|
||||
editor.showConsole();
|
||||
}
|
||||
// Make sure astGen has at least one CU to start with
|
||||
// This is when the loaded sketch already has syntax errors.
|
||||
// Completion wouldn't be complete, but it'd be still something
|
||||
// better than nothing
|
||||
if (ASTGenerator.SHOW_DEBUG_TREE) {
|
||||
astGenerator.updateDebugTree(latestResult.compilationUnit);
|
||||
}
|
||||
|
||||
PreprocessedSketch prevResult = PreprocessedSketch.empty();
|
||||
while (running) {
|
||||
try {
|
||||
requestQueue.take(); // blocking until there is more work
|
||||
requestQueue.take(); // blocking until check requested
|
||||
} catch (InterruptedException e) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
requestQueue.clear();
|
||||
|
||||
try {
|
||||
Messages.log("Starting error check");
|
||||
|
||||
Messages.log("Starting error check");
|
||||
PreprocessedSketch result = checkCode();
|
||||
prevResult = preprocessSketch(prevResult);
|
||||
|
||||
if (!JavaMode.errorCheckEnabled) {
|
||||
latestResult.problems.clear();
|
||||
Messages.log("Error Check disabled, so not updating UI.");
|
||||
synchronized (requestLock) {
|
||||
if (requestQueue.isEmpty()) {
|
||||
preprocessingTask.complete(prevResult);
|
||||
}
|
||||
|
||||
latestResult = result;
|
||||
|
||||
if (ASTGenerator.SHOW_DEBUG_TREE) {
|
||||
astGenerator.updateDebugTree(latestResult.compilationUnit);
|
||||
}
|
||||
|
||||
astGenerator.reloadShowUsage();
|
||||
|
||||
if (JavaMode.errorCheckEnabled) {
|
||||
if (scheduledUiUpdate != null) {
|
||||
scheduledUiUpdate.cancel(true);
|
||||
}
|
||||
// Update UI after a delay. See #2677
|
||||
long delay = nextUiUpdate - System.currentTimeMillis();
|
||||
Runnable uiUpdater = new Runnable() {
|
||||
final PreprocessedSketch result = latestResult;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (nextUiUpdate > 0 &&
|
||||
System.currentTimeMillis() >= nextUiUpdate) {
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateErrorTable(result.problems);
|
||||
editor.updateErrorBar(result.problems);
|
||||
editor.getTextArea().repaint();
|
||||
editor.updateErrorToggle(result.hasSyntaxErrors || result.hasCompilationErrors);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate = scheduler.schedule(uiUpdater, delay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (astGenerator) {
|
||||
astGenerator.getGui().disposeAllWindows();
|
||||
}
|
||||
Messages.loge("Thread stopped: " + editor.getSketch().getName());
|
||||
|
||||
latestResult = null;
|
||||
|
||||
running = false;
|
||||
astGenerator.getGui().disposeAllWindows();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -222,7 +182,10 @@ public class ErrorCheckerService {
|
||||
public void stop() {
|
||||
cancel();
|
||||
running = false;
|
||||
errorCheckerThread.interrupt();
|
||||
if (errorCheckerThread != null) {
|
||||
running = false;
|
||||
errorCheckerThread.interrupt();
|
||||
}
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
@@ -238,13 +201,99 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
public void request() {
|
||||
nextUiUpdate = System.currentTimeMillis() + errorCheckInterval;
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
public void notifySketchChanged() {
|
||||
if (editor.hasJavaTabs()) return;
|
||||
synchronized (requestLock) {
|
||||
if (preprocessingTask.isDone()) {
|
||||
preprocessingTask = new CompletableFuture<>();
|
||||
lastErrorCheckTask = preprocessingTask
|
||||
// Run error handler after both preprocessing
|
||||
// task and previous error handler completed
|
||||
.thenAcceptBothAsync(lastErrorCheckTask,
|
||||
(ps, a) -> handleSketchErrors(ps))
|
||||
// Make sure exception in error handler won't spoil the chain
|
||||
.handleAsync((res, e) -> {
|
||||
if (e != null) Messages.loge("problem during error handling", e);
|
||||
return res;
|
||||
});
|
||||
// Fire listeners, don't trigger check
|
||||
acceptWhenDone(this::fireDoneListeners, false);
|
||||
}
|
||||
if (isContinuousCheckEnabled()) {
|
||||
// Continuous check enabled, request
|
||||
nextUiUpdate = System.currentTimeMillis() + errorCheckInterval;
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
} else {
|
||||
// Continuous check not enabled, take note
|
||||
needsCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addListener(Document doc) {
|
||||
public void acceptWhenDone(Consumer<PreprocessedSketch> callback) {
|
||||
// Public version always triggers check
|
||||
acceptWhenDone(callback, true);
|
||||
}
|
||||
|
||||
|
||||
private void acceptWhenDone(Consumer<PreprocessedSketch> callback, boolean triggerCheck) {
|
||||
if (editor.hasJavaTabs()) return;
|
||||
if (triggerCheck) {
|
||||
// Continuous check not enabled, request check now
|
||||
synchronized (requestLock) {
|
||||
if (!isContinuousCheckEnabled() && needsCheck) {
|
||||
needsCheck = false;
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (serialCallbackLock) {
|
||||
lastCallback = preprocessingTask
|
||||
// Run callback after both preprocessing task and previous callback
|
||||
.thenAcceptBothAsync(lastCallback, (ps, a) -> callback.accept(ps))
|
||||
// Make sure exception in callback won't cancel whole callback chain
|
||||
.handleAsync((res, e) -> {
|
||||
if (e != null) Messages.loge("problem during preprocessing callback", e);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// LISTENERS ----------------------------------------------------------------
|
||||
|
||||
|
||||
private Set<Consumer<PreprocessedSketch>> doneListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
|
||||
public void registerDoneListener(Consumer<PreprocessedSketch> listener) {
|
||||
if (listener != null) doneListeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
public void unregisterDoneListener(Consumer<PreprocessedSketch> listener) {
|
||||
doneListeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
private void fireDoneListeners(PreprocessedSketch ps) {
|
||||
for (Consumer<PreprocessedSketch> listener : doneListeners) {
|
||||
try {
|
||||
listener.accept(ps);
|
||||
} catch (Exception e) {
|
||||
Messages.loge("error when firing ecs listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
public void addDocumentListener(Document doc) {
|
||||
if (doc != null) doc.addDocumentListener(sketchChangedListener);
|
||||
}
|
||||
|
||||
@@ -257,25 +306,24 @@ public class ErrorCheckerService {
|
||||
protected final DocumentListener sketchChangedListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
if (JavaMode.errorCheckEnabled) request();
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
if (JavaMode.errorCheckEnabled) request();
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
if (JavaMode.errorCheckEnabled) request();
|
||||
notifySketchChanged();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
protected PreprocessedSketch checkCode() {
|
||||
protected PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
|
||||
|
||||
PreprocessedSketch.Builder result = new PreprocessedSketch.Builder();
|
||||
PreprocessedSketch prevResult = latestResult;
|
||||
|
||||
List<ImportStatement> coreAndDefaultImports = result.coreAndDefaultImports;
|
||||
List<ImportStatement> codeFolderImports = result.codeFolderImports;
|
||||
@@ -335,8 +383,6 @@ public class ErrorCheckerService {
|
||||
|
||||
// TODO: convert unicode escapes to chars
|
||||
|
||||
List<IProblem> problems = new ArrayList<>();
|
||||
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
|
||||
Mode mode = PdePreprocessor.parseMode(workBuffer);
|
||||
@@ -403,7 +449,7 @@ public class ErrorCheckerService {
|
||||
|
||||
// Get syntax problems from compilable AST
|
||||
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
|
||||
problems.addAll(syntaxProblems);
|
||||
result.problems.addAll(syntaxProblems);
|
||||
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
|
||||
|
||||
// Generate bindings after getting problems - avoids
|
||||
@@ -414,9 +460,9 @@ public class ErrorCheckerService {
|
||||
|
||||
// Show compilation problems only when there are no syntax problems
|
||||
if (!result.hasSyntaxErrors) {
|
||||
problems.clear(); // clear warnings, they will be generated again
|
||||
result.problems.clear(); // clear warnings, they will be generated again
|
||||
List<IProblem> bindingsProblems = Arrays.asList(bindingsCU.getProblems());
|
||||
problems.addAll(bindingsProblems);
|
||||
result.problems.addAll(bindingsProblems);
|
||||
result.hasCompilationErrors = bindingsProblems.stream().anyMatch(IProblem::isError);
|
||||
}
|
||||
|
||||
@@ -426,45 +472,51 @@ public class ErrorCheckerService {
|
||||
result.compilationUnit = bindingsCU;
|
||||
|
||||
// Build it
|
||||
PreprocessedSketch ps = result.build();
|
||||
return 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;
|
||||
|
||||
private void handleSketchErrors(PreprocessedSketch ps) {
|
||||
// Process problems
|
||||
final List<Problem> problems = ps.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());
|
||||
|
||||
// Handle import suggestions
|
||||
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF)) {
|
||||
Map<String, List<Problem>> undefinedTypeProblems = problems.stream()
|
||||
// Get only problems with undefined types/names
|
||||
.filter(p -> {
|
||||
int id = p.getIProblem().getID();
|
||||
return id == IProblem.UndefinedType ||
|
||||
id == IProblem.UndefinedName ||
|
||||
id == IProblem.UnresolvedVariable;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF)) {
|
||||
Map<String, List<Problem>> undefinedTypeProblems = mappedCompilationProblems.stream()
|
||||
// Get only problems with undefined types/names
|
||||
.filter(p -> {
|
||||
int id = p.getIProblem().getID();
|
||||
return id == IProblem.UndefinedType || id == IProblem.UndefinedName;
|
||||
})
|
||||
// Group problems by the missing type/name
|
||||
.collect(Collectors.groupingBy(p -> p.getIProblem().getArguments()[0]));
|
||||
// Group problems by the missing type/name
|
||||
.collect(Collectors.groupingBy(p -> p.getIProblem().getArguments()[0]));
|
||||
|
||||
if (!undefinedTypeProblems.isEmpty()) {
|
||||
// TODO: cache this, invalidate if code folder or libraries change
|
||||
final ClassPath cp = undefinedTypeProblems.isEmpty() ?
|
||||
null :
|
||||
classPathFactory.createFromPaths(buildClassPath(null));
|
||||
String[] searchClassPath = buildClassPath(null);
|
||||
final ClassPath cp = classPathFactory.createFromPaths(searchClassPath);
|
||||
|
||||
// Get suggestions for each missing type, update the problems
|
||||
undefinedTypeProblems.entrySet().stream()
|
||||
@@ -475,11 +527,30 @@ public class ErrorCheckerService {
|
||||
affectedProblems.forEach(p -> p.setImportSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
|
||||
ps.problems.addAll(mappedCompilationProblems);
|
||||
}
|
||||
|
||||
return ps;
|
||||
final boolean updateErrorToggle = ps.hasSyntaxErrors ||
|
||||
ps.hasCompilationErrors;
|
||||
|
||||
if (scheduledUiUpdate != null) {
|
||||
scheduledUiUpdate.cancel(true);
|
||||
}
|
||||
// Update UI after a delay. See #2677
|
||||
long delay = nextUiUpdate - System.currentTimeMillis();
|
||||
Runnable uiUpdater = () -> {
|
||||
if (nextUiUpdate > 0 && System.currentTimeMillis() >= nextUiUpdate) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
if (JavaMode.errorCheckEnabled) {
|
||||
updateErrorTable(problems);
|
||||
editor.updateErrorBar(problems);
|
||||
editor.getTextArea().repaint();
|
||||
editor.updateErrorToggle(updateErrorToggle);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate = scheduler.schedule(uiUpdater, delay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
@@ -589,6 +660,7 @@ public class ErrorCheckerService {
|
||||
return (CompilationUnit) parser.createAST(null);
|
||||
}
|
||||
|
||||
|
||||
protected static CompilationUnit makeASTWithBindings(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options,
|
||||
@@ -606,11 +678,6 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
public CompilationUnit getLatestCU() {
|
||||
return latestResult.compilationUnit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ignore processing packages, java.*.*. etc.
|
||||
*/
|
||||
@@ -620,12 +687,12 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
protected boolean ignorableSuggestionImport(String impName) {
|
||||
protected static boolean ignorableSuggestionImport(PreprocessedSketch ps, String impName) {
|
||||
|
||||
String impNameLc = impName.toLowerCase();
|
||||
|
||||
List<ImportStatement> programImports = latestResult.programImports;
|
||||
List<ImportStatement> codeFolderImports = latestResult.codeFolderImports;
|
||||
List<ImportStatement> programImports = ps.programImports;
|
||||
List<ImportStatement> codeFolderImports = ps.codeFolderImports;
|
||||
|
||||
boolean isImported = Stream
|
||||
.concat(programImports.stream(), codeFolderImports.stream())
|
||||
@@ -654,6 +721,7 @@ public class ErrorCheckerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static private final Map<String, String> COMPILER_OPTIONS;
|
||||
static {
|
||||
Map<String, String> compilerOptions = new HashMap<>();
|
||||
@@ -721,7 +789,6 @@ public class ErrorCheckerService {
|
||||
} catch (Exception e) {
|
||||
Messages.loge("Exception at updateErrorTable()", e);
|
||||
e.printStackTrace();
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,8 +860,7 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
public void highlightNode(ASTNode node) {
|
||||
PreprocessedSketch ps = latestResult;
|
||||
public void highlightNode(PreprocessedSketch ps, ASTNode node) {
|
||||
SketchInterval si = ps.mapJavaToSketch(node);
|
||||
EventQueue.invokeLater(() -> {
|
||||
highlightTabRange(si.tabIndex, si.startTabOffset, si.stopTabOffset);
|
||||
@@ -822,18 +888,22 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
private static boolean isContinuousCheckEnabled() {
|
||||
return JavaMode.errorCheckEnabled;
|
||||
}
|
||||
|
||||
|
||||
public void handleErrorCheckingToggle() {
|
||||
if (!JavaMode.errorCheckEnabled) {
|
||||
Messages.log(editor.getSketch().getName() + " Error Checker disabled.");
|
||||
editor.getErrorPoints().clear();
|
||||
latestResult.problems.clear();
|
||||
updateErrorTable(Collections.<Problem>emptyList());
|
||||
updateErrorTable(Collections.emptyList());
|
||||
updateEditorStatus();
|
||||
editor.getTextArea().repaint();
|
||||
editor.repaintErrorBar();
|
||||
} else {
|
||||
Messages.log(editor.getSketch().getName() + " Error Checker enabled.");
|
||||
request();
|
||||
notifySketchChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.SwingWorker;
|
||||
@@ -315,72 +314,67 @@ public class JavaTextArea extends JEditTextArea {
|
||||
text = lineText.substring(0, caretLinePosition);
|
||||
}
|
||||
|
||||
suggestionWorker = new SwingWorker<Void, Void>() {
|
||||
// Adjust line number for tabbed sketches
|
||||
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
|
||||
int lineStartOffset = editor.getTextArea().getLineStartOffset(caretLineIndex);
|
||||
|
||||
editor.getErrorChecker().acceptWhenDone(ps -> {
|
||||
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
|
||||
|
||||
String phrase = null;
|
||||
DefaultListModel<CompletionCandidate> defListModel = null;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
try {
|
||||
Messages.log("phrase parse start");
|
||||
phrase = parsePhrase(text);
|
||||
Messages.log("phrase: " + phrase);
|
||||
if (phrase == null) return null;
|
||||
if (phrase != null) {
|
||||
List<CompletionCandidate> candidates;
|
||||
|
||||
List<CompletionCandidate> candidates;
|
||||
ASTGenerator astGenerator = editor.getErrorChecker().getASTGenerator();
|
||||
candidates = astGenerator.preparePredictions(ps, phrase, lineNumber);
|
||||
|
||||
ASTGenerator astGenerator = editor.getErrorChecker().getASTGenerator();
|
||||
synchronized (astGenerator) {
|
||||
int lineOffset = caretLineIndex;
|
||||
if (!suggestionRequested) {
|
||||
|
||||
// // don't show completions when the outline is visible
|
||||
// boolean showSuggestions =
|
||||
// astGenerator.sketchOutline == null || !astGenerator.sketchOutline.isVisible();
|
||||
|
||||
// if (showSuggestions && phrase != null &&
|
||||
if (candidates != null && !candidates.isEmpty()) {
|
||||
Collections.sort(candidates);
|
||||
defListModel = ASTGenerator.filterPredictions(candidates);
|
||||
Messages.log("Got: " + candidates.size() + " candidates, " + defListModel.size() + " filtered");
|
||||
}
|
||||
}
|
||||
|
||||
candidates = astGenerator.preparePredictions(phrase, lineOffset);
|
||||
}
|
||||
|
||||
if (suggestionRequested) return null;
|
||||
final String finalPhrase = phrase;
|
||||
final DefaultListModel<CompletionCandidate> finalDefListModel = defListModel;
|
||||
|
||||
// // don't show completions when the outline is visible
|
||||
// boolean showSuggestions =
|
||||
// astGenerator.sketchOutline == null || !astGenerator.sketchOutline.isVisible();
|
||||
EventQueue.invokeLater(() -> {
|
||||
|
||||
// if (showSuggestions && phrase != null &&
|
||||
if (phrase != null && candidates != null && !candidates.isEmpty()) {
|
||||
Collections.sort(candidates);
|
||||
defListModel = ASTGenerator.filterPredictions(candidates);
|
||||
Messages.log("Got: " + candidates.size() + " candidates, " + defListModel.size() + " filtered");
|
||||
}
|
||||
return null;
|
||||
suggestionRunning = false;
|
||||
if (suggestionRequested) {
|
||||
Messages.log("completion invalidated");
|
||||
hideSuggestion();
|
||||
fetchPhrase();
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.log("completion finishing");
|
||||
|
||||
if (finalDefListModel != null) {
|
||||
showSuggestion(finalDefListModel, finalPhrase);
|
||||
} else {
|
||||
hideSuggestion();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Messages.loge("error while preparing suggestions", e.getCause());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
|
||||
try {
|
||||
get();
|
||||
} catch (ExecutionException e) {
|
||||
Messages.loge("error while preparing suggestions", e.getCause());
|
||||
} catch (InterruptedException e) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
suggestionRunning = false;
|
||||
if (suggestionRequested) {
|
||||
Messages.log("completion invalidated");
|
||||
hideSuggestion();
|
||||
fetchPhrase();
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.log("completion finishing");
|
||||
|
||||
if (defListModel != null) {
|
||||
showSuggestion(defListModel, phrase);
|
||||
} else {
|
||||
hideSuggestion();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
suggestionWorker.execute();
|
||||
});
|
||||
}
|
||||
|
||||
protected static String parsePhrase(final String lineText) {
|
||||
|
||||
@@ -122,16 +122,14 @@ public class JavaTextAreaPainter extends TextAreaPainter
|
||||
|
||||
|
||||
void handleCtrlClick(MouseEvent evt) {
|
||||
Messages.log("--handleCtrlClick--");
|
||||
int off = textArea.xyToOffset(evt.getX(), evt.getY());
|
||||
if (off < 0) return;
|
||||
|
||||
int tabIndex = getEditor().getSketch().getCurrentCodeIndex();
|
||||
|
||||
ASTGenerator astGenerator = getJavaEditor().getErrorChecker().getASTGenerator();
|
||||
synchronized (astGenerator) {
|
||||
astGenerator.handleCtrlClick(tabIndex, off);
|
||||
}
|
||||
|
||||
astGenerator.handleCtrlClick(tabIndex, off);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
@@ -34,7 +35,7 @@ public class PreprocessedSketch {
|
||||
public final boolean hasSyntaxErrors;
|
||||
public final boolean hasCompilationErrors;
|
||||
|
||||
public final List<Problem> problems;
|
||||
public final List<IProblem> problems;
|
||||
|
||||
public final List<ImportStatement> programImports;
|
||||
public final List<ImportStatement> coreAndDefaultImports;
|
||||
@@ -178,6 +179,8 @@ public class PreprocessedSketch {
|
||||
public boolean hasSyntaxErrors;
|
||||
public boolean hasCompilationErrors;
|
||||
|
||||
public List<IProblem> problems = new ArrayList<>();
|
||||
|
||||
public final List<ImportStatement> programImports = new ArrayList<>();
|
||||
public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
|
||||
public final List<ImportStatement> codeFolderImports = new ArrayList<>();
|
||||
@@ -210,7 +213,7 @@ public class PreprocessedSketch {
|
||||
hasSyntaxErrors = b.hasSyntaxErrors;
|
||||
hasCompilationErrors = b.hasCompilationErrors;
|
||||
|
||||
problems = new ArrayList<>();
|
||||
problems = Collections.unmodifiableList(b.problems);
|
||||
|
||||
programImports = Collections.unmodifiableList(b.programImports);
|
||||
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);
|
||||
|
||||
Reference in New Issue
Block a user