- Removed files related to previous java auto completion algorithm

This commit is contained in:
itsaky
2021-08-01 15:38:35 +05:30
parent 8cbed76564
commit 1bb7842bc6
61 changed files with 103 additions and 4707 deletions
Binary file not shown.
@@ -719,11 +719,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void onServerStarted(int currentId) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
while(server != null && !pendingMessages.isEmpty()) {
Message msg = pendingMessages.pop();
server.send(msg);
}
new Thread(() -> {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
while(server != null && !pendingMessages.isEmpty()) {
Message msg = pendingMessages.pop();
server.send(msg);
}
}).start();
}
@Override
@@ -9,15 +9,13 @@ import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutCompletionItemBinding;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.CompletionListItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import com.itsaky.androidide.utils.TypefaceUtils;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.widget.EditorCompletionAdapter;
import java.util.Locale;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
public class CompletionListAdapter extends EditorCompletionAdapter {
@Override
@@ -45,7 +43,7 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
type = item.getItemType().contains(".") ? item.getItemType().substring(item.getItemType().lastIndexOf(".") + 1) : item.getItemType();
desc = item.getType() != CompletionListItem.Type.NOT_IMPORTED_CLASS ? item.getDetail() : getContext().getString(R.string.label_not_imported, item.getDetail());
}
type = JavaUtil.getArrayTypeIfNeeded(type);
binding.completionIconText.setText(header);
binding.completionLabel.setText(label);
binding.completionType.setText(type);
@@ -60,17 +58,4 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
return binding.getRoot();
}
@Override
protected String getHtmlJavadocAt(int index) {
Either<SuggestItem, CompletionItem> item = getItem(index);
if(item.isLeft() && item.getLeft() instanceof IJavaDocCommentable) {
IJavaDocCommentable commentable = (IJavaDocCommentable) item;
String doc = commentable.getJavaDoc();
if(doc != null && doc.trim().length() > 0) {
return doc;
}
}
return "";
}
}
@@ -20,7 +20,6 @@ import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.interfaces.LanguageServerListener;
import com.itsaky.androidide.language.java.manager.JavaCharacter;
import com.itsaky.androidide.language.java.provider.JavaCompletionProvider;
import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
@@ -251,10 +250,14 @@ public class StudioApp extends MultiDexApplication
}
public void createCompletionService(AndroidProject project, LanguageClient client) {
this.languageServer = new JavaLanguageServer(project, client);
this.mXmlCompletionService = new XMLCompletionService();
this.languageServer.startServer();
if(languageServer == null) {
this.languageServer = new JavaLanguageServer(project, client);
this.languageServer.startServer();
} else {
this.languageServer.initialize(project);
}
}
public JavaLanguageServer getJavaLanguageServer() {
@@ -1,7 +1,7 @@
package com.itsaky.androidide.language.groovy;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.GradleSuggestItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.struct.CompletionItem;
@@ -3,9 +3,9 @@ package com.itsaky.androidide.language.java;
import androidx.core.util.Pair;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.language.java.JavaLanguageAnalyzer;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.server.JavaLanguageServer;
import com.itsaky.androidide.models.CompletionItemWrapper;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import com.itsaky.lsp.CompletionList;
import com.itsaky.lsp.Position;
@@ -17,7 +17,6 @@ import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
import com.itsaky.androidide.utils.Logger;
public class JavaAutoComplete implements AutoCompleteProvider {
@@ -1,27 +0,0 @@
package com.itsaky.androidide.language.java.parser;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import java.util.Collections;
import java.util.ArrayList;
import javax.tools.JavaFileObject;
public class DiagnosticCollector<S> implements DiagnosticListener<S> {
private List<Diagnostic<? extends S>> diagnostics = Collections.synchronizedList(new ArrayList<Diagnostic<? extends S>>());
@Override
public void report(Diagnostic<? extends S> diagnostic) {
diagnostic.getClass();
diagnostics.add(diagnostic);
}
public void clearDiagnostics() {
diagnostics.clear();
}
public List<Diagnostic<? extends S>> getDiagnostics() {
return Collections.unmodifiableList(diagnostics);
}
}
@@ -1,60 +0,0 @@
package com.itsaky.androidide.language.java.parser;
import com.sun.tools.javac.tree.JCTree;
import java.util.LinkedList;
import java.util.List;
import static com.sun.tools.javac.tree.JCTree.JCExpression;
public class Expression {
/**
* Parent scope of statement, from higher to lower
* parentOfStatement.get(0) will be contains parentOfStatement.get(1)
*/
public final LinkedList<JCTree> parentOfStatement = new LinkedList<>();
/**
* Expression at cursor
*/
public JCExpression expression;
public Expression(JCExpression expression) {
this.expression = expression;
}
public Expression(JCTree root, JCExpression expression) {
this.expression = expression;
}
public void addRoot(JCTree tree) {
if (tree != null) {
parentOfStatement.addFirst(tree);
}
}
public JCTree getRootAt(int level) {
return parentOfStatement.get(level);
}
public JCExpression getExpression() {
return expression;
}
public int getParentLevelCount() {
return parentOfStatement.size();
}
public LinkedList<JCTree> getParentOfExpression() {
return parentOfStatement;
}
@Override
public String toString() {
if (expression instanceof JCTree.JCErroneous) {
List<? extends JCTree> errorTrees
= ((JCTree.JCErroneous) expression).getErrorTrees();
return errorTrees.toString();
}
return expression.toString();
}
}
@@ -1,411 +0,0 @@
package com.itsaky.androidide.language.java.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCAssignOp;
import com.sun.tools.javac.tree.JCTree.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCConditional;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCInstanceOf;
import com.sun.tools.javac.tree.JCTree.JCLabeledStatement;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.List;
import java.util.Map;
import com.sun.tools.javac.tree.EndPosTable;
public class ExpressionResolver {
private int mCursor;
private EndPosTable mEndPositions;
private JCTree.JCCompilationUnit mAst;
public ExpressionResolver(@NonNull JCTree.JCCompilationUnit unit, @NonNull CodeEditor editor) {
unit.getClass();
editor.getClass();
mAst = unit;
mCursor = editor.getCursor().getLeft();
mEndPositions = unit.endPositions;
}
@Nullable
public Expression getExpressionAtCursor() {
for (JCTree jcTree : mAst.getTypeDecls()) {
Expression expression = getExpressionContainsCursor(jcTree);
if (expression != null) {
return expression;
}
}
return null;
}
@Nullable
private Expression getExpressionContainsCursor(JCTree tree) {
if (!isCursorInsideTree(tree)) {
return null;
}
if (tree instanceof JCClassDecl) {
return getExpressionFromClass((JCClassDecl) tree);
}
return null;
}
private Expression getExpressionFromStatement(JCTree.JCStatement statement) {
if (!isCursorInsideTree(statement)) {
return null;
}
Expression result;
if (statement instanceof JCBlock) {
JCBlock jcBlock = (JCBlock) statement;
List<JCStatement> statements = jcBlock.getStatements();
return addRootIfNeeded(jcBlock/*root*/,
getExpressionFromStatements(statements));
} else if (statement instanceof JCClassDecl) {
return getExpressionFromClass((JCClassDecl) statement);
} else if (statement instanceof JCDoWhileLoop) {
JCDoWhileLoop jcDoWhileLoop = (JCDoWhileLoop) statement;
JCExpression cond = jcDoWhileLoop.cond;
if (isCursorInsideTree(cond)) {
return resolveExactlyExpression(statement, cond);
}
JCStatement body = jcDoWhileLoop.getStatement();
result = getExpressionFromStatement(body);
return addRootIfNeeded(statement, result);
} else if (statement instanceof JCEnhancedForLoop) {
JCEnhancedForLoop jcEnhancedForLoop = (JCEnhancedForLoop) statement;
JCExpression expression = jcEnhancedForLoop.getExpression();
if (isCursorInsideTree(expression)) {
return resolveExactlyExpression(jcEnhancedForLoop, expression);
}
JCVariableDecl variable = jcEnhancedForLoop.getVariable();
return addRootIfNeeded(
jcEnhancedForLoop/*root*/,
getExpressionFromStatement(variable));
} else if (statement instanceof JCExpressionStatement) {
JCExpression expression = ((JCExpressionStatement) statement).getExpression();
if (isCursorInsideTree(expression)) {
return resolveExactlyExpression(statement, expression);
}
} else if (statement instanceof JCForLoop) {
JCForLoop jcForLoop = (JCForLoop) statement;
List<JCStatement> initializer = jcForLoop.getInitializer();
result = getExpressionFromStatements(initializer);
if (result != null) {
return addRootIfNeeded(jcForLoop, result);
}
JCExpression condition = jcForLoop.getCondition();
if (isCursorInsideTree(condition)) {
return resolveExactlyExpression(jcForLoop, condition);
}
List<JCExpressionStatement> update = jcForLoop.getUpdate();
result = getExpressionFromStatements(update);
if (result != null) {
return addRootIfNeeded(jcForLoop, result);
}
result = getExpressionFromStatement(jcForLoop.getStatement());
if (result != null) {
return addRootIfNeeded(jcForLoop, result);
}
} else if (statement instanceof JCIf) {
JCIf jcIf = (JCIf) statement;
JCExpression condition = jcIf.getCondition();
if (isCursorInsideTree(condition)) {
return resolveExactlyExpression(jcIf, condition);
}
JCStatement thenStatement = jcIf.getThenStatement();
result = getExpressionFromStatement(thenStatement);
if (result != null) {
return addRootIfNeeded(jcIf, result);
}
JCStatement elseStatement = jcIf.getElseStatement();
if (elseStatement != null) {
result = getExpressionFromStatement(thenStatement);
if (result != null) {
return addRootIfNeeded(jcIf, result);
}
}
} else if (statement instanceof JCLabeledStatement) {
JCLabeledStatement jcLabeledStatement = (JCLabeledStatement) statement;
return addRootIfNeeded(jcLabeledStatement,
getExpressionFromStatement(jcLabeledStatement.getStatement()));
} else if (statement instanceof JCReturn) {
JCReturn jcReturn = (JCReturn) statement;
JCExpression expression = jcReturn.getExpression();
if (isCursorInsideTree(expression)) {
return resolveExactlyExpression(jcReturn, expression);
}
} else if (statement instanceof JCSwitch) {
JCSwitch jcSwitch = (JCSwitch) statement;
JCExpression jcExpression = jcSwitch.getExpression();
if (isCursorInsideTree(jcExpression)) {
return resolveExactlyExpression(jcSwitch, jcExpression);
}
List<JCCase> jcCases = jcSwitch.getCases();
for (JCCase jcCase : jcCases) {
JCExpression jcCaseExpression = jcCase.getExpression();
if (isCursorInsideTree(jcCaseExpression)) {
return resolveExactlyExpression(jcCase, jcCaseExpression);
}
List<JCStatement> statements = jcCase.getStatements();
result = getExpressionFromStatements(statements);
if (result != null) {
return result;
}
}
} else if (statement instanceof JCSynchronized) {
JCSynchronized jcSynchronized = (JCSynchronized) statement;
JCExpression jcExpression = jcSynchronized.getExpression();
if (isCursorInsideTree(jcExpression)) {
return resolveExactlyExpression(jcSynchronized, jcExpression);
}
JCBlock block = jcSynchronized.getBlock();
return addRootIfNeeded(
jcSynchronized/*root*/,
getExpressionFromStatement(block));
} else if (statement instanceof JCThrow) {
JCThrow jcThrow = (JCThrow) statement;
if (isCursorInsideTree(jcThrow.getExpression())) {
return resolveExactlyExpression(jcThrow, jcThrow.getExpression());
}
} else if (statement instanceof JCTry) {
JCTry jcTry = (JCTry) statement;
JCBlock block = jcTry.getBlock();
result = getExpressionFromStatement(block);
if (result != null) {
return addRootIfNeeded(jcTry, result);
}
List<JCCatch> catches = jcTry.getCatches();
for (JCCatch aCatch : catches) {
JCVariableDecl parameter = aCatch.getParameter();
result = getExpressionFromStatement(parameter);
if (result == null) {
block = aCatch.getBlock();
result = getExpressionFromStatement(block);
}
if (result != null) {
result.addRoot(aCatch);
result.addRoot(jcTry);
return result;
}
}
block = jcTry.getFinallyBlock();
result = getExpressionFromStatement(block);
return addRootIfNeeded(jcTry, result);
} else if (statement instanceof JCVariableDecl) {
JCVariableDecl jcVariableDecl = (JCVariableDecl) statement;
JCExpression initializer = jcVariableDecl.getInitializer();
if (isCursorInsideTree(initializer)) {
return resolveExactlyExpression(jcVariableDecl, initializer);
}
JCExpression vartype = jcVariableDecl.vartype;
if (isCursorInsideTree(vartype)) {
return resolveExactlyExpression(jcVariableDecl, vartype);
}
} else if (statement instanceof JCWhileLoop) {
JCWhileLoop jcWhileLoop = (JCWhileLoop) statement;
JCExpression condition = jcWhileLoop.getCondition();
if (isCursorInsideTree(condition)) {
return resolveExactlyExpression(jcWhileLoop, condition);
}
JCStatement body = jcWhileLoop.getStatement();
return addRootIfNeeded(
jcWhileLoop,
getExpressionFromStatement(body));
}
return null;
}
private Expression resolveExactlyExpression(JCTree parent, JCExpression expression) {
if (expression instanceof JCAssign) {
JCAssign jcAssign = (JCAssign) expression;
JCExpression lhs = jcAssign.getVariable();
if (isCursorInsideTree(lhs)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcAssign, lhs));
}
JCExpression rhs = jcAssign.getExpression();
if (isCursorInsideTree(rhs)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcAssign, rhs));
}
} else if (expression instanceof JCAssignOp) {
JCAssignOp jcAssign = (JCAssignOp) expression;
JCExpression lhs = jcAssign.getVariable();
if (isCursorInsideTree(lhs)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcAssign, lhs));
}
JCExpression rhs = jcAssign.getExpression();
if (isCursorInsideTree(rhs)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcAssign, rhs));
}
} else if (expression instanceof JCBinary) {
JCBinary jcBinary = (JCBinary) expression;
JCExpression leftOperand = jcBinary.getLeftOperand();
if (isCursorInsideTree(leftOperand)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcBinary, leftOperand));
}
JCExpression rightOperand = jcBinary.getRightOperand();
if (isCursorInsideTree(rightOperand)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcBinary, rightOperand));
}
} else if (expression instanceof JCConditional) {
JCConditional jcConditional = (JCConditional) expression;
JCExpression condition = jcConditional.getCondition();
if (isCursorInsideTree(condition)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcConditional, condition));
}
JCExpression trueExpression = jcConditional.getTrueExpression();
if (isCursorInsideTree(trueExpression)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcConditional, trueExpression));
}
JCExpression falseExpression = jcConditional.getFalseExpression();
if (isCursorInsideTree(falseExpression)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcConditional, falseExpression));
}
} else if (expression instanceof JCInstanceOf) {
JCInstanceOf jcInstanceOf = (JCInstanceOf) expression;
JCExpression lhs = jcInstanceOf.getExpression();
if (isCursorInsideTree(lhs)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcInstanceOf, lhs));
}
} else if (expression instanceof JCUnary) {
JCUnary jcUnary = (JCUnary) expression;
JCExpression jcExpression = jcUnary.getExpression();
if (isCursorInsideTree(jcExpression)) {
return addRootIfNeeded(
parent, resolveExactlyExpression(jcUnary, jcExpression));
}
}
return new Expression(parent, expression);
}
@Nullable
private Expression getExpressionFromClass(JCClassDecl tree) {
if (!isCursorInsideTree(tree)) {
return null;
}
Expression expression = null;
List<JCTree> members = tree.getMembers();
for (JCTree member : members) {
if (member instanceof JCMethodDecl) {
expression = getExpressionFromMethod((JCMethodDecl) member);
} else if (member instanceof JCVariableDecl) {
expression = getExpressionFromStatement((JCVariableDecl) member);
} else if (member instanceof JCClassDecl) {
expression = getExpressionFromClass((JCClassDecl) member);
}
if (expression != null) {
return addRootIfNeeded(tree, expression);
}
}
return null;
}
@Nullable
private Expression getExpressionFromMethod(JCMethodDecl method) {
Expression expression;
List<JCVariableDecl> parameters = method.getParameters();
for (JCVariableDecl parameter : parameters) {
expression = getExpressionFromStatement(parameter);
if (expression != null) {
expression.addRoot(method);
return expression;
}
}
expression = getExpressionFromStatement(method.getBody());
return addRootIfNeeded(method, expression);
}
@Nullable
private Expression getExpressionFromStatements(
@NonNull List<? extends JCStatement> statements) {
for (JCStatement statement : statements) {
Expression expression = getExpressionFromStatement(statement);
if (expression != null) {
return expression;
}
}
return null;
}
private Expression addRootIfNeeded(JCTree root, Expression expression) {
if (expression != null) {
expression.addRoot(root);
return expression;
}
return null;
}
private boolean isCursorInsideTree(JCTree tree) {
if (tree == null) {
return false;
}
int startPosition = tree.getStartPosition();
int endPosition = tree.getEndPosition(mEndPositions);
return startPosition <= mCursor && mCursor <= endPosition;
}
}
@@ -1,283 +0,0 @@
package com.itsaky.androidide.language.java.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.itsaky.androidide.javadoc.JavaDoc;
import com.itsaky.androidide.javadoc.JavaDocParser;
import com.itsaky.androidide.javadoc.JavaDocParserBuilder;
import com.itsaky.androidide.javadoc.OutputType;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.model.ClassDescription;
import com.itsaky.androidide.language.java.parser.model.ConstructorDescription;
import com.itsaky.androidide.language.java.parser.model.FieldDescription;
import com.itsaky.androidide.language.java.parser.model.MethodDescription;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.parser.Parser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.parser.Tokens;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import io.github.rosemoe.editor.text.Content;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Modifier;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardLocation;
import static com.sun.tools.javac.tree.JCTree.*;
public class JavacParser {
private Context context;
private ParserFactory parserFactory;
private JavaDocParser javadocParser;
private DiagnosticCollector<JavaFileObject> diagnostics;
private boolean canParse = true;
private static final String DOT = ".";
private static final String CONSTRUCTOR_NAME = "<init>";
public JavacParser() {
context = new Context();
diagnostics = new DiagnosticCollector<JavaFileObject>();
context.put(DiagnosticListener.class, diagnostics);
Options.instance(context).put("allowStringFolding", "false");
JavacFileManager fileManager = new JavacFileManager(context, true, Charsets.UTF_8);
try {
fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.<File>of());
} catch (IOException e) {
canParse = false;
}
parserFactory = ParserFactory.instance(context);
}
@Nullable
public JCCompilationUnit parse(Content src) {
return parse(src.toString());
}
@Nullable
public JCCompilationUnit parse(final String src) {
if (!canParse) return null;
SimpleJavaFileObject source = new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return src;
}
};
Log.instance(context).useSource(source);
diagnostics.clearDiagnostics();
Parser parser =
parserFactory.newParser(src,
/*keepDocComments=*/ true, // Necessary, because we show doc comments in AndroidIDE
/*keepEndPos=*/ true,
/*keepLineMap=*/ true);
JCCompilationUnit unit;
unit = parser.parseCompilationUnit();
unit.sourcefile = source;
return unit;
}
@Nullable
public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
if (!canParse) return null;
return diagnostics.getDiagnostics();
}
public List<IClass> parseClasses(JCCompilationUnit unit) {
List<IClass> classes = new ArrayList<>();
List<JCTree> typeDecls = unit.getTypeDecls();
for (JCTree typeDecl : typeDecls) {
if (typeDecl instanceof JCClassDecl) {
classes.add(parseClass(unit, (JCClassDecl) typeDecl));
}
}
return classes;
}
private IClass parseClass(JCCompilationUnit unit, JCClassDecl classDecl) {
final String className = unit.getPackageName() + DOT + classDecl.getSimpleName();
final int modifiers = JavaUtil.toJavaModifiers(classDecl.getModifiers().getFlags());
ClassDescription clazz = new ClassDescription(
className,
modifiers,
false,
classDecl.getKind() == Tree.Kind.ANNOTATION_TYPE,
classDecl.getKind() == Tree.Kind.ENUM,
false);
IClass extendsClass = JavaUtil.jcTypeToClass(unit, classDecl.getExtendsClause());
if (extendsClass != null) {
clazz.setSuperclass(extendsClass);
} else {
clazz.setSuperclass(JavaClassManager.getInstance().getParsedClass(Object.class.getName()));
}
List<JCTree> members = classDecl.getMembers();
for (JCTree member : members) {
if (member instanceof JCTree.JCMethodDecl) {
addMethod(unit, clazz, (JCTree.JCMethodDecl) member);
} else if (member instanceof JCTree.JCVariableDecl) {
addVariable(unit, clazz, classDecl, (JCTree.JCVariableDecl) member);
} else if(member instanceof JCTree.JCClassDecl) {
addNestedClass(unit, classDecl, (JCTree.JCClassDecl) member);
}
}
if(clazz != null && unit.docComments != null && unit.docComments.hasComment(classDecl)) {
Tokens.Comment comment = unit.docComments.getComment(classDecl);
if(comment.getStyle() == Tokens.Comment.CommentStyle.JAVADOC) {
processJavaDoc(comment, clazz);
}
}
//now add constructor
//add methods
return clazz;
}
private void addNestedClass(@NonNull JCCompilationUnit ast, JCClassDecl parent, JCClassDecl classDecl) {
final String className = ast.getPackageName() + DOT + parent.getSimpleName() + "$" + classDecl.getSimpleName();
final int modifiers = JavaUtil.toJavaModifiers(classDecl.getModifiers().getFlags());
ClassDescription clazz = new ClassDescription(
className,
modifiers,
false,
classDecl.getKind() == Tree.Kind.ANNOTATION_TYPE,
classDecl.getKind() == Tree.Kind.ENUM,
false);
IClass extendsClass = JavaUtil.jcTypeToClass(ast, classDecl.getExtendsClause());
if (extendsClass != null) {
clazz.setSuperclass(extendsClass);
} else {
clazz.setSuperclass(JavaClassManager.getInstance().getParsedClass(Object.class.getName()));
}
List<JCTree> members = classDecl.getMembers();
for (JCTree member : members) {
if (member instanceof JCTree.JCMethodDecl) {
addMethod(ast, clazz, (JCTree.JCMethodDecl) member);
} else if (member instanceof JCTree.JCVariableDecl) {
addVariable(ast, clazz, classDecl, (JCTree.JCVariableDecl) member);
} else if(member instanceof JCTree.JCClassDecl) {
addNestedClass(ast, classDecl, (JCTree.JCClassDecl) member);
}
}
if(clazz != null && ast.docComments != null && ast.docComments.hasComment(classDecl)) {
Tokens.Comment comment = ast.docComments.getComment(classDecl);
if(comment.getStyle() == Tokens.Comment.CommentStyle.JAVADOC) {
processJavaDoc(comment, clazz);
}
}
}
private void addVariable(@NonNull JCCompilationUnit unit,
ClassDescription clazz, @NonNull JCClassDecl classDecl,
@NonNull JCTree.JCVariableDecl member) {
String name = member.getName().toString();
int modifiers = JavaUtil.toJavaModifiers(member.getModifiers().getFlags());
IClass type = JavaUtil.jcTypeToClass(unit, member.getType());
JCTree.JCExpression initializer = member.getInitializer();
FieldDescription fieldDescription = new FieldDescription(
modifiers,
type,
name,
initializer != null ? initializer.toString() : null);
clazz.addField(fieldDescription);
if(fieldDescription != null && unit.docComments != null && unit.docComments.hasComment(member)) {
Tokens.Comment comment = unit.docComments.getComment(member);
if(comment.getStyle() == Tokens.Comment.CommentStyle.JAVADOC) {
processJavaDoc(comment, fieldDescription);
}
}
}
private void addMethod(@NonNull JCCompilationUnit unit,
@NonNull ClassDescription clazz,
@NonNull JCTree.JCMethodDecl member) {
final String methodName = member.getName().toString();
final int modifiers = JavaUtil.toJavaModifiers(member.getModifiers().getFlags());
final List<IClass> methodParameters = new ArrayList<>();
List<JCTree.JCVariableDecl> parameters = member.getParameters();
for (JCTree.JCVariableDecl parameter : parameters) {
JCTree type = parameter.getType();
IClass paramType = JavaUtil.jcTypeToClass(unit, type);
methodParameters.add(paramType);
}
IClass returnType = JavaUtil.jcTypeToClass(unit, member.getReturnType());
IJavaDocCommentable commetable = null;
if (member.getName().toString().equals(CONSTRUCTOR_NAME)) {
ConstructorDescription constructor = new ConstructorDescription(
clazz.getFullClassName(),
methodParameters);
clazz.addConstructor(constructor);
commetable = constructor;
} else {
MethodDescription methodDescription = new MethodDescription(
methodName,
modifiers,
methodParameters,
returnType);
clazz.addMethod(methodDescription);
commetable = methodDescription;
}
if(commetable != null && unit.docComments != null && unit.docComments.hasComment(member)) {
Tokens.Comment comment = unit.docComments.getComment(member);
if(comment.getStyle() == Tokens.Comment.CommentStyle.JAVADOC) {
processJavaDoc(comment, commetable);
}
}
}
private void processJavaDoc(Tokens.Comment comment, IJavaDocCommentable commetable) {
if(javadocParser == null) {
javadocParser = JavaDocParserBuilder.withBasicTags()
.withOutputType(OutputType.HTML)
.build();
}
final JavaDoc doc = javadocParser.parse(comment.getText());
final String html = doc.getSummary() + "\n" + doc.getDescription();
commetable.setHtmlJavaDoc(html);
}
private void addConstructor(ClassDescription clazz, JCTree.JCMethodDecl member) {
ConstructorDescription constructorDescription;
}
private JCClassDecl resolveClassSimpleName(List<JCTree> typeDecls) {
for (JCTree typeDecl : typeDecls) {
if (typeDecl instanceof JCClassDecl) {
JCModifiers modifiers = ((JCClassDecl) typeDecl).getModifiers();
Set<Modifier> flags = modifiers.getFlags();
for (Modifier flag : flags) {
if (flag.equals(Modifier.PUBLIC)) {
return (JCClassDecl) typeDecl;
}
}
}
}
return null;
}
}
@@ -1,151 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IField;
import com.itsaky.androidide.language.java.parser.internal.IMethod;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static com.sun.tools.javac.tree.JCTree.JCErroneous;
import static com.sun.tools.javac.tree.JCTree.JCExpression;
/**
* This class very complex, implement after
*/
public class CompleteExpression extends JavaCompleteMatcherImpl {
private static final String TAG = "CompleteExpression";
private final JavaDexClassLoader mClassLoader;
private EndPosTable mEndPositions;
private JCCompilationUnit mAst;
private IClass mCurrentType;
private int mCursor;
private CodeEditor mEditor;
public CompleteExpression(@NonNull JavaDexClassLoader classLoader) {
mClassLoader = classLoader;
}
public void prepare(CodeEditor editor, JCCompilationUnit ast) {
mEditor = editor;
mCursor = editor.getCursor().getLeft();
mAst = ast;
mEndPositions = ast.endPositions;
List<JCTree> typeDecls = mAst.getTypeDecls();
for (JCTree typeDecl : typeDecls) {
if (typeDecl instanceof JCTree.JCClassDecl) {
int startPosition = typeDecl.getStartPosition();
int endPosition = typeDecl.getEndPosition(mEndPositions);
if (startPosition <= mCursor && mCursor <= endPosition) {
String simpleName = ((JCTree.JCClassDecl) typeDecl).getSimpleName().toString();
JCExpression packageName = ast.getPackageName();
mCurrentType = JavaClassManager.getInstance()
.getParsedClass(packageName + "." + simpleName);
}
}
}
}
@Override
public void getSuggestion(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
}
@Override
public boolean process(JCCompilationUnit ast, CodeEditor editor, Expression expression,
String unused, ArrayList<SuggestItem> result) {
try {
prepare(editor, ast);
return expression != null && performComplete(expression.getExpression(), result);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean performComplete(JCExpression jcExpression, ArrayList<SuggestItem> result) {
if (jcExpression instanceof JCErroneous) {
List<? extends JCTree> errorTrees = ((JCErroneous) jcExpression).getErrorTrees();
JCTree errExpr = errorTrees.get(0);
if (errExpr instanceof JCExpression) {
return performComplete((JCExpression) errExpr, result);
}
} else if (jcExpression instanceof JCFieldAccess) {
return performCompleteFieldAccess((JCFieldAccess) jcExpression, result);
} else if (jcExpression instanceof JCTree.JCIdent) {
return performCompleteIdent((JCTree.JCIdent) jcExpression, result);
}
return false;
}
/**
* Suggestion method, variable
*/
private boolean performCompleteIdent(JCTree.JCIdent jcIdent, ArrayList<SuggestItem> result) {
return false;
}
private boolean performCompleteFieldAccess(@NonNull JCFieldAccess jcFieldAccess,
@NonNull ArrayList<SuggestItem> result) {
int startPosition = jcFieldAccess.getStartPosition();
int cursorOffset = mCursor - startPosition;
int incompleteLength = jcFieldAccess.getIdentifier().length();
String incomplete = jcFieldAccess.getIdentifier().toString();
//simple hack, improve later
if (incomplete.equalsIgnoreCase("<error>")) {
incomplete = "";
}
JCExpression expression = jcFieldAccess.getExpression();
TypeResolver resolver = new TypeResolver(mClassLoader, mAst, mCurrentType);
IClass type = resolver.resolveType(expression, mCursor);
if (type != null) {
List<IMethod> methods = type.getMethods();
for (IMethod method : methods) {
if(Modifier.isPublic(method.getModifiers())) {
if(resolver.isStatic() && !Modifier.isStatic(method.getModifiers()))
continue;
if (method.getMethodName().toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
setInfo(method, mEditor, incomplete);
result.add(method);
}
}
}
ArrayList<IField> fields = type.getFields();
for (IField field : fields) {
if(Modifier.isPublic(field.getFieldModifiers())) {
if(resolver.isStatic() && !Modifier.isStatic(field.getFieldModifiers()))
continue;
if (field.getFieldName().toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
setInfo(field, mEditor, incomplete);
result.add(field);
}
}
}
return true;
}
return false;
}
}
@@ -1,81 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.parser.model.ConstructorDescription;
import com.itsaky.androidide.language.java.parser.model.PrimitiveArrayConstructorDescription;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
/**
* Suggest constructor
*/
public class CompleteNewKeyword extends JavaCompleteMatcherImpl {
private static final Pattern NEW_CLASS = Pattern.compile(
"(\\s*new\\s+)(" + CONSTRUCTOR.pattern() + ")$", Pattern.MULTILINE);
private static final String[] PRIMITIVE_ARRAY_TYPE =
{"boolean", "byte", "double", "char", "float", "int", "long", "short"};
private final JavaDexClassLoader mClassLoader;
public CompleteNewKeyword(JavaDexClassLoader classLoader) {
mClassLoader = classLoader;
}
@Override
public boolean process(JCTree.JCCompilationUnit ast,
CodeEditor editor, Expression expression,
String statement, ArrayList<SuggestItem> result) {
Matcher matcher = NEW_CLASS.matcher(statement);
if (matcher.find()) {
String incompleteCts = matcher.group(2);
return getSuggestionInternal(editor, incompleteCts, result);
}
return false;
}
@Override
public void getSuggestion(@NonNull CodeEditor editor,
@NonNull String incomplete,
@NonNull List<SuggestItem> suggestItems) {
getSuggestionInternal(editor, incomplete, suggestItems);
}
private boolean getSuggestionInternal(@NonNull CodeEditor editor, @NonNull String incomplete,
@NonNull List<SuggestItem> suggestItems) {
if (incomplete.isEmpty()) {
return false;
}
boolean handled = false;
//try to find constructor
List<IClass> classes = mClassLoader.findAllWithPrefix(incomplete);
for (IClass clazz : classes) {
List<ConstructorDescription> constructors = clazz.getConstructors();
setInfo(constructors, editor, incomplete);
suggestItems.addAll(constructors);
handled = true;
}
for (String type : PRIMITIVE_ARRAY_TYPE) {
if (type.toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
PrimitiveArrayConstructorDescription c
= new PrimitiveArrayConstructorDescription(type);
setInfo(c, editor, incomplete);
suggestItems.add(c);
handled = true;
}
}
return handled;
}
}
@@ -1,93 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.PackageManager;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.parser.model.PackageDescription;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
/**
* Complete package
* e.g.
* <p>
* java.i|
* java.util.|
* java.util.Hash|
*/
public class CompletePackage extends JavaCompleteMatcherImpl {
private static final String TAG = "CompletePackage";
private static final Pattern PACKAGE
= Pattern.compile("^\\s*(package)\\s+([_A-Za-z0-9.]+)");
private static final Pattern IMPORT_OR_IMPORT_STATIC
= Pattern.compile("^\\s*import(\\s+static)?\\s+([_A-Za-z0-9.]+)");
@NonNull
private final PackageManager mPackageManager;
public CompletePackage(@NonNull PackageManager packageManager) {
mPackageManager = packageManager;
}
@Override
public boolean process(JCTree.JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> result) {
Matcher matcher = PACKAGE.matcher(statement);
if (matcher.find()) {
String incompletePkg = matcher.group(2);
getSuggestion(editor, incompletePkg, result);
return true;
}
matcher = IMPORT_OR_IMPORT_STATIC.matcher(statement);
if (matcher.find()) {
boolean isStatic = matcher.group(1) != null;
String incompletePkg = matcher.group(2);
getSuggestion(editor, incompletePkg, result);
}
return false;
}
@Override
public void getSuggestion(CodeEditor editor, String expr, List<SuggestItem> suggestItems) {
getSuggestionInternal(editor, expr, suggestItems, false);
}
private void getSuggestionInternal(CodeEditor editor, String expr, List<SuggestItem> suggestItems,
boolean addSemicolon) {
//package must be contains dot (.)
if (!expr.contains(".")) {
return;
}
//completed package java.lang
int lastDotIndex = expr.lastIndexOf(".");
String completedPart = expr.substring(0, lastDotIndex /*not contains dot*/);
String incompletePart = expr.substring(lastDotIndex + 1);
PackageDescription packages = mPackageManager.trace(completedPart);
if (packages != null) {
//members of current package
//such as java has more member (util, io, lang)
HashMap<String, PackageDescription> members = packages.getChild();
for (Map.Entry<String, PackageDescription> entry : members.entrySet()) {
PackageDescription packageDescription = entry.getValue();
if (packageDescription.getName().toLowerCase(Locale.US).startsWith(incompletePart.toLowerCase(Locale.US))) {
setInfo(packageDescription, editor, incompletePart);
suggestItems.add(packageDescription);
}
}
}
}
}
@@ -1,65 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IMethod;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
import java.lang.reflect.Modifier;
public class CompleteString extends JavaCompleteMatcherImpl {
public static final Pattern STRING_DOT =
Pattern.compile("\"\\s*\\.\\s*$", Pattern.MULTILINE);
public static final Pattern STRING_DOT_EXPR
= Pattern.compile("\"\\s*\\.\\s*(" + METHOD_NAME.pattern() + ")$");
private static final String TAG = "CompleteString";
private final JavaDexClassLoader mClassLoader;
public CompleteString(JavaDexClassLoader classLoader) {
mClassLoader = classLoader;
}
@Override
public boolean process(JCTree.JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> suggestItems) {
Matcher matcher = STRING_DOT.matcher(statement);
if (matcher.find()) {
getSuggestion(editor, "", suggestItems);
return true;
}
matcher = STRING_DOT_EXPR.matcher(statement);
if (matcher.find()) {
String incompleteMethod = matcher.group(1);
getSuggestion(editor, incompleteMethod, suggestItems);
return true;
}
return false;
}
/**
* Complete string only filter method, not filter field or constructor
*/
@Override
public void getSuggestion(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
JavaClassManager reader = mClassLoader.getClassReader();
IClass stringClass = reader.getParsedClass(String.class.getName());
assert stringClass != null;
ArrayList<SuggestItem> methods = new ArrayList<>();
for (IMethod method : stringClass.getMethods()) {
if(!Modifier.isPublic(method.getModifiers()))
continue;
if (method.getMethodName().toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
suggestItems.add(method);
}
}
setInfo(methods, editor, incomplete);
suggestItems.addAll(methods);
}
}
@@ -1,124 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.parser.model.FieldDescription;
import com.itsaky.androidide.language.java.parser.model.MethodDescription;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.sun.tools.javac.tree.JCTree.JCClassDecl;
import static com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import static com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import static com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import static com.sun.tools.javac.tree.JCTree.JCVariableDecl;
public class CompleteThisKeyword extends JavaCompleteMatcherImpl {
private static final Pattern THIS_DOT = Pattern.compile("(\\W)this\\s*\\.\\s*$");
private static final Pattern THIS_DOT_EXPR = Pattern.compile("\\Wthis\\s*\\.\\s*(" + Patterns.IDENTIFIER.pattern() + ")$");
private static final String TAG = "CompleteThisKeyword";
private static JavaDexClassLoader mClassLoader;
public CompleteThisKeyword(JavaDexClassLoader classLoader) {
this.mClassLoader = classLoader;
}
protected static void addMethod(JCCompilationUnit unit, JCMethodDecl method, CodeEditor editor, String incomplete,
List<SuggestItem> result) {
if (method.getName().toString().toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
List<JCTypeParameter> typeParameters
= method.getTypeParameters();
ArrayList<String> paramsStr = new ArrayList<>();
for (JCTypeParameter typeParameter : typeParameters) {
paramsStr.add(typeParameter.toString());
}
IClass returnType = null;
String rName = JavaUtil.findImportedClassName(unit, method.getReturnType().toString());
if(rName != null && rName.trim().length() > 0) {
returnType = mClassLoader.getClassReader().getParsedClass(rName);
}
MethodDescription desc = new MethodDescription(
method.getName().toString(),
returnType,
(int) method.getModifiers().flags,
paramsStr);
setInfo(desc, editor, incomplete);
result.add(desc);
}
}
protected static void addVariable(JCCompilationUnit unit, JCVariableDecl member, CodeEditor editor, String incomplete,
List<SuggestItem> result) {
if (member.getName().toString().toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US))) {
IClass returnType = null;
String rName = JavaUtil.findImportedClassName(unit, member.getType().toString());
if(rName != null && rName.trim().length() > 0) {
returnType = mClassLoader.getClassReader().getParsedClass(rName);
}
int flags = (int) member.getModifiers().flags;
FieldDescription desc = new FieldDescription(
flags,
returnType,
member.getName().toString(),
null);
setInfo(desc, editor, incomplete);
result.add(desc);
}
}
@Override
public boolean process(JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> result) {
Matcher matcher = THIS_DOT.matcher(statement);
if (matcher.find()) {
getSuggestionInternal(editor, result, ast, "");
return true;
}
matcher = THIS_DOT_EXPR.matcher(statement);
if (matcher.find()) {
String incomplete = matcher.group(1);
getSuggestionInternal(editor, result, ast, incomplete);
return true;
}
return false;
}
@Override
public void getSuggestion(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
}
private void getSuggestionInternal(@NonNull CodeEditor editor, @NonNull ArrayList<SuggestItem> result,
@NonNull JCCompilationUnit unit,
@NonNull String incomplete) {
if (unit == null) {
return;
}
//current file declare
List<JCTree> typeDecls = unit.getTypeDecls();
if (typeDecls.isEmpty()) {
return;
}
JCTree jcTree = typeDecls.get(0);
if (jcTree instanceof JCClassDecl) {
List<JCTree> members =
((JCClassDecl) jcTree).getMembers();
for (JCTree member : members) {
if (member instanceof JCVariableDecl) {
addVariable(unit, (JCVariableDecl) member, editor, incomplete, result);
} else if (member instanceof JCMethodDecl) {
JCMethodDecl method = (JCMethodDecl) member;
addMethod(unit, method, editor, incomplete, result);
}
}
}
}
}
@@ -1,130 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.interfaces.Filter;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Complete class declaration
* <p>
* public? static? final? class Name (extends otherClass)? (implements otherInterfaces)?
* public? enum Name
* public? final? interface Name (extends otherInterfaces)?
*/
public class CompleteTypeDeclared extends JavaCompleteMatcherImpl {
//case: public class A
//case: public class Name extends OtherClass
//case: class Name extends C implements D
public static final Pattern CLASS_DECLARE = Pattern.compile(
//more modifiers, public static final ....
"((public|protected|private|abstract|static|final|strictfp)\\s+)*" +
//type
"((class|inteface|enum)\\s+)" +
//name
"([a-zA-Z_][a-zA-Z0-9_]*)" +
//inherit
"(\\s+extends\\s+([a-zA-Z_][a-zA-Z0-9_]*))?" +
"(\\s+implements\\s+([a-zA-Z_][a-zA-Z0-9_]*))?");
private static final Pattern END_EXTENDS
= Pattern.compile("\\s+extends\\s+([a-zA-Z_][a-zA-Z0-9_]*)$");
private static final Pattern END_IMPLEMENTS
= Pattern.compile("\\s+implements\\s+([a-zA-Z_][a-zA-Z0-9_]*)$");
private static final String TAG = "CompleteClassDeclared";
private JavaDexClassLoader mClassLoader;
public CompleteTypeDeclared(JavaDexClassLoader classLoader) {
mClassLoader = classLoader;
}
@Override
public boolean process(JCTree.JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> result) {
Matcher matcher = CLASS_DECLARE.matcher(statement);
if (matcher.find()) {
matcher = END_IMPLEMENTS.matcher(statement);
if (matcher.find()) {
String incompleteInterface = matcher.group(1);
return getSuggestionInternal(editor, incompleteInterface, result,
"interface");
}
matcher = END_EXTENDS.matcher(statement);
if (matcher.find()) {
String incompleteInterface = matcher.group(1);
return getSuggestionInternal(editor, incompleteInterface, result,
"class");
}
}
return false;
}
/**
* @param declareType - enum, class, annotation, interface
*/
private boolean getSuggestionInternal(@NonNull CodeEditor editor,
@NonNull String incomplete,
@NonNull List<SuggestItem> result,
@Nullable String declareType) {
//filter interfaces or classes
Filter<IClass> filter = null;
switch (declareType) {
case "interface":
filter = new Filter<IClass>() {
@Override
public boolean accept(IClass clazz) {
if (Modifier.isFinal(clazz.getModifiers())) {
return false;
}
if (!Modifier.isPublic(clazz.getModifiers())) {
return false;
}
return clazz.isInterface();
}
};
break;
case "class":
filter = new Filter<IClass>() {
@Override
public boolean accept(IClass clazz) {
if (Modifier.isFinal(clazz.getModifiers())) {
return false;
}
if (!Modifier.isPublic(clazz.getModifiers())) {
return false;
}
return !clazz.isInterface();
}
};
break;
}
List<IClass> classes = mClassLoader.findAllWithPrefix(incomplete, filter);
if (classes.size() > 0) {
setInfo(classes, editor, incomplete);
result.addAll(classes);
return true;
}
return false;
}
@Override
public void getSuggestion(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
getSuggestionInternal(editor, incomplete, suggestItems, null);
}
}
@@ -1,157 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.JavacParser;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.parser.model.KeywordDescription;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.itsaky.androidide.language.java.parser.expression.CompleteThisKeyword.*;
import java.util.Locale;
/**
* Final complete, no context, suggest class name, method name,
* variable or any start with incomplete
*/
public class CompleteWord extends JavaCompleteMatcherImpl {
private static final String TAG = "CompleteWord";
private final static Set<String> KEYWORDS;
static {
String[] kws = {
"abstract", "continue", "for", "new", "switch",
"assert", "default", "if", "package", "synchronized",
"boolean", "do", "goto", "private", "this",
"break", "double", "implements", "protected", "throw",
"byte", "else", "import", "public", "throws",
"case", "enum", "instanceof", "return", "transient",
"catch", "extends", "int", "short", "try",
"char", "final", "interface", "static", "void",
"class", "finally", "long", "strictfp", "volatile",
"const", "float", "native", "super", "while",
"null", "true", "false"
};
Set<String> s = new HashSet<>(Arrays.asList(kws));
KEYWORDS = Collections.unmodifiableSet(s);
}
private JavacParser mJavaParser;
private JavaDexClassLoader mClassLoader;
public CompleteWord(JavacParser javaParser, JavaDexClassLoader classLoader) {
this.mJavaParser = javaParser;
mClassLoader = classLoader;
}
@Override
public boolean process(JCTree.JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> result) {
return getSuggestionInternal(editor, statement, result);
}
@Override
public void getSuggestion(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
getSuggestionInternal(editor, incomplete, suggestItems);
}
private boolean getSuggestionInternal(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
boolean result = getPossibleInCurrentFile(editor, incomplete, suggestItems);
result |= getKeyword(editor, incomplete, suggestItems);
return result;
}
private boolean getKeyword(CodeEditor editor, String incomplete, List<SuggestItem> suggestItems) {
if (incomplete.trim().isEmpty()) {
return false;
}
boolean found = false;
for (String keyword : KEYWORDS) {
if (keyword.toLowerCase(Locale.US).startsWith(incomplete.toLowerCase(Locale.US)) && !keyword.equalsIgnoreCase(incomplete)) {
KeywordDescription e = new KeywordDescription(keyword);
setInfo(e, editor, incomplete);
suggestItems.add(e);
found = true;
}
}
return found;
}
private boolean getPossibleInCurrentFile(@NonNull CodeEditor editor, @NonNull String incomplete,
@NonNull List<SuggestItem> suggestItems) {
if (incomplete.trim().isEmpty()) {
return false;
}
JCTree.JCCompilationUnit ast;
try {
ast = mJavaParser.parse(editor.getText());
} catch (Exception e) {
return false;
}
getPossibleResult(editor, suggestItems, ast, incomplete);
List<IClass> classes = mClassLoader.findAllWithPrefix(incomplete);
setInfo(classes, editor, incomplete);
suggestItems.addAll(classes);
return classes.size() > 0;
}
private void getPossibleResult(CodeEditor editor, List<SuggestItem> result,
JCTree.JCCompilationUnit ast, String incomplete) {
if (ast == null) {
return;
}
List<JCTree> typeDecls = ast.getTypeDecls();
if (typeDecls.isEmpty()) {
return;
}
JCTree jcTree = typeDecls.get(0);
if (jcTree instanceof JCTree.JCClassDecl) {
List<JCTree> members =
((JCTree.JCClassDecl) jcTree).getMembers();
for (JCTree member : members) {
if (member instanceof JCTree.JCVariableDecl) {
addVariable(ast, (JCTree.JCVariableDecl) member, editor, incomplete, result);
} else if (member instanceof JCTree.JCMethodDecl) {
JCTree.JCMethodDecl method = (JCTree.JCMethodDecl) member;
addMethod(ast, method, editor, incomplete, result);
if (method.getStartPosition() <= editor.getCursor().getLeft()
&& method.getBody().getEndPosition(ast.endPositions) >= editor.getCursor().getLeft()) {
collectFromMethod(ast, editor, incomplete, result, method);
}
}
}
}
}
private void collectFromMethod(JCTree.JCCompilationUnit unit, CodeEditor editor, String incomplete, List<SuggestItem> result,
JCTree.JCMethodDecl method) {
List<JCTree.JCStatement> statements
= method.getBody().getStatements();
for (JCTree.JCStatement statement : statements) {
if (statement instanceof JCTree.JCVariableDecl) {
JCTree.JCVariableDecl field = (JCTree.JCVariableDecl) statement;
addVariable(unit, field, editor, incomplete, result);
}
}
List<JCTree.JCVariableDecl> parameters = method.getParameters();
for (JCTree.JCVariableDecl parameter : parameters) {
addVariable(unit, parameter, editor, incomplete, result);
}
}
}
@@ -1,18 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
public interface IJavaCompleteMatcher {
boolean process(JCTree.JCCompilationUnit ast, CodeEditor editor, Expression expression, String statement, ArrayList<SuggestItem> result)
throws Exception;
void getSuggestion(@NonNull CodeEditor editor,
@NonNull String incomplete,
@NonNull List<SuggestItem> suggestItems);
}
@@ -1,58 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.language.java.parser.model.JavaSuggestItemImpl;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.List;
import java.util.regex.Pattern;
public abstract class JavaCompleteMatcherImpl implements IJavaCompleteMatcher {
//statement end with character, number or dot
public static final Pattern END_WITH_CHARACTER_OR_DOT
= Pattern.compile("[.0-9A-Za-z_]\\s*$", Pattern.MULTILINE);
//statement end with dot "str."
public static final Pattern END_WITH_DOT = Pattern.compile("\\.\\s*$", Pattern.MULTILINE);
/**
* Check statement is valid when end with dot
*/
public static final Pattern VALID_WHEN_END_WITH_DOT
= Pattern.compile(
"[" +
"\"" + //string: "Hello".|
")" + //expression in parentheses: (new String("")).|
"0-9A-Za-z_" + //name of variable or identifier: variable.|
"\\]" + //array access: array[].|
"]\\s*\\.\\s*$", Pattern.MULTILINE);
public static final Pattern KEYWORD_DOT
= Pattern.compile("(" + Patterns.RE_KEYWORDS.pattern() + ")\\s*\\.\\s*$");
public static final Pattern METHOD_NAME = Patterns.IDENTIFIER;
public static final Pattern VARIABLE_NAME = Patterns.IDENTIFIER;
//String or java.lang.String
public static final Pattern CLASS_NAME = Pattern.compile(
Patterns.IDENTIFIER.pattern() + "(\\s*\\.\\s*" + Patterns.IDENTIFIER.pattern() + ")*");
//java.util.*
public static final Pattern PACKAGE_NAME = CLASS_NAME;
//java.io.FileInputStream
public static final Pattern CONSTRUCTOR = CLASS_NAME;
private static final String TAG = "JavaCompleteMatcherImpl";
protected static void setInfo(List<? extends SuggestItem> members, CodeEditor editor, String incomplete) {
for (SuggestItem member : members) {
if (member instanceof JavaSuggestItemImpl) {
setInfo((JavaSuggestItemImpl) member, editor, incomplete);
} else {
}
}
}
protected static void setInfo(Object member, CodeEditor editor, String incomplete) {
if (member instanceof JavaSuggestItemImpl) {
((JavaSuggestItemImpl) member).setEditor(editor);
((JavaSuggestItemImpl) member).setIncomplete(incomplete);
}
}
}
@@ -1,102 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import android.text.Editable;
import android.widget.EditText;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.util.EditorUtil;
import io.github.rosemoe.editor.text.Content;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.github.rosemoe.editor.text.CharPosition;
import android.text.TextUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import java.util.Set;
import java.util.HashSet;
/**
* Created by Duy on 21-Jul-17.
*/
public class PackageImporter {
private static final String TAG = "ImportUtil";
/**
* Add import statement if import does not already exist.
*/
public static void importClass(Content editor, String className) {
String packageName = JavaUtil.getPackageName(className);
if (getImportedClassName(editor, className) == null
&& !packageName.isEmpty()
&& !packageName.equals("java.lang")
&& !packageName.equals(EditorUtil.getCurrentPackage(editor.toString()))) {
organizeImports(editor, "import " + className.replace("$", ".") + ";");
}
}
public static String getImportedClassName(EditText editor, @Nullable String className) {
return getImportedClassName(editor, className);
}
public static String getImportedClassName(CharSequence src, @Nullable String className) {
if (className == null) return null;
Pattern pattern = PatternFactory.makeImportClass(className);
Matcher matcher = pattern.matcher(src);
if (matcher.find()) {
return matcher.group(2);
}
return PatternFactory.match(src, pattern);
}
public static void organizeImports(Content editor, String importStr) {
try {
ArrayList<String> imprts = getImports(editor);
imprts.add(importStr);
Collections.sort(imprts);
final Set<String> imports = new HashSet<String>();
for(String imprt : imprts) {
imports.add(imprt.trim());
}
String imprt = TextUtils.join("\n", imports).trim();
int s = PatternFactory.firstMatch(editor, PatternFactory.IMPORT);
int e = PatternFactory.lastMatch(editor, PatternFactory.IMPORT);
if (s >= 0 && e >= 0 && e < editor.length()) {
CharPosition start = editor.getIndexer().getCharPosition(s);
CharPosition end = editor.getIndexer().getCharPosition(e);
if (s < e) {
editor.replace(start.line, start.column, end.line, end.column, imprt);
} else if (s == e) {
editor.insert(start.line, start.column, imprt);
}
} else {
int p = PatternFactory.lastMatch(editor, PatternFactory.PACKAGE);
int c = PatternFactory.firstMatch(editor, CompleteTypeDeclared.CLASS_DECLARE);
if(p >= 0 && c >= 0 && p < c) {
CharPosition start = editor.getIndexer().getCharPosition(p);
CharPosition end = editor.getIndexer().getCharPosition(c);
imprt = "\n\n" + imprt + "\n";
if(p < c) {
editor.replace(start.line, start.column, end.line, end.column, imprt);
} else if(p == c) {
editor.insert(start.line, start.column, imprt);
} else {
editor.insert(0, 0, imprt);
}
}
}
} catch (Throwable th) {}
}
public static ArrayList<String> getImports(Content editor) {
return PatternFactory.allMatch(editor, PatternFactory.IMPORT);
}
}
@@ -1,119 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PatternFactory {
public static final Pattern PACKAGE = Pattern.compile("package\\s+[^;]*;");
public static final Pattern PACKAGE_NAME = Pattern.compile("[A-Za-z_]+(.[A-Za-z][A-Za-z0-9_]*)*");
public static final Pattern IMPORT = Pattern.compile("(import\\s+)([^;]*)(\\s?);");
public static final Pattern WORD = Pattern.compile("[^\\s-]+$");
public static final String IDENTIFIER_STR = "[A-Za-z][A-Za-z0-9]*";
public static final Pattern IDENTIFIER = Pattern.compile(IDENTIFIER_STR);
public static final Pattern ANNOTATION = Pattern.compile("@[A-Za-z][A-Za-z0-9]*");
public static final Pattern BRACKET = Pattern.compile("\\[(.*?)\\]");
public static final Pattern MODIFIERS = Pattern.compile("\\b(public|protected|private|abstract|static|final|strictfp)\\b");
public static final Pattern SPLIT_NON_WORD = Pattern.compile("\\W+");
public static final String SPLIT_NON_WORD_STR = "\\W+";
public static final String[] PRIMITIVE_TYPE = new String[]{"boolean", "byte", "char", "int",
"short", "long", "float", "double"};
public static final String[] KEYWORD_MODIFIERS = new String[]{"public", "private", "protected",
"static", "final", "synchronized", "volatile", "transient", "native", "strictfp"};
public static final String[] KEYWORD_TYPE = new String[]{"class", "interface", "enum"};
public static final String[] KEYWORD;
public static final String GENERIC_STR = "<[A-Z][a-zA-Z0-9_<>, ]*>";
public static final Pattern FILE_NAME = Pattern.compile("[A-Za-z][A-Za-z0-9_\\-.]*");
static {
KEYWORD = new String[PRIMITIVE_TYPE.length + KEYWORD_MODIFIERS.length + KEYWORD_TYPE.length];
System.arraycopy(PRIMITIVE_TYPE, 0, KEYWORD, 0, PRIMITIVE_TYPE.length);
System.arraycopy(KEYWORD_MODIFIERS, 0, KEYWORD, PRIMITIVE_TYPE.length, KEYWORD_MODIFIERS.length);
System.arraycopy(KEYWORD_TYPE, 0, KEYWORD, KEYWORD_MODIFIERS.length, KEYWORD_TYPE.length);
}
public static Pattern makeImportClass(String className) {
return Pattern.compile("(import\\s+)(.*" + className + ")(\\s?;)");
}
@Nullable
public static String lastMatchStr(CharSequence text, Pattern pattern) {
Matcher matcher = pattern.matcher(text);
ArrayList<String> list = new ArrayList<>();
while (matcher.find()) {
list.add(matcher.group());
}
if (list.size() == 0) return null;
return list.get(list.size() - 1);
}
@Nullable
public static String match(CharSequence text, Pattern pattern) {
Matcher matcher = pattern.matcher(text);
if (matcher.find()) return matcher.group();
else return null;
}
public static ArrayList<String> allMatch(CharSequence text, Pattern pattern) {
Matcher matcher = pattern.matcher(text);
ArrayList<String> list = new ArrayList<>();
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
public static int lastMatch(CharSequence editor, Pattern pattern) {
int last = -1;
Matcher matcher = pattern.matcher(editor);
while (matcher.find()) last = matcher.end();
return last;
}
public static int matchEnd(CharSequence editor, Pattern pattern, int start) {
Matcher matcher = pattern.matcher(editor);
if (matcher.find(start)) {
return matcher.end();
}
return -1;
}
public static int firstMatch(CharSequence editor, Pattern pattern) {
Matcher matcher = pattern.matcher(editor);
if (matcher.find()) {
return matcher.start();
}
return -1;
}
public static Pattern makeInstance(String prefix) {
//ArrayList<String> list = new ArrayList();
return Pattern.compile("(" + IDENTIFIER_STR + ")" + "(\\s?)" + //type
"((" + GENERIC_STR + ")?(\\s?)|(\\s+))" + //generic or space
"(" + prefix + ")(\\s?)([,;=)])"); //name
}
public static int lastMatch(String statement, String pattern) {
return lastMatch(statement, Pattern.compile(pattern));
}
public static int lastMatch(String statement, Pattern pattern) {
return lastMatch(statement, pattern, 0);
}
public static int lastMatch(String expr, Pattern pattern, int start) {
Matcher matcher = pattern.matcher(expr.substring(start));
int index = -1;
while (matcher.find()) {
index = matcher.end();
}
return index != 0 ? index + start : index;
}
}
@@ -1,68 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* Created by Duy on 11-Feb-17.
*/
@SuppressWarnings("DefaultFileTemplate")
public class Patterns {
public static final Pattern JAVA_COMMENTS = compile("(//.*)|(/\\*(?:.|[\\n\\r])*?\\*/)");
/**
* match string java
* include
* <p>
* 'string'
* <p>
* And can not find close quote
* <p>
* 'sadhasdhasdhashdhas ds asda sd
*/
public static final Pattern STRINGS = Pattern.compile(
"((\")(.*?)(\"))" +//'string'
"|((\")(.+))", Pattern.DOTALL); // no end string 'asdasdasd
public static final Pattern PRIMITIVE_TYPES = compile("boolean|byte|char|int|short|long|float|double");
public static final Pattern KEYWORD_TYPES = compile("class|interface|enum");
public static final Pattern KEYWORDS_MODS = compile("public|private|protected|static|final|" +
"synchronized|volatile|transient|native|strictfp|abstract");
public static final Pattern KEYWORDS = compile(PRIMITIVE_TYPES.pattern()
+ KEYWORDS_MODS.pattern() + "|"
+ KEYWORD_TYPES.pattern() + "|" +
"super|this|void|assert|break|case|catch|" +
"const|continue|default|do|else|extends|finally|for|goto|if|implements|import|instanceof|" +
"interface|new|package|return|switch|throw|throws|try|while|true|false|null");
public static final Pattern RE_BRACKETS = compile("(\\s*\\[\\s*\\])");
public static final Pattern IDENTIFIER = compile("[a-zA-Z_][a-zA-Z0-9_]*");
//case: JCTree.JCClassDecl
//case: java.util.ArrayList)
public static final Pattern RE_QUALID = compile(IDENTIFIER + "(\\s*\\.\\s*" + IDENTIFIER + ")?");
public static final Pattern RE_REFERENCE_TYPE = compile(RE_QUALID + RE_BRACKETS.pattern() + "\\*");
public static final Pattern RE_TYPE = RE_REFERENCE_TYPE;
public static final Pattern TYPE_DECLARE_MODIFIERS = compile("public|protected|private|abstract|static|final|strictfp");
public static final Pattern TYPE_DECLARE_HEAD = compile("class|inteface|enum");
//case: public class A
//case: public class Lamborghini extends Car
public static final Pattern RE_TYPE_DECL = compile(
"((public|protected|private|abstract|static|final|strictfp)\\s+)*" +
"(class|inteface|enum)\\s+" +
"[a-zA-Z_][a-zA-Z0-9_]*" +
"(\\s+(extends|implements)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*))?");
public static final Pattern RE_ARRAY_TYPE = compile("^\\s*(" + RE_QUALID + ")(" + RE_BRACKETS + ")+\\s*");
public static final Pattern RE_SELECT_OR_ACCESS = compile("^\\s*(" + IDENTIFIER + ")\\s*(\\[.*])?\\s*$");
public static final Pattern RE_ARRAY_ACCESS = compile("^\\s*(" + IDENTIFIER + ")" + "\\s*(\\[.*])+\\s*$");
public static final Pattern RE_CASTING = compile("\\s*\\((" + RE_QUALID + "\\s*)\\)\\s*" + IDENTIFIER);
public static final Pattern RE_KEYWORDS = KEYWORDS;
public static final Pattern PACKAGE_NAME = compile("([A-Za-z0-9_]([.][a-zA-Z0-9_])?)*");
}
@@ -1,68 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import com.itsaky.androidide.language.java.parser.util.EditorUtil;
import io.github.rosemoe.editor.widget.CodeEditor;
public class StatementParser {
/**
* " Search back from the cursor position till meeting '{' or ';'.
* " '{' means statement start, ';' means end of a previous statement.
*
* @return statement before cursor
* " Note: It's the base for parsing. And It's OK for most cases.
*/
public static String resolveStatementFromCursor(CodeEditor editor) {
String lineBeforeCursor = getCurrentLine(editor);
if (lineBeforeCursor.matches("^\\s*(import|package)\\s+")) {
return lineBeforeCursor;
}
int oldCursor = editor.getCursor().getLeft();
int newCursor = oldCursor - 1;
while (true) {
if (newCursor == 0) break;
char c = editor.getText().charAt(newCursor);
if (c == '{' || c == '}' || c == ';') {
newCursor++;
break;
}
newCursor--;
}
String statement = editor.getText().subSequence(newCursor, oldCursor).toString();
return mergeLine(statement);
}
public static String getCurrentLine(CodeEditor editText) {
return EditorUtil.getLineBeforeCursor(editText, editText.getCursor().getLeft());
}
public static String mergeLine(String statement) {
statement = cleanStatement(statement);
return statement;
}
/**
* set string literal empty, remove comments, trim begining or ending spaces
* case: ' sb. /* block comment"/ append( "stringliteral" ) // comment '
* return 'sb.append("")'
*/
private static String cleanStatement(String code) {
if (code.matches("\\s*")) {
return "";
}
code = removeComment(code); //clear all comment
//clear all string content
code = code.replaceAll(Patterns.STRINGS.toString(), "\"\"");
code = EditorUtil.trimLeft(code);
code = code.replaceAll("[\n\t\r]", "");
return code;
}
/**
* remove all comment
*/
private static String removeComment(String code) {
return code.replaceAll(Patterns.JAVA_COMMENTS.toString(), "");
}
}
@@ -1,376 +0,0 @@
package com.itsaky.androidide.language.java.parser.expression;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IField;
import com.itsaky.androidide.language.java.parser.internal.IMethod;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.sun.tools.javac.tree.JCTree;
import java.util.LinkedList;
import java.util.List;
import static com.itsaky.androidide.language.java.parser.internal.JavaUtil.*;
import static com.sun.tools.javac.tree.JCTree.JCArrayAccess;
import static com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import static com.sun.tools.javac.tree.JCTree.JCBlock;
import static com.sun.tools.javac.tree.JCTree.JCCatch;
import static com.sun.tools.javac.tree.JCTree.JCClassDecl;
import static com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import static com.sun.tools.javac.tree.JCTree.JCExpression;
import static com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import static com.sun.tools.javac.tree.JCTree.JCIdent;
import static com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import static com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import static com.sun.tools.javac.tree.JCTree.JCStatement;
import static com.sun.tools.javac.tree.JCTree.JCTry;
import static com.sun.tools.javac.tree.JCTree.JCVariableDecl;
public class TypeResolver {
private static final String TAG = "TypeResolver";
private JavaDexClassLoader mClassLoader;
private JCCompilationUnit mUnit;
private IClass mCurrentType;
private boolean isStatic= false;
public boolean isStatic() {
return isStatic;
}
public TypeResolver(JavaDexClassLoader classLoader,
JCCompilationUnit unit,
IClass currentType) {
mClassLoader = classLoader;
mUnit = unit;
mCurrentType = currentType;
}
public IClass resolveType(@NonNull JCExpression expression, int cursor) {
return resolveTypeImpl(expression, cursor);
}
@Nullable
private IClass resolveTypeImpl(@NonNull final JCExpression expression, int cursor) {
List<JCTree> list = extractExpressionAtCursor(expression, cursor);
if (list == null) {
return null;
}
String exceptionMessage = "Can not resolve type of expression ";
IClass currentType = mCurrentType;
for (JCTree tree : list) {
//only once time on this case
if (tree instanceof JCIdent) {
JCIdent jcIdent = (JCIdent) tree;
//variable declaration, static import or inner class
//case: variableDecl
JCVariableDecl variableDecl = getVariableDeclaration(mUnit, jcIdent);
if (variableDecl != null) {
currentType = JavaUtil.jcTypeToClass(mUnit, variableDecl.getType());
} else {
//case: System.out -> find imported class, this expression can be static access
String className = findImportedClassName(mUnit, jcIdent.getName().toString());
currentType = mClassLoader.getClassReader().getParsedClass(className);
isStatic = true;
}
// TODO: 13-Jun-18 case: static import
// TODO: 13-Jun-18 case inner class
} else if (tree instanceof JCMethodInvocation) {
// TODO: 13-Jun-18 find method in current class or import static
JCMethodInvocation jcMethod = (JCMethodInvocation) tree;
JCExpression methodSelect = jcMethod.getMethodSelect();
if (methodSelect instanceof JCFieldAccess) {
String methodName = ((JCFieldAccess) methodSelect).getIdentifier().toString();
List<JCExpression> arguments = jcMethod.getArguments();
IClass[] types = new IClass[arguments.size()];
IMethod method = currentType.getMethod(methodName, types);
if (method == null) {
throw new UnsupportedOperationException(exceptionMessage + tree);
}
currentType = method.getMethodReturnType();
} else if (methodSelect instanceof JCIdent) { //method in current class
String methodName = ((JCIdent) methodSelect).getName().toString();
IMethod method = currentType.getMethod(methodName, null);
if (method == null) {
throw new UnsupportedOperationException(exceptionMessage + tree);
}
currentType = method.getMethodReturnType();
} else {
throw new UnsupportedOperationException(exceptionMessage + tree);
}
} else if (tree instanceof JCFieldAccess) {
if (currentType == null) {
throw new UnsupportedOperationException(exceptionMessage + tree);
}
String name = ((JCFieldAccess) tree).getIdentifier().toString();
IField field = currentType.getField(name);
if (field == null) {
throw new UnsupportedOperationException(exceptionMessage + tree);
}
currentType = field.getFieldType();
} else if (tree instanceof JCArrayAccess) {
JCIdent jcIdent = (JCIdent) ((JCArrayAccess) tree).getExpression();
//variable declaration, static import or inner class
//case: variableDecl
JCVariableDecl variableDecl = getVariableDeclaration(mUnit, jcIdent);
if (variableDecl != null) {
if (!(variableDecl.getType() instanceof JCArrayTypeTree)) {
throw new UnsupportedOperationException("can not resolve type of array access " + tree);
}
String className = ((JCArrayTypeTree) variableDecl.getType())
.getType().toString();
className = findImportedClassName(mUnit, className);
currentType = mClassLoader.getClassReader().getParsedClass(className);
}
}
}
return currentType;
}
@Nullable
private List<JCTree> extractExpressionAtCursor(JCExpression expression, int cursor) {
JCTree tree = expression;
LinkedList<JCTree> list = new LinkedList<>();
while (tree != null) {
if (getEndPosition(tree) > cursor) {
break;
}
if (tree instanceof JCMethodInvocation) {
list.addFirst(tree);
JCExpression methodSelect = ((JCMethodInvocation) tree).getMethodSelect();
if (methodSelect instanceof JCIdent) {
break;
} else if (methodSelect instanceof JCFieldAccess) {
tree = ((JCFieldAccess) methodSelect).getExpression();
} else { //unsupported
return null;
}
} else if (tree instanceof JCFieldAccess) { //var.field
list.addFirst(tree);
//select before
tree = ((JCFieldAccess) tree).getExpression();
} else if (tree instanceof JCIdent) { //var, it should be before any expression
list.addFirst(tree);
break;
} else if (tree instanceof JCArrayAccess) { //variable.array[i].toString
list.addFirst(tree);
//select declare name
tree = ((JCArrayAccess) tree).getExpression(); //select array name
if (tree instanceof JCIdent) {
break;
} else if (tree instanceof JCFieldAccess) {
} else {
throw new UnsupportedOperationException("can not extract expression at array " + tree);
}
} else { //unsupported
//should not happen
return null;
}
}
return list;
}
@Nullable
private JCVariableDecl getVariableDeclaration(final JCCompilationUnit unit,
final JCIdent jcIdent) {
List<JCTree> typeDecls = unit.getTypeDecls();
for (JCTree typeDecl : typeDecls) {
JCVariableDecl variableDecl = getVariableDeclaration(typeDecl, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
}
return null;
}
/**
* Visit all scope and find variable declaration when given jcIdent
*
* @param parent - parent block to find
*/
@Nullable
private JCVariableDecl getVariableDeclaration(final JCTree parent,
final JCIdent jcIdent) {
// TODO: 14-Jun-18 need add parent if jcIdent to resolve exactly scope
if (parent instanceof JCBlock) {
List<JCStatement> statements = ((JCBlock) parent).getStatements();
return getVariableDeclarationFromStatements(parent, statements, jcIdent);
} else if (parent instanceof JCTree.JCCase) {
JCTree.JCCase jcCase = (JCTree.JCCase) parent;
return getVariableDeclarationFromStatements(parent, jcCase.getStatements(), jcIdent);
} else if (parent instanceof JCClassDecl) {
List<JCTree> members = ((JCClassDecl) parent).getMembers();
//all member equals scope
for (JCTree member : members) {
if (member instanceof JCVariableDecl) { //variable
JCVariableDecl variableDecl = (JCVariableDecl) member;
if (canBeSampleVariable(parent, variableDecl, jcIdent)) {
return variableDecl;
}
} else if (member instanceof JCMethodDecl) { //method
JCMethodDecl jcMethodDecl = (JCMethodDecl) member;
JCBlock jcMethodDeclBody = jcMethodDecl.getBody();
JCVariableDecl variableDecl = getVariableDeclaration(jcMethodDeclBody, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
variableDecl = getVariableDeclarationFromStatements(member,
((JCMethodDecl) member).getParameters(), jcIdent);
if (variableDecl != null) {
return variableDecl;
}
} else {
JCVariableDecl variableDecl = getVariableDeclaration(member, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
}
}
} else if (parent instanceof JCTree.JCDoWhileLoop) {
return getVariableDeclaration(((JCTree.JCDoWhileLoop) parent).getStatement(), jcIdent);
} else if (parent instanceof JCTree.JCEnhancedForLoop) {
JCVariableDecl variable = ((JCTree.JCEnhancedForLoop) parent).getVariable();
if (canBeSampleVariable(parent, variable, jcIdent)) {
return variable;
}
return getVariableDeclaration(((JCTree.JCEnhancedForLoop) parent).getStatement(), jcIdent);
} else if (parent instanceof JCTree.JCForLoop) {
List<JCStatement> initializer = ((JCTree.JCForLoop) parent).getInitializer();
JCVariableDecl variableDecl = getVariableDeclarationFromStatements(parent, initializer, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
return getVariableDeclaration(((JCTree.JCForLoop) parent).getStatement(), jcIdent);
} else if (parent instanceof JCTree.JCIf) {
JCTree.JCIf jcIf = (JCTree.JCIf) parent;
JCStatement thenStatement = jcIf.getThenStatement();
JCVariableDecl variableDecl = getVariableDeclaration(thenStatement, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
JCStatement elseStatement = jcIf.getElseStatement();
if (elseStatement != null) {
return getVariableDeclaration(elseStatement, jcIdent);
}
} else if (parent instanceof JCTree.JCLabeledStatement) {
return getVariableDeclaration(((JCTree.JCLabeledStatement) parent).getStatement(), jcIdent);
} else if (parent instanceof JCTree.JCSynchronized) {
return getVariableDeclaration(((JCTree.JCSynchronized) parent).getBlock(), jcIdent);
} else if (parent instanceof JCTry) {
JCTry jcTry = (JCTry) parent;
JCBlock jcTryBlock = jcTry.getBlock();
JCVariableDecl variableDecl = getVariableDeclaration(jcTryBlock, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
for (JCCatch jcCatch : jcTry.getCatches()) {
JCVariableDecl parameter = jcCatch.getParameter();
if (canBeSampleVariable(jcCatch, parameter, jcIdent)) {
return parameter;
}
JCBlock jcCatchBlock = jcCatch.getBlock();
variableDecl = getVariableDeclaration(jcCatchBlock, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
}
} else if (parent instanceof JCTree.JCWhileLoop) {
return getVariableDeclaration(((JCTree.JCWhileLoop) parent).getStatement(), jcIdent);
}
return null;
}
@Nullable
private JCVariableDecl getVariableDeclarationFromStatements(JCTree parent,
List<? extends JCStatement> statements,
JCIdent jcIdent) {
for (JCStatement statement : statements) {
if (statement instanceof JCVariableDecl) {
JCVariableDecl variableDecl = (JCVariableDecl) statement;
if (canBeSampleVariable(parent, variableDecl, jcIdent)) {
return variableDecl;
}
} else {
JCVariableDecl variableDecl = getVariableDeclaration(statement, jcIdent);
if (variableDecl != null) {
return variableDecl;
}
}
}
return null;
}
/**
* @param parent - scope of variable
*/
private boolean canBeSampleVariable(@NonNull JCTree parent,
@NonNull JCVariableDecl variable,
/*should all parent if identifier*/
@NonNull JCIdent ident) {
if (!variable.getName().equals(ident.getName())) {
return false;
}
//identifier inside or equal scope if variable o
//-------------------------------------------
// private ArrayList list = new ArrayList();
// void method(){
// list.toString()
//}
//-------------------------------------------
// void method(ArrayList list){
// list.toString()
//}
//-------------------------------------------
//void method(){
// ArrayList list;
// list.toString()
//}
if (isChildOfParent(parent, ident)) {
int startPosition = variable.getStartPosition();
int startPosition1 = ident.getStartPosition();
return true;
}
return false;
}
private boolean isChildOfParent(JCTree parent, JCTree child) {
//scope of child inside parent
return parent.getStartPosition() <= child.getStartPosition()
&& getEndPosition(parent) >= getEndPosition(child);
}
private int getEndPosition(JCTree tree) {
return tree.getEndPosition(mUnit.endPositions);
}
static class c {
static void cd() {
}
}
}
@@ -1,44 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.model.ConstructorDescription;
import java.util.ArrayList;
import java.util.List;
public interface IClass extends IJavaDocCommentable {
int getModifiers();
@Nullable
String getFullClassName();
@Nullable
String getSimpleName();
boolean isInterface();
boolean isEnum();
boolean isPrimitive();
boolean isAnnotation();
boolean isMemberClass();
@Nullable
IMethod getMethod(@NonNull String methodName, @Nullable IClass[] argsType);
@Nullable
IField getField(@NonNull String name);
List<IMethod> getMethods();
@Nullable
IClass getSuperclass();
ArrayList<IField> getFields();
List<SuggestItem> getMember(String prefix);
List<ConstructorDescription> getConstructors();
}
@@ -1,16 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import com.itsaky.androidide.interfaces.Filter;
import java.util.List;
public interface IClassManager {
void update(IClass clazz);
void remove(String fullClassName);
@NonNull
List<IClass> find(@NonNull String simpleNamePrefix,
@Nullable Filter<IClass> filter);
}
@@ -1,17 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface IField extends IJavaDocCommentable {
@NonNull
String getFieldName();
@NonNull
IClass getFieldType();
int getFieldModifiers();
@Nullable
Object getFieldValue();
}
@@ -1,11 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.Nullable;
public interface IJavaDocCommentable extends SuggestItem {
IJavaDocCommentable setHtmlJavaDoc(String javadoc);
@Nullable
String getJavaDoc();
}
@@ -1,16 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.NonNull;
public interface IMethod extends IJavaDocCommentable {
@NonNull
String getMethodName();
@NonNull
IClass getMethodReturnType();
@NonNull
IClass[] getMethodParameterTypes();
int getModifiers();
}
@@ -1,301 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils;
import com.itsaky.androidide.interfaces.Filter;
import com.itsaky.androidide.language.java.parser.JavacParser;
import com.itsaky.androidide.language.java.parser.model.ClassDescription;
import com.itsaky.androidide.language.java.parser.model.FieldDescription;
import com.itsaky.androidide.language.java.parser.model.MethodDescription;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.utils.Logger;
import com.sun.tools.javac.tree.JCTree;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.itsaky.androidide.language.java.parser.model.ConstructorDescription;
import com.itsaky.androidide.utils.Either;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.utils.Environment;
public class JavaClassManager implements IClassManager {
private static final String TAG = "JavaClassReader";
private static final String JAVA_DOT_LANG_DOT = "java.lang.";
private static JavaClassManager INSTANCE;
/**
* All classes sorted by simple class name, fastest find list of classes start with prefix
*/
private final ArrayList<Pair<String, IClass>> mSimpleNames = new ArrayList<>();
/**
* All classes sorted by full class name, use for faster find class name by using binary search
*/
private final ArrayList<IClass> mFullNames = new ArrayList<>();
/**
* Map contains parsed class, parsed class is java class in jar file or from user
*/
private final HashMap<String, IClass> mLoaded = new HashMap<>();
private File mBootClasspath;
private JavaClassManager(File bootClassPath) {
mBootClasspath = bootClassPath;
}
public static JavaClassManager getInstance(File classpath) {
if (INSTANCE == null) {
INSTANCE = new JavaClassManager(classpath);
}
return INSTANCE;
}
public static JavaClassManager getInstance() {
if (INSTANCE == null) {
throw new RuntimeException("JavaClassManager not init");
}
return INSTANCE;
}
@NonNull
public ArrayList<IClass> getAllClasses() {
return mFullNames;
}
public void loadFromProject(AndroidProject project) {
try {
JavacParser parser = new JavacParser();
List<String> javaSrcDirs = project.getSourcePaths();
for (String dir : javaSrcDirs) {
final File javaSrcDir = new File(dir);
Collection<File> javaFiles = FileUtils.listFilesInDirWithFilter(javaSrcDir, (f -> f.isFile() && f.getName().endsWith(".java")), true);
for (File javaFile : javaFiles) {
String content = FileIOUtils.readFile2String(javaFile);
JCTree.JCCompilationUnit ast = parser.parse(content);
List<IClass> parseClasses = parser.parseClasses(ast);
for (IClass aClass : parseClasses) {
update(aClass);
}
}
}
} catch (Throwable e) {}
}
/**
* @param fullName - full class name
*/
@Nullable
public IClass getParsedClass(String fullName) {
IClass cache = mLoaded.get(fullName);
if (cache != null) {
return cache;
}
return mLoaded.get(JAVA_DOT_LANG_DOT + fullName);
}
@NonNull
public IClass getClassWrapper(@NonNull Class clazz) {
IClass cache = mLoaded.get(clazz.getName());
if (cache != null) {
return cache;
}
ClassDescription wrapper = new ClassDescription(clazz);
update(wrapper);
wrapper.initMembers(clazz);
return wrapper;
}
@Override
public void update(IClass value) {
String fullClassName = value.getFullClassName();
if (mLoaded.containsKey(fullClassName)) {
mLoaded.put(fullClassName, value);
return;
}
mLoaded.put(fullClassName, value);
if (mFullNames.size() == 0) {
mFullNames.add(value);
} else {
boolean found = false;
for (int i = 0; i < mFullNames.size(); i++) {
IClass iClass = mFullNames.get(i);
if (fullClassName.compareTo(iClass.getFullClassName()) <= 0) {
mFullNames.add(i, value);
found = true;
break;
}
}
if (!found) {
mFullNames.add(value);
}
}
String simpleName = value.getSimpleName();
Pair<String, IClass> simplePair = new Pair<String, IClass>(simpleName, value);
if (mSimpleNames.size() == 0) {
mSimpleNames.add(simplePair);
} else {
boolean found = false;
for (int i = 0; i < mSimpleNames.size(); i++) {
Pair<String, IClass> pair = mSimpleNames.get(i);
if (simpleName.compareTo(pair.first) <= 0) {
mSimpleNames.add(i, simplePair);
found = true;
break;
}
}
if (!found) {
mSimpleNames.add(simplePair);
}
}
}
@Override
public void remove(String fullClassName) {
IClass remove = mLoaded.remove(fullClassName);
int index = Collections.binarySearch(mFullNames, remove,
new Comparator<IClass>() {
@Override
public int compare(IClass o1, IClass o2) {
return o1.getFullClassName().compareTo(o2.getFullClassName());
}
});
if (index >= 0) {
mFullNames.remove(index);
}
Pair<String, IClass> key = new Pair<String, IClass>(remove.getSimpleName(), remove);
index = Collections.binarySearch(mSimpleNames, key,
new Comparator<Pair<String, IClass>>() {
@Override
public int compare(Pair<String, IClass> o1,
Pair<String, IClass> o2) {
return o1.first.compareTo(o2.first);
}
});
if (index >= 0) {
mSimpleNames.remove(index);
}
}
/**
* search class with binary search
*
* @param classes - sorted classes by name
* @param prefix - prefix if class name
*/
@Nullable
private List<Pair<String, IClass>> binarySearch(
@NonNull List<Pair<String, IClass>> classes,
@NonNull String prefix) {
int start = -1, end = -1;
int left = 0;
int right = classes.size() - 1;
while (left <= right) {
int mid = (left + right) / 2;
String name = classes.get(mid).first;
String midValue = name.substring(0, Math.min(name.length(), prefix.length()));
if (midValue.trim().toLowerCase(Locale.US).compareTo(prefix.toLowerCase(Locale.US)) < 0) {
left = mid + 1;
} else if (midValue.trim().toLowerCase(Locale.US).compareTo(prefix.toLowerCase(Locale.US)) > 0) {
right = mid - 1;
} else {
start = mid;
end = mid;
while (start >= 0 &&
classes.get(start)
.first.trim().toLowerCase(Locale.US)
.startsWith(prefix.toLowerCase(Locale.US))) {
start--;
}
while (end < classes.size() &&
classes.get(end)
.first.trim().toLowerCase(Locale.US)
.startsWith(prefix.toLowerCase(Locale.US))) {
end++;
}
start++;
end--;
break;
}
}
if (end >= 0 && start >= 0 && end - start + 1 /*length*/ >= 1) {
return classes.subList(start, end + 1);
}
return null;
}
@Override
@NonNull
public List<IClass> find(@NonNull String simpleNamePrefix, @Nullable Filter<IClass> filter) {
ArrayList<IClass> result = new ArrayList<>();
List<Pair<String, IClass>> simple = binarySearch(mSimpleNames, simpleNamePrefix);
if (simple != null) {
for (Pair<String, IClass> c : simple) {
if (filter != null) {
if (!filter.accept(c.second)) {
continue;
}
}
result.add(c.second);
}
}
return result;
}
/**
* Kept as a backup if loading jar files fails for some reasons
*/
private ArrayList<Class> loadFromDexes(AndroidProject project) {
ArrayList<Class> classes = new ArrayList<>();
try {
final List<String> dexes = ConstantsBridge.PROJECT_DEXES;
final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader());
if (dexes == null || dexes.size() <= 0) return classes;
for (String dex : dexes) {
final DexFile d = new DexFile(new File(dex));
Enumeration<String> entries = d.entries();
String name = null;
while (entries.hasMoreElements() && (name = entries.nextElement()) != null) {
while(name.startsWith(File.separator))
name = name.substring(1);
if(name.contains(File.separator))
name = name.replace(File.separator, ".");
Class<?> load = tryLoad(loader, name);
if (load != null)
classes.add(load);
}
d.close();
}
ConstantsBridge.CLASS_LOAD_SUCCESS = true;
return classes;
} catch (Throwable th) {
ConstantsBridge.CLASS_LOAD_SUCCESS = false;
return classes;
}
}
private Class<?> tryLoad(ClassLoader loader, String name) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException e) {
return null;
}
}
}
@@ -1,95 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.interfaces.Filter;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.models.AndroidProject;
import java.io.File;
import java.util.List;
/**
* Created by Duy on 20-Jul-17.
*/
public class JavaDexClassLoader {
private static final Filter<IClass> mClassFilter = new Filter<IClass>() {
@Override
public boolean accept(IClass aClass) {
return !(aClass.isAnnotation() || aClass.isInterface() || aClass.isEnum() || aClass.isMemberClass());
}
};
private static final Filter<IClass> mEnumFilter = new Filter<IClass>() {
@Override
public boolean accept(IClass aClass) {
return aClass.isEnum();
}
};
private static final Filter<IClass> mInterfaceFilter = new Filter<IClass>() {
@Override
public boolean accept(IClass aClass) {
return aClass.isInterface();
}
};
private static final Filter<IClass> mAnnotationFilter = new Filter<IClass>() {
@Override
public boolean accept(IClass aClass) {
return aClass.isAnnotation();
}
};
private JavaClassManager mClassReader;
public JavaDexClassLoader(File classpath) {
mClassReader = JavaClassManager.getInstance(classpath);
}
public JavaClassManager getClassReader() {
return mClassReader;
}
@NonNull
public List<IClass> findAllWithPrefix(String simpleNamePrefix) {
return mClassReader.find(simpleNamePrefix, null);
}
@NonNull
public List<IClass> findAllWithPrefix(@NonNull String simpleNamePrefix,
@Nullable Filter<IClass> filter) {
return mClassReader.find(simpleNamePrefix, filter);
}
public List<IClass> findClasses(String simpleNamePrefix) {
return mClassReader.find(simpleNamePrefix, mClassFilter);
}
public List<IClass> findInterfaces(String simpleNamePrefix) {
return mClassReader.find(simpleNamePrefix, mInterfaceFilter);
}
public List<IClass> findEnums(String simpleNamePrefix) {
return mClassReader.find(simpleNamePrefix, mEnumFilter);
}
public List<IClass> findAnnotations(String simpleNamePrefix) {
return mClassReader.find(simpleNamePrefix, mAnnotationFilter);
}
public void loadAllClasses(AndroidProject projectFile) {
mClassReader.loadFromProject(projectFile);
}
public void updateClasses(List<IClass> classes) {
for (IClass aClass : classes) {
mClassReader.update(aClass);
}
}
public void updateClass(IClass... classes) {
for (IClass aClass : classes) {
mClassReader.update(aClass);
}
}
}
@@ -1,191 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.FileUtils;
import com.sun.tools.javac.tree.JCTree;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Modifier;
/**
* Created by Duy on 20-Jul-17.
*/
public class JavaUtil {
public static String getSimpleName(String className) {
if (className.contains(".")) {
className = className.substring(className.lastIndexOf(".") + 1);
}
return getArrayTypeIfNeeded(className);
}
@NonNull
public static String getPackageName(String classname) {
if (classname.contains(".")) {
return classname.substring(0, classname.lastIndexOf("."));
} else {
return "";
}
}
public static String getInverseName(String className) {
String[] split = className.split(".");
String result = "";
for (String s : split) {
result = s + result;
}
return result;
}
public static boolean isValidClassName(@Nullable String name) {
return name != null && name.matches("[A-Za-z_][A-Za-z0-9_]*");
}
@Nullable
public static String getClassName(File javaSrc, String filePath) {
if (filePath.startsWith(javaSrc.getPath())) {
//hello/src/main/java
//hello ->
String filename = filePath.substring(filePath.indexOf(javaSrc.getPath()) + javaSrc.getPath().length() + 1);
filename = filename.replace(File.separator, ".");
if (filename.endsWith(".java")) {
filename = filename.substring(0, filename.lastIndexOf(".java"));
}
return filename;
} else {
return null;
}
}
public static int toJavaModifiers(Set<Modifier> modifierSet) {
int modifiers = 0;
for (Modifier modifier : modifierSet) {
switch (modifier) {
case FINAL:
modifiers |= java.lang.reflect.Modifier.FINAL;
break;
case PROTECTED:
modifiers |= java.lang.reflect.Modifier.PROTECTED;
break;
case PRIVATE:
modifiers |= java.lang.reflect.Modifier.PRIVATE;
break;
case ABSTRACT:
modifiers |= java.lang.reflect.Modifier.ABSTRACT;
break;
case NATIVE:
modifiers |= java.lang.reflect.Modifier.NATIVE;
case PUBLIC:
modifiers |= java.lang.reflect.Modifier.PUBLIC;
case STATIC:
modifiers |= java.lang.reflect.Modifier.STATIC;
case TRANSIENT:
modifiers |= java.lang.reflect.Modifier.TRANSIENT;
break;
case VOLATILE:
modifiers |= java.lang.reflect.Modifier.VOLATILE;
break;
case SYNCHRONIZED:
modifiers |= java.lang.reflect.Modifier.SYNCHRONIZED;
break;
case STRICTFP:
modifiers |= java.lang.reflect.Modifier.STRICT;
break;
}
}
return modifiers;
}
@NonNull
public static String findImportedClassName(@NonNull JCTree.JCCompilationUnit mUnit,
@NonNull String className) {
List<JCTree.JCImport> imports = mUnit.getImports();
for (JCTree.JCImport jcImport : imports) {
String fullName = jcImport.getQualifiedIdentifier().toString();
if (fullName.equals(className) || fullName.endsWith("." + className)) {
return fullName;
}
}
return className;
}
@Nullable
public static IClass jcTypeToClass(JCTree.JCCompilationUnit unit, JCTree type) {
if (type == null) {
return null;
}
String className;
if (type instanceof JCTree.JCTypeApply) {
return jcTypeToClass(unit, ((JCTree.JCTypeApply) type).getType());
} else {
className = type.toString();
}
className = findImportedClassName(unit, className);
return JavaClassManager.getInstance().getParsedClass(className);
}
public static ArrayList<String> listClassName(File src) {
if (!src.exists()) return new ArrayList<String>();
Collection<File> files = FileUtils.listFilesInDirWithFilter(src, (f -> f.isFile() && f.getName().endsWith(".java")), true);
ArrayList<String> classes = new ArrayList<>();
String srcPath = src.getPath();
for (File file : files) {
String javaPath = file.getPath();
javaPath = javaPath.substring(srcPath.length() + 1, javaPath.length() - 5); //.java
javaPath = javaPath.replace(File.separator, ".");
classes.add(javaPath);
}
return classes;
}
public static String getArrayTypeIfNeeded(String currentType) {
if(currentType == null)
return "";
String type = new String(currentType);
if(type.startsWith("[")) {
type = type.substring(1).trim();
if (type.equals("Z")) {
type = "boolean[]";
} else if (type.equals("B")) {
type = "byte[]";
} else if (type.equals("C")) {
type = "char[]";
} else if (type.equals("S")) {
type = "short[]";
} else if (type.equals("I")) {
type = "int[]";
} else if (type.equals("J")) {
type = "long[]";
} else if (type.equals("F")) {
type = "float[]";
} else if (type.equals("D")) {
type = "double[]";
} else {
if(type.endsWith(";"))
type = type.substring(0, type.length());
type = type + "[]";
}
}
return type;
}
}
@@ -1,62 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import android.os.FileObserver;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.model.PackageDescription;
import com.itsaky.androidide.models.AndroidProject;
import java.io.File;
import java.util.ArrayList;
public class PackageManager {
private static final String TAG = "AutoCompletePackage";
private PackageDescription root;
private FileObserver fileObserver;
public PackageManager() {
root = PackageDescription.root();
}
public void init(AndroidProject projectFile, JavaClassManager classReader) {
ArrayList<IClass> classes = classReader.getAllClasses();
for (IClass clazz : classes) {
root.put(clazz.getFullClassName());
}
final String parentPath = new File(projectFile.getMainModulePath(), "src/main/java").getPath();
fileObserver = new FileObserver(parentPath) {
@Override
public void onEvent(int event, String path) {
remove(path, parentPath);
add(path, parentPath);
}
};
fileObserver.startWatching();
}
private void add(String child, String parent) {
if (child.startsWith(parent)) {
child = child.substring(parent.length() + 1);
child = child.replace(File.separator, ".");
root.put(child);
}
}
@Nullable
private PackageDescription remove(String child, String parent) {
if (child.startsWith(parent)) {
child = child.substring(parent.length() + 1);
child = child.replace(File.separator, ".");
return root.remove(child);
}
return null;
}
public void destroy() {
if (fileObserver != null) fileObserver.stopWatching();
}
@Nullable
public PackageDescription trace(String child) {
return this.root.get(child);
}
}
@@ -1,13 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import io.github.rosemoe.editor.widget.CodeEditor;
public interface SuggestItem {
public String getName();
public String getDescription();
public String getReturnType();
public char getTypeHeader();
public int getSuggestionPriority();
public void onSelectThis(CodeEditor editor);
}
@@ -1,295 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.expression.PackageImporter;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IField;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
import com.itsaky.androidide.language.java.parser.internal.IMethod;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class ClassDescription extends JavaSuggestItemImpl implements IClass {
private static final String TAG = "ClassDescription";
@NonNull
private final String mClassName;
private final ArrayList<ConstructorDescription> mConstructors = new ArrayList<>();
private final ArrayList<IField> mFields = new ArrayList<>();
private final ArrayList<IMethod> mMethods = new ArrayList<>();
private final ArrayList<IClass> mImplements = new ArrayList<>();
private final int mModifiers;
private final boolean mPrimitive, mAnnotation, mEum, isMemberClass;
//init later
@Nullable
private IClass mSuperClass;
public ClassDescription(Class c) {
mClassName = c.getName();
mModifiers = c.getModifiers();
mPrimitive = c.isPrimitive();
mAnnotation = c.isAnnotation();
isMemberClass = c.isMemberClass();
mEum = c.isEnum();
}
public ClassDescription(String className, int modifiers,
boolean isPrimitive,
boolean isAnnotation,
boolean isEnum,
boolean isMember) {
this.mClassName = className;
this.mModifiers = modifiers;
this.mPrimitive = isPrimitive;
this.mAnnotation = isAnnotation;
this.isMemberClass = isMember;
this.mEum = isEnum;
}
public static ClassDescription ofObject() {
ClassDescription desc = new ClassDescription(Object.class);
desc.initMembers(Object.class);
return desc;
}
@Override
public boolean isMemberClass() {
return isMemberClass;
}
private String javadoc;
@Override
public IJavaDocCommentable setHtmlJavaDoc(String javadoc) {
this.javadoc = javadoc;
return this;
}
@Override
public String getJavaDoc() {
return javadoc;
}
@Override
public String getName() {
if (getPackageName().isEmpty()) {
return getSimpleName();
}
return getSimpleName() + " (" + getPackageName() + ")";
}
@Override
public final String getDescription() {
return getPackageName().replace("$", ".");
}
@Override
public String getReturnType() {
return "";
}
@Override
public char getTypeHeader() {
return 'c';
}
@Override
public int getSuggestionPriority() {
return JavaSuggestItemImpl.CLASS_DESC;
}
public void onSelectThis(CodeEditor editorView) {
try {
final int length = getIncomplete().length();
final int start = editorView.getCursor().getLeft() - length;
final int end = editorView.getCursor().getLeft();
final CharPosition s = editorView.getText().getIndexer().getCharPosition(start);
Content editable = editorView.getText();
editable.delete(start, end);
editable.insert(s.line, s.column, getSimpleName());
PackageImporter.importClass(editable, getFullClassName());
} catch (Exception e) {
}
}
@Override
public String getSimpleName() {
return JavaUtil.getSimpleName(mClassName).replace("$", ".");
}
@Override
public String getFullClassName() {
return mClassName;
}
@Override
@Nullable
public IClass getSuperclass() {
return mSuperClass;
}
public String getPackageName() {
return JavaUtil.getPackageName(mClassName);
}
public ArrayList<ConstructorDescription> getConstructors() {
return mConstructors;
}
public ArrayList<IField> getFields() {
return mFields;
}
public void addConstructor(ConstructorDescription constructorDescription) {
this.mConstructors.add(constructorDescription);
}
public void addField(IField fieldDescription) {
mFields.add(fieldDescription);
}
public void addMethod(IMethod methodDescription) {
mMethods.add(methodDescription);
}
public List<IMethod> getMethods() {
return mMethods;
}
@Override
public String toString() {
return mClassName;
}
@SuppressWarnings("ConstantConditions")
public ArrayList<SuggestItem> getMember(String prefix) {
ArrayList<SuggestItem> result = new ArrayList<>();
for (ConstructorDescription constructor : mConstructors) {
if (!prefix.isEmpty()) {
if (constructor.getName().startsWith(prefix)) {
result.add(constructor);
}
}
}
for (IField field : mFields) {
if (prefix.isEmpty() || field.getFieldName().startsWith(prefix)) {
result.add(field);
}
}
getMethods(result, prefix);
return result;
}
public void getMethods(ArrayList<SuggestItem> result, String prefix) {
for (IMethod method : mMethods) {
if (prefix.isEmpty() || method.getMethodName().startsWith(prefix)) {
result.add(method);
}
}
}
@Override
public int getModifiers() {
return mModifiers;
}
@Override
public boolean isInterface() {
return false;
}
public boolean isEnum() {
return mEum;
}
@Override
public boolean isPrimitive() {
return mPrimitive;
}
@Override
public boolean isAnnotation() {
return mAnnotation;
}
@Override
public IMethod getMethod(String methodName, IClass[] argsType) {
for (IMethod method : mMethods) {
if (method.getMethodName().equals(methodName)) {
return method;
}
}
if (getSuperclass() != null) {
return getSuperclass().getMethod(methodName, argsType);
}
return null;
}
@Override
public IField getField(String name) {
for (IField field : mFields) {
if (field.getFieldName().equals(name)) {
return field;
}
}
if (getSuperclass() != null) {
return getSuperclass().getField(name);
}
return null;
}
public void initMembers(Class c) {
mImplements.clear();
Class[] interfaces = c.getInterfaces();
for (Class anInterface : interfaces) {
IClass implement = JavaClassManager.getInstance().getClassWrapper(anInterface);
mImplements.add(implement);
}
if (c.getSuperclass() != null) {
mSuperClass = JavaClassManager.getInstance().getClassWrapper(c.getSuperclass());
} else {
if (!getFullClassName().equals(Object.class.getName())
&& mSuperClass == null) {
mSuperClass = JavaClassManager.getInstance().getParsedClass(Object.class.getName());
}
}
for (Constructor constructor : c.getConstructors()) {
if (Modifier.isPublic(constructor.getModifiers())) {
addConstructor(new ConstructorDescription(constructor));
}
}
for (Field field : c.getFields()) {
if (Modifier.isPublic(field.getModifiers())) {
if (!field.getName().equals(field.getDeclaringClass().getName())) {
addField(new FieldDescription(field));
}
}
}
for (Method method : c.getMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
addMethod(new MethodDescription(method));
}
}
}
public void setSuperclass(IClass superclass) {
this.mSuperClass = superclass;
}
}
@@ -1,124 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.language.java.parser.expression.PackageImporter;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ConstructorDescription extends JavaSuggestItemImpl implements IJavaDocCommentable {
private ArrayList<String> mParameterTypes = new ArrayList<>();
private String mConstructorName;
public ConstructorDescription(Constructor constructor) {
mConstructorName = constructor.getName();
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
mParameterTypes.add(parameterType.getName());
}
}
public ConstructorDescription(String name, List<IClass> paramTypes) {
mConstructorName = name;
mParameterTypes.addAll(paramTypes.stream().filter(c -> c != null).map(c -> c.getFullClassName()).collect(Collectors.toList()));
}
public ConstructorDescription(String name, ArrayList<String> paramTypes) {
mConstructorName = name;
mParameterTypes.addAll(paramTypes);
}
private String javadoc;
@Override
public IJavaDocCommentable setHtmlJavaDoc(String javadoc) {
this.javadoc = javadoc;
return this;
}
@Override
public String getJavaDoc() {
return javadoc;
}
@Override
public void onSelectThis(@NonNull CodeEditor editorView) {
try {
final int length = getIncomplete().length();
final int cursor = editorView.getCursor().getLeft();
final int start = cursor - length;
Content editable = editorView.getText();
final CharPosition sPos = editable.getIndexer().getCharPosition(start);
final CharPosition cPos = editable.getIndexer().getCharPosition(cursor);
if (getParameterTypes().size() > 0) {
editable.replace(sPos.line, sPos.column, cPos.line, cPos.column, getSimpleName() + "()");
CharPosition select = editable.getIndexer().getCharPosition(start + getSimpleName().length() + 1);
editorView.setSelection(select.line, select.column);
} else {
String text = getSimpleName() + "();";
editable.replace(sPos.line, sPos.column, cPos.line, cPos.column, text);
CharPosition select = editable.getIndexer().getCharPosition(start + text.length());
editorView.setSelection(select.line, select.column);
}
PackageImporter.importClass(editorView.getText(), mConstructorName);
} catch (Exception e) {
}
}
@Override
public String getName() {
return getSimpleName() + "(" + paramsToString(getParameterTypes()) + ")";
}
@Override
public char getTypeHeader() {
return 'c';
}
@Override
public String getDescription() {
return "Class constructor";
}
@Override
public String getReturnType() {
return "";
}
@Override
public String toString() {
return getSimpleName() + "()";
}
private String paramsToString(@NonNull ArrayList<String> parameterTypes) {
StringBuilder result = new StringBuilder();
boolean firstTime = true;
for (String parameterType : parameterTypes) {
if (firstTime) {
firstTime = false;
} else {
result.append(",");
}
result.append(parameterType);
}
return result.toString();
}
public ArrayList<String> getParameterTypes() {
return mParameterTypes;
}
private String getSimpleName() {
return JavaUtil.getSimpleName(mConstructorName);
}
}
@@ -1,128 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IField;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldDescription extends JavaSuggestItemImpl implements Member, IField {
@NonNull
private String mName;
@NonNull
private IClass mType;
private int mModifiers;
@Nullable
private String mValue;
public FieldDescription(int modifiers, IClass type, @NonNull String name, String initValue) {
mName = name;
mType = type;
mModifiers = modifiers;
mValue = initValue;
}
public FieldDescription(Field field) {
mName = field.getName();
mType = JavaClassManager.getInstance().getClassWrapper(field.getType());
mModifiers = field.getModifiers();
if (Modifier.isStatic(mModifiers)) {
try {
boolean primitive = field.getType().isPrimitive();
Object o = field.get(null);
if (primitive) {
mValue = o.toString();
} else {
mValue = o.getClass().getSimpleName();
}
} catch (Exception ignored) {
}
}
}
private String javadoc;
@Override
public IJavaDocCommentable setHtmlJavaDoc(String javadoc) {
this.javadoc = javadoc;
return this;
}
@Override
public String getJavaDoc() {
return javadoc;
}
@Override
public void onSelectThis(@NonNull CodeEditor editorView) {
insertImpl(editorView, mName);
}
@Override
public char getTypeHeader() {
return 'f';
}
@Override
public String getName() {
if (mValue == null) {
return mName;
} else {
return mName + "(" + mValue + ")";
}
}
@Override
public String getDescription() {
return mValue;
}
@Override
public String getReturnType() {
if (mType == null) {
return "";
}
return mType.getSimpleName();
}
@Override
public int getSuggestionPriority() {
return JavaSuggestItemImpl.FIELD_DESC;
}
@Override
public String toString() {
return mName;
}
@Override
public int getModifiers() {
return mModifiers;
}
@Override
public String getFieldName() {
return mName;
}
@Override
public IClass getFieldType() {
return mType;
}
@Override
public int getFieldModifiers() {
return mModifiers;
}
@Override
public Object getFieldValue() {
return mValue;
}
}
@@ -1,60 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
public abstract class JavaSuggestItemImpl implements SuggestItem, Cloneable {
public static final int FIELD_DESC = 0;
public static final int METHOD_DESC = 1;
public static final int CLASS_DESC = 2;
public static final int OTHER_DESC = 3;
private CodeEditor editor;
private String incomplete;
@Override
public int getSuggestionPriority() {
return OTHER_DESC;
}
public CodeEditor getEditor() {
return editor;
}
public void setEditor(CodeEditor editor) {
this.editor = editor;
}
public String getIncomplete() {
return incomplete;
}
public void setIncomplete(@NonNull String incomplete) {
this.incomplete = incomplete;
}
protected void insertImpl(CodeEditor editorView, String text) {
try {
final int length = getIncomplete().length();
final int cursor = getEditor().getCursor().getLeft();
final int start = cursor - length;
Content editable = editorView.getText();
CharPosition s = editable.getIndexer().getCharPosition(start);
CharPosition c = editable.getIndexer().getCharPosition(cursor);
editable.replace(s.line, s.column, c.line, c.column, text);
CharPosition select = editable.getIndexer().getCharPosition(start + text.length());
editorView.setSelection(select.line, select.column);
} catch (Exception e) {
}
}
@Override
public String toString() {
return getName();
}
}
@@ -1,41 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.rosemoe.editor.widget.CodeEditor;
public class KeywordDescription extends JavaSuggestItemImpl {
private final String keyword;
public KeywordDescription(String keyword) {
this.keyword = keyword;
}
@Nullable
@Override
public String getName() {
return keyword;
}
@Nullable
@Override
public String getDescription() {
return null;
}
@Nullable
@Override
public String getReturnType() {
return null;
}
@Override
public char getTypeHeader() {
return 'k';
}
@Override
public void onSelectThis(@NonNull CodeEditor iEditAreaView) {
insertImpl(iEditAreaView, keyword + " ");
}
}
@@ -1,7 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
public interface Member {
String getName();
int getModifiers();
}
@@ -1,200 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.IJavaDocCommentable;
import com.itsaky.androidide.language.java.parser.internal.IMethod;
import com.itsaky.androidide.language.java.parser.internal.JavaClassManager;
import com.itsaky.androidide.language.java.parser.internal.JavaUtil;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class MethodDescription extends JavaSuggestItemImpl implements Member, SuggestItem, IMethod {
private String mMethodName;
private int mModifiers;
private ArrayList<String> mParameterTypes = new ArrayList<>();
private ArrayList<IClass> mParameters = new ArrayList<>();
/**
* Return type of method, null if it is constructor
*/
@Nullable
private IClass mReturnType;
public MethodDescription(String name, IClass returnType,
int modifiers, ArrayList<String> parameterTypes) {
mMethodName = name;
mReturnType = returnType;
mModifiers = modifiers;
mParameterTypes = parameterTypes;
}
public MethodDescription(@NonNull String methodName,
int modifiers,
@NonNull List<IClass> parameters,
@Nullable IClass returnType) {
mMethodName = methodName;
mModifiers = modifiers;
mParameters.addAll(parameters);
mReturnType = returnType;
}
public MethodDescription(@NonNull Method method) {
mMethodName = method.getName();
mModifiers = method.getModifiers();
mReturnType = JavaClassManager.getInstance().getClassWrapper(method.getReturnType());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
mParameterTypes.add(parameterType.getName());
}
}
private String javadoc;
@Override
public IJavaDocCommentable setHtmlJavaDoc(String javadoc) {
this.javadoc = javadoc;
return this;
}
@Override
public String getJavaDoc() {
return javadoc;
}
@Override
public void onSelectThis(@NonNull CodeEditor editorView) {
try {
final int length = getIncomplete().length();
final int start = editorView.getCursor().getLeft() - length;
Content editable = editorView.getText();
CharPosition s = editable.getIndexer().getCharPosition(start);
editable.delete(start, editorView.getCursor().getLeft());
String simpleName = JavaUtil.getSimpleName(mMethodName);
String text = simpleName + "()" + (shouldAddSemicolon(getEditor()) ? ";" : "");
if (getParameterTypes().size() > 0) {
editable.insert(s.line, s.column, text);
CharPosition select = editable.getIndexer().getCharPosition(start + simpleName.length() + 1);
editorView.setSelection(select.line, select.column);
} else {
editable.insert(s.line, s.column, text);
CharPosition select = editable.getIndexer().getCharPosition(start + text.length());
editorView.setSelection(select.line, select.column);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean shouldAddSemicolon(CodeEditor editor) {
int cursor = editor.getCursor().getLeft();
String text = editor.getText().toString();
while (cursor < text.length()) {
char c = text.charAt(cursor);
if (c == ' ' || c == '\n') {
cursor++;
continue;
}
if (c == ';') {
return false;
} else {
break;
}
}
if (getMethodReturnType() == null) {
return true;
}
if (void.class.getName().equals(getMethodReturnType().getFullClassName())) {
return true;
}
return false;
}
public ArrayList<String> getParameterTypes() {
return mParameterTypes;
}
@Override
public char getTypeHeader() {
return 'm';
}
@Override
public String getName() {
return mMethodName + "(" + paramsToString() + ")";
}
private String paramsToString() {
StringBuilder result = new StringBuilder();
boolean firstTime = true;
ArrayList<String> parameterTypes = getParameterTypes();
for (String parameterType : parameterTypes) {
if (firstTime) {
firstTime = false;
} else {
result.append(",");
}
result.append(JavaUtil.getSimpleName(parameterType));
}
return result.toString();
}
@Override
public String getDescription() {
return null;
}
@Override
public String getReturnType() {
if (mReturnType == null) {
return "";
}
return mReturnType.getSimpleName();
}
@Override
public int getSuggestionPriority() {
return METHOD_DESC;
}
@Override
public String toString() {
StringBuilder params = new StringBuilder();
for (int i = 0; i < mParameterTypes.size(); i++) {
String parameterType = mParameterTypes.get(i);
if (i == mParameterTypes.size() - 1) {
params.append(JavaUtil.getSimpleName(parameterType));
break;
}
params.append(JavaUtil.getSimpleName(parameterType)).append(",");
}
return mMethodName + "(" + params.toString() + ")";
}
@Override
public int getModifiers() {
return mModifiers;
}
@Override
public String getMethodName() {
return mMethodName;
}
@Override
public IClass[] getMethodParameterTypes() {
return null;
}
@Override
public IClass getMethodReturnType() {
return mReturnType;
}
}
@@ -1,109 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import android.text.Editable;
import java.util.HashMap;
import io.github.rosemoe.editor.widget.CodeEditor;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.text.CharPosition;
public class PackageDescription extends JavaSuggestItemImpl {
private String mName;
private PackageDescription mParentPkg;
private HashMap<String, PackageDescription> mChild = new HashMap<>();
private PackageDescription(String childName, PackageDescription parent) {
this.mName = childName;
this.mParentPkg = parent;
}
public static PackageDescription root() {
return new PackageDescription("", null);
}
@Override
public void onSelectThis(@NonNull CodeEditor editorView) {
try {
final int cursor = getEditor().getCursor().getLeft();
final int length = getIncomplete().length();
final int start = cursor - length;
Content editable = editorView.getText();
CharPosition s = editable.getIndexer().getCharPosition(start);
CharPosition c = editable.getIndexer().getCharPosition(cursor);
String text;
text = isLeaf() ? mName.replace("$", ".") : mName + ".";
editable.replace(s.line, s.column, c.line, c.column, text);
CharPosition select = editable.getIndexer().getCharPosition(start + text.length());
editorView.setSelection(select.line, select.column);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public char getTypeHeader() {
return 'p';
}
@Override
public String getName() {
return mName;
}
@Override
public String getDescription() {
return null;
}
@Override
public String getReturnType() {
return null;
}
public HashMap<String, PackageDescription> getChild() {
return mChild;
}
private boolean isRoot() {
return mName.isEmpty() || mParentPkg == null;
}
private boolean isLeaf() {
return mChild.isEmpty();
}
public PackageDescription get(String key) {
if (!key.contains(".")) {
return mChild.get(key);
} else {
return get(key.substring(0, key.indexOf(".")))
.get(key.substring(key.indexOf(".") + 1));
}
}
public void put(String pkg) {
if (pkg.contains(".")) {
String first = pkg.substring(0, pkg.indexOf("."));
if (get(first) == null) {
put(first);
}
get(first).put(pkg.substring(pkg.indexOf(".") + 1));
} else {
this.mChild.put(pkg, new PackageDescription(pkg, this));
}
}
public PackageDescription remove(String child) {
if (child.contains(".")) {
return this.mChild.remove(child.substring(0, child.indexOf(".")));
} else {
return this.mChild.remove(child);
}
}
@Override
public String toString() {
return mName;
}
}
@@ -1,62 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
import androidx.annotation.NonNull;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
/**
* Created by Duy on 20-Jul-17.
*/
public class PrimitiveArrayConstructorDescription extends JavaSuggestItemImpl {
private String name;
public PrimitiveArrayConstructorDescription(String name) {
this.name = name;
}
@Override
public void onSelectThis(@NonNull CodeEditor editorView) {
try {
final int length = getIncomplete().length();
final int cursor = getEditor().getCursor().getLeft();
final int start = cursor - length;
Content editable = editorView.getText();
CharPosition s = editable.getIndexer().getCharPosition(start);
CharPosition c = editable.getIndexer().getCharPosition(cursor);
editable.replace(s.line, s.column, c.line, c.column, name + "[]");
CharPosition select = editable.getIndexer().getCharPosition(start + name.length() + 1);
editorView.setSelection(select.line, select.column);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getName() {
return name + "[]";
}
@Override
public char getTypeHeader() {
return 'a';
}
@Override
public String getDescription() {
return null;
}
@Override
public String getReturnType() {
return "";
}
@Override
public String toString() {
return name + "[]";
}
}
@@ -1,19 +0,0 @@
package com.itsaky.androidide.language.java.parser.model;
public class Statement {
private final String statement;
private final int offset;
public Statement(String statement, int offset) {
this.statement = statement;
this.offset = offset;
}
public String getStatement() {
return statement;
}
public int getOffset() {
return offset;
}
}
@@ -1,147 +0,0 @@
package com.itsaky.androidide.language.java.parser.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.Log;
import android.widget.EditText;
import com.itsaky.androidide.language.java.parser.expression.PackageImporter;
import com.itsaky.androidide.language.java.parser.expression.PatternFactory;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.regex.Matcher;
import io.github.rosemoe.editor.text.CharPosition;
/**
* Created by Duy on 20-Jul-17.
*/
public class EditorUtil {
private static final String TAG = "EditorUtil";
public EditorUtil() {
}
@Nullable
public static String getCurrentPackage(String editText) {
Matcher matcher = PatternFactory.PACKAGE.matcher(editText);
if (matcher.find()) {
return matcher.group();
}
return null;
}
public static String getCurrentClassName(String editor) {
return "";
}
@NonNull
public static String getCurrentClassSimpleName(String editor) {
String className = getCurrentClassName(editor);
int i = className.indexOf(".");
if (i == -1) return className;
else {
return className.substring(className.lastIndexOf(".") + 1);
}
}
public static ArrayList<String> getPossibleClassName(String source, String simpleName, String prefix) {
Log.d(TAG, "getPossibleClassName() called with: simpleName = [" + simpleName + "], prefix = [" + prefix + "]");
ArrayList<String> classList = new ArrayList<>();
String importedClassName = PackageImporter.getImportedClassName(source, simpleName);
Log.d(TAG, "getPossibleClassName importedClassName = " + importedClassName);
if (importedClassName != null) {
classList.add(importedClassName);
} else {
if (!prefix.contains(".")) {
classList.add(getCurrentClassName(source)); //current member
if (simpleName != null && !simpleName.isEmpty()) {
classList.add("java.lang." + simpleName); //default java.lang package
} else if (!prefix.isEmpty()) {
classList.add("java.lang." + prefix);
}
} else {
classList.add(prefix);
}
}
Log.d(TAG, "getPossibleClassName() returned: " + classList);
return classList;
}
public static String getLine(EditText editText, int pos) {
if (pos < 0 || pos > editText.length()) return "";
int line = LineUtils.getLineFromIndex(pos, editText.getLayout().getLineCount(), editText.getLayout());
int lineStart = editText.getLayout().getLineStart(line);
int lineEnd = editText.getLayout().getLineEnd(line);
return editText.getText().subSequence(lineStart, lineEnd).toString();
}
@Nullable
public static String getWord(EditText editText, int pos, boolean removeParentheses) {
String line = getLine(editText, pos).trim();
return getLastWord(line, removeParentheses);
}
@NonNull
public static String getWord(EditText editText, int pos) {
String line = getLine(editText, pos).trim();
return getLastWord(line, false);
}
@NonNull
public static String getLastWord(String line, boolean removeParentheses) {
String result = PatternFactory.lastMatchStr(line, PatternFactory.WORD);
if (result != null) {
return removeParentheses ? result.replaceAll(".*\\(", "") : result;
} else {
return "";
}
}
@Nullable
public static String getPreWord(EditText editor, int pos) {
String line = getLine(editor, pos);
String[] split = line.split(PatternFactory.SPLIT_NON_WORD_STR);
return split.length >= 2 ? split[split.length - 2] : null;
}
@NonNull
public static String getLineBeforeCursor(@NonNull CodeEditor editor, final int cursor) {
int start = cursor - 1;
String text = editor.getText().toString();
while (start > 0 && text.charAt(start) != '\n') {
start = start - 1;
if (start >= 0 && text.charAt(start) == '\n'){
start++;
break;
}
}
CharPosition s = editor.getText().getIndexer().getCharPosition(start);
CharPosition c = editor.getText().getIndexer().getCharPosition(cursor - 1);
return editor.getText().subContent(s.line, s.column, c.line, c.column).toString();
}
public static String trimLeft(String code) {
if (code == null) {
return null;
}
if (code.length() == 0) {
return "";
}
int index = 0;
while (index < code.length()) {
if (Character.isWhitespace(code.charAt(index))) {
index++;
} else {
break;
}
}
if (index == code.length()) {
return "";
}
return code.substring(index);
}
}
@@ -1,129 +0,0 @@
package com.itsaky.androidide.language.java.parser.util;
import androidx.annotation.NonNull;
import android.text.Layout;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.ScrollView;
public class LineUtils {
private boolean[] toCountLinesArray;
private int[] realLines;
public static int getYAtLine(ScrollView scrollView, int lineCount, int line) {
if (lineCount == 0) return 0;
return scrollView.getChildAt(0).getHeight() / lineCount * line;
}
public static int getFirstVisibleLine(@NonNull ScrollView scrollView, int childHeight,
int lineCount) throws ArithmeticException {
if (childHeight == 0) return 0;
int line = (scrollView.getScrollY() * lineCount) / childHeight;
if (line < 0) line = 0;
return line;
}
public static int getLastVisibleLine(@NonNull ScrollView scrollView,
int childHeight, int lineCount) {
if (childHeight == 0) return 0;
int line = (scrollView.getScrollY() * lineCount) / childHeight;
if (line > lineCount) line = lineCount;
return line;
}
/**
* Gets the lineInfo from the index of the letter in the text
*/
public static int getLineFromIndex(int index, int lineCount, Layout layout) {
int line;
int currentIndex = 0;
for (line = 0; line < lineCount; line++) {
currentIndex += layout.getLineEnd(line) - layout.getLineStart(line);
if (currentIndex >= index) {
break;
}
}
return line;
}
public static int getStartIndexAtLine(EditText editable, int line) {
Layout layout = editable.getLayout();
if (layout != null) {
return layout.getLineStart(line);
}
return 0;
}
public boolean[] getGoodLines() {
return toCountLinesArray;
}
public int[] getRealLines() {
return realLines;
}
public void updateHasNewLineArray(int lineCount, Layout layout, String text) {
boolean[] hasNewLineArray = new boolean[lineCount];
toCountLinesArray = new boolean[lineCount];
realLines = new int[lineCount];
if (TextUtils.isEmpty(text)) {
toCountLinesArray[0] = false;
realLines[0] = 0;
return;
}
if (lineCount == 0) return;
int i;
// for every lineInfo on the edittext
for (i = 0; i < lineCount; i++) {
// check if this lineInfo contains "\n"
if (layout.getLineEnd(i) == 0) {
hasNewLineArray[i] = false;
} else {
hasNewLineArray[i] = text.charAt(layout.getLineEnd(i) - 1) == '\n';
}
// if true
if (hasNewLineArray[i]) {
int j = i - 1;
while (j >= 0 && !hasNewLineArray[j]) {
j--;
}
toCountLinesArray[j + 1] = true;
}
}
toCountLinesArray[lineCount - 1] = true;
int realLine = 0;
for (i = 0; i < toCountLinesArray.length; i++) {
realLines[i] = realLine;
if (toCountLinesArray[i]) {
realLine++;
}
}
}
public int firstReadLine() {
return realLines[0];
}
public int lastReadLine() {
return realLines[realLines.length - 1];
}
public int fakeLineFromRealLine(int realLine) {
int i;
int fakeLine = 0;
for (i = 0; i < realLines.length; i++) {
if (realLine == realLines[i]) {
fakeLine = i;
break;
}
}
return fakeLine;
}
}
@@ -1,27 +0,0 @@
package com.itsaky.androidide.language.java.parser.util;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.language.java.parser.model.ClassDescription;
/**
* Created by duy on 19/07/2017.
*/
public class SpanUtil {
public static Spannable formatClass(Context context, ClassDescription item) {
SpannableString simpleName = new SpannableString(item.getSimpleName());
SpannableString packageName = new SpannableString("(" + item.getPackageName() + ")");
simpleName.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context,
android.R.color.primary_text_dark)), 0, simpleName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
packageName.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context,
android.R.color.secondary_text_dark)), 0, packageName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return new SpannableStringBuilder().append(simpleName).append(packageName);
}
}
@@ -1,101 +0,0 @@
package com.itsaky.androidide.language.java.provider;
import androidx.core.util.Pair;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.language.java.parser.Expression;
import com.itsaky.androidide.language.java.parser.ExpressionResolver;
import com.itsaky.androidide.language.java.parser.JavacParser;
import com.itsaky.androidide.language.java.parser.expression.CompleteExpression;
import com.itsaky.androidide.language.java.parser.expression.CompleteNewKeyword;
import com.itsaky.androidide.language.java.parser.expression.CompletePackage;
import com.itsaky.androidide.language.java.parser.expression.CompleteString;
import com.itsaky.androidide.language.java.parser.expression.CompleteThisKeyword;
import com.itsaky.androidide.language.java.parser.expression.CompleteTypeDeclared;
import com.itsaky.androidide.language.java.parser.expression.CompleteWord;
import com.itsaky.androidide.language.java.parser.expression.IJavaCompleteMatcher;
import com.itsaky.androidide.language.java.parser.internal.IClass;
import com.itsaky.androidide.language.java.parser.internal.JavaDexClassLoader;
import com.itsaky.androidide.language.java.parser.internal.PackageManager;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.utils.Environment;
import com.sun.tools.javac.tree.JCTree;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
public class JavaCompletionProvider {
private final List<IJavaCompleteMatcher> mCompletors;
private JavaDexClassLoader mClassLoader;
private PackageManager mPackageManager;
private JavacParser mJavaParser;
private boolean initiated = false;
public JavaCompletionProvider() {
this.mCompletors = new ArrayList<>();
this.mClassLoader = new JavaDexClassLoader(Environment.BOOTCLASSPATH);
this.mPackageManager = new PackageManager();
this.mJavaParser = new JavacParser();
addMatchers();
}
public boolean loadProject(AndroidProject project) {
mClassLoader.loadAllClasses(project);
mPackageManager.init(project, mClassLoader.getClassReader());
return initiated = true;
}
public boolean isInitiated() {
return initiated;
}
private void addMatchers() {
mCompletors.add(new CompleteExpression(mClassLoader));
mCompletors.add(new CompleteNewKeyword(mClassLoader));
mCompletors.add(new CompletePackage(mPackageManager));
mCompletors.add(new CompleteString(mClassLoader));
mCompletors.add(new CompleteTypeDeclared(mClassLoader));
mCompletors.add(new CompleteThisKeyword(mClassLoader));
mCompletors.add(new CompleteWord(mJavaParser, mClassLoader));
}
public Pair<ArrayList<SuggestItem>, List<Diagnostic<? extends JavaFileObject>>> getSuggestions(CodeEditor editor) {
ArrayList<SuggestItem> result = new ArrayList<>();
try {
JCTree.JCCompilationUnit ast = mJavaParser.parse(editor.getText());
List<IClass> classes = mJavaParser.parseClasses(ast);
mClassLoader.updateClasses(classes);
ExpressionResolver resolver = new ExpressionResolver(ast, editor);
Expression expression = resolver.getExpressionAtCursor();
JCTree.JCExpression jcExpression = expression.getExpression();
int startPosition = jcExpression.getStartPosition();
CharPosition s = editor.getText().getIndexer().getCharPosition(startPosition);
CharPosition c = new CharPosition();
c.line = editor.getCursor().getLeftLine();
c.column = editor.getCursor().getLeftColumn();
String statement = editor.getText().subContent(s.line, s.column, c.line, c.column).toString();
for (IJavaCompleteMatcher autoComplete : mCompletors) {
try {
boolean handled = autoComplete.process(ast, editor, expression, statement, result);
if (handled) {
break;
}
} catch (Throwable e) {
}
}
} catch (Throwable e) {
}
return Pair.create(result, mJavaParser.getDiagnostics());
}
}
@@ -82,7 +82,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
new Thread(() -> {
try {
Socket socket = connectAndGet();
onConnectToServer();
sendInit();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = reader.readLine()) != null) {
@@ -96,6 +96,11 @@ public class JavaLanguageServer implements ShellServer.Callback {
}
}).start();
}
public void initialize(AndroidProject project) {
this.project = project;
sendInit();
}
private Socket connectAndGet() {
int tryCount = 0;
@@ -113,7 +118,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
return server;
}
protected void onConnectToServer() {
protected void sendInit() {
InitializeParams p = new InitializeParams();
p.rootUri = new File(project.getProjectPath()).toURI();
initialize(p);
@@ -1,7 +1,7 @@
package com.itsaky.androidide.language.logs;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
@@ -3,18 +3,17 @@ package com.itsaky.androidide.language.xml;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
import com.itsaky.androidide.models.CompletionListItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import com.itsaky.androidide.utils.EitherOfResult;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Collections;
import com.itsaky.androidide.utils.Either;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import java.util.stream.Collectors;
import com.itsaky.androidide.utils.EitherOfResult;
public class XMLAutoComplete implements AutoCompleteProvider {
@@ -1,7 +1,6 @@
package com.itsaky.androidide.models;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.lsp.CompletionItem;
import com.itsaky.lsp.CompletionItemKind;
@@ -1,6 +1,5 @@
package com.itsaky.androidide.models;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import io.github.rosemoe.editor.widget.CodeEditor;
@@ -0,0 +1,12 @@
package com.itsaky.androidide.models;
import io.github.rosemoe.editor.widget.CodeEditor;
public interface SuggestItem {
public String getName();
public String getDescription();
public String getReturnType();
public char getTypeHeader();
public int getSuggestionPriority();
public void onSelectThis(CodeEditor editor);
}
@@ -1,6 +1,6 @@
package com.itsaky.androidide.utils;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.SuggestItem;
import io.github.rosemoe.editor.struct.CompletionItem;
public class EitherOfResult extends Either<SuggestItem, CompletionItem> {
@@ -15,7 +15,7 @@
*/
package io.github.rosemoe.editor.interfaces;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
@@ -15,7 +15,7 @@
*/
package io.github.rosemoe.editor.langs;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
@@ -16,18 +16,18 @@
package io.github.rosemoe.editor.widget;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.utils.Either;
import com.itsaky.androidide.R;
import io.github.rosemoe.editor.struct.CompletionItem;
import android.graphics.drawable.Drawable;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.R;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.struct.CompletionItem;
/**
* Adapter to display results
@@ -21,9 +21,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import com.itsaky.androidide.databinding.LayoutCompletionWindowBinding;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.CompletionEdit;
import com.itsaky.androidide.models.CompletionListItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.struct.CompletionItem;
@@ -32,7 +32,6 @@ import io.github.rosemoe.editor.text.Cursor;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import java.util.ArrayList;
import java.util.List;
import com.blankj.utilcode.util.ThreadUtils;
/**
* Auto complete window for editing code quicker
@@ -19,7 +19,7 @@ import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.struct.CompletionItem;
import java.util.List;
+52 -52
View File
@@ -3,8 +3,8 @@
<string name="diagnostic_position">Start: %s, %s End: %s, %s</string>
<string name="diagnostic_error">ERROR</string>
<string name="diagnostic_warning">WARNING</string>
<string name="diagnostic_hint">INFO</string>
<string name="diagnostic_info">HINT</string>
<string name="diagnostic_hint">HINT</string>
<string name="diagnostic_info">INFO</string>
<string name="msg_project_deleted">Project deleted</string>
<string name="msg_project_delete_failed">Failed to delete project!</string>
<string name="msg_failed_load_icon_path">Failed to load icon path!</string>
@@ -17,8 +17,8 @@
<string name="msg_indexing">Indexing: %s</string>
<string name="msg_daemons_force_stopping">Gradle build process is running. Killing daemons...</string>
<string name="msg_daemons_stopped">Gradle Daemons Stopped</string>
<string name="recreate_completion_services">Recreate Completion Services</string>
<string name="gradle_daemon_status">Gradle Daemon Status</string>
<string name="recreate_completion_services">Recreate services</string>
<string name="gradle_daemon_status">Gradle daemon status</string>
<string name="msg_newfile_dest">Destination: %s/</string>
<string name="restype_drawable">Drawable</string>
<string name="restype_layout">Layout</string>
@@ -56,22 +56,22 @@
<string name="msg_clear_cache">This will delete caches directory created by Gradle. If you delete it, the next build may take a bit longer than normal as Gradle will download required files again.\n\nAre you sure you want to delete caches?</string>
<string name="msg_install_gradle">Currently installed gradle version will be DELETE PEEMANENTLY!\n\nPlease enter full path of the zip file which contains Gradle distribution.</string>
<string name="reset">Reset</string>
<string name="title_change_text_size">Editor Font Size</string>
<string name="title_change_text_size">Editor font size</string>
<string name="idepref_build_clearCache_title">Clear Gradle caches</string>
<string name="idepref_build_clearCache_summary">Clear Gradle caches directory. This will delete all dependencies downloaded by Gradle. Required files will be downloaded again in next build.</string>
<string name="idepref_build_choosegradle_title">Choose Gradle Version</string>
<string name="idepref_build_choosegradle_title">Choose Gradle version</string>
<string name="idepref_build_choosegradle_summary">Choose one of the installed Gradle versions.</string>
<string name="idepref_build_installgradle_title">Custom Gradle</string>
<string name="idepref_build_installgradle_summary">Define custom Gradle that will be used in AndroidIDE.</string>
<string name="idepref_build_customgradlecommands_title">Additional Gradle Commands</string>
<string name="idepref_build_customgradlecommands_title">Additional Gradle commands</string>
<string name="idepref_build_customgradlecommands_summary">Choose additional commands that will be added while executing every task</string>
<string name="idepref_build_editgradleproperties_title">Edit global Gradle Properties</string>
<string name="idepref_build_editgradleproperties_title">Edit global Gradle properties</string>
<string name="idepref_build_editgradleproperties_summary">Edit gradle.properties in GRADLE_HOME</string>
<string name="idepref_editor_fontsize_title">Font Size</string>
<string name="idepref_editor_fontsize_title">Font size</string>
<string name="idepref_editor_fontsize_summary">Font size of the editor</string>
<string name="idepref_editor_paintingflags_title">Non-printable painting flags</string>
<string name="idepref_editor_paintingflags_summary">Choose what non-printable characters should be drawn by the editor</string>
<string name="idepref_editor_drawhexcolors_title">Draw Hex Color Strings</string>
<string name="idepref_editor_drawhexcolors_title">Draw hex color strings</string>
<string name="idepref_editor_drawhexcolors_summary">Should the editor draw hex color string?</string>
<string name="idepref_appearance_summary">Customize look and feel of AndroidIDE</string>
<string name="idepref_appearance_title">Appearance</string>
@@ -84,14 +84,14 @@
<string name="msg_first_build">This is the first time build after you installed AndroidIDE. First build usually takes time as Gradle downloads all dependencies and plugins. This process may take about 10&#8211;15 minutes. But this totally depends on your network connection.\n\nPlease be patient &#8230;</string>
<string name="msg_share_app">AndroidIDE is an Integrated Development Environment for building Android apps on your Android Smartphone. It supports :\n1. Gradle \n2. OpenJDK 11.0.1.\n3. Advanced Java auto completion\n4. XML Auto Completion \n5. Kotlin Support and much more.\n\nDownload AndroidIDE at https://androidide.com</string>
<string name="msg_need_help">If you\'re facing issues while using AndroidIDE or with building Android Apps, you can join the Telegram Group ask for help there.\n\nYou can also send suggesstions on our email.</string>
<string name="discuss_amp_help">Discuss &amp; Help</string>
<string name="user_suggestions">Issue Tracker</string>
<string name="discuss_amp_help">Discuss &amp; help</string>
<string name="user_suggestions">Issue tracker</string>
<string name="issue_tracker_summary">See what issues, suggesstions, requests we are currently working on.</string>
<string name="need_help">Need Help?</string>
<string name="other">Other</string>
<string name="share_with_friends">Share with Friends</string>
<string name="ide_preferences">IDE Preferences</string>
<string name="discussions_on_telegram">Discussions On Telegram</string>
<string name="ide_preferences">IDE preferences</string>
<string name="discussions_on_telegram">Discussions on Telegram</string>
<string name="discussions_on_telegram_summary">Discuss issues, suggesstions, requests on Telegram</string>
<string name="project_exists">A project with same name already exists!</string>
<string name="copying_files">Copying files</string>
@@ -104,7 +104,7 @@
<string name="project_created_successfully">Project created successfully!</string>
<string name="msg_begin_project_write">Starting writing process</string>
<string name="msg_first_prepare_failed">Something went wrong while preparing for first build. Would you like to retry?</string>
<string name="title_restart">Please Restart</string>
<string name="title_restart">Please restart</string>
<string name="msg_restart">Installation is finished successfully. Please restart AndroidIDE.</string>
<string name="installed">Installed</string>
<string name="installing">Installing &#8230;</string>
@@ -129,14 +129,14 @@
<string name="msg_can_contain_slashes">You can use forward slashes (\'/\') to define a relative path to this folder. This will create directories if they do not already exist and will save time in creating multiple directories.</string>
<string name="folder_name">Folder Name</string>
<string name="file_name">File Name</string>
<string name="new_name">New Name</string>
<string name="new_file">New File</string>
<string name="new_java_class">New Java Class</string>
<string name="new_xml_resource">New XML Resource</string>
<string name="new_resource">New %s Resource</string>
<string name="new_folder">New Folder</string>
<string name="new_name">New name</string>
<string name="new_file">New file</string>
<string name="new_java_class">New Java class</string>
<string name="new_xml_resource">New XML resource</string>
<string name="new_resource">New %s resource</string>
<string name="new_folder">New folder</string>
<string name="logs">Logs</string>
<string name="title_confirm_delete">Confirm Delete</string>
<string name="title_confirm_delete">Confirm delete</string>
<string name="msg_confirm_delete">Are you sure you want to delete:\n%s?</string>
<string name="copied">Copied successfully!</string>
<string name="msg_rename_file">Enter new name for file/folder.</string>
@@ -144,17 +144,17 @@
<string name="rename_failed">Unable to rename file!</string>
<string name="deleted">Deleted successfully!</string>
<string name="delete_failed">Unable to delete file!</string>
<string name="copy_path">Copy Path</string>
<string name="copy_path">Copy path</string>
<string name="rename_file">Rename</string>
<string name="delete_file">Delete</string>
<string name="file_info">File Info</string>
<string name="file_options">File Options</string>
<string name="action_closeAll">Close All</string>
<string name="action_closeOthers">Close Others</string>
<string name="action_closeThis">Close This</string>
<string name="file_info">File info</string>
<string name="file_options">File options</string>
<string name="action_closeAll">Close all</string>
<string name="action_closeOthers">Close others</string>
<string name="action_closeThis">Close this</string>
<string name="all_saved">All files saved!</string>
<string name="auto_saved">Automatically saved all files</string>
<string name="build_failed">Build Failed</string>
<string name="build_failed">Build failed</string>
<string name="compiling">Compiling</string>
<string name="install_failed">Failed to install apk. Please try again!</string>
<string name="invalid_app_name">Invalid app name</string>
@@ -163,14 +163,14 @@
<string name="invalid_target_sdk">Invalid target SDK</string>
<string name="invalid_values">Invalid values</string>
<string name="label_not_imported">[IMPORT] [%s]</string>
<string name="label_splash_image">Splash Image</string>
<string name="label_splash_image">Splash image</string>
<string name="msg_checking_storage_permissions">Checking storage permissions</string>
<string name="msg_failed_list_files">Failed to list project files!</string>
<string name="msg_failed_save">Failed to save some files!</string>
<string name="msg_service_started">Service Started</string>
<string name="msg_starting_completion">Starting Completion Services</string>
<string name="msg_starting_completion">Starting services</string>
<string name="msg_starting_completion_failed">Failed to start Completion Services</string>
<string name="msg_starting_daemon">Starting a Gradle Daemon</string>
<string name="msg_starting_daemon">Starting a Gradle daemon</string>
<string name="msg_storage_denied">Can\'t proceed without storage permissions</string>
<string name="msg_storage_granted">Storage permissions granted!</string>
<string name="msg_sync_needed">Gradle files have changed. Please rebuild your project to sync dependencies and configurations.</string>
@@ -181,41 +181,41 @@
<string name="saved_file">%s saved!</string>
<string name="task_merge_dex">Merge Dex</string>
<string name="template_basic">Basic Project</string>
<string name="template_basic_kotlin">Basic Kotlin Project</string>
<string name="template_bottom_navigation">Bottom Navigation Project</string>
<string name="template_basic_kotlin">Basic Kotlin project</string>
<string name="template_bottom_navigation">Bottom navigation project</string>
<string name="template_description_basic">A basic project with AppCompat Toolbar and a Floating Action Button</string>
<string name="template_description_bottom_navigation">Project with a BottomNavigationView and without any Toolbar</string>
<string name="template_description_empty">An empty project with default toolbar. Suitable when you want to do all the basic things yourself.</string>
<string name="template_description_kotlin">Basic project written in kotlin</string>
<string name="template_description_navigation_drawer">Project with AppCompat Toolbar, DrawerLayout and NavigationView</string>
<string name="template_empty">Empty Project</string>
<string name="template_navigation_drawer">Navigation Drawer Project</string>
<string name="unknown_type">Unknown Type</string>
<string name="template_empty">Empty project</string>
<string name="template_navigation_drawer">Navigation drawer project</string>
<string name="unknown_type">Unknown type</string>
<string name="app_name">Android IDE</string>
<string name="build">Build</string>
<string name="build_debug">Assemble Debug</string>
<string name="build_output">Build Output</string>
<string name="build_release">Assemble Release</string>
<string name="build_successful">Build Successful</string>
<string name="build_tasks">Build Tasks</string>
<string name="build_debug">assembleDebug</string>
<string name="build_output">Build output</string>
<string name="build_release">assembleRelease</string>
<string name="build_successful">Build successful</string>
<string name="build_tasks">Build tasks</string>
<string name="cancel">Cancel</string>
<string name="clean_amp_build">Clean Build</string>
<string name="clean_amp_build">Clean &amp; build</string>
<string name="clean_project">Clean</string>
<string name="create_aab">Create AAB</string>
<string name="create_project">Create Project</string>
<string name="create_project">Create project</string>
<string name="do_you_want_to_install_apk">Do you want to install APK?</string>
<string name="get_started">Get Started</string>
<string name="get_started">Get started</string>
<string name="install">Install</string>
<string name="lint">Lint</string>
<string name="lint_debug">Lint Debug</string>
<string name="lint_release">Lint Release</string>
<string name="lint_tasks">Lint Tasks</string>
<string name="lint_debug">lintDebug</string>
<string name="lint_release">lintRelease</string>
<string name="lint_tasks">Lint tasks</string>
<string name="looks_like_you_havent_created">Looks like you haven\'t created any projects yet.</string>
<string name="minimum_sdk">Minimum SDK</string>
<string name="new_project">New project</string>
<string name="package_name">Package Name</string>
<string name="project_app_name">App Name</string>
<string name="quick_run_debug">Quick Run</string>
<string name="package_name">Package name</string>
<string name="project_app_name">App name</string>
<string name="quick_run_debug">Quick run</string>
<string name="redo">Redo</string>
<string name="run">Run</string>
<string name="save">Save</string>
@@ -239,7 +239,7 @@
<item name="last" type="string">Last</item>
<item name="next" type="string">Next</item>
<item name="replace" type="string">Replace</item>
<item name="replaceAll" type="string">Replace All</item>
<item name="replaceAll" type="string">Replace all</item>
<item name="replacement" type="string">Replacement</item>
<item name="text_to_search" type="string">Text to search</item>
<item name="more" type="string" translatable="false" >More</item>