mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
Rename ECS to preprocessing service, move error checking to PDEX
This commit is contained in:
@@ -29,7 +29,7 @@ import processing.app.ui.Toolkit;
|
||||
import processing.mode.java.debug.LineBreakpoint;
|
||||
import processing.mode.java.debug.LineHighlight;
|
||||
import processing.mode.java.debug.LineID;
|
||||
import processing.mode.java.pdex.ErrorCheckerService;
|
||||
import processing.mode.java.pdex.PreprocessingService;
|
||||
import processing.mode.java.pdex.ImportStatement;
|
||||
import processing.mode.java.pdex.JavaTextArea;
|
||||
import processing.mode.java.pdex.PDEX;
|
||||
@@ -70,7 +70,7 @@ public class JavaEditor extends Editor {
|
||||
private boolean hasJavaTabs;
|
||||
private boolean javaTabWarned;
|
||||
|
||||
protected ErrorCheckerService errorCheckerService;
|
||||
protected PreprocessingService preprocessingService;
|
||||
protected PDEX pdex;
|
||||
|
||||
protected List<Problem> problems = Collections.emptyList();
|
||||
@@ -150,7 +150,7 @@ public class JavaEditor extends Editor {
|
||||
|
||||
getJavaTextArea().setMode(jmode);
|
||||
|
||||
initErrorChecker();
|
||||
initPDEX();
|
||||
|
||||
// ensure completion is hidden when editor loses focus
|
||||
addWindowFocusListener(new WindowFocusListener() {
|
||||
@@ -174,18 +174,9 @@ public class JavaEditor extends Editor {
|
||||
}
|
||||
|
||||
|
||||
protected void initErrorChecker() {
|
||||
errorCheckerService = new ErrorCheckerService(this);
|
||||
for (SketchCode code : getSketch().getCode()) {
|
||||
Document document = code.getDocument();
|
||||
if (document != null) {
|
||||
errorCheckerService.addDocumentListener(document);
|
||||
}
|
||||
}
|
||||
errorCheckerService.start();
|
||||
errorCheckerService.notifySketchChanged();
|
||||
|
||||
pdex = new PDEX(this, errorCheckerService);
|
||||
protected void initPDEX() {
|
||||
preprocessingService = new PreprocessingService(this);
|
||||
pdex = new PDEX(this, preprocessingService);
|
||||
|
||||
// Add ctrl+click listener
|
||||
getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() {
|
||||
@@ -199,6 +190,44 @@ public class JavaEditor extends Editor {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sketchChanged();
|
||||
|
||||
for (SketchCode code : getSketch().getCode()) {
|
||||
Document document = code.getDocument();
|
||||
if (document != null) {
|
||||
addDocumentListener(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addDocumentListener(Document doc) {
|
||||
if (doc != null) doc.addDocumentListener(sketchChangedListener);
|
||||
}
|
||||
|
||||
|
||||
protected final DocumentListener sketchChangedListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
protected void sketchChanged() {
|
||||
pdex.notifySketchChanged();
|
||||
preprocessingService.notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +243,7 @@ public class JavaEditor extends Editor {
|
||||
|
||||
private int previousTabCount = 1;
|
||||
|
||||
// TODO: this is a clumsy way to get notified when tabs get added/deleted
|
||||
// Override the parent call to add hook to the rebuild() method
|
||||
public EditorHeader createHeader() {
|
||||
return new EditorHeader(this) {
|
||||
@@ -225,9 +255,9 @@ public class JavaEditor extends Editor {
|
||||
boolean hasJavaTabsChanged = hasJavaTabs != newHasJavaTabs;
|
||||
hasJavaTabs = newHasJavaTabs;
|
||||
|
||||
if (errorCheckerService != null) {
|
||||
if (preprocessingService != null) {
|
||||
if (hasJavaTabsChanged) {
|
||||
errorCheckerService.handleHasJavaTabsChange(hasJavaTabs);
|
||||
preprocessingService.handleHasJavaTabsChange(hasJavaTabs);
|
||||
pdex.handleHasJavaTabsChange(hasJavaTabs);
|
||||
if (hasJavaTabs) {
|
||||
setProblemList(Collections.emptyList());
|
||||
@@ -237,7 +267,7 @@ public class JavaEditor extends Editor {
|
||||
int currentTabCount = sketch.getCodeCount();
|
||||
if (currentTabCount != previousTabCount) {
|
||||
previousTabCount = currentTabCount;
|
||||
errorCheckerService.notifySketchChanged();
|
||||
sketchChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1327,13 +1357,13 @@ public class JavaEditor extends Editor {
|
||||
|
||||
@Override
|
||||
public void librariesChanged() {
|
||||
errorCheckerService.notifyLibrariesChanged();
|
||||
preprocessingService.notifyLibrariesChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void codeFolderChanged() {
|
||||
errorCheckerService.notifyCodeFolderChanged();
|
||||
preprocessingService.notifyCodeFolderChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -1374,111 +1404,12 @@ public class JavaEditor extends Editor {
|
||||
if (inspector != null) {
|
||||
inspector.dispose();
|
||||
}
|
||||
errorCheckerService.stop();
|
||||
preprocessingService.dispose();
|
||||
pdex.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
// Not sure how this was supposed to work, tempErrorLog is always empty [jv]
|
||||
/**
|
||||
* Writes all error messages to a csv file.
|
||||
* For analytics purposes only.
|
||||
*/
|
||||
/*
|
||||
private void writeErrorsToFile() {
|
||||
if (errorCheckerService.tempErrorLog.size() == 0) return;
|
||||
|
||||
try {
|
||||
System.out.println("Writing errors");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Sketch: " + getSketch().getFolder() + ", "
|
||||
+ new java.sql.Timestamp(new java.util.Date().getTime())
|
||||
+ "\nComma in error msg is substituted with ^ symbol\nFor separating arguments in error args | symbol is used\n");
|
||||
sb.append("ERROR TYPE, ERROR ARGS, ERROR MSG\n");
|
||||
|
||||
for (String errMsg : errorCheckerService.tempErrorLog.keySet()) {
|
||||
IProblem ip = errorCheckerService.tempErrorLog.get(errMsg);
|
||||
if (ip != null) {
|
||||
sb.append(ErrorMessageSimplifier.getIDName(ip.getID()));
|
||||
sb.append(',');
|
||||
sb.append("{");
|
||||
for (int i = 0; i < ip.getArguments().length; i++) {
|
||||
sb.append(ip.getArguments()[i]);
|
||||
if (i < ip.getArguments().length-1)
|
||||
sb.append("| ");
|
||||
}
|
||||
sb.append("}");
|
||||
sb.append(',');
|
||||
sb.append(ip.getMessage().replace(',', '^'));
|
||||
sb.append("\n");
|
||||
}
|
||||
}
|
||||
System.out.println(sb);
|
||||
File opFile = new File(getSketch().getFolder(), "ErrorLogs"
|
||||
+ File.separator + "ErrorLog_" + System.currentTimeMillis() + ".csv");
|
||||
PApplet.saveStream(opFile, new ByteArrayInputStream(sb.toString()
|
||||
.getBytes(Charset.defaultCharset())));
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to save log file for sketch " + getSketch().getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/*
|
||||
private AtomicBoolean debugToolbarEnabled;
|
||||
|
||||
public boolean isDebugToolbarEnabled() {
|
||||
return debugToolbarEnabled != null && debugToolbarEnabled.get();
|
||||
}
|
||||
|
||||
|
||||
/// Toggles between java mode and debug mode toolbar
|
||||
protected void switchToolbars(){
|
||||
final EditorToolbar nextToolbar;
|
||||
if(debugToolbarEnabled.get()){
|
||||
// switch to java
|
||||
if(javaToolbar == null)
|
||||
javaToolbar = createToolbar();
|
||||
nextToolbar = javaToolbar;
|
||||
debugToolbarEnabled.set(false);
|
||||
Base.log("Switching to Java Mode Toolbar");
|
||||
}
|
||||
else{
|
||||
// switch to debug
|
||||
if(debugToolbar == null)
|
||||
debugToolbar = new DebugToolbar(this, getBase());
|
||||
nextToolbar = debugToolbar;
|
||||
debugToolbarEnabled.set(true);
|
||||
Base.log("Switching to Debugger Toolbar");
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
Box upper = (Box)splitPane.getComponent(0);
|
||||
upper.remove(0);
|
||||
upper.add(nextToolbar, 0);
|
||||
upper.validate();
|
||||
nextToolbar.repaint();
|
||||
toolbar = nextToolbar;
|
||||
// The toolbar responds to shift down/up events
|
||||
// in order to show the alt version of toolbar buttons.
|
||||
// With toolbar switch, KeyListener has to be changed as well
|
||||
for (KeyListener kl : textarea.getKeyListeners()) {
|
||||
if(kl instanceof EditorToolbar)
|
||||
{
|
||||
textarea.removeKeyListener(kl);
|
||||
textarea.addKeyListener(toolbar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
textarea.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates the debug menu. Includes ActionListeners for the menu items.
|
||||
* Intended for adding to the menu bar.
|
||||
@@ -1660,26 +1591,6 @@ public class JavaEditor extends Editor {
|
||||
// });
|
||||
// debugMenu.add(item);
|
||||
|
||||
/*
|
||||
item = Toolkit.newJMenuItem(Language.text("menu.debug.show_sketch_outline"), KeyEvent.VK_L);
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Messages.log("Show Sketch Outline:");
|
||||
errorCheckerService.getASTGenerator().showSketchOutline();
|
||||
}
|
||||
});
|
||||
debugMenu.add(item);
|
||||
|
||||
item = Toolkit.newJMenuItem(Language.text("menu.debug.show_tabs_list"), KeyEvent.VK_Y);
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Messages.log("Show Tab Outline:");
|
||||
errorCheckerService.getASTGenerator().showTabOutline();
|
||||
}
|
||||
});
|
||||
debugMenu.add(item);
|
||||
*/
|
||||
|
||||
return debugMenu;
|
||||
}
|
||||
|
||||
@@ -1928,8 +1839,8 @@ public class JavaEditor extends Editor {
|
||||
}
|
||||
|
||||
|
||||
public ErrorCheckerService getErrorChecker() {
|
||||
return errorCheckerService;
|
||||
public PreprocessingService getPreprocessingService() {
|
||||
return preprocessingService;
|
||||
}
|
||||
|
||||
|
||||
@@ -1942,7 +1853,7 @@ public class JavaEditor extends Editor {
|
||||
autoSave();
|
||||
super.prepareRun();
|
||||
downloadImports();
|
||||
errorCheckerService.cancel();
|
||||
preprocessingService.cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -2388,8 +2299,8 @@ public class JavaEditor extends Editor {
|
||||
super.setCode(code);
|
||||
|
||||
Document newDoc = code.getDocument();
|
||||
if (oldDoc != newDoc && errorCheckerService != null) {
|
||||
errorCheckerService.addDocumentListener(newDoc);
|
||||
if (oldDoc != newDoc && preprocessingService != null) {
|
||||
addDocumentListener(newDoc);
|
||||
}
|
||||
|
||||
// set line background colors for tab
|
||||
@@ -2529,24 +2440,6 @@ public class JavaEditor extends Editor {
|
||||
*/
|
||||
|
||||
|
||||
// /**
|
||||
// * Initializes and starts Error Checker Service
|
||||
// */
|
||||
// private void initializeErrorChecker() {
|
||||
// Thread errorCheckerThread = null;
|
||||
//
|
||||
// if (errorCheckerThread == null) {
|
||||
// errorCheckerService = new ErrorCheckerService(this);
|
||||
// errorCheckerThread = new Thread(errorCheckerService);
|
||||
// try {
|
||||
// errorCheckerThread.start();
|
||||
// } catch (Exception e) {
|
||||
// Base.loge("Error Checker Service not initialized", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public void setProblemList(List<Problem> problems) {
|
||||
this.problems = problems;
|
||||
boolean hasErrors = problems.stream().anyMatch(Problem::isError);
|
||||
@@ -2566,7 +2459,7 @@ public class JavaEditor extends Editor {
|
||||
|
||||
for (Problem p : problems) {
|
||||
String message = p.getMessage();
|
||||
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF) &&
|
||||
if (JavaMode.importSuggestEnabled &&
|
||||
p.getImportSuggestions() != null &&
|
||||
p.getImportSuggestions().length > 0) {
|
||||
message += " (double-click for suggestions)";
|
||||
@@ -2616,19 +2509,17 @@ public class JavaEditor extends Editor {
|
||||
* line or not
|
||||
*/
|
||||
public void updateEditorStatus() {
|
||||
if (JavaMode.errorCheckEnabled) {
|
||||
Problem problem = findError(textarea.getCaretLine());
|
||||
if (problem != null) {
|
||||
int type = problem.isError() ?
|
||||
EditorStatus.CURSOR_LINE_ERROR : EditorStatus.CURSOR_LINE_WARNING;
|
||||
statusMessage(problem.getMessage(), type);
|
||||
} else {
|
||||
switch (getStatusMode()) {
|
||||
case EditorStatus.CURSOR_LINE_ERROR:
|
||||
case EditorStatus.CURSOR_LINE_WARNING:
|
||||
statusEmpty();
|
||||
break;
|
||||
}
|
||||
Problem problem = findError(textarea.getCaretLine());
|
||||
if (problem != null) {
|
||||
int type = problem.isError() ?
|
||||
EditorStatus.CURSOR_LINE_ERROR : EditorStatus.CURSOR_LINE_WARNING;
|
||||
statusMessage(problem.getMessage(), type);
|
||||
} else {
|
||||
switch (getStatusMode()) {
|
||||
case EditorStatus.CURSOR_LINE_ERROR:
|
||||
case EditorStatus.CURSOR_LINE_WARNING:
|
||||
statusEmpty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2797,16 +2688,13 @@ public class JavaEditor extends Editor {
|
||||
* the error button at the bottom of the PDE
|
||||
*/
|
||||
public void updateErrorToggle(boolean hasErrors) {
|
||||
footer.setNotification(errorTable.getParent(), //errorTableScrollPane,
|
||||
JavaMode.errorCheckEnabled &&
|
||||
hasErrors);
|
||||
footer.setNotification(errorTable.getParent(), hasErrors);
|
||||
// String title = Language.text("editor.footer.errors");
|
||||
// if (JavaMode.errorCheckEnabled && errorCheckerService.hasErrors()) {
|
||||
// if (hasErrors) {
|
||||
// title += "*";
|
||||
// }
|
||||
// ((JTabbedPane) footer).setTitleAt(ERROR_TAB_INDEX, title);
|
||||
//// btnShowErrors.updateMarker(JavaMode.errorCheckEnabled &&
|
||||
//// errorCheckerService.hasErrors(),
|
||||
//// btnShowErrors.updateMarker(hasErrors,
|
||||
//// errorBar.errorColor);
|
||||
}
|
||||
|
||||
@@ -2875,8 +2763,7 @@ public class JavaEditor extends Editor {
|
||||
jmode.loadPreferences();
|
||||
Messages.log("Applying prefs");
|
||||
// trigger it once to refresh UI
|
||||
errorCheckerService.handlePreferencesChange();
|
||||
setProblemList(Collections.emptyList());
|
||||
sketchChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,33 +117,6 @@ public class JavaMode extends Mode {
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
/*
|
||||
public Runner handleRun(Sketch sketch,
|
||||
RunnerListener listener) throws SketchException {
|
||||
final JavaEditor editor = (JavaEditor) listener;
|
||||
editor.errorCheckerService.quickErrorCheck();
|
||||
// if (enableTweak) {
|
||||
// enableTweak = false;
|
||||
// return handleTweak(sketch, listener, false);
|
||||
// } else {
|
||||
return handleLaunch(sketch, listener, false);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
public Runner handlePresent(Sketch sketch,
|
||||
RunnerListener listener) throws SketchException {
|
||||
final JavaEditor editor = (JavaEditor) listener;
|
||||
editor.errorCheckerService.quickErrorCheck();
|
||||
// if (enableTweak) {
|
||||
// enableTweak = false;
|
||||
// return handleTweak(sketch, listener, true);
|
||||
// } else {
|
||||
return handleLaunch(sketch, listener, true);
|
||||
// }
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/** Handles the standard Java "Run" or "Present" */
|
||||
public Runner handleLaunch(Sketch sketch, RunnerListener listener,
|
||||
@@ -174,7 +147,6 @@ public class JavaMode extends Mode {
|
||||
RunnerListener listener) throws SketchException {
|
||||
// final boolean present) throws SketchException {
|
||||
final JavaEditor editor = (JavaEditor) listener;
|
||||
// editor.errorCheckerService.quickErrorCheck(); // done in prepareRun()
|
||||
|
||||
if (isSketchModified(sketch)) {
|
||||
editor.deactivateRun();
|
||||
@@ -334,7 +306,7 @@ public class JavaMode extends Mode {
|
||||
// }
|
||||
|
||||
|
||||
static public volatile boolean errorCheckEnabled = true;
|
||||
// static public volatile boolean errorCheckEnabled = true;
|
||||
static public volatile boolean warningsEnabled = true;
|
||||
static public volatile boolean codeCompletionsEnabled = true;
|
||||
static public volatile boolean debugOutputEnabled = false;
|
||||
@@ -343,7 +315,7 @@ public class JavaMode extends Mode {
|
||||
static public volatile boolean autoSavePromptEnabled = true;
|
||||
static public volatile boolean defaultAutoSaveEnabled = true;
|
||||
static public volatile boolean ccTriggerEnabled = false;
|
||||
// static public volatile boolean importSuggestEnabled = true;
|
||||
static public volatile boolean importSuggestEnabled = true;
|
||||
static public int autoSaveInterval = 3; //in minutes
|
||||
|
||||
|
||||
@@ -352,7 +324,7 @@ public class JavaMode extends Mode {
|
||||
*/
|
||||
volatile public static int codeCompletionTriggerLength = 1;
|
||||
|
||||
static public final String prefErrorCheck = "pdex.errorCheckEnabled";
|
||||
// static public final String prefErrorCheck = "pdex.errorCheckEnabled";
|
||||
static public final String prefWarnings = "pdex.warningsEnabled";
|
||||
static public final String prefDebugOP = "pdex.dbgOutput";
|
||||
static public final String prefErrorLogs = "pdex.writeErrorLogs";
|
||||
@@ -377,7 +349,7 @@ public class JavaMode extends Mode {
|
||||
public void loadPreferences() {
|
||||
Messages.log("Load PDEX prefs");
|
||||
ensurePrefsExist();
|
||||
errorCheckEnabled = Preferences.getBoolean(prefErrorCheck);
|
||||
// errorCheckEnabled = Preferences.getBoolean(prefErrorCheck);
|
||||
warningsEnabled = Preferences.getBoolean(prefWarnings);
|
||||
codeCompletionsEnabled = Preferences.getBoolean(COMPLETION_PREF);
|
||||
// DEBUG = Preferences.getBoolean(prefDebugOP);
|
||||
@@ -388,14 +360,14 @@ public class JavaMode extends Mode {
|
||||
autoSavePromptEnabled = Preferences.getBoolean(prefAutoSavePrompt);
|
||||
defaultAutoSaveEnabled = Preferences.getBoolean(prefDefaultAutoSave);
|
||||
ccTriggerEnabled = Preferences.getBoolean(COMPLETION_TRIGGER_PREF);
|
||||
// importSuggestEnabled = Preferences.getBoolean(prefImportSuggestEnabled);
|
||||
importSuggestEnabled = Preferences.getBoolean(SUGGEST_IMPORTS_PREF);
|
||||
loadSuggestionsMap();
|
||||
}
|
||||
|
||||
|
||||
public void savePreferences() {
|
||||
Messages.log("Saving PDEX prefs");
|
||||
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
// Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
Preferences.setBoolean(prefWarnings, warningsEnabled);
|
||||
Preferences.setBoolean(COMPLETION_PREF, codeCompletionsEnabled);
|
||||
// Preferences.setBoolean(prefDebugOP, DEBUG);
|
||||
@@ -406,7 +378,7 @@ public class JavaMode extends Mode {
|
||||
Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled);
|
||||
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
|
||||
Preferences.setBoolean(COMPLETION_TRIGGER_PREF, ccTriggerEnabled);
|
||||
// Preferences.setBoolean(prefImportSuggestEnabled, importSuggestEnabled);
|
||||
Preferences.setBoolean(SUGGEST_IMPORTS_PREF, importSuggestEnabled);
|
||||
}
|
||||
|
||||
public void loadSuggestionsMap() {
|
||||
@@ -453,8 +425,8 @@ public class JavaMode extends Mode {
|
||||
|
||||
public void ensurePrefsExist() {
|
||||
//TODO: Need to do a better job of managing prefs. Think lists.
|
||||
if (Preferences.get(prefErrorCheck) == null)
|
||||
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
// if (Preferences.get(prefErrorCheck) == null)
|
||||
// Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
if (Preferences.get(prefWarnings) == null)
|
||||
Preferences.setBoolean(prefWarnings, warningsEnabled);
|
||||
if (Preferences.get(COMPLETION_PREF) == null)
|
||||
@@ -475,8 +447,8 @@ public class JavaMode extends Mode {
|
||||
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
|
||||
if (Preferences.get(COMPLETION_TRIGGER_PREF) == null)
|
||||
Preferences.setBoolean(COMPLETION_TRIGGER_PREF, ccTriggerEnabled);
|
||||
// if (Preferences.get(prefImportSuggestEnabled) == null)
|
||||
// Preferences.setBoolean(prefImportSuggestEnabled, importSuggestEnabled);
|
||||
if (Preferences.get(SUGGEST_IMPORTS_PREF) == null)
|
||||
Preferences.setBoolean(SUGGEST_IMPORTS_PREF, importSuggestEnabled);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.mode.java.JavaMode;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
@@ -687,8 +688,6 @@ public class CompletionGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
//PreprocessedSketch ps = ecs.requestResult();
|
||||
|
||||
if (className.indexOf('.') >= 0) {
|
||||
// Figure out what is package and what is class
|
||||
String[] parts = className.split("\\.");
|
||||
@@ -1319,6 +1318,41 @@ public class CompletionGenerator {
|
||||
}
|
||||
|
||||
|
||||
protected static boolean ignorableSuggestionImport(PreprocessedSketch ps, String impName) {
|
||||
|
||||
String impNameLc = impName.toLowerCase();
|
||||
|
||||
List<ImportStatement> programImports = ps.programImports;
|
||||
List<ImportStatement> codeFolderImports = ps.codeFolderImports;
|
||||
|
||||
boolean isImported = Stream
|
||||
.concat(programImports.stream(), codeFolderImports.stream())
|
||||
.anyMatch(impS -> {
|
||||
String packageNameLc = impS.getPackageName().toLowerCase();
|
||||
return impNameLc.startsWith(packageNameLc);
|
||||
});
|
||||
|
||||
if (isImported) return false;
|
||||
|
||||
final String include = "include";
|
||||
final String exclude = "exclude";
|
||||
|
||||
if (impName.startsWith("processing")) {
|
||||
if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
|
||||
return false;
|
||||
} else if (JavaMode.suggestionsMap.containsKey(exclude) && JavaMode.suggestionsMap.get(exclude).contains(impName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (impName.startsWith("java")) {
|
||||
if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper for java.lang.reflect types.
|
||||
* Will have to see if the usage turns out to be internal only here or not
|
||||
@@ -1850,7 +1884,7 @@ public class CompletionGenerator {
|
||||
matchedClass2 = matchedClass2.replace('/', '.'); //package name
|
||||
String matchedClass = matchedClass2.substring(0, matchedClass2.length() - 6);
|
||||
int d = matchedClass.lastIndexOf('.');
|
||||
if (!ErrorCheckerService.ignorableSuggestionImport(ps, matchedClass)) {
|
||||
if (!ignorableSuggestionImport(ps, matchedClass)) {
|
||||
matchedClass = matchedClass.substring(d + 1); //class name
|
||||
// display package name in grey
|
||||
String html = "<html>" + matchedClass + " : <font color=#777777>" +
|
||||
|
||||
@@ -322,7 +322,7 @@ public class JavaTextArea extends JEditTextArea {
|
||||
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
|
||||
int lineStartOffset = editor.getTextArea().getLineStartOffset(caretLineIndex);
|
||||
|
||||
editor.getErrorChecker().acceptWhenDone(ps -> {
|
||||
editor.getPreprocessingService().whenDone(ps -> {
|
||||
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
|
||||
|
||||
String phrase = null;
|
||||
|
||||
@@ -342,7 +342,7 @@ public class JavaTextAreaPainter extends TextAreaPainter
|
||||
|
||||
|
||||
/**
|
||||
* Sets ErrorCheckerService and loads theme for TextAreaPainter(XQMode)
|
||||
* Loads theme for TextAreaPainter(XQMode)
|
||||
*/
|
||||
public void setMode(Mode mode) {
|
||||
errorUnderlineColor = mode.getColor("editor.error.underline.color");
|
||||
@@ -385,96 +385,6 @@ public class JavaTextAreaPainter extends TextAreaPainter
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
if (!getJavaEditor().hasJavaTabs()) {
|
||||
int off = textArea.xyToOffset(event.getX(), event.getY());
|
||||
if (off < 0) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
int line = textArea.getLineOfOffset(off);
|
||||
if (line < 0) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
|
||||
String s = textArea.getLineText(line);
|
||||
if (s == null || s.isEmpty()) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
|
||||
} else {
|
||||
int x = textArea.xToOffset(line, event.getX()), x2 = x + 1, x1 = x - 1;
|
||||
int xLS = off - textArea.getLineStartNonWhiteSpaceOffset(line);
|
||||
if (x < 0 || x >= s.length()) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
String word = s.charAt(x) + "";
|
||||
if (s.charAt(x) == ' ') {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
if (!(Character.isLetterOrDigit(s.charAt(x)) ||
|
||||
s.charAt(x) == '_' || s.charAt(x) == '$' || s.charAt(x) == '{' ||
|
||||
s.charAt(x) == '}')) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i++;
|
||||
if (x1 >= 0 && x1 < s.length()) {
|
||||
if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') {
|
||||
word = s.charAt(x1--) + word;
|
||||
xLS--;
|
||||
} else
|
||||
x1 = -1;
|
||||
} else
|
||||
x1 = -1;
|
||||
|
||||
if (x2 >= 0 && x2 < s.length()) {
|
||||
if (Character.isLetterOrDigit(s.charAt(x2)) || s.charAt(x2) == '_'
|
||||
|| s.charAt(x2) == '$')
|
||||
word = word + s.charAt(x2++);
|
||||
else
|
||||
x2 = -1;
|
||||
} else
|
||||
x2 = -1;
|
||||
|
||||
if (x1 < 0 && x2 < 0)
|
||||
break;
|
||||
if (i > 200) {
|
||||
// time out!
|
||||
// System.err.println("Whoopsy! :P");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Character.isDigit(word.charAt(0))) {
|
||||
setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
ASTGenerator ast = getJavaEditor().getErrorChecker().getASTGenerator();
|
||||
synchronized (ast) {
|
||||
String tooltipText = ast.getLabelForASTNode(line, word, xLS);
|
||||
|
||||
// log(errorCheckerService.mainClassOffset + " MCO "
|
||||
// + "|" + line + "| offset " + xLS + word + " <= offf: "+off+ "\n");
|
||||
if (tooltipText != null) {
|
||||
return tooltipText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Used when there are Java tabs, but also the fall-through case from above
|
||||
// setToolTipText(null);
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// TweakMode code
|
||||
protected int horizontalAdjustment = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
@@ -27,7 +31,12 @@ import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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 javax.swing.BorderFactory;
|
||||
@@ -48,10 +57,12 @@ import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeModel;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.ui.EditorStatus;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
import static processing.mode.java.pdex.ASTUtils.*;
|
||||
@@ -62,24 +73,28 @@ public class PDEX {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private ErrorChecker errorChecker;
|
||||
|
||||
private ShowUsage showUsage;
|
||||
private Rename rename;
|
||||
private DebugTree debugTree;
|
||||
|
||||
private JavaEditor editor;
|
||||
private ErrorCheckerService ecs;
|
||||
private PreprocessingService pps;
|
||||
|
||||
|
||||
public PDEX(JavaEditor editor, ErrorCheckerService ecs) {
|
||||
public PDEX(JavaEditor editor, PreprocessingService pps) {
|
||||
this.editor = editor;
|
||||
this.ecs = ecs;
|
||||
this.pps = pps;
|
||||
|
||||
this.enabled = !editor.hasJavaTabs();
|
||||
|
||||
showUsage = new ShowUsage(editor, ecs);
|
||||
errorChecker = new ErrorChecker(editor, pps);
|
||||
|
||||
showUsage = new ShowUsage(editor, pps);
|
||||
rename = new Rename(editor);
|
||||
if (SHOW_DEBUG_TREE) {
|
||||
debugTree = new DebugTree(editor, ecs);
|
||||
debugTree = new DebugTree(editor, pps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,21 +102,21 @@ public class PDEX {
|
||||
public void handleShowUsage(int tabIndex, int startTabOffset, int stopTabOffset) {
|
||||
Messages.log("* handleShowUsage");
|
||||
if (!enabled) return; // show usage disabled if java tabs
|
||||
ecs.acceptWhenDone(ps -> showUsage.findUsageAndUpdateTree(ps, tabIndex, startTabOffset, stopTabOffset));
|
||||
pps.whenDone(ps -> showUsage.findUsageAndUpdateTree(ps, tabIndex, startTabOffset, stopTabOffset));
|
||||
}
|
||||
|
||||
|
||||
public void handleRename(int tabIndex, int startTabOffset, int stopTabOffset) {
|
||||
Messages.log("* handleRename");
|
||||
if (!enabled) return; // refactoring disabled w/ java tabs
|
||||
ecs.acceptWhenDone(ps -> rename.handleRename(ps, tabIndex, startTabOffset, stopTabOffset));
|
||||
pps.whenDone(ps -> rename.handleRename(ps, tabIndex, startTabOffset, stopTabOffset));
|
||||
}
|
||||
|
||||
|
||||
public void handleCtrlClick(int tabIndex, int offset) {
|
||||
Messages.log("* handleCtrlClick");
|
||||
if (!enabled) return; // disabled w/ java tabs
|
||||
ecs.acceptWhenDone(ps -> handleCtrlClick(ps, tabIndex, offset));
|
||||
pps.whenDone(ps -> handleCtrlClick(ps, tabIndex, offset));
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +128,13 @@ public class PDEX {
|
||||
}
|
||||
|
||||
|
||||
public void notifySketchChanged() {
|
||||
errorChecker.notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
errorChecker.dispose();
|
||||
showUsage.dispose();
|
||||
rename.dispose();
|
||||
if (debugTree != null) {
|
||||
@@ -178,16 +199,16 @@ public class PDEX {
|
||||
final JTree tree;
|
||||
|
||||
final JavaEditor editor;
|
||||
final ErrorCheckerService ecs;
|
||||
final PreprocessingService pps;
|
||||
|
||||
final Consumer<PreprocessedSketch> reloadListener;
|
||||
|
||||
IBinding binding;
|
||||
|
||||
|
||||
ShowUsage(JavaEditor editor, ErrorCheckerService ecs) {
|
||||
ShowUsage(JavaEditor editor, PreprocessingService pps) {
|
||||
this.editor = editor;
|
||||
this.ecs = ecs;
|
||||
this.pps = pps;
|
||||
|
||||
reloadListener = this::reloadShowUsage;
|
||||
|
||||
@@ -201,12 +222,12 @@ public class PDEX {
|
||||
// Delete references to ASTNodes so that whole AST can be GC'd
|
||||
binding = null;
|
||||
tree.setModel(null);
|
||||
ecs.unregisterDoneListener(reloadListener);
|
||||
pps.unregisterListener(reloadListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
ecs.registerDoneListener(reloadListener);
|
||||
pps.registerListener(reloadListener);
|
||||
}
|
||||
});
|
||||
window.setSize(300, 400);
|
||||
@@ -689,7 +710,7 @@ public class PDEX {
|
||||
final Consumer<PreprocessedSketch> updateListener;
|
||||
|
||||
|
||||
DebugTree(JavaEditor editor, ErrorCheckerService ecs) {
|
||||
DebugTree(JavaEditor editor, PreprocessingService pps) {
|
||||
updateListener = this::buildAndUpdateTree;
|
||||
|
||||
window = new JDialog(editor);
|
||||
@@ -713,7 +734,7 @@ public class PDEX {
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
ecs.unregisterDoneListener(updateListener);
|
||||
pps.unregisterListener(updateListener);
|
||||
tree.setModel(null);
|
||||
}
|
||||
});
|
||||
@@ -723,8 +744,8 @@ public class PDEX {
|
||||
JScrollPane sp = new JScrollPane();
|
||||
sp.setViewportView(tree);
|
||||
window.add(sp);
|
||||
ecs.acceptWhenDone(updateListener);
|
||||
ecs.registerDoneListener(updateListener);
|
||||
pps.whenDone(updateListener);
|
||||
pps.registerListener(updateListener);
|
||||
|
||||
|
||||
tree.addTreeSelectionListener(e -> {
|
||||
@@ -735,7 +756,7 @@ public class PDEX {
|
||||
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
|
||||
if (tnode.getUserObject() instanceof ASTNode) {
|
||||
ASTNode node = (ASTNode) tnode.getUserObject();
|
||||
ecs.acceptWhenDone(ps -> {
|
||||
pps.whenDone(ps -> {
|
||||
SketchInterval si = ps.mapJavaToSketch(node);
|
||||
EventQueue.invokeLater(() -> {
|
||||
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
|
||||
@@ -797,4 +818,134 @@ public class PDEX {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class ErrorChecker {
|
||||
|
||||
// Delay delivering error check result after last sketch change #2677
|
||||
private final static long DELAY_BEFORE_UPDATE = 650;
|
||||
|
||||
private ScheduledExecutorService scheduler;
|
||||
private volatile ScheduledFuture<?> scheduledUiUpdate = null;
|
||||
private volatile long nextUiUpdate = 0;
|
||||
|
||||
private final Consumer<PreprocessedSketch> errorHandlerListener = this::handleSketchProblems;
|
||||
|
||||
private JavaEditor editor;
|
||||
|
||||
|
||||
public ErrorChecker(JavaEditor editor, PreprocessingService pps) {
|
||||
this.editor = editor;
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
pps.registerListener(errorHandlerListener);
|
||||
}
|
||||
|
||||
|
||||
public void notifySketchChanged() {
|
||||
nextUiUpdate = System.currentTimeMillis() + DELAY_BEFORE_UPDATE;
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleSketchProblems(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 (JavaMode.importSuggestEnabled) {
|
||||
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;
|
||||
})
|
||||
// Group problems by the missing type/name
|
||||
.collect(Collectors.groupingBy(p -> p.getIProblem().getArguments()[0]));
|
||||
|
||||
if (!undefinedTypeProblems.isEmpty()) {
|
||||
final ClassPath cp = ps.searchClassPath;
|
||||
|
||||
// Get suggestions for each missing type, update the problems
|
||||
undefinedTypeProblems.entrySet().stream()
|
||||
.forEach(entry -> {
|
||||
String missingClass = entry.getKey();
|
||||
List<Problem> affectedProblems = entry.getValue();
|
||||
String[] suggestions = getImportSuggestions(cp, missingClass);
|
||||
affectedProblems.forEach(p -> p.setImportSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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(() -> editor.setProblemList(problems));
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate = scheduler.schedule(uiUpdater, delay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
public static String[] getImportSuggestions(ClassPath cp, String className) {
|
||||
RegExpResourceFilter regf = new RegExpResourceFilter(
|
||||
Pattern.compile(".*"),
|
||||
Pattern.compile("(.*\\$)?" + className + "\\.class",
|
||||
Pattern.CASE_INSENSITIVE));
|
||||
|
||||
String[] resources = cp.findResources("", regf);
|
||||
return Arrays.stream(resources)
|
||||
// remove ".class" suffix
|
||||
.map(res -> res.substring(0, res.length() - 6))
|
||||
// replace path separators with dots
|
||||
.map(res -> res.replace('/', '.'))
|
||||
// replace inner class separators with dots
|
||||
.map(res -> res.replace('$', '.'))
|
||||
// sort, prioritize clases from java. package
|
||||
.sorted((o1, o2) -> {
|
||||
// put java.* first, should be prioritized more
|
||||
boolean o1StartsWithJava = o1.startsWith("java");
|
||||
boolean o2StartsWithJava = o2.startsWith("java");
|
||||
if (o1StartsWithJava != o2StartsWithJava) {
|
||||
if (o1StartsWithJava) return -1;
|
||||
return 1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
})
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
import com.google.classpath.ClassPathFactory;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -41,21 +38,12 @@ 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.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
@@ -65,7 +53,6 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import processing.app.Library;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchCode;
|
||||
import processing.app.SketchException;
|
||||
@@ -74,7 +61,6 @@ import processing.data.IntList;
|
||||
import processing.data.StringList;
|
||||
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;
|
||||
import processing.mode.java.preproc.PdePreprocessor.Mode;
|
||||
@@ -84,39 +70,23 @@ import processing.mode.java.preproc.PdePreprocessor.Mode;
|
||||
* The main error checking service
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ErrorCheckerService {
|
||||
public class PreprocessingService {
|
||||
|
||||
protected final JavaEditor editor;
|
||||
|
||||
/** The amazing eclipse ast parser */
|
||||
protected final ASTParser parser = ASTParser.newParser(AST.JLS8);
|
||||
|
||||
/** Class path factory for ASTGenerator */
|
||||
protected final ClassPathFactory classPathFactory = new ClassPathFactory();
|
||||
private final ClassPathFactory classPathFactory = new ClassPathFactory();
|
||||
|
||||
/**
|
||||
* Used to indirectly stop the Error Checker Thread
|
||||
*/
|
||||
private volatile boolean running;
|
||||
|
||||
/**
|
||||
* Error checking doesn't happen before this interval has ellapsed since the
|
||||
* last request() call.
|
||||
*/
|
||||
private final static long errorCheckInterval = 650;
|
||||
|
||||
private Thread errorCheckerThread;
|
||||
private final Thread preprocessingThread;
|
||||
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 boolean needsCheck = false;
|
||||
|
||||
private AtomicBoolean codeFolderChanged = new AtomicBoolean(true);
|
||||
private AtomicBoolean librariesChanged = new AtomicBoolean(true);
|
||||
private final AtomicBoolean codeFolderChanged = new AtomicBoolean(true);
|
||||
private final AtomicBoolean librariesChanged = new AtomicBoolean(true);
|
||||
|
||||
private volatile boolean running;
|
||||
private CompletableFuture<PreprocessedSketch> preprocessingTask = new CompletableFuture<>();
|
||||
|
||||
private CompletableFuture<?> lastCallback =
|
||||
@@ -124,75 +94,58 @@ public class ErrorCheckerService {
|
||||
complete(null); // initialization block
|
||||
}};
|
||||
|
||||
private final Consumer<PreprocessedSketch> errorHandlerListener = this::handleSketchProblems;
|
||||
|
||||
private volatile boolean isEnabled = true;
|
||||
private volatile boolean isContinuousCheckEnabled = true;
|
||||
|
||||
|
||||
public ErrorCheckerService(JavaEditor editor) {
|
||||
public PreprocessingService(JavaEditor editor) {
|
||||
this.editor = editor;
|
||||
isEnabled = !editor.hasJavaTabs();
|
||||
isContinuousCheckEnabled = JavaMode.errorCheckEnabled;
|
||||
registerDoneListener(errorHandlerListener);
|
||||
|
||||
preprocessingThread = new Thread(this::mainLoop, "ECS");
|
||||
preprocessingThread.start();
|
||||
}
|
||||
|
||||
|
||||
private void mainLoop() {
|
||||
running = true;
|
||||
PreprocessedSketch prevResult = null;
|
||||
Messages.log("PPS: Hi!");
|
||||
while (running) {
|
||||
try {
|
||||
try {
|
||||
requestQueue.take(); // blocking until check requested
|
||||
requestQueue.take(); // blocking until requested
|
||||
} catch (InterruptedException e) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Messages.log("Starting preprocessing");
|
||||
Messages.log("PPS: Starting");
|
||||
|
||||
prevResult = preprocessSketch(prevResult);
|
||||
|
||||
synchronized (requestLock) {
|
||||
if (requestQueue.isEmpty()) {
|
||||
Messages.log("Completing preprocessing");
|
||||
Messages.log("PPS: Completed");
|
||||
preprocessingTask.complete(prevResult);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Messages.loge("problem in error checker loop", e);
|
||||
Messages.loge("problem in preprocessor service loop", e);
|
||||
}
|
||||
}
|
||||
Messages.log("PPS: Bye!");
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
errorCheckerThread = new Thread(this::mainLoop, "ECS");
|
||||
errorCheckerThread.start();
|
||||
}
|
||||
|
||||
|
||||
public void stop() {
|
||||
public void dispose() {
|
||||
cancel();
|
||||
running = false;
|
||||
if (errorCheckerThread != null) {
|
||||
running = false;
|
||||
errorCheckerThread.interrupt();
|
||||
}
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
preprocessingThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
public void cancel() {
|
||||
requestQueue.clear();
|
||||
nextUiUpdate = 0;
|
||||
if (scheduledUiUpdate != null) {
|
||||
scheduledUiUpdate.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,35 +155,28 @@ public class ErrorCheckerService {
|
||||
if (preprocessingTask.isDone()) {
|
||||
preprocessingTask = new CompletableFuture<>();
|
||||
// Register callback which executes all listeners
|
||||
registerCallback(this::fireDoneListeners);
|
||||
}
|
||||
if (isContinuousCheckEnabled) {
|
||||
// Continuous check enabled, request
|
||||
nextUiUpdate = System.currentTimeMillis() + errorCheckInterval;
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
} else {
|
||||
// Continuous check not enabled, take note
|
||||
needsCheck = true;
|
||||
whenDone(this::fireListeners);
|
||||
}
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void notifyLibrariesChanged() {
|
||||
Messages.log("PPS: notified libraries changed");
|
||||
librariesChanged.set(true);
|
||||
Messages.log("Notify libraries changed");
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void notifyCodeFolderChanged() {
|
||||
Messages.log("PPS: snotified code folder changed");
|
||||
codeFolderChanged.set(true);
|
||||
Messages.log("Notify code folder changed");
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
private void registerCallback(Consumer<PreprocessedSketch> callback) {
|
||||
public void whenDone(Consumer<PreprocessedSketch> callback) {
|
||||
if (!isEnabled) return;
|
||||
synchronized (requestLock) {
|
||||
lastCallback = preprocessingTask
|
||||
@@ -238,49 +184,36 @@ public class ErrorCheckerService {
|
||||
.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);
|
||||
if (e != null) Messages.loge("exception in preprocessing callback", e);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void acceptWhenDone(Consumer<PreprocessedSketch> callback) {
|
||||
if (!isEnabled) return;
|
||||
synchronized (requestLock) {
|
||||
// Continuous check not enabled, request check now
|
||||
if (needsCheck && !isContinuousCheckEnabled) {
|
||||
needsCheck = false;
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
}
|
||||
registerCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// LISTENERS ----------------------------------------------------------------
|
||||
|
||||
|
||||
private Set<Consumer<PreprocessedSketch>> doneListeners = new CopyOnWriteArraySet<>();
|
||||
private Set<Consumer<PreprocessedSketch>> listeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
|
||||
public void registerDoneListener(Consumer<PreprocessedSketch> listener) {
|
||||
if (listener != null) doneListeners.add(listener);
|
||||
public void registerListener(Consumer<PreprocessedSketch> listener) {
|
||||
if (listener != null) listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
public void unregisterDoneListener(Consumer<PreprocessedSketch> listener) {
|
||||
doneListeners.remove(listener);
|
||||
public void unregisterListener(Consumer<PreprocessedSketch> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
private void fireDoneListeners(PreprocessedSketch ps) {
|
||||
for (Consumer<PreprocessedSketch> listener : doneListeners) {
|
||||
private void fireListeners(PreprocessedSketch ps) {
|
||||
for (Consumer<PreprocessedSketch> listener : listeners) {
|
||||
try {
|
||||
listener.accept(ps);
|
||||
} catch (Exception e) {
|
||||
Messages.loge("error when firing ecs listener", e);
|
||||
Messages.loge("error when firing preprocessing listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,30 +222,6 @@ public class ErrorCheckerService {
|
||||
/// --------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
public void addDocumentListener(Document doc) {
|
||||
if (doc != null) doc.addDocumentListener(sketchChangedListener);
|
||||
}
|
||||
|
||||
|
||||
protected final DocumentListener sketchChangedListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
notifySketchChanged();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
|
||||
|
||||
boolean firstCheck = prevResult == null;
|
||||
@@ -505,77 +414,6 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
private void handleSketchProblems(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;
|
||||
})
|
||||
// Group problems by the missing type/name
|
||||
.collect(Collectors.groupingBy(p -> p.getIProblem().getArguments()[0]));
|
||||
|
||||
if (!undefinedTypeProblems.isEmpty()) {
|
||||
final ClassPath cp = ps.searchClassPath;
|
||||
|
||||
// Get suggestions for each missing type, update the problems
|
||||
undefinedTypeProblems.entrySet().stream()
|
||||
.forEach(entry -> {
|
||||
String missingClass = entry.getKey();
|
||||
List<Problem> affectedProblems = entry.getValue();
|
||||
String[] suggestions = getImportSuggestions(cp, missingClass);
|
||||
affectedProblems.forEach(p -> p.setImportSuggestions(suggestions));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 (isContinuousCheckEnabled) {
|
||||
editor.setProblemList(problems);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate = scheduler.schedule(uiUpdater, delay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// IMPORTS -----------------------------------------------------------------
|
||||
|
||||
private List<ImportStatement> coreAndDefaultImports;
|
||||
@@ -608,6 +446,22 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
private static boolean checkIfImportsChanged(List<ImportStatement> prevImports,
|
||||
List<ImportStatement> imports) {
|
||||
if (imports.size() != prevImports.size()) {
|
||||
return true;
|
||||
} else {
|
||||
int count = imports.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!imports.get(i).isSameAs(prevImports.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// CLASSPATHS ---------------------------------------------------------------
|
||||
|
||||
@@ -621,8 +475,8 @@ public class ErrorCheckerService {
|
||||
|
||||
private List<String> codeFolderClassPath;
|
||||
|
||||
private List<String> searchLibraryClassPath;
|
||||
private List<String> sketchLibraryClassPath;
|
||||
private List<String> searchLibraryClassPath;
|
||||
|
||||
|
||||
private static List<String> buildCodeFolderClassPath(Sketch sketch) {
|
||||
@@ -670,6 +524,7 @@ public class ErrorCheckerService {
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
private static List<String> buildSearchLibraryClassPath(JavaMode mode) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
@@ -735,36 +590,7 @@ public class ErrorCheckerService {
|
||||
|
||||
|
||||
|
||||
public static String[] getImportSuggestions(ClassPath cp, String className) {
|
||||
RegExpResourceFilter regf = new RegExpResourceFilter(
|
||||
Pattern.compile(".*"),
|
||||
Pattern.compile("(.*\\$)?" + className + "\\.class",
|
||||
Pattern.CASE_INSENSITIVE));
|
||||
|
||||
String[] resources = cp.findResources("", regf);
|
||||
return Arrays.stream(resources)
|
||||
// remove ".class" suffix
|
||||
.map(res -> res.substring(0, res.length() - 6))
|
||||
// replace path separators with dots
|
||||
.map(res -> res.replace('/', '.'))
|
||||
// replace inner class separators with dots
|
||||
.map(res -> res.replace('$', '.'))
|
||||
// sort, prioritize clases from java. package
|
||||
.sorted((o1, o2) -> {
|
||||
// put java.* first, should be prioritized more
|
||||
boolean o1StartsWithJava = o1.startsWith("java");
|
||||
boolean o2StartsWithJava = o2.startsWith("java");
|
||||
if (o1StartsWithJava != o2StartsWithJava) {
|
||||
if (o1StartsWithJava) return -1;
|
||||
return 1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
})
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
||||
protected static CompilationUnit makeAST(ASTParser parser,
|
||||
private static CompilationUnit makeAST(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options) {
|
||||
parser.setSource(source);
|
||||
@@ -776,7 +602,7 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
protected static CompilationUnit makeASTWithBindings(ASTParser parser,
|
||||
private static CompilationUnit makeASTWithBindings(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options,
|
||||
String className,
|
||||
@@ -796,47 +622,12 @@ public class ErrorCheckerService {
|
||||
/**
|
||||
* Ignore processing packages, java.*.*. etc.
|
||||
*/
|
||||
protected boolean ignorableImport(String packageName) {
|
||||
private boolean ignorableImport(String packageName) {
|
||||
return (packageName.startsWith("java.") ||
|
||||
packageName.startsWith("javax."));
|
||||
}
|
||||
|
||||
|
||||
protected static boolean ignorableSuggestionImport(PreprocessedSketch ps, String impName) {
|
||||
|
||||
String impNameLc = impName.toLowerCase();
|
||||
|
||||
List<ImportStatement> programImports = ps.programImports;
|
||||
List<ImportStatement> codeFolderImports = ps.codeFolderImports;
|
||||
|
||||
boolean isImported = Stream
|
||||
.concat(programImports.stream(), codeFolderImports.stream())
|
||||
.anyMatch(impS -> {
|
||||
String packageNameLc = impS.getPackageName().toLowerCase();
|
||||
return impNameLc.startsWith(packageNameLc);
|
||||
});
|
||||
|
||||
if (isImported) return false;
|
||||
|
||||
final String include = "include";
|
||||
final String exclude = "exclude";
|
||||
|
||||
if (impName.startsWith("processing")) {
|
||||
if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
|
||||
return false;
|
||||
} else if (JavaMode.suggestionsMap.containsKey(exclude) && JavaMode.suggestionsMap.get(exclude).contains(impName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (impName.startsWith("java")) {
|
||||
if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static private final Map<String, String> COMPILER_OPTIONS;
|
||||
static {
|
||||
Map<String, String> compilerOptions = new HashMap<>();
|
||||
@@ -879,37 +670,6 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if import statements in the sketch have changed. If they have,
|
||||
* compiler classpath needs to be updated.
|
||||
*/
|
||||
protected static boolean checkIfImportsChanged(List<ImportStatement> prevImports,
|
||||
List<ImportStatement> imports) {
|
||||
if (imports.size() != prevImports.size()) {
|
||||
return true;
|
||||
} else {
|
||||
int count = imports.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!imports.get(i).isSameAs(prevImports.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void handlePreferencesChange() {
|
||||
isContinuousCheckEnabled = JavaMode.errorCheckEnabled;
|
||||
if (isContinuousCheckEnabled) {
|
||||
Messages.log(editor.getSketch().getName() + " Error Checker enabled.");
|
||||
notifySketchChanged();
|
||||
} else {
|
||||
Messages.log(editor.getSketch().getName() + " Error Checker disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleHasJavaTabsChange(boolean hasJavaTabs) {
|
||||
isEnabled = !hasJavaTabs;
|
||||
if (isEnabled) {
|
||||
Reference in New Issue
Block a user