mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
Code suggestions refactoring
- fixed threading - support for array types - support for static access - support for local classes
This commit is contained in:
@@ -35,7 +35,6 @@ import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -43,7 +42,6 @@ import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -157,7 +155,6 @@ public class ASTGenerator {
|
||||
//addCompletionPopupListner();
|
||||
addListeners();
|
||||
//loadJavaDoc();
|
||||
predictionOngoing = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +395,7 @@ public class ASTGenerator {
|
||||
}
|
||||
jdocMap.put(methodName, msg);
|
||||
}
|
||||
System.out.println("JDoc loaded "+jdocMap.size());
|
||||
System.out.println("JDoc loaded " + jdocMap.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -539,11 +536,42 @@ public class ASTGenerator {
|
||||
if(decl != null){
|
||||
// see if locally defined
|
||||
log(getNodeAsString(astNode)+" found decl -> " + getNodeAsString(decl));
|
||||
|
||||
{
|
||||
if (decl.getNodeType() == ASTNode.TYPE_DECLARATION) {
|
||||
TypeDeclaration td = (TypeDeclaration) decl;
|
||||
return new ClassMember(td);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Handle "array." x "array[1]."
|
||||
Type type = extracTypeInfo2(decl);
|
||||
if (type != null && type.isArrayType() &&
|
||||
astNode.getParent().getNodeType() != ASTNode.ARRAY_ACCESS) {
|
||||
// No array access, we want members of the array itself
|
||||
Type elementType = ((ArrayType) type).getElementType();
|
||||
|
||||
// Get name of the element class
|
||||
String name = "";
|
||||
if (elementType.isSimpleType()) {
|
||||
Class<?> c = findClassIfExists(elementType.toString());
|
||||
if (c != null) name = c.getName();
|
||||
} else if (elementType.isPrimitiveType()) {
|
||||
name = ((PrimitiveType) elementType).getPrimitiveTypeCode().toString();
|
||||
}
|
||||
|
||||
// Convert element class to array class
|
||||
Class<?> arrayClass = getArrayClass(name);
|
||||
|
||||
return arrayClass == null ? null : new ClassMember(arrayClass);
|
||||
}
|
||||
}
|
||||
|
||||
return new ClassMember(extracTypeInfo(decl));
|
||||
}
|
||||
else {
|
||||
// or in a predefined class?
|
||||
Class<?> tehClass = findClassIfExists(((SimpleName) astNode).toString());
|
||||
Class<?> tehClass = findClassIfExists(astNode.toString());
|
||||
if (tehClass != null) {
|
||||
return new ClassMember(tehClass);
|
||||
}
|
||||
@@ -596,6 +624,30 @@ public class ASTGenerator {
|
||||
if(temp instanceof MethodDeclaration){
|
||||
// method is locally defined
|
||||
log(mi.getName() + " was found locally," + getNodeAsString(extracTypeInfo(temp)));
|
||||
|
||||
{ // Handle "array." x "array[1]."
|
||||
Type type = extracTypeInfo2(temp);
|
||||
if (type != null && type.isArrayType() &&
|
||||
astNode.getParent().getNodeType() != ASTNode.ARRAY_ACCESS) {
|
||||
// No array access, we want members of the array itself
|
||||
Type elementType = ((ArrayType) type).getElementType();
|
||||
|
||||
// Get name of the element class
|
||||
String name = "";
|
||||
if (elementType.isSimpleType()) {
|
||||
Class<?> c = findClassIfExists(elementType.toString());
|
||||
if (c != null) name = c.getName();
|
||||
} else if (elementType.isPrimitiveType()) {
|
||||
name = ((PrimitiveType) elementType).getPrimitiveTypeCode().toString();
|
||||
}
|
||||
|
||||
// Convert element class to array class
|
||||
Class<?> arrayClass = getArrayClass(name);
|
||||
|
||||
return arrayClass == null ? null : new ClassMember(arrayClass);
|
||||
}
|
||||
}
|
||||
|
||||
return new ClassMember(extracTypeInfo(temp));
|
||||
}
|
||||
if (mi.getExpression() == null) {
|
||||
@@ -605,38 +657,46 @@ public class ASTGenerator {
|
||||
return null;
|
||||
} else {
|
||||
if (mi.getExpression() instanceof SimpleName) {
|
||||
stp = extracTypeInfo(findDeclaration2((SimpleName) mi.getExpression(),
|
||||
nearestNode));
|
||||
if(stp == null){
|
||||
ASTNode decl = findDeclaration2((SimpleName) mi.getExpression(),
|
||||
nearestNode);
|
||||
if (decl != null) {
|
||||
if (decl.getNodeType() == ASTNode.TYPE_DECLARATION) {
|
||||
TypeDeclaration td = (TypeDeclaration) decl;
|
||||
return new ClassMember(td);
|
||||
}
|
||||
|
||||
stp = extracTypeInfo(decl);
|
||||
if(stp == null){
|
||||
/*The type wasn't found in local code, so it might be something like
|
||||
* System.console()., or maybe belonging to super class, etc.
|
||||
*/
|
||||
Class<?> tehClass = findClassIfExists(((SimpleName)mi.getExpression()).toString());
|
||||
if (tehClass != null) {
|
||||
// Method Expression is a simple name and wasn't located locally, but found in a class
|
||||
// so look for method in this class.
|
||||
return definedIn3rdPartyClass(new ClassMember(tehClass), mi
|
||||
.getName().toString());
|
||||
Class<?> tehClass = findClassIfExists(((SimpleName)mi.getExpression()).toString());
|
||||
if (tehClass != null) {
|
||||
// Method Expression is a simple name and wasn't located locally, but found in a class
|
||||
// so look for method in this class.
|
||||
return definedIn3rdPartyClass(new ClassMember(tehClass), mi
|
||||
.getName().toString());
|
||||
}
|
||||
log("MI resolve 3rd par, Can't resolve " + mi.getExpression());
|
||||
return null;
|
||||
}
|
||||
log("MI resolve 3rd par, Can't resolve " + mi.getExpression());
|
||||
return null;
|
||||
}
|
||||
log("MI, SN Type " + getNodeAsString(stp));
|
||||
ASTNode typeDec = findDeclaration2(stp.getName(),nearestNode);
|
||||
if(typeDec == null){
|
||||
log(stp.getName() + " couldn't be found locally..");
|
||||
Class<?> tehClass = findClassIfExists(stp.getName().toString());
|
||||
if (tehClass != null) {
|
||||
// Method Expression is a simple name and wasn't located locally, but found in a class
|
||||
// so look for method in this class.
|
||||
return definedIn3rdPartyClass(new ClassMember(tehClass), mi
|
||||
.getName().toString());
|
||||
log("MI, SN Type " + getNodeAsString(stp));
|
||||
ASTNode typeDec = findDeclaration2(stp.getName(),nearestNode);
|
||||
if(typeDec == null){
|
||||
log(stp.getName() + " couldn't be found locally..");
|
||||
Class<?> tehClass = findClassIfExists(stp.getName().toString());
|
||||
if (tehClass != null) {
|
||||
// Method Expression is a simple name and wasn't located locally, but found in a class
|
||||
// so look for method in this class.
|
||||
return definedIn3rdPartyClass(new ClassMember(tehClass), mi
|
||||
.getName().toString());
|
||||
}
|
||||
//return new ClassMember(findClassIfExists(stp.getName().toString()));
|
||||
}
|
||||
//return new ClassMember(findClassIfExists(stp.getName().toString()));
|
||||
//scopeParent = definedIn3rdPartyClass(stp.getName().toString(), "THIS");
|
||||
return definedIn3rdPartyClass(new ClassMember(typeDec), mi
|
||||
.getName().toString());
|
||||
}
|
||||
//scopeParent = definedIn3rdPartyClass(stp.getName().toString(), "THIS");
|
||||
return definedIn3rdPartyClass(new ClassMember(typeDec), mi
|
||||
.getName().toString());
|
||||
} else {
|
||||
log("MI EXP.."+getNodeAsString(mi.getExpression()));
|
||||
// return null;
|
||||
@@ -711,6 +771,36 @@ public class ASTGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Class<?> getArrayClass(String elementClass) {
|
||||
String name;
|
||||
if (elementClass.startsWith("[")) {
|
||||
// just add a leading "["
|
||||
name = "[" + elementClass;
|
||||
} else if (elementClass.equals("boolean")) {
|
||||
name = "[Z";
|
||||
} else if (elementClass.equals("byte")) {
|
||||
name = "[B";
|
||||
} else if (elementClass.equals("char")) {
|
||||
name = "[C";
|
||||
} else if (elementClass.equals("double")) {
|
||||
name = "[D";
|
||||
} else if (elementClass.equals("float")) {
|
||||
name = "[F";
|
||||
} else if (elementClass.equals("int")) {
|
||||
name = "[I";
|
||||
} else if (elementClass.equals("long")) {
|
||||
name = "[J";
|
||||
} else if (elementClass.equals("short")) {
|
||||
name = "[S";
|
||||
} else {
|
||||
// must be an object non-array class
|
||||
name = "[L" + elementClass + ";";
|
||||
}
|
||||
return loadClass(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For a().abc.a123 this would return a123
|
||||
*
|
||||
@@ -753,151 +843,113 @@ public class ASTGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void trimCandidates(String newWord) {
|
||||
ArrayList<CompletionCandidate> newCandidate = new ArrayList<CompletionCandidate>();
|
||||
protected static List<CompletionCandidate> trimCandidates(String newWord, List<CompletionCandidate> candidates) {
|
||||
ArrayList<CompletionCandidate> newCandidate = new ArrayList<>();
|
||||
newWord = newWord.toLowerCase();
|
||||
for (CompletionCandidate comp : candidates) {
|
||||
if(comp.getNoHtmlLabel().toLowerCase().startsWith(newWord)){
|
||||
newCandidate.add(comp);
|
||||
}
|
||||
}
|
||||
candidates = newCandidate;
|
||||
return newCandidate;
|
||||
}
|
||||
|
||||
protected List<CompletionCandidate> candidates;
|
||||
protected String lastPredictedWord = " ";
|
||||
protected int predictionMinLength = 2;
|
||||
|
||||
|
||||
private AtomicBoolean predictionOngoing;
|
||||
protected String lastPredictedPhrase = " ";
|
||||
protected static final int predictionMinLength = 2;
|
||||
|
||||
/**
|
||||
* The main function that calculates possible code completion candidates
|
||||
*
|
||||
* @param word
|
||||
* @param pdePhrase
|
||||
* @param line
|
||||
* @param lineStartNonWSOffset
|
||||
*/
|
||||
public void preparePredictions(final String word, final int line,
|
||||
final int lineStartNonWSOffset) {
|
||||
// EventQueue.invokeLater(new Runnable() {
|
||||
// public void run() {
|
||||
// preparePredictions2(word, line, lineStartNonWSOffset);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// public void preparePredictions2(final String word, final int line,
|
||||
// final int lineStartNonWSOffset) {
|
||||
// System.out.println(EventQueue.isDispatchThread() + " " + predictionOngoing.get() + " " + (!JavaMode.codeCompletionsEnabled) + " " + (word.length() < predictionMinLength));
|
||||
// new Exception("preparing predictions " + EventQueue.isDispatchThread() + " " + predictionOngoing.get() + " " + (!JavaMode.codeCompletionsEnabled) + " " + (word.length() < predictionMinLength)).printStackTrace(System.out);
|
||||
if (predictionOngoing.get()) return;
|
||||
if (!JavaMode.codeCompletionsEnabled) return;
|
||||
if (word.length() < predictionMinLength) return;
|
||||
|
||||
predictionOngoing.set(true);
|
||||
// This method is called from TextArea.fetchPhrase, which is called via a SwingWorker instance
|
||||
// in TextArea.processKeyEvent
|
||||
if (caretWithinLineComment()) {
|
||||
log("No predictions.");
|
||||
predictionOngoing.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// presumably this was removed because the caller is running from a SwingWorker [fry]
|
||||
// SwingWorker worker = new SwingWorker() {
|
||||
//
|
||||
// @Override
|
||||
// protected Object doInBackground() throws Exception {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// protected void done() {
|
||||
public List<CompletionCandidate> preparePredictions(final String pdePhrase,
|
||||
final int line) {
|
||||
ErrorCheckerService errorCheckerService = editor.getErrorChecker();
|
||||
ASTNode astRootNode = (ASTNode) errorCheckerService.getLatestCU().types().get(0);
|
||||
|
||||
// If the parsed code contains pde enhancements, take 'em out.
|
||||
String word2 = ASTNodeWrapper.getJavaCode(word);
|
||||
String phrase = ASTNodeWrapper.getJavaCode(pdePhrase);
|
||||
|
||||
//After typing 'arg.' all members of arg type are to be listed. This one is a flag for it
|
||||
boolean noCompare = false;
|
||||
if (word2.endsWith(".")) {
|
||||
// return all matches
|
||||
word2 = word2.substring(0, word2.length() - 1);
|
||||
noCompare = true;
|
||||
boolean noCompare = phrase.endsWith(".");
|
||||
|
||||
if (noCompare) {
|
||||
phrase = phrase.substring(0, phrase.length() - 1);
|
||||
}
|
||||
|
||||
if (word2.length() >= predictionMinLength && !noCompare
|
||||
&& word2.length() > lastPredictedWord.length()) {
|
||||
if (word2.startsWith(lastPredictedWord)) {
|
||||
log(word + " starts with " + lastPredictedWord);
|
||||
log("Don't recalc");
|
||||
if (word2.contains(".")) {
|
||||
int x = word2.lastIndexOf('.');
|
||||
trimCandidates(word2.substring(x + 1));
|
||||
} else {
|
||||
trimCandidates(word2);
|
||||
}
|
||||
showPredictions(word);
|
||||
lastPredictedWord = word2;
|
||||
predictionOngoing.set(false);
|
||||
return;
|
||||
boolean incremental = !noCompare &&
|
||||
phrase.length() > lastPredictedPhrase.length() &&
|
||||
phrase.startsWith(lastPredictedPhrase);
|
||||
|
||||
|
||||
if (incremental) {
|
||||
log(pdePhrase + " starts with " + lastPredictedPhrase);
|
||||
log("Don't recalc");
|
||||
|
||||
if (phrase.contains(".")) {
|
||||
int x = phrase.lastIndexOf('.');
|
||||
candidates = trimCandidates(phrase.substring(x + 1), candidates);
|
||||
} else {
|
||||
candidates = trimCandidates(phrase, candidates);
|
||||
}
|
||||
lastPredictedPhrase = phrase;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
int lineNumber = line;
|
||||
// Adjust line number for tabbed sketches
|
||||
if (errorCheckerService != null) {
|
||||
editor = errorCheckerService.getEditor();
|
||||
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
|
||||
if (codeIndex > 0) {
|
||||
for (int i = 0; i < codeIndex; i++) {
|
||||
SketchCode sc = editor.getSketch().getCode(i);
|
||||
int len = Util.countLines(sc.getProgram()) + 1;
|
||||
lineNumber += len;
|
||||
}
|
||||
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
|
||||
if (codeIndex > 0) {
|
||||
for (int i = 0; i < codeIndex; i++) {
|
||||
SketchCode sc = editor.getSketch().getCode(i);
|
||||
int len = Util.countLines(sc.getProgram()) + 1;
|
||||
lineNumber += len;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we're not inside a comment. TODO: Binary search
|
||||
// Ensure that we're not inside a comment. TODO: Binary search
|
||||
|
||||
/*for (Comment comm : getCodeComments()) {
|
||||
int commLineNo = PdeToJavaLineNumber(compilationUnit
|
||||
.getLineNumber(comm.getStartPosition()));
|
||||
if(commLineNo == lineNumber){
|
||||
log("Found a comment line " + comm);
|
||||
log("Comment LSO "
|
||||
+ javaCodeOffsetToLineStartOffset(compilationUnit
|
||||
.getLineNumber(comm.getStartPosition()),
|
||||
comm.getStartPosition()));
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
/*for (Comment comm : getCodeComments()) {
|
||||
int commLineNo = PdeToJavaLineNumber(compilationUnit
|
||||
.getLineNumber(comm.getStartPosition()));
|
||||
if(commLineNo == lineNumber){
|
||||
log("Found a comment line " + comm);
|
||||
log("Comment LSO "
|
||||
+ javaCodeOffsetToLineStartOffset(compilationUnit
|
||||
.getLineNumber(comm.getStartPosition()),
|
||||
comm.getStartPosition()));
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Now parse the expression into an ASTNode object
|
||||
ASTNode nearestNode = null;
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS8);
|
||||
parser.setKind(ASTParser.K_EXPRESSION);
|
||||
parser.setSource(word2.toCharArray());
|
||||
parser.setSource(phrase.toCharArray());
|
||||
ASTNode testnode = parser.createAST(null);
|
||||
//Base.loge("PREDICTION PARSER PROBLEMS: " + parser);
|
||||
// Find closest ASTNode of the document to this word
|
||||
Messages.loge("Typed: " + word2 + "|" + " temp Node type: " + testnode.getClass().getSimpleName());
|
||||
Messages.loge("Typed: " + phrase + "|" + " temp Node type: " + testnode.getClass().getSimpleName());
|
||||
if(testnode instanceof MethodInvocation){
|
||||
MethodInvocation mi = (MethodInvocation)testnode;
|
||||
log(mi.getName() + "," + mi.getExpression() + "," + mi.typeArguments().size());
|
||||
}
|
||||
|
||||
// find nearest ASTNode
|
||||
nearestNode = findClosestNode(lineNumber, (ASTNode) errorCheckerService.getLastCorrectCU().types()
|
||||
.get(0));
|
||||
nearestNode = findClosestNode(lineNumber, astRootNode);
|
||||
if (nearestNode == null) {
|
||||
// Make sure nearestNode is not NULL if couldn't find a closeset node
|
||||
nearestNode = (ASTNode) errorCheckerService.getLastCorrectCU().types().get(0);
|
||||
nearestNode = astRootNode;
|
||||
}
|
||||
Messages.loge(lineNumber + " Nearest ASTNode to PRED "
|
||||
+ getNodeAsString(nearestNode));
|
||||
|
||||
candidates = new ArrayList<CompletionCandidate>();
|
||||
lastPredictedWord = word2;
|
||||
candidates = new ArrayList<>();
|
||||
lastPredictedPhrase = phrase;
|
||||
// Determine the expression typed
|
||||
|
||||
if (testnode instanceof SimpleName && !noCompare) {
|
||||
@@ -916,7 +968,7 @@ public class ASTGenerator {
|
||||
SimpleType st = (SimpleType) td.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY);
|
||||
log("Superclass " + st.getName());
|
||||
ArrayList<CompletionCandidate> tempCandidates =
|
||||
getMembersForType(st.getName().toString(), word2, noCompare, false);
|
||||
getMembersForType(st.getName().toString(), phrase, noCompare, false);
|
||||
for (CompletionCandidate can : tempCandidates) {
|
||||
candidates.add(can);
|
||||
}
|
||||
@@ -933,7 +985,7 @@ public class ASTGenerator {
|
||||
CompletionCandidate[] types = checkForTypes(cnode);
|
||||
if (types != null) {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i].getElementName().toLowerCase().startsWith(word2.toLowerCase()))
|
||||
if (types[i].getElementName().toLowerCase().startsWith(phrase.toLowerCase()))
|
||||
candidates.add(types[i]);
|
||||
}
|
||||
}
|
||||
@@ -946,7 +998,7 @@ public class ASTGenerator {
|
||||
CompletionCandidate[] types = checkForTypes(clnode);
|
||||
if (types != null) {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i].getElementName().toLowerCase().startsWith(word2.toLowerCase()))
|
||||
if (types[i].getElementName().toLowerCase().startsWith(phrase.toLowerCase()))
|
||||
candidates.add(types[i]);
|
||||
}
|
||||
}
|
||||
@@ -957,11 +1009,11 @@ public class ASTGenerator {
|
||||
}
|
||||
// We're seeing a simple name that's not defined locally or in
|
||||
// the parent class. So most probably a pre-defined type.
|
||||
log("Empty can. " + word2);
|
||||
log("Empty can. " + phrase);
|
||||
if (classPath != null) {
|
||||
RegExpResourceFilter regExpResourceFilter =
|
||||
new RegExpResourceFilter(Pattern.compile(".*"),
|
||||
Pattern.compile(word2 + "[a-zA-Z_0-9]*.class",
|
||||
Pattern.compile(phrase + "[a-zA-Z_0-9]*.class",
|
||||
Pattern.CASE_INSENSITIVE));
|
||||
String[] resources = classPath.findResources("", regExpResourceFilter);
|
||||
|
||||
@@ -988,62 +1040,33 @@ public class ASTGenerator {
|
||||
ASTNode childExpr = getChildExpression(testnode);
|
||||
log("Parent expression : " + getParentExpression(testnode));
|
||||
log("Child expression : " + childExpr);
|
||||
if (childExpr != null) {
|
||||
if (!noCompare) {
|
||||
log("Original testnode " + getNodeAsString(testnode));
|
||||
testnode = getParentExpression(testnode);
|
||||
log("Corrected testnode " + getNodeAsString(testnode));
|
||||
}
|
||||
ClassMember expr =
|
||||
if (!noCompare) {
|
||||
log("Original testnode " + getNodeAsString(testnode));
|
||||
testnode = getParentExpression(testnode);
|
||||
log("Corrected testnode " + getNodeAsString(testnode));
|
||||
}
|
||||
ClassMember expr =
|
||||
resolveExpression3rdParty(nearestNode, testnode, noCompare);
|
||||
if (expr == null) {
|
||||
log("Expr is null");
|
||||
} else {
|
||||
log("Expr is " + expr.toString());
|
||||
candidates = getMembersForType(expr, childExpr.toString(),
|
||||
noCompare, false);
|
||||
}
|
||||
if (expr == null) {
|
||||
log("Expr is null");
|
||||
} else {
|
||||
log("ChildExpr is null");
|
||||
boolean isArray = expr.thisclass != null && expr.thisclass.isArray();
|
||||
boolean isSimpleType = (expr.astNode != null) &&
|
||||
expr.astNode.getNodeType() == ASTNode.SIMPLE_TYPE;
|
||||
boolean isMethod = expr.method != null;
|
||||
boolean staticOnly = !isMethod && !isArray && !isSimpleType;
|
||||
log("Expr is " + expr.toString());
|
||||
String lookFor = (noCompare || (childExpr == null)) ?
|
||||
"" : childExpr.toString();
|
||||
candidates = getMembersForType(expr, lookFor, noCompare, staticOnly);
|
||||
}
|
||||
}
|
||||
showPredictions(word);
|
||||
predictionOngoing.set(false);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// worker.execute();
|
||||
return candidates;
|
||||
}
|
||||
|
||||
|
||||
protected void showPredictions(final String word) {
|
||||
if (sketchOutline != null && sketchOutline.isVisible()) {
|
||||
// don't show completions when the outline is visible
|
||||
return;
|
||||
}
|
||||
|
||||
Collections.sort(candidates);
|
||||
// CompletionCandidate[][] candi = new CompletionCandidate[candidates.size()][1];
|
||||
// DefaultListModel<CompletionCandidate> defListModel = new DefaultListModel<CompletionCandidate>();
|
||||
//
|
||||
// for (int i = 0; i < candidates.size(); i++) {
|
||||
//// candi[i][0] = candidates.get(i);
|
||||
// defListModel.addElement(candidates.get(i));
|
||||
// }
|
||||
// log("Total preds = " + candidates.size());
|
||||
DefaultListModel<CompletionCandidate> defListModel = filterPredictions();
|
||||
// DefaultTableModel tm = new DefaultTableModel(candi,
|
||||
// new String[] { "Suggestions" });
|
||||
// if (tableAuto.isVisible()) {
|
||||
// tableAuto.setModel(tm);
|
||||
// tableAuto.validate();
|
||||
// tableAuto.repaint();
|
||||
// }
|
||||
errorCheckerService.getEditor().getJavaTextArea().showSuggestion(defListModel, word);
|
||||
}
|
||||
|
||||
private DefaultListModel<CompletionCandidate> filterPredictions(){
|
||||
DefaultListModel<CompletionCandidate> defListModel = new DefaultListModel<CompletionCandidate>();
|
||||
protected static DefaultListModel<CompletionCandidate> filterPredictions(List<CompletionCandidate> candidates){
|
||||
DefaultListModel<CompletionCandidate> defListModel = new DefaultListModel<>();
|
||||
if (candidates.isEmpty())
|
||||
return defListModel;
|
||||
// check if first & last CompCandidate are the same methods, only then show all overloaded methods
|
||||
@@ -1117,7 +1140,7 @@ public class ASTGenerator {
|
||||
boolean noCompare,
|
||||
boolean staticOnly) {
|
||||
String child = childToLookFor.toLowerCase();
|
||||
ArrayList<CompletionCandidate> candidates = new ArrayList<CompletionCandidate>();
|
||||
ArrayList<CompletionCandidate> candidates = new ArrayList<>();
|
||||
log("getMemFoType-> Looking for match " + child.toString()
|
||||
+ " inside " + tehClass + " noCompare " + noCompare + " staticOnly "
|
||||
+ staticOnly);
|
||||
@@ -1126,28 +1149,34 @@ public class ASTGenerator {
|
||||
}
|
||||
// tehClass will either be a TypeDecl defined locally
|
||||
if(tehClass.getDeclaringNode() instanceof TypeDeclaration){
|
||||
|
||||
TypeDeclaration td = (TypeDeclaration) tehClass.getDeclaringNode();
|
||||
for (int i = 0; i < td.getFields().length; i++) {
|
||||
List<VariableDeclarationFragment> vdfs = td.getFields()[i]
|
||||
.fragments();
|
||||
for (VariableDeclarationFragment vdf : vdfs) {
|
||||
if (noCompare) {
|
||||
candidates
|
||||
.add(new CompletionCandidate(vdf));
|
||||
} else if (vdf.getName().toString().toLowerCase()
|
||||
.startsWith(child))
|
||||
candidates
|
||||
.add(new CompletionCandidate(vdf));
|
||||
{
|
||||
FieldDeclaration[] fields = td.getFields();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (staticOnly && !isStatic(fields[i].modifiers())) {
|
||||
continue;
|
||||
}
|
||||
List<VariableDeclarationFragment> vdfs = fields[i].fragments();
|
||||
for (VariableDeclarationFragment vdf : vdfs) {
|
||||
if (noCompare) {
|
||||
candidates.add(new CompletionCandidate(vdf));
|
||||
} else if (vdf.getName().toString().toLowerCase().startsWith(child))
|
||||
candidates.add(new CompletionCandidate(vdf));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
for (int i = 0; i < td.getMethods().length; i++) {
|
||||
if (noCompare) {
|
||||
candidates.add(new CompletionCandidate(td.getMethods()[i]));
|
||||
} else if (td.getMethods()[i].getName().toString().toLowerCase()
|
||||
.startsWith(child))
|
||||
candidates.add(new CompletionCandidate(td.getMethods()[i]));
|
||||
{
|
||||
MethodDeclaration[] methods = td.getMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (staticOnly && !isStatic(methods[i].modifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (noCompare) {
|
||||
candidates.add(new CompletionCandidate(methods[i]));
|
||||
} else if (methods[i].getName().toString().toLowerCase()
|
||||
.startsWith(child))
|
||||
candidates.add(new CompletionCandidate(methods[i]));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<CompletionCandidate> superClassCandidates = new ArrayList<CompletionCandidate>();
|
||||
@@ -1209,9 +1238,35 @@ public class ASTGenerator {
|
||||
candidates.add(new CompletionCandidate(field));
|
||||
}
|
||||
}
|
||||
if (probableClass.isArray() && !staticOnly) {
|
||||
// add array members manually, they can't be fetched through code
|
||||
|
||||
String className = probableClass.getSimpleName();
|
||||
|
||||
if (noCompare || "clone()".startsWith(child)) {
|
||||
String methodLabel = "<html>clone() : " + className +
|
||||
" - <font color=#777777>" + className + "</font></html>";
|
||||
candidates.add(new CompletionCandidate("clone()", methodLabel, "clone()",
|
||||
CompletionCandidate.PREDEF_METHOD));
|
||||
}
|
||||
|
||||
if ("length".startsWith(child)) {
|
||||
String fieldLabel = "<html>length : int - <font color=#777777>" +
|
||||
className + "</font></html>";
|
||||
candidates.add(new CompletionCandidate("length", fieldLabel, "length",
|
||||
CompletionCandidate.PREDEF_FIELD));
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private static boolean isStatic(List<org.eclipse.jdt.core.dom.Modifier> modifiers) {
|
||||
for (org.eclipse.jdt.core.dom.Modifier m : modifiers) {
|
||||
if (m.isStatic()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getPDESourceCodeLine(int javaLineNumber) {
|
||||
int res[] = errorCheckerService
|
||||
.calculateTabIndexAndLineNumber(javaLineNumber);
|
||||
@@ -3022,19 +3077,6 @@ public class ASTGenerator {
|
||||
}
|
||||
|
||||
|
||||
protected boolean caretWithinLineComment() {
|
||||
final JEditTextArea ta = editor.getTextArea();
|
||||
String pdeLine = editor.getLineText(ta.getCaretLine()).trim();
|
||||
int caretPos = ta.getCaretPosition() - ta.getLineStartNonWhiteSpaceOffset(ta.getCaretLine());
|
||||
int x = pdeLine.indexOf("//");
|
||||
|
||||
if (x >= 0 && caretPos > x) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper for java.lang.reflect types.
|
||||
* Will have to see if the usage turns out to be internal only here or not
|
||||
|
||||
@@ -112,7 +112,7 @@ public class CompletionPanel {
|
||||
this.textarea = (JavaTextArea) textarea;
|
||||
this.editor = editor;
|
||||
this.insertionPosition = position;
|
||||
if (subWord.indexOf('.') != -1) {
|
||||
if (subWord.indexOf('.') != -1 && subWord.indexOf('.') != subWord.length()-1) {
|
||||
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
|
||||
} else {
|
||||
this.subWord = subWord;
|
||||
@@ -398,13 +398,7 @@ public class CompletionPanel {
|
||||
|
||||
if(mouseClickOnOverloadedMethods) {
|
||||
// See #2755
|
||||
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
|
||||
protected Object doInBackground() throws Exception {
|
||||
((JavaTextArea) editor.getTextArea()).fetchPhrase(null);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
((JavaTextArea) editor.getTextArea()).fetchPhrase();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import processing.core.PVector;
|
||||
import processing.mode.java.JavaInputHandler;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.JavaEditor;
|
||||
@@ -28,12 +29,22 @@ import processing.mode.java.tweak.Handle;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.app.Mode;
|
||||
@@ -222,7 +233,8 @@ public class JavaTextArea extends JEditTextArea {
|
||||
if (!editor.hasJavaTabs()) {
|
||||
if (evt.getID() == KeyEvent.KEY_TYPED) {
|
||||
processCompletionKeys(evt);
|
||||
|
||||
} else if (!Platform.isMacOS() && evt.getID() == KeyEvent.KEY_RELEASED) {
|
||||
processCompletionKeys(evt);
|
||||
} else if (Platform.isMacOS() && evt.getID() == KeyEvent.KEY_RELEASED) {
|
||||
processControlSpace(evt);
|
||||
}
|
||||
@@ -234,28 +246,24 @@ public class JavaTextArea extends JEditTextArea {
|
||||
// https://github.com/processing/processing/issues/2699
|
||||
private void processControlSpace(final KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.VK_SPACE && event.isControlDown()) {
|
||||
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
|
||||
protected Object doInBackground() throws Exception {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
Messages.log("[KeyEvent]" + KeyEvent.getKeyText(event.getKeyCode()) + " |Prediction started");
|
||||
Messages.log("Typing: " + fetchPhrase(event));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
Messages.log("[KeyEvent]" + KeyEvent.getKeyText(event.getKeyCode()) + " |Prediction started");
|
||||
fetchPhrase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processCompletionKeys(final KeyEvent event) {
|
||||
char keyChar = event.getKeyChar();
|
||||
int keyCode = event.getKeyCode();
|
||||
if (keyChar == KeyEvent.VK_ENTER ||
|
||||
keyChar == KeyEvent.VK_ESCAPE ||
|
||||
keyChar == KeyEvent.VK_TAB ||
|
||||
keyChar == KeyEvent.CHAR_UNDEFINED) {
|
||||
|
||||
(event.getID() == KeyEvent.KEY_RELEASED &&
|
||||
keyCode != KeyEvent.VK_LEFT && keyCode != KeyEvent.VK_RIGHT)) {
|
||||
// ignore
|
||||
} else if (keyChar == ')') {
|
||||
// https://github.com/processing/processing/issues/2741
|
||||
hideSuggestion();
|
||||
@@ -263,28 +271,33 @@ public class JavaTextArea extends JEditTextArea {
|
||||
} else if (keyChar == '.') {
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
Messages.log("[KeyEvent]" + KeyEvent.getKeyText(event.getKeyCode()) + " |Prediction started");
|
||||
Messages.log("Typing: " + fetchPhrase(event));
|
||||
fetchPhrase();
|
||||
}
|
||||
} else if (keyChar == ' ') { // Trigger on Ctrl-Space
|
||||
if (!Platform.isMacOS() && JavaMode.codeCompletionsEnabled &&
|
||||
(event.isControlDown() || event.isMetaDown())) {
|
||||
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
|
||||
protected Object doInBackground() throws Exception {
|
||||
//SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
|
||||
// protected Object doInBackground() throws Exception {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
getDocument().remove(getCaretPosition() - 1, 1); // Remove the typed space
|
||||
Messages.log("[KeyEvent]" + event.getKeyChar() + " |Prediction started");
|
||||
Messages.log("Typing: " + fetchPhrase(event));
|
||||
try {
|
||||
getDocument().remove(getCaretPosition() - 1, 1); // Remove the typed space
|
||||
Messages.log("[KeyEvent]" + event.getKeyChar() + " |Prediction started");
|
||||
fetchPhrase();
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
// return null;
|
||||
// }
|
||||
//};
|
||||
//worker.execute();
|
||||
} else {
|
||||
hideSuggestion(); // hide on spacebar
|
||||
}
|
||||
} else {
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
//fetchPhrase();
|
||||
prepareSuggestions(event);
|
||||
}
|
||||
}
|
||||
@@ -293,17 +306,13 @@ public class JavaTextArea extends JEditTextArea {
|
||||
|
||||
/** Kickstart auto-complete suggestions */
|
||||
private void prepareSuggestions(final KeyEvent evt) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
protected Object doInBackground() throws Exception {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled &&
|
||||
(JavaMode.ccTriggerEnabled || suggestion.isVisible())) {
|
||||
Messages.log("[KeyEvent]" + evt.getKeyChar() + " |Prediction started");
|
||||
Messages.log("Typing: " + fetchPhrase(evt));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled &&
|
||||
(JavaMode.ccTriggerEnabled ||
|
||||
(suggestion != null && suggestion.isVisible()))) {
|
||||
Messages.log("[KeyEvent]" + evt.getKeyChar() + " |Prediction started");
|
||||
fetchPhrase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -377,128 +386,300 @@ public class JavaTextArea extends JEditTextArea {
|
||||
}
|
||||
|
||||
|
||||
SwingWorker<Void, Void> suggestionWorker = null;
|
||||
|
||||
int lastCaretPosition = 0;
|
||||
String lastPhrase = "";
|
||||
|
||||
volatile boolean suggestionRunning = false;
|
||||
volatile boolean suggestionRequested = false;
|
||||
|
||||
/**
|
||||
* Retrieves the current word typed just before the caret.
|
||||
* Then triggers code completion for that word.
|
||||
* @param evt - the KeyEvent which triggered this method
|
||||
*/
|
||||
protected String fetchPhrase(KeyEvent evt) {
|
||||
int off = getCaretPosition();
|
||||
Messages.log("off " + off);
|
||||
if (off < 0)
|
||||
return null;
|
||||
int line = getCaretLine();
|
||||
if (line < 0)
|
||||
return null;
|
||||
String s = getLineText(line);
|
||||
Messages.log(" line " + line);
|
||||
protected void fetchPhrase() {
|
||||
|
||||
//log2(s + " len " + s.length());
|
||||
|
||||
int x = getCaretPosition() - getLineStartOffset(line) - 1, x1 = x - 1;
|
||||
if(x >= s.length() || x < 0) {
|
||||
//log("X is " + x + ". Returning null");
|
||||
hideSuggestion();
|
||||
return null; //TODO: Does this check cause problems? Verify.
|
||||
if (suggestionRunning) {
|
||||
suggestionRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.log(" x char: " + s.charAt(x));
|
||||
suggestionRunning = true;
|
||||
suggestionRequested = false;
|
||||
|
||||
if (!(Character.isLetterOrDigit(s.charAt(x)) || s.charAt(x) == '_'
|
||||
|| s.charAt(x) == '(' || s.charAt(x) == '.')) {
|
||||
//log("Char before caret isn't a letter/digit/_(. so no predictions");
|
||||
hideSuggestion();
|
||||
return null;
|
||||
} else if (x > 0 && (s.charAt(x - 1) == ' ' || s.charAt(x - 1) == '(')
|
||||
&& Character.isDigit(s.charAt(x))) {
|
||||
//log("Char before caret isn't a letter, but ' ' or '(', so no predictions");
|
||||
hideSuggestion(); // See #2755, Option 2 comment
|
||||
return null;
|
||||
} else if (x == 0){
|
||||
//log("X is zero");
|
||||
hideSuggestion();
|
||||
return null;
|
||||
final String text;
|
||||
final int caretLineIndex;
|
||||
final int caretLinePosition;
|
||||
{
|
||||
// Get caret position
|
||||
int caretPosition = getCaretPosition();
|
||||
if (caretPosition < 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get line index
|
||||
caretLineIndex = getCaretLine();
|
||||
if (caretLineIndex < 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get text of the line
|
||||
String lineText = getLineText(caretLineIndex);
|
||||
if (lineText == null) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get caret position on the line
|
||||
caretLinePosition = getCaretPosition() - getLineStartOffset(caretLineIndex);
|
||||
if (caretLinePosition <= 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get part of the line to the left of the caret
|
||||
if (caretLinePosition > lineText.length()) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
text = lineText.substring(0, caretLinePosition);
|
||||
}
|
||||
|
||||
//int xLS = off - getLineStartNonWhiteSpaceOffset(line);
|
||||
suggestionWorker = new SwingWorker<Void, Void>() {
|
||||
|
||||
String word = (x < s.length() ? s.charAt(x) : "") + "";
|
||||
if (s.trim().length() == 1) {
|
||||
// word = ""
|
||||
// + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
|
||||
//word = (x < s.length()?s.charAt(x):"") + "";
|
||||
word = word.trim();
|
||||
if (word.endsWith("."))
|
||||
word = word.substring(0, word.length() - 1);
|
||||
String phrase = null;
|
||||
DefaultListModel<CompletionCandidate> defListModel = null;
|
||||
|
||||
editor.getErrorChecker().getASTGenerator().preparePredictions(word, line + editor.getErrorChecker().mainClassOffset,0);
|
||||
return word;
|
||||
}
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
Messages.log("phrase parse start");
|
||||
phrase = parsePhrase(text, caretLinePosition);
|
||||
Messages.log("phrase: " + phrase);
|
||||
if (phrase == null) return null;
|
||||
|
||||
int i = 0;
|
||||
int closeB = 0;
|
||||
List<CompletionCandidate> candidates = null;
|
||||
|
||||
while (true) {
|
||||
i++;
|
||||
//TODO: currently works on single line only. "a. <new line> b()" won't be detected
|
||||
if (x1 >= 0) {
|
||||
// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
|
||||
if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_'
|
||||
|| s.charAt(x1) == '.' || s.charAt(x1) == ')' || s.charAt(x1) == ']') {
|
||||
ASTGenerator astGenerator = editor.getErrorChecker().getASTGenerator();
|
||||
synchronized (astGenerator) {
|
||||
int lineOffset = caretLineIndex +
|
||||
editor.getErrorChecker().mainClassOffset;
|
||||
|
||||
if (s.charAt(x1) == ')') {
|
||||
word = s.charAt(x1--) + word;
|
||||
closeB++;
|
||||
while (x1 >= 0 && closeB > 0) {
|
||||
word = s.charAt(x1) + word;
|
||||
if (s.charAt(x1) == '(')
|
||||
closeB--;
|
||||
if (s.charAt(x1) == ')')
|
||||
closeB++;
|
||||
x1--;
|
||||
candidates = astGenerator.preparePredictions(phrase, lineOffset);
|
||||
}
|
||||
|
||||
if (suggestionRequested) return null;
|
||||
|
||||
// don't show completions when the outline is visible
|
||||
boolean showSuggestions = astGenerator.sketchOutline == null ||
|
||||
!astGenerator.sketchOutline.isVisible();
|
||||
|
||||
if (showSuggestions && phrase != null &&
|
||||
candidates != null && !candidates.isEmpty()) {
|
||||
Collections.sort(candidates);
|
||||
defListModel = ASTGenerator.filterPredictions(candidates);
|
||||
Messages.log("Got: " + candidates.size() + " candidates, " + defListModel.size() + " filtered");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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(String lineText, int caretLinePosition) {
|
||||
|
||||
boolean overloading = false;
|
||||
|
||||
{ // Check if we can provide suggestions for this phrase ending
|
||||
String trimmedLineText = lineText.trim();
|
||||
if (trimmedLineText.length() == 0) return null;
|
||||
|
||||
int lastCodePoint = trimmedLineText.codePointAt(trimmedLineText.length() - 1);
|
||||
if (lastCodePoint == '.') {
|
||||
trimmedLineText = trimmedLineText.substring(0, trimmedLineText.length() - 1).trim();
|
||||
if (trimmedLineText.length() == 0) return null;
|
||||
lastCodePoint = trimmedLineText.codePointAt(trimmedLineText.length() - 1);
|
||||
switch (lastCodePoint) {
|
||||
case ')':
|
||||
case ']':
|
||||
case '"':
|
||||
break; // We can suggest for these
|
||||
default:
|
||||
if (!Character.isJavaIdentifierPart(lastCodePoint)) {
|
||||
return null; // Not something we can suggest
|
||||
}
|
||||
}
|
||||
else if (s.charAt(x1) == ']') {
|
||||
word = s.charAt(x1--) + word;
|
||||
closeB++;
|
||||
while (x1 >= 0 && closeB > 0) {
|
||||
word = s.charAt(x1) + word;
|
||||
if (s.charAt(x1) == '[')
|
||||
closeB--;
|
||||
if (s.charAt(x1) == ']')
|
||||
closeB++;
|
||||
x1--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
word = s.charAt(x1--) + word;
|
||||
break;
|
||||
}
|
||||
} else if (lastCodePoint == '(') {
|
||||
overloading = true; // We can suggest overloaded methods
|
||||
} else if (!Character.isJavaIdentifierPart(lastCodePoint)) {
|
||||
return null; // Not something we can suggest
|
||||
}
|
||||
}
|
||||
|
||||
final int currentCharIndex = caretLinePosition - 1;
|
||||
|
||||
{ // Check if the caret is in the comment
|
||||
int commentStart = lineText.indexOf("//", 0);
|
||||
if (commentStart >= 0 && currentCharIndex > commentStart) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Index the line
|
||||
BitSet isInLiteral = new BitSet(lineText.length());
|
||||
BitSet isInBrackets = new BitSet(lineText.length());
|
||||
|
||||
{ // Mark parts in literals
|
||||
boolean inString = false;
|
||||
boolean inChar = false;
|
||||
boolean inEscaped = false;
|
||||
|
||||
for (int i = 0; i < lineText.length(); i++) {
|
||||
if (!inEscaped) {
|
||||
switch (lineText.codePointAt(i)) {
|
||||
case '\"':
|
||||
if (!inChar) inString = !inString;
|
||||
break;
|
||||
case '\'':
|
||||
if (!inString) inChar = !inChar;
|
||||
break;
|
||||
case '\\':
|
||||
if (inString || inChar) {
|
||||
inEscaped = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
inEscaped = false;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (i > 200) {
|
||||
// time out!
|
||||
break;
|
||||
isInLiteral.set(i, inString || inChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.isDigit(word.charAt(0)))
|
||||
return null;
|
||||
word = word.trim();
|
||||
// if (word.endsWith("."))
|
||||
// word = word.substring(0, word.length() - 1);
|
||||
int lineStartNonWSOffset = 0;
|
||||
if (word.length() >= JavaMode.codeCompletionTriggerLength) {
|
||||
editor.getErrorChecker().getASTGenerator()
|
||||
.preparePredictions(word, line + editor.getErrorChecker().mainClassOffset,
|
||||
lineStartNonWSOffset);
|
||||
}
|
||||
return word;
|
||||
if (isInLiteral.get(currentCharIndex)) return null;
|
||||
|
||||
{ // Mark parts in top level brackets
|
||||
int depth = overloading ? 1 : 0;
|
||||
int bracketStart = overloading ? lineText.length() : 0;
|
||||
int squareDepth = 0;
|
||||
int squareBracketStart = 0;
|
||||
|
||||
bracketLoop: for (int i = lineText.length() - 1; i >= 0; i--) {
|
||||
if (!isInLiteral.get(i)) {
|
||||
switch (lineText.codePointAt(i)) {
|
||||
case ')':
|
||||
if (depth == 0) bracketStart = i;
|
||||
depth++;
|
||||
break;
|
||||
case '(':
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
isInBrackets.set(i, bracketStart);
|
||||
} else if (depth < 0) {
|
||||
break bracketLoop;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if (squareDepth == 0) squareBracketStart = i;
|
||||
squareDepth++;
|
||||
break;
|
||||
case '[':
|
||||
squareDepth--;
|
||||
if (squareDepth == 0) {
|
||||
isInBrackets.set(i, squareBracketStart);
|
||||
} else if (squareDepth < 0) {
|
||||
break bracketLoop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > 0) isInBrackets.set(0, bracketStart);
|
||||
if (squareDepth > 0) isInBrackets.set(0, squareBracketStart);
|
||||
}
|
||||
|
||||
// Walk the line from the end until it makes sense
|
||||
int position = currentCharIndex;
|
||||
parseLoop: while (position >= 0) {
|
||||
int codePoint = lineText.codePointAt(position);
|
||||
switch (codePoint) {
|
||||
case '.': // Grab it
|
||||
position--;
|
||||
break;
|
||||
case '[':
|
||||
break parseLoop; // End of scope
|
||||
case ']': // Grab the whole region in square brackets
|
||||
position = isInBrackets.previousClearBit(position-1);
|
||||
break;
|
||||
case '(':
|
||||
if (isInBrackets.get(position)) {
|
||||
position--; // This checks for first bracket while overloading
|
||||
break;
|
||||
}
|
||||
break parseLoop; // End of scope
|
||||
case ')': // Grab the whole region in brackets
|
||||
position = isInBrackets.previousClearBit(position-1);
|
||||
break;
|
||||
case '"': // Grab the whole literal and quit
|
||||
position = isInLiteral.previousClearBit(position - 1);
|
||||
break parseLoop;
|
||||
default:
|
||||
if (Character.isJavaIdentifierPart(codePoint)) {
|
||||
position--; // Grab the identifier
|
||||
} else if (Character.isWhitespace(codePoint)) {
|
||||
position--; // Grab whitespace too
|
||||
} else {
|
||||
break parseLoop; // Got a char ending the phrase
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
// Extract phrase
|
||||
String phrase = lineText.substring(position, caretLinePosition).trim();
|
||||
Messages.log(phrase);
|
||||
|
||||
if (phrase.length() == 0 || Character.isDigit(phrase.codePointAt(0))) {
|
||||
return null; // Can't suggest for numbers or empty phrases
|
||||
}
|
||||
|
||||
return phrase;
|
||||
}
|
||||
|
||||
|
||||
@@ -799,9 +980,6 @@ public class JavaTextArea extends JEditTextArea {
|
||||
return;
|
||||
}
|
||||
|
||||
if (subWord.length() < 2) {
|
||||
return;
|
||||
}
|
||||
suggestion = new CompletionPanel(this, position, subWord,
|
||||
listModel, location, editor);
|
||||
requestFocusInWindow();
|
||||
@@ -814,7 +992,7 @@ public class JavaTextArea extends JEditTextArea {
|
||||
if (suggestion != null) {
|
||||
suggestion.setInvisible();
|
||||
//log("Suggestion hidden.");
|
||||
suggestion = null;
|
||||
suggestion = null; // TODO: check if we dispose the window properly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user