mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Implemented CodeActionProvider
This commit is contained in:
@@ -18,8 +18,10 @@
|
||||
package com.itsaky.lsp.models
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
data class CodeActionParams(var cursor: Position, var diagnostics: List<DiagnosticItem>)
|
||||
data class CodeActionParams(var file: Path, var range: Range, var diagnostics: List<DiagnosticItem>)
|
||||
|
||||
data class CodeActionResult (var actions: List <CodeActionItem>) {
|
||||
constructor() : this (ArrayList())
|
||||
@@ -27,8 +29,24 @@ data class CodeActionResult (var actions: List <CodeActionItem>) {
|
||||
|
||||
data class CodeActionItem (
|
||||
var title: String,
|
||||
var changes: List<DocumentChange>)
|
||||
var changes: List<DocumentChange>,
|
||||
var kind: CodeActionKind
|
||||
) {
|
||||
constructor() : this ("", Collections.emptyList(), CodeActionKind.None)
|
||||
}
|
||||
|
||||
data class DocumentChange (var file: Path, var edits: List<TextEdit>)
|
||||
enum class CodeActionKind {
|
||||
QuickFix,
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
data class TextEdit (var range: Range, var newText: String)
|
||||
data class DocumentChange (var file: Path?, var edits: List<TextEdit>) {
|
||||
constructor() : this (null, Collections.emptyList())
|
||||
}
|
||||
|
||||
data class TextEdit (var range: Range, var newText: String) {
|
||||
companion object {
|
||||
@JvmField val NONE: TextEdit = TextEdit(Range.NONE, "");
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ data class Location (var file: Path, var range: Range)
|
||||
|
||||
data class Position(var line: Int, var column: Int) {
|
||||
companion object {
|
||||
@JvmField val NONE = Position (0, 0)
|
||||
@JvmField val NONE = Position (-1, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Cache maps a file + an arbitrary key to a value. When the file is modified, the mapping expires. */
|
||||
class Cache<K, V> {
|
||||
public class Cache<K, V> {
|
||||
private static class Key<K> {
|
||||
final Path file;
|
||||
final K key;
|
||||
@@ -41,11 +41,11 @@ class Cache<K, V> {
|
||||
|
||||
private final Map<Key, Value> map = new HashMap<>();
|
||||
|
||||
boolean has(Path file, K k) {
|
||||
public boolean has(Path file, K k) {
|
||||
return !needs(file, k);
|
||||
}
|
||||
|
||||
boolean needs(Path file, K k) {
|
||||
public boolean needs(Path file, K k) {
|
||||
// If key is not in map, it needs to be loaded
|
||||
Key key = new Key<K>(file, k);
|
||||
if (!map.containsKey(key)) return true;
|
||||
@@ -57,14 +57,14 @@ class Cache<K, V> {
|
||||
return value.created.isBefore(modified);
|
||||
}
|
||||
|
||||
void load(Path file, K k, V v) {
|
||||
public void load(Path file, K k, V v) {
|
||||
// TODO limit total size of cache
|
||||
Key key = new Key<K>(file, k);
|
||||
Value value = new Value(v);
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
V get(Path file, K k) {
|
||||
public V get(Path file, K k) {
|
||||
Key key = new Key<K>(file, k);
|
||||
if (!map.containsKey(key)) {
|
||||
throw new IllegalArgumentException(k + " is not in map " + map);
|
||||
|
||||
@@ -58,7 +58,7 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static void setWorkspaceRoots (Set<Path> newRoots) {
|
||||
public static void setWorkspaceRoots (Set<Path> newRoots) {
|
||||
for (Path root : workspaceRoots) {
|
||||
if (!newRoots.contains (root)) {
|
||||
workspaceRoots.removeIf (f -> f.startsWith (root));
|
||||
@@ -81,7 +81,7 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static class FindJavaSources extends SimpleFileVisitor<Path> {
|
||||
public static class FindJavaSources extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
|
||||
if (attrs.isSymbolicLink()) {
|
||||
@@ -100,11 +100,11 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static Collection<Path> all () {
|
||||
public static Collection<Path> all () {
|
||||
return javaSources.keySet ();
|
||||
}
|
||||
|
||||
static List<Path> list (String packageName) {
|
||||
public static List<Path> list (String packageName) {
|
||||
List<Path> list = new ArrayList<> ();
|
||||
for (Path file : javaSources.keySet ()) {
|
||||
if (Objects.requireNonNull (javaSources.get (file)).packageName.equals (packageName)) {
|
||||
@@ -140,11 +140,11 @@ public class FileStore {
|
||||
return dir;
|
||||
}
|
||||
|
||||
static boolean contains (Path file) {
|
||||
public static boolean contains (Path file) {
|
||||
return isJavaFile (file) && javaSources.containsKey (file);
|
||||
}
|
||||
|
||||
static Instant modified (Path file) {
|
||||
public static Instant modified (Path file) {
|
||||
// If file is open, use last in-memory modification time
|
||||
if (activeDocuments.containsKey (file)) {
|
||||
return Objects.requireNonNull (activeDocuments.get (file)).modified;
|
||||
@@ -157,7 +157,7 @@ public class FileStore {
|
||||
return Objects.requireNonNull (javaSources.get (file)).modified;
|
||||
}
|
||||
|
||||
static String packageName (Path file) {
|
||||
public static String packageName (Path file) {
|
||||
// If we've never checked before, look up package name on disk
|
||||
if (!javaSources.containsKey (file)) {
|
||||
readInfoFromDisk (file);
|
||||
@@ -201,15 +201,15 @@ public class FileStore {
|
||||
return list;
|
||||
}
|
||||
|
||||
static void externalCreate (Path file) {
|
||||
public static void externalCreate (Path file) {
|
||||
readInfoFromDisk (file);
|
||||
}
|
||||
|
||||
static void externalChange (Path file) {
|
||||
public static void externalChange (Path file) {
|
||||
readInfoFromDisk (file);
|
||||
}
|
||||
|
||||
static void externalDelete (Path file) {
|
||||
public static void externalDelete (Path file) {
|
||||
javaSources.remove (file);
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static void open (DocumentOpenEvent params) {
|
||||
public static void open (DocumentOpenEvent params) {
|
||||
Path document = params.getOpenedFile ();
|
||||
if (!isJavaFile (document)) {
|
||||
LOG.error ("Opened file is not a java file. Ignoring...");
|
||||
@@ -236,7 +236,7 @@ public class FileStore {
|
||||
activeDocuments.put (document, new VersionedContent (params.getText (), params.getVersion ()));
|
||||
}
|
||||
|
||||
static void change (DocumentChangeEvent params) {
|
||||
public static void change (DocumentChangeEvent params) {
|
||||
final Path document = params.getChangedFile ();
|
||||
if (!isJavaFile (document)) {
|
||||
LOG.error ("Changed file is not a java file. Ignoring...");
|
||||
@@ -248,7 +248,7 @@ public class FileStore {
|
||||
activeDocuments.put (document, new VersionedContent (newText, params.getVersion ()));
|
||||
}
|
||||
|
||||
static void close (DocumentCloseEvent params) {
|
||||
public static void close (DocumentCloseEvent params) {
|
||||
final Path document = params.getClosedFile ();
|
||||
if (!isJavaFile (document)) {
|
||||
LOG.error ("Closed file is not a java file. Ignoring...");
|
||||
@@ -257,7 +257,7 @@ public class FileStore {
|
||||
activeDocuments.remove (document);
|
||||
}
|
||||
|
||||
static Set<Path> activeDocuments () {
|
||||
public static Set<Path> activeDocuments () {
|
||||
return activeDocuments.keySet ();
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static InputStream inputStream (Path file) {
|
||||
public static InputStream inputStream (Path file) {
|
||||
if (activeDocuments.containsKey (file)) {
|
||||
String string = Objects.requireNonNull (activeDocuments.get (file)).content;
|
||||
byte[] bytes = string.getBytes ();
|
||||
@@ -302,7 +302,7 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static BufferedReader bufferedReader (Path file) {
|
||||
public static BufferedReader bufferedReader (Path file) {
|
||||
if (activeDocuments.containsKey (file)) {
|
||||
String string = Objects.requireNonNull (activeDocuments.get (file)).content;
|
||||
return new BufferedReader (new StringReader (string));
|
||||
@@ -318,14 +318,14 @@ public class FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
static BufferedReader lines (Path file) {
|
||||
public static BufferedReader lines (Path file) {
|
||||
return bufferedReader (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from line/column (1-based) to offset (0-based)
|
||||
*/
|
||||
static int offset (String contents, int line, int column) {
|
||||
public static int offset (String contents, int line, int column) {
|
||||
line--;
|
||||
column--;
|
||||
int cursor = 0;
|
||||
@@ -338,7 +338,7 @@ public class FileStore {
|
||||
return cursor + column;
|
||||
}
|
||||
|
||||
static boolean isJavaFile (@NonNull Path file) {
|
||||
public static boolean isJavaFile (@NonNull Path file) {
|
||||
String name = file.getFileName ().toString ();
|
||||
// We hide module-info.java from javac, because when javac sees module-info.java
|
||||
// it goes into "module mode" and starts looking for classes on the module class path.
|
||||
@@ -349,11 +349,11 @@ public class FileStore {
|
||||
return name.endsWith (".java") && !Files.isDirectory (file) && !name.equals ("module-info.java");
|
||||
}
|
||||
|
||||
static boolean isJavaFile (@NonNull URI uri) {
|
||||
public static boolean isJavaFile (@NonNull URI uri) {
|
||||
return uri.getScheme ().equals ("file") && isJavaFile (Paths.get (uri));
|
||||
}
|
||||
|
||||
static Optional<Path> findDeclaringFile (@NonNull TypeElement el) {
|
||||
public static Optional<Path> findDeclaringFile (@NonNull TypeElement el) {
|
||||
String qualifiedName = el.getQualifiedName ().toString ();
|
||||
String packageName = StringSearch.mostName (qualifiedName);
|
||||
String className = StringSearch.lastName (qualifiedName);
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.itsaky.lsp.java;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
public class FindTypeDeclarationAt extends TreeScanner<ClassTree, Long> {
|
||||
private final SourcePositions pos;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
public FindTypeDeclarationAt(JavacTask task) {
|
||||
pos = Trees.instance(task).getSourcePositions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree visitCompilationUnit(CompilationUnitTree t, Long find) {
|
||||
root = t;
|
||||
return super.visitCompilationUnit(t, find);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree visitClass(ClassTree t, Long find) {
|
||||
ClassTree smaller = super.visitClass(t, find);
|
||||
if (smaller != null) {
|
||||
return smaller;
|
||||
}
|
||||
if (pos.getStartPosition(root, t) <= find && find < pos.getEndPosition(root, t)) {
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree reduce(ClassTree a, ClassTree b) {
|
||||
if (a != null) return a;
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.itsaky.lsp.java;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
class FindTypeDeclarations extends TreeScanner<Void, List<String>> {
|
||||
private List<CharSequence> qualifiedName = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree root, List<String> found) {
|
||||
String name = Objects.toString(root.getPackageName(), "");
|
||||
qualifiedName.add(name);
|
||||
return super.visitCompilationUnit(root, found);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClass(ClassTree type, List<String> found) {
|
||||
qualifiedName.add(type.getSimpleName());
|
||||
found.add(String.join(".", qualifiedName));
|
||||
super.visitClass(type, found);
|
||||
qualifiedName.remove(qualifiedName.size() - 1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.itsaky.lsp.java;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.utils.Extractors;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarations;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.api.IReferenceProvider;
|
||||
import com.itsaky.lsp.api.IServerSettings;
|
||||
import com.itsaky.lsp.api.ISignatureHelpProvider;
|
||||
import com.itsaky.lsp.java.completion.JavaCompletionProvider;
|
||||
import com.itsaky.lsp.java.completion.SignatureProvider;
|
||||
import com.itsaky.lsp.java.providers.JavaCompletionProvider;
|
||||
import com.itsaky.lsp.java.providers.SignatureProvider;
|
||||
import com.itsaky.lsp.java.models.JavaServerConfiguration;
|
||||
import com.itsaky.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.lsp.models.DocumentCloseEvent;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.itsaky.lsp.java;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class JsonHelper {
|
||||
public static final Gson GSON = new GsonBuilder().create();
|
||||
}
|
||||
@@ -44,12 +44,14 @@ import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.DefinedBy;
|
||||
import com.sun.tools.javac.util.DefinedBy.Api;
|
||||
import com.sun.tools.javac.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaFileManager;
|
||||
@@ -77,205 +79,210 @@ import javax.tools.JavaFileObject;
|
||||
* code and its internal interfaces are subject to change or deletion without notice.</b>
|
||||
*/
|
||||
class ReusableCompiler {
|
||||
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
private static final JavacTool systemProvider = JavacTool.create();
|
||||
|
||||
private List<String> currentOptions = new ArrayList<>();
|
||||
private static final JavacTool systemProvider = JavacTool.create ();
|
||||
|
||||
private List<String> currentOptions = new ArrayList<> ();
|
||||
private ReusableContext currentContext;
|
||||
private boolean checkedOut;
|
||||
|
||||
|
||||
private static Borrow lastBorrow;
|
||||
|
||||
/**
|
||||
* Creates a new task as if by {@link javax.tools.JavaCompiler#getTask} and runs the provided worker with it. The
|
||||
* task is only valid while the worker is running. The internal structures may be reused from some previous
|
||||
* compilation.
|
||||
*
|
||||
* @param fileManager a file manager; if {@code null} use the compiler's standard filemanager
|
||||
* @param fileManager a file manager; if {@code null} use the compiler's standard filemanager
|
||||
* @param diagnosticListener a diagnostic listener; if {@code null} use the compiler's default method for reporting
|
||||
* diagnostics
|
||||
* @param options compiler options, {@code null} means no options
|
||||
* @param classes names of classes to be processed by annotation processing, {@code null} means no class names
|
||||
* @param compilationUnits the compilation units to compile, {@code null} means no compilation units
|
||||
* diagnostics
|
||||
* @param options compiler options, {@code null} means no options
|
||||
* @param classes names of classes to be processed by annotation processing, {@code null} means no class names
|
||||
* @param compilationUnits the compilation units to compile, {@code null} means no compilation units
|
||||
* @return an object representing the compilation
|
||||
* @throws RuntimeException if an unrecoverable error occurred in a user supplied component. The {@linkplain
|
||||
* Throwable#getCause() cause} will be the error in user code.
|
||||
* @throws RuntimeException if an unrecoverable error occurred in a user supplied component. The {@linkplain
|
||||
* Throwable#getCause() cause} will be the error in user code.
|
||||
* @throws IllegalArgumentException if any of the options are invalid, or if any of the given compilation units are
|
||||
* of other kind than {@linkplain JavaFileObject.Kind#SOURCE source}
|
||||
* of other kind than {@linkplain JavaFileObject.Kind#SOURCE source}
|
||||
*/
|
||||
Borrow getTask(
|
||||
Borrow getTask (
|
||||
JavaFileManager fileManager,
|
||||
DiagnosticListener<? super JavaFileObject> diagnosticListener,
|
||||
Iterable<String> options,
|
||||
Iterable<String> classes,
|
||||
Iterable<? extends JavaFileObject> compilationUnits) {
|
||||
|
||||
if (checkedOut) {
|
||||
throw new RuntimeException("Compiler is already in-use!");
|
||||
if (lastBorrow != null && checkedOut) {
|
||||
// If the compiler is in use, try to cleanup the last task
|
||||
lastBorrow.close ();
|
||||
}
|
||||
|
||||
checkedOut = true;
|
||||
List<String> opts =
|
||||
StreamSupport.stream(options.spliterator(), false).collect(Collectors.toCollection(ArrayList::new));
|
||||
if (!opts.equals(currentOptions)) {
|
||||
LOG.warn(String.format("Options changed from %s to %s, creating new compiler", options, opts));
|
||||
StreamSupport.stream (options.spliterator (), false).collect (Collectors.toCollection (ArrayList::new));
|
||||
if (!opts.equals (currentOptions)) {
|
||||
LOG.warn (String.format ("Options changed from %s to %s, creating new compiler", options, opts));
|
||||
currentOptions = opts;
|
||||
currentContext = new ReusableContext(opts);
|
||||
currentContext = new ReusableContext (opts);
|
||||
}
|
||||
JavacTaskImpl task =
|
||||
(JavacTaskImpl)
|
||||
systemProvider.getTask(
|
||||
systemProvider.getTask (
|
||||
null, fileManager, diagnosticListener, opts, classes, compilationUnits, currentContext);
|
||||
|
||||
task.addTaskListener(currentContext);
|
||||
|
||||
return new Borrow(task, currentContext);
|
||||
|
||||
task.addTaskListener (currentContext);
|
||||
|
||||
return new Borrow (task, currentContext);
|
||||
}
|
||||
|
||||
|
||||
class Borrow implements AutoCloseable {
|
||||
|
||||
final JavacTask task;
|
||||
boolean closed;
|
||||
|
||||
Borrow(JavacTask task, ReusableContext ctx) {
|
||||
|
||||
Borrow (JavacTask task, ReusableContext ctx) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed) return;
|
||||
public void close () {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
// not returning the context to the pool if task crashes with an exception
|
||||
// the task/context may be in a broken state
|
||||
currentContext.clear();
|
||||
currentContext.clear ();
|
||||
try {
|
||||
Method method = JavacTaskImpl.class.getDeclaredMethod("cleanup");
|
||||
method.setAccessible(true);
|
||||
method.invoke(task);
|
||||
Method method = JavacTaskImpl.class.getDeclaredMethod ("cleanup");
|
||||
method.setAccessible (true);
|
||||
method.invoke (task);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException("Unable to call cleanup() on JavacTaskImpl", e);
|
||||
throw new RuntimeException ("Unable to call cleanup() on JavacTaskImpl", e);
|
||||
}
|
||||
|
||||
checkedOut = false;
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ReusableContext extends Context implements TaskListener {
|
||||
|
||||
|
||||
List<String> arguments;
|
||||
|
||||
ReusableContext(List<String> arguments) {
|
||||
super();
|
||||
|
||||
ReusableContext (List<String> arguments) {
|
||||
super ();
|
||||
this.arguments = arguments;
|
||||
put(Log.logKey, ReusableLog.factory);
|
||||
put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory);
|
||||
put (Log.logKey, ReusableLog.factory);
|
||||
put (JavaCompiler.compilerKey, ReusableJavaCompiler.factory);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
drop(Arguments.argsKey);
|
||||
drop(DiagnosticListener.class);
|
||||
drop(Log.outKey);
|
||||
drop(Log.errKey);
|
||||
drop(JavaFileManager.class);
|
||||
drop(JavacTask.class);
|
||||
drop(JavacTrees.class);
|
||||
drop(JavacElements.class);
|
||||
|
||||
if (ht.get(Log.logKey) instanceof ReusableLog) {
|
||||
|
||||
void clear () {
|
||||
drop (Arguments.argsKey);
|
||||
drop (DiagnosticListener.class);
|
||||
drop (Log.outKey);
|
||||
drop (Log.errKey);
|
||||
drop (JavaFileManager.class);
|
||||
drop (JavacTask.class);
|
||||
drop (JavacTrees.class);
|
||||
drop (JavacElements.class);
|
||||
|
||||
if (ht.get (Log.logKey) instanceof ReusableLog) {
|
||||
// log already init-ed - not first round
|
||||
((ReusableLog) Log.instance(this)).clear();
|
||||
Enter.instance(this).newRound();
|
||||
((ReusableJavaCompiler) ReusableJavaCompiler.instance(this)).clear();
|
||||
Types.instance(this).newRound();
|
||||
Check.instance(this).newRound();
|
||||
Modules.instance(this).newRound();
|
||||
Annotate.instance(this).newRound();
|
||||
CompileStates.instance(this).clear();
|
||||
MultiTaskListener.instance(this).clear();
|
||||
((ReusableLog) Log.instance (this)).clear ();
|
||||
Enter.instance (this).newRound ();
|
||||
((ReusableJavaCompiler) ReusableJavaCompiler.instance (this)).clear ();
|
||||
Types.instance (this).newRound ();
|
||||
Check.instance (this).newRound ();
|
||||
Modules.instance (this).newRound ();
|
||||
Annotate.instance (this).newRound ();
|
||||
CompileStates.instance (this).clear ();
|
||||
MultiTaskListener.instance (this).clear ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@DefinedBy(Api.COMPILER_TREE)
|
||||
public void finished(TaskEvent e) {
|
||||
public void finished (TaskEvent e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@DefinedBy(Api.COMPILER_TREE)
|
||||
public void started(TaskEvent e) {
|
||||
public void started (TaskEvent e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
<T> void drop(Key<T> k) {
|
||||
ht.remove(k);
|
||||
|
||||
<T> void drop (Key<T> k) {
|
||||
ht.remove (k);
|
||||
}
|
||||
|
||||
<T> void drop(Class<T> c) {
|
||||
ht.remove(key(c));
|
||||
|
||||
<T> void drop (Class<T> c) {
|
||||
ht.remove (key (c));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with previous
|
||||
* compilations.
|
||||
*/
|
||||
static class ReusableJavaCompiler extends JavaCompiler {
|
||||
|
||||
|
||||
static final Factory<JavaCompiler> factory = ReusableJavaCompiler::new;
|
||||
|
||||
ReusableJavaCompiler(Context context) {
|
||||
super(context);
|
||||
|
||||
ReusableJavaCompiler (Context context) {
|
||||
super (context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
public void close () {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void clear() {
|
||||
newRound();
|
||||
|
||||
void clear () {
|
||||
newRound ();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void checkReusable() {
|
||||
protected void checkReusable () {
|
||||
// do nothing - it's ok to reuse the compiler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reusable Log; exposes a method to clean up the component from leftovers associated with previous
|
||||
* compilations.
|
||||
*/
|
||||
static class ReusableLog extends Log {
|
||||
|
||||
|
||||
static final Factory<Log> factory = ReusableLog::new;
|
||||
|
||||
|
||||
Context context;
|
||||
|
||||
ReusableLog(Context context) {
|
||||
super(context);
|
||||
|
||||
ReusableLog (Context context) {
|
||||
super (context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
recorded.clear();
|
||||
sourceMap.clear();
|
||||
|
||||
void clear () {
|
||||
recorded.clear ();
|
||||
sourceMap.clear ();
|
||||
nerrors = 0;
|
||||
nwarnings = 0;
|
||||
// Set a fake listener that will lazily lookup the context for the 'real' listener. Since
|
||||
// this field is never updated when a new task is created, we cannot simply reset the field
|
||||
// or keep old value. This is a hack to workaround the limitations in the current infrastructure.
|
||||
diagListener =
|
||||
new DiagnosticListener<JavaFileObject>() {
|
||||
new DiagnosticListener<JavaFileObject> () {
|
||||
DiagnosticListener<JavaFileObject> cachedListener;
|
||||
|
||||
|
||||
@Override
|
||||
@DefinedBy(Api.COMPILER)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
public void report (Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
if (cachedListener == null) {
|
||||
cachedListener = context.get(DiagnosticListener.class);
|
||||
cachedListener = context.get (DiagnosticListener.class);
|
||||
}
|
||||
cachedListener.report(diagnostic);
|
||||
cachedListener.report (diagnostic);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.api.ICodeActionProvider;
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.rewrite.AddException;
|
||||
import com.itsaky.lsp.java.rewrite.AddImport;
|
||||
import com.itsaky.lsp.java.rewrite.AddSuppressWarningAnnotation;
|
||||
import com.itsaky.lsp.java.rewrite.ConvertFieldToBlock;
|
||||
import com.itsaky.lsp.java.rewrite.ConvertVariableToStatement;
|
||||
import com.itsaky.lsp.java.rewrite.CreateMissingMethod;
|
||||
import com.itsaky.lsp.java.rewrite.GenerateRecordConstructor;
|
||||
import com.itsaky.lsp.java.rewrite.ImplementAbstractMethods;
|
||||
import com.itsaky.lsp.java.rewrite.OverrideInheritedMethod;
|
||||
import com.itsaky.lsp.java.rewrite.RemoveClass;
|
||||
import com.itsaky.lsp.java.rewrite.RemoveException;
|
||||
import com.itsaky.lsp.java.rewrite.RemoveMethod;
|
||||
import com.itsaky.lsp.java.rewrite.Rewrite;
|
||||
import com.itsaky.lsp.java.visitors.FindMethodDeclarationAt;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.CodeActionKind;
|
||||
import com.itsaky.lsp.models.CodeActionParams;
|
||||
import com.itsaky.lsp.models.CodeActionResult;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.DocumentChange;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
public class CodeActionProvider implements ICodeActionProvider {
|
||||
|
||||
private final CompilerProvider compiler;
|
||||
|
||||
public CodeActionProvider(CompilerProvider compiler) {
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CodeActionResult codeActions (@NonNull CodeActionParams params) {
|
||||
if (params.getDiagnostics ().isEmpty ()) {
|
||||
return codeActionsForCursor (params);
|
||||
} else {
|
||||
return new CodeActionResult (codeActionForDiagnostics (params));
|
||||
}
|
||||
}
|
||||
|
||||
public CodeActionResult codeActionsForCursor(CodeActionParams params) {
|
||||
final Path file = params.getFile ();
|
||||
LOG.info(String.format(Locale.getDefault (), "Find code actions at %s(%d)...", file.getFileName (), params.getRange().getStart().getLine() + 1));
|
||||
Instant started = Instant.now();
|
||||
// TODO this get-map / convert-to-CodeAction split is an ugly workaround of the fact that we need a new compile
|
||||
// task to generate the code actions
|
||||
// If we switch to resolving code actions asynchronously using Command, that will fix this problem.
|
||||
final TreeMap<String, Rewrite> rewrites = new TreeMap<> ();
|
||||
try (final CompileTask task = compiler.compile(file)) {
|
||||
long elapsed = Duration.between(started, Instant.now()).toMillis();
|
||||
LOG.info(String.format(Locale.getDefault (), "...compiled in %d ms", elapsed));
|
||||
final LineMap lines = task.root().getLineMap();
|
||||
final long cursor = lines.getPosition(params.getRange().getStart().getLine() + 1, params.getRange().getStart().getColumn () + 1);
|
||||
rewrites.putAll(overrideInheritedMethods(task, file, cursor));
|
||||
}
|
||||
|
||||
List<CodeActionItem> actions = new ArrayList<> ();
|
||||
for (String title : rewrites.keySet()) {
|
||||
// TODO are these all quick fixes?
|
||||
actions.addAll(createQuickFix(title, rewrites.get(title)));
|
||||
}
|
||||
long elapsed = Duration.between(started, Instant.now()).toMillis();
|
||||
LOG.info(String.format(Locale.getDefault (), "...created %d actions in %d ms", actions.size(), elapsed));
|
||||
return new CodeActionResult (actions);
|
||||
}
|
||||
|
||||
private Map<String, Rewrite> overrideInheritedMethods(CompileTask task, Path file, long cursor) {
|
||||
if (!isBlankLine(task.root(), cursor)) return Collections.emptyMap ();
|
||||
if (isInMethod(task, cursor)) return Collections.emptyMap ();
|
||||
|
||||
MethodTree methodTree = new FindMethodDeclarationAt(task.task).scan(task.root(), cursor);
|
||||
if (methodTree != null) return Collections.emptyMap ();
|
||||
|
||||
final TreeMap<String, Rewrite> actions = new TreeMap<> ();
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final ClassTree classTree = new FindTypeDeclarationAt(task.task).scan(task.root(), cursor);
|
||||
if (classTree == null) return Collections.emptyMap ();
|
||||
final TreePath classPath = trees.getPath(task.root(), classTree);
|
||||
final Elements elements = task.task.getElements();
|
||||
final TypeElement classElement = (TypeElement) trees.getElement(classPath);
|
||||
for (Element member : elements.getAllMembers(classElement)) {
|
||||
if (member.getModifiers().contains(Modifier.FINAL)) continue;
|
||||
if (member.getKind() != ElementKind.METHOD) continue;
|
||||
final ExecutableElement method = (ExecutableElement) member;
|
||||
final TypeElement methodSource = (TypeElement) member.getEnclosingElement();
|
||||
if (methodSource.getQualifiedName().contentEquals("java.lang.Object")) continue;
|
||||
if (methodSource.equals(classElement)) continue;
|
||||
final MethodPtr ptr = new MethodPtr (task.task, method);
|
||||
|
||||
final Rewrite rewrite =
|
||||
new OverrideInheritedMethod (
|
||||
ptr.className, ptr.methodName, ptr.erasedParameterTypes, file, (int) cursor);
|
||||
final String title = "Override '" + method.getSimpleName() + "' from " + ptr.className;
|
||||
|
||||
actions.put(title, rewrite);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
private boolean isInMethod(CompileTask task, long cursor) {
|
||||
MethodTree method = new FindMethodDeclarationAt(task.task).scan(task.root(), cursor);
|
||||
return method != null;
|
||||
}
|
||||
|
||||
private boolean isBlankLine(CompilationUnitTree root, long cursor) {
|
||||
LineMap lines = root.getLineMap();
|
||||
long line = lines.getLineNumber(cursor);
|
||||
long start = lines.getStartPosition(line);
|
||||
CharSequence contents;
|
||||
try {
|
||||
contents = root.getSourceFile().getCharContent(true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (long i = start; i < cursor; i++) {
|
||||
if (!Character.isWhitespace(contents.charAt((int) i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<CodeActionItem> codeActionForDiagnostics(CodeActionParams params) {
|
||||
LOG.info(String.format(Locale.getDefault (), "Check %d diagnostics for quick fixes...", params.getDiagnostics().size()));
|
||||
Instant started = Instant.now();
|
||||
Path file = params.getFile ();
|
||||
try (final CompileTask task = compiler.compile(file)) {
|
||||
List<CodeActionItem> actions = new ArrayList<>();
|
||||
for (DiagnosticItem d : params.getDiagnostics()) {
|
||||
List<CodeActionItem> newActions = codeActionForDiagnostic(task, file, d);
|
||||
actions.addAll(newActions);
|
||||
}
|
||||
long elapsed = Duration.between(started, Instant.now()).toMillis();
|
||||
LOG.info(String.format(Locale.getDefault (), "...created %d quick fixes in %d ms", actions.size(), elapsed));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CodeActionItem> codeActionForDiagnostic(CompileTask task, Path file, DiagnosticItem d) {
|
||||
// TODO this should be done asynchronously using executeCommand
|
||||
switch (d.getCode()) {
|
||||
case "unused_local":
|
||||
final Rewrite toStatement = new ConvertVariableToStatement (file, findPosition(task, d.getRange().getStart()));
|
||||
return createQuickFix("Convert to statement", toStatement);
|
||||
case "unused_field":
|
||||
final Rewrite toBlock = new ConvertFieldToBlock (file, findPosition(task, d.getRange().getStart()));
|
||||
return createQuickFix("Convert to block", toBlock);
|
||||
case "unused_class":
|
||||
final Rewrite removeClass = new RemoveClass (file, findPosition(task, d.getRange().getStart()));
|
||||
return createQuickFix("Remove class", removeClass);
|
||||
case "unused_method":
|
||||
final MethodPtr unusedMethod = findMethod(task, d.getRange());
|
||||
final Rewrite removeMethod = new RemoveMethod (unusedMethod.className, unusedMethod.methodName, unusedMethod.erasedParameterTypes);
|
||||
return createQuickFix("Remove method", removeMethod);
|
||||
case "unused_throws":
|
||||
final CharSequence shortExceptionName = extractRange(task, d.getRange());
|
||||
final String notThrown = extractNotThrownExceptionName(d.getMessage());
|
||||
final MethodPtr methodWithExtraThrow = findMethod(task, d.getRange());
|
||||
final Rewrite removeThrow =
|
||||
new RemoveException (
|
||||
methodWithExtraThrow.className,
|
||||
methodWithExtraThrow.methodName,
|
||||
methodWithExtraThrow.erasedParameterTypes,
|
||||
notThrown);
|
||||
return createQuickFix("Remove '" + shortExceptionName + "'", removeThrow);
|
||||
case "compiler.warn.unchecked.call.mbr.of.raw.type":
|
||||
final MethodPtr warnedMethod = findMethod(task, d.getRange());
|
||||
final Rewrite suppressWarning =
|
||||
new AddSuppressWarningAnnotation (
|
||||
warnedMethod.className, warnedMethod.methodName, warnedMethod.erasedParameterTypes);
|
||||
return createQuickFix("Suppress 'unchecked' warning", suppressWarning);
|
||||
case "compiler.err.unreported.exception.need.to.catch.or.throw":
|
||||
final MethodPtr needsThrow = findMethod(task, d.getRange());
|
||||
final String exceptionName = extractExceptionName(d.getMessage());
|
||||
final Rewrite addThrows =
|
||||
new AddException (
|
||||
needsThrow.className,
|
||||
needsThrow.methodName,
|
||||
needsThrow.erasedParameterTypes,
|
||||
exceptionName);
|
||||
return createQuickFix("Add 'throws'", addThrows);
|
||||
case "compiler.err.cant.resolve.location":
|
||||
CharSequence simpleName = extractRange(task, d.getRange());
|
||||
List<CodeActionItem> allImports = new ArrayList<>();
|
||||
for (String qualifiedName : compiler.publicTopLevelTypes()) {
|
||||
if (qualifiedName.endsWith("." + simpleName)) {
|
||||
String title = "Import '" + qualifiedName + "'";
|
||||
final Rewrite addImport = new AddImport (file, qualifiedName);
|
||||
allImports.addAll(createQuickFix(title, addImport));
|
||||
}
|
||||
}
|
||||
return allImports;
|
||||
case "compiler.err.var.not.initialized.in.default.constructor":
|
||||
final String needsConstructor = findClassNeedingConstructor(task, d.getRange());
|
||||
if (needsConstructor == null) return Collections.emptyList ();
|
||||
final Rewrite generateConstructor = new GenerateRecordConstructor (needsConstructor);
|
||||
return createQuickFix("Generate constructor", generateConstructor);
|
||||
case "compiler.err.does.not.override.abstract":
|
||||
final CompilationUnitTree root = task.root();
|
||||
final LineMap lines = root.getLineMap();
|
||||
final FindTypeDeclarationAt treeFinder = newClassFinder(task);
|
||||
final Range range = d.getRange();
|
||||
final long position = lines.getPosition(range.getStart().getLine() + 1, range.getStart().getColumn () + 1);
|
||||
final ClassTree tree = treeFinder.scan(root, position);
|
||||
final Rewrite implementAbstracts = new ImplementAbstractMethods (file, tree, treeFinder.getStoredTreePath());
|
||||
return createQuickFix("Implement abstract methods", implementAbstracts);
|
||||
case "compiler.err.cant.resolve.location.args":
|
||||
final Rewrite missingMethod = new CreateMissingMethod (file, findPosition(task, d.getRange().getStart()));
|
||||
return createQuickFix("Create missing method", missingMethod);
|
||||
default:
|
||||
return Collections.emptyList ();
|
||||
}
|
||||
}
|
||||
|
||||
private int findPosition(CompileTask task, Position position) {
|
||||
final LineMap lines = task.root().getLineMap();
|
||||
return (int) lines.getPosition(position.getLine() + 1, position.getColumn () + 1);
|
||||
}
|
||||
|
||||
private String findClassNeedingConstructor(CompileTask task, Range range) {
|
||||
final ClassTree type = findClassTree(task, range);
|
||||
if (type == null || hasConstructor(task, type)) return null;
|
||||
return qualifiedName(task, type);
|
||||
}
|
||||
|
||||
private String findClass(CompileTask task, Range range) {
|
||||
final ClassTree type = findClassTree(task, range);
|
||||
if (type == null) return null;
|
||||
return qualifiedName(task, type);
|
||||
}
|
||||
|
||||
private FindTypeDeclarationAt newClassFinder (CompileTask task) {
|
||||
return new FindTypeDeclarationAt(task.task);
|
||||
}
|
||||
|
||||
private ClassTree findClassTree(CompileTask task, Range range) {
|
||||
final long position = task.root().getLineMap().getPosition(range.getStart().getLine() + 1, range.getStart().getColumn () + 1);
|
||||
return newClassFinder(task).scan(task.root(), position);
|
||||
}
|
||||
|
||||
private String qualifiedName(CompileTask task, ClassTree tree) {
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final TreePath path = trees.getPath(task.root(), tree);
|
||||
final TypeElement type = (TypeElement) trees.getElement(path);
|
||||
return type.getQualifiedName().toString();
|
||||
}
|
||||
|
||||
private boolean hasConstructor(CompileTask task, ClassTree type) {
|
||||
for (Tree member : type.getMembers()) {
|
||||
if (member instanceof MethodTree) {
|
||||
MethodTree method = (MethodTree) member;
|
||||
if (isConstructor(task, method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isConstructor(CompileTask task, MethodTree method) {
|
||||
return method.getName().contentEquals("<init>") && !synthetic (task, method);
|
||||
}
|
||||
|
||||
private boolean synthetic (CompileTask task, MethodTree method) {
|
||||
return Trees.instance(task.task).getSourcePositions().getStartPosition(task.root(), method) != -1;
|
||||
}
|
||||
|
||||
private MethodPtr findMethod(CompileTask task, Range range) {
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final long position = task.root().getLineMap().getPosition(range.getStart().getLine() + 1, range.getStart().getColumn () + 1);
|
||||
final MethodTree tree = new FindMethodDeclarationAt (task.task).scan(task.root(), position);
|
||||
final TreePath path = trees.getPath(task.root(), tree);
|
||||
final ExecutableElement method = (ExecutableElement) trees.getElement(path);
|
||||
return new MethodPtr (task.task, method);
|
||||
}
|
||||
|
||||
static class MethodPtr {
|
||||
String className, methodName;
|
||||
String[] erasedParameterTypes;
|
||||
|
||||
MethodPtr(JavacTask task, ExecutableElement method) {
|
||||
final Types types = task.getTypes();
|
||||
final TypeElement parent = (TypeElement) method.getEnclosingElement();
|
||||
className = parent.getQualifiedName().toString();
|
||||
methodName = method.getSimpleName().toString();
|
||||
erasedParameterTypes = new String[method.getParameters().size()];
|
||||
for (int i = 0; i < erasedParameterTypes.length; i++) {
|
||||
final VariableElement param = method.getParameters().get(i);
|
||||
final TypeMirror type = param.asType();
|
||||
final TypeMirror erased = types.erasure(type);
|
||||
erasedParameterTypes[i] = erased.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern NOT_THROWN_EXCEPTION = Pattern.compile("^'((\\w+\\.)*\\w+)' is not thrown");
|
||||
|
||||
private String extractNotThrownExceptionName(String message) {
|
||||
final Matcher matcher = NOT_THROWN_EXCEPTION.matcher(message);
|
||||
if (!matcher.find()) {
|
||||
LOG.warn(String.format("`%s` doesn't match `%s`", message, NOT_THROWN_EXCEPTION));
|
||||
return "";
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
private static final Pattern UNREPORTED_EXCEPTION = Pattern.compile("unreported exception ((\\w+\\.)*\\w+)");
|
||||
|
||||
private String extractExceptionName(String message) {
|
||||
final Matcher matcher = UNREPORTED_EXCEPTION.matcher(message);
|
||||
if (!matcher.find()) {
|
||||
LOG.warn (String.format("`%s` doesn't match `%s`", message, UNREPORTED_EXCEPTION));
|
||||
return "";
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
private CharSequence extractRange(CompileTask task, Range range) {
|
||||
CharSequence contents;
|
||||
try {
|
||||
contents = task.root().getSourceFile().getCharContent(true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int start = (int) task.root().getLineMap().getPosition(range.getStart().getLine() + 1,
|
||||
range.getStart().getColumn () + 1);
|
||||
int end = (int) task.root().getLineMap().getPosition(range.getEnd().getLine() + 1,
|
||||
range.getEnd().getColumn () + 1);
|
||||
return contents.subSequence(start, end);
|
||||
}
|
||||
|
||||
private List<CodeActionItem> createQuickFix (String title, Rewrite rewrite) {
|
||||
final Map<Path, TextEdit[]> edits = rewrite.rewrite(compiler);
|
||||
|
||||
if (edits == Rewrite.CANCELLED) {
|
||||
return Collections.emptyList ();
|
||||
}
|
||||
|
||||
CodeActionItem action = new CodeActionItem ();
|
||||
action.setKind(CodeActionKind.QuickFix);
|
||||
action.setTitle(title);
|
||||
for (final Path file : edits.keySet()) {
|
||||
TextEdit[] textEdits = edits.get (file);
|
||||
if (textEdits == null) {
|
||||
continue;
|
||||
}
|
||||
final DocumentChange change = new DocumentChange ();
|
||||
change.setEdits (Arrays.asList (textEdits));
|
||||
action.getChanges ().add (change);
|
||||
}
|
||||
return Collections.singletonList (action);
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
}
|
||||
+28
-14
@@ -1,4 +1,23 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.providers;
|
||||
|
||||
import static com.itsaky.lsp.java.utils.EditHelper.repeatSpaces;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -14,6 +33,9 @@ import com.itsaky.lsp.java.StringSearch;
|
||||
import com.itsaky.lsp.java.rewrite.AddImport;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.utils.Extractors;
|
||||
import com.itsaky.lsp.java.utils.ScopeHelper;
|
||||
import com.itsaky.lsp.java.visitors.FindCompletionsAt;
|
||||
import com.itsaky.lsp.java.visitors.PruneMethodBodies;
|
||||
import com.itsaky.lsp.models.Command;
|
||||
import com.itsaky.lsp.models.CompletionData;
|
||||
import com.itsaky.lsp.models.CompletionItem;
|
||||
@@ -41,6 +63,7 @@ import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -63,7 +86,6 @@ import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.NoType;
|
||||
import javax.lang.model.type.PrimitiveType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.type.TypeVariable;
|
||||
import javax.lang.model.util.Types;
|
||||
@@ -100,7 +122,7 @@ public class JavaCompletionProvider implements ICompletionProvider {
|
||||
|
||||
this.completingFile = file;
|
||||
this.cursor = task.root.getLineMap().getPosition(line, column);
|
||||
StringBuilder contents = new PruneMethodBodies(task.task).scan(task.root, cursor);
|
||||
StringBuilder contents = new PruneMethodBodies (task.task).scan(task.root, cursor);
|
||||
int endOfLine = endOfLine(contents, (int) cursor);
|
||||
contents.insert(endOfLine, ';');
|
||||
CompletionResult list = compileAndComplete(file, contents.toString(), cursor);
|
||||
@@ -125,7 +147,7 @@ public class JavaCompletionProvider implements ICompletionProvider {
|
||||
boolean endsWithParen = endsWithParen(contents, (int) cursor);
|
||||
try (CompileTask task = compiler.compile(Collections.singletonList (source))) {
|
||||
LOG.info("...compiled in " + Duration.between(started, Instant.now()).toMillis() + "ms");
|
||||
TreePath path = new FindCompletionsAt(task.task).scan(task.root(), cursor);
|
||||
TreePath path = new FindCompletionsAt (task.task).scan(task.root(), cursor);
|
||||
switch (path.getLeaf().getKind()) {
|
||||
case IDENTIFIER:
|
||||
return completeIdentifier(task, path, partial, endsWithParen);
|
||||
@@ -595,7 +617,7 @@ public class JavaCompletionProvider implements ICompletionProvider {
|
||||
}
|
||||
|
||||
AddImport addImport = new AddImport(path, className);
|
||||
return Collections.singletonList (addImport.rewrite(compiler));
|
||||
return Arrays.asList (Objects.requireNonNull (addImport.rewrite (compiler).get (path)));
|
||||
}
|
||||
|
||||
private CompletionItem snippetItem(String label, String snippet) {
|
||||
@@ -697,7 +719,7 @@ public class JavaCompletionProvider implements ICompletionProvider {
|
||||
String insertText = built.toString ();
|
||||
insertText = insertText.replace ("\n", "\n" + repeatSpaces (indent));
|
||||
|
||||
// TODO Auto-import required classes.
|
||||
// TODO Auto-import required classes instead of specifying qualified names.
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel (built.name);
|
||||
item.setKind (CompletionItemKind.METHOD);
|
||||
@@ -728,14 +750,6 @@ public class JavaCompletionProvider implements ICompletionProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String repeatSpaces (int count) {
|
||||
StringBuilder result = new StringBuilder ();
|
||||
for (int i = 0; i < count; i++) {
|
||||
result.append (" ");
|
||||
}
|
||||
return result.toString ();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CompletionData data(CompileTask task, Element element, int overloads) {
|
||||
CompletionData data = new CompletionData();
|
||||
+29
-14
@@ -1,20 +1,38 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.api.ISignatureHelpProvider;
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.FindHelper;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.java.MarkdownHelper;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.utils.ScopeHelper;
|
||||
import com.itsaky.lsp.java.utils.ShortTypePrinter;
|
||||
import com.itsaky.lsp.java.visitors.FindInvocationAt;
|
||||
import com.itsaky.lsp.models.ParameterInformation;
|
||||
import com.itsaky.lsp.models.SignatureHelp;
|
||||
import com.itsaky.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.lsp.models.SignatureInformation;
|
||||
import com.sun.source.doctree.DocCommentTree;
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
@@ -23,14 +41,11 @@ import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewClassTree;
|
||||
import com.sun.source.tree.Scope;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.DocTrees;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.parser.DocCommentParser;
|
||||
import com.sun.tools.javac.parser.Parser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -76,12 +91,12 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
// TODO prune
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
long cursor = task.root().getLineMap().getPosition(line, column);
|
||||
TreePath path = new FindInvocationAt(task.task).scan(task.root(), cursor);
|
||||
TreePath path = new FindInvocationAt (task.task).scan(task.root(), cursor);
|
||||
if (path == null) return NOT_SUPPORTED;
|
||||
if (path.getLeaf() instanceof MethodInvocationTree) {
|
||||
MethodInvocationTree invoke = (MethodInvocationTree) path.getLeaf();
|
||||
List<ExecutableElement> overloads = methodOverloads(task, invoke);
|
||||
List<SignatureInformation> signatures = new ArrayList<SignatureInformation>();
|
||||
List<SignatureInformation> signatures = new ArrayList<> ();
|
||||
for (ExecutableElement method : overloads) {
|
||||
SignatureInformation info = info(method);
|
||||
addSourceInfo(task, method, info);
|
||||
@@ -95,7 +110,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
if (path.getLeaf() instanceof NewClassTree) {
|
||||
NewClassTree invoke = (NewClassTree) path.getLeaf();
|
||||
List<ExecutableElement> overloads = constructorOverloads(task, invoke);
|
||||
List<SignatureInformation> signatures = new ArrayList<SignatureInformation>();
|
||||
List<SignatureInformation> signatures = new ArrayList<> ();
|
||||
for (ExecutableElement method : overloads) {
|
||||
SignatureInformation info = info(method);
|
||||
addSourceInfo(task, method, info);
|
||||
@@ -127,7 +142,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
TreePath path = trees.getPath(task.root(), method);
|
||||
Scope scope = trees.getScope(path);
|
||||
List<ExecutableElement> list = new ArrayList<ExecutableElement>();
|
||||
List<ExecutableElement> list = new ArrayList<> ();
|
||||
Predicate<CharSequence> filter = name -> method.getName().contentEquals(name);
|
||||
// TODO add static imports
|
||||
for (Element member : ScopeHelper.scopeMembers(task, scope, filter)) {
|
||||
@@ -150,7 +165,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
return Collections.emptyList ();
|
||||
}
|
||||
|
||||
List<ExecutableElement> list = new ArrayList<ExecutableElement>();
|
||||
List<ExecutableElement> list = new ArrayList<> ();
|
||||
for (Element member : task.task.getElements().getAllMembers(type)) {
|
||||
if (member.getKind() != ElementKind.METHOD) continue;
|
||||
if (!member.getSimpleName().contentEquals(method.getIdentifier())) continue;
|
||||
@@ -179,7 +194,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
TreePath path = trees.getPath(task.root(), method.getIdentifier());
|
||||
Scope scope = trees.getScope(path);
|
||||
TypeElement type = (TypeElement) trees.getElement(path);
|
||||
List<ExecutableElement> list = new ArrayList<ExecutableElement>();
|
||||
List<ExecutableElement> list = new ArrayList<> ();
|
||||
for (Element member : task.task.getElements().getAllMembers(type)) {
|
||||
if (member.getKind() != ElementKind.CONSTRUCTOR) continue;
|
||||
if (!trees.isAccessible(scope, member, (DeclaredType) type.asType())) continue;
|
||||
@@ -201,7 +216,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
|
||||
@NonNull
|
||||
private List<ParameterInformation> parameters(@NonNull ExecutableElement method) {
|
||||
List<ParameterInformation> list = new ArrayList<ParameterInformation>();
|
||||
List<ParameterInformation> list = new ArrayList<> ();
|
||||
for (VariableElement p : method.getParameters()) {
|
||||
list.add(parameter(p));
|
||||
}
|
||||
@@ -249,7 +264,7 @@ public class SignatureProvider implements ISignatureHelpProvider {
|
||||
|
||||
@NonNull
|
||||
private List<ParameterInformation> parametersFromSource(MethodTree source) {
|
||||
List<ParameterInformation> list = new ArrayList<ParameterInformation>();
|
||||
List<ParameterInformation> list = new ArrayList<> ();
|
||||
for (VariableTree p : source.getParameters()) {
|
||||
ParameterInformation info = new ParameterInformation ();
|
||||
info.setLabel (p.getType () + " " + p.getName ());
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class AddException implements Rewrite {
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
final String exceptionType;
|
||||
|
||||
public AddException(String className, String methodName, String[] erasedParameterTypes, String exceptionType) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.erasedParameterTypes = erasedParameterTypes;
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
if (file == CompilerProvider.NOT_FOUND) {
|
||||
return CANCELLED;
|
||||
}
|
||||
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes);
|
||||
MethodTree methodTree = trees.getTree(methodElement);
|
||||
SourcePositions pos = trees.getSourcePositions();
|
||||
LineMap lines = task.root().getLineMap();
|
||||
long startBody = pos.getStartPosition(task.root(), methodTree.getBody());
|
||||
int line = (int) lines.getLineNumber(startBody);
|
||||
int column = (int) lines.getColumnNumber(startBody);
|
||||
Position insertPos = new Position(line - 1, column - 1);
|
||||
String simpleName = exceptionType;
|
||||
int lastDot = simpleName.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
simpleName = exceptionType.substring(lastDot + 1);
|
||||
}
|
||||
|
||||
String insertText;
|
||||
if (methodTree.getThrows().isEmpty()) {
|
||||
insertText = "throws " + simpleName + " ";
|
||||
} else {
|
||||
insertText = ", " + simpleName + " ";
|
||||
}
|
||||
TextEdit insertThrows = new TextEdit(new Range (insertPos, insertPos), insertText);
|
||||
// TODO add import if needed
|
||||
TextEdit[] edits = {insertThrows};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,9 @@ import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AddImport implements Rewrite {
|
||||
final Path file;
|
||||
@@ -40,11 +42,11 @@ public class AddImport implements Rewrite {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextEdit rewrite(CompilerProvider compiler) {
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
final ParseTask task = compiler.parse(file);
|
||||
Position point = insertPosition(task);
|
||||
String text = "import " + className + ";\n";
|
||||
return new TextEdit(new Range (point, point), text);
|
||||
return Collections.singletonMap (file, new TextEdit[]{new TextEdit(new Range (point, point), text)});
|
||||
}
|
||||
|
||||
private Position insertPosition(ParseTask task) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class AddSuppressWarningAnnotation implements Rewrite {
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
|
||||
public AddSuppressWarningAnnotation(String className, String methodName, String[] erasedParameterTypes) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.erasedParameterTypes = erasedParameterTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
if (file == CompilerProvider.NOT_FOUND) {
|
||||
return CANCELLED;
|
||||
}
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes);
|
||||
MethodTree methodTree = trees.getTree(methodElement);
|
||||
SourcePositions pos = trees.getSourcePositions();
|
||||
int startMethod = (int) pos.getStartPosition(task.root(), methodTree);
|
||||
LineMap lines = task.root().getLineMap();
|
||||
int line = (int) lines.getLineNumber(startMethod);
|
||||
int column = (int) lines.getColumnNumber(startMethod);
|
||||
int startLine = (int) lines.getStartPosition(line);
|
||||
String indent = EditHelper.repeatSpaces (startMethod - startLine);
|
||||
String insertText = "@SuppressWarnings(\"unchecked\")\n" + indent;
|
||||
Position insertPoint = new Position(line - 1, column - 1);
|
||||
TextEdit insert = new TextEdit(new Range(insertPoint, insertPoint), insertText);
|
||||
TextEdit[] edits = {insert};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
public class ConvertFieldToBlock implements Rewrite {
|
||||
final Path file;
|
||||
final int position;
|
||||
|
||||
public ConvertFieldToBlock(Path file, int position) {
|
||||
this.file = file;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
ParseTask task = compiler.parse(file);
|
||||
Trees trees = Trees.instance(task.task);
|
||||
SourcePositions pos = trees.getSourcePositions();
|
||||
LineMap lines = task.root.getLineMap();
|
||||
VariableTree variable = ConvertVariableToStatement.findVariable(task, position);
|
||||
if (variable == null) {
|
||||
return CANCELLED;
|
||||
}
|
||||
ExpressionTree expression = variable.getInitializer();
|
||||
if (!ConvertVariableToStatement.isExpressionStatement(expression)) {
|
||||
return CANCELLED;
|
||||
}
|
||||
long start = pos.getStartPosition(task.root, variable);
|
||||
long end = pos.getStartPosition(task.root, expression);
|
||||
int startLine = (int) lines.getLineNumber(start);
|
||||
int startColumn = (int) lines.getColumnNumber(start);
|
||||
Position startPos = new Position (startLine - 1, startColumn - 1);
|
||||
int endLine = (int) lines.getLineNumber(end);
|
||||
int endColumn = (int) lines.getColumnNumber(end);
|
||||
Position endPos = new Position(endLine - 1, endColumn - 1);
|
||||
Range deleteLhs = new Range(startPos, endPos);
|
||||
TextEdit fixLhs = new TextEdit(deleteLhs, "{ ");
|
||||
if (variable.getModifiers().getFlags().contains(Modifier.STATIC)) {
|
||||
fixLhs.setNewText("static { ");
|
||||
}
|
||||
long right = pos.getEndPosition(task.root, variable);
|
||||
int rightLine = (int) lines.getLineNumber(right);
|
||||
int rightColumn = (int) lines.getColumnNumber(right);
|
||||
Position rightPos = new Position(rightLine - 1, rightColumn - 1);
|
||||
Range insertRight = new Range(rightPos, rightPos);
|
||||
TextEdit fixRhs = new TextEdit(insertRight, " }");
|
||||
TextEdit[] edits = {fixLhs, fixRhs};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.visitors.FindVariableAt;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class ConvertVariableToStatement implements Rewrite {
|
||||
final Path file;
|
||||
final int position;
|
||||
|
||||
public ConvertVariableToStatement(Path file, int position) {
|
||||
this.file = file;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
final ParseTask task = compiler.parse(file);
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final SourcePositions pos = trees.getSourcePositions();
|
||||
final LineMap lines = task.root.getLineMap();
|
||||
final VariableTree variable = findVariable(task, position);
|
||||
if (variable == null) {
|
||||
return CANCELLED;
|
||||
}
|
||||
ExpressionTree expression = variable.getInitializer();
|
||||
if (expression == null) {
|
||||
return CANCELLED;
|
||||
}
|
||||
if (!isExpressionStatement(expression)) {
|
||||
return CANCELLED;
|
||||
}
|
||||
long start = pos.getStartPosition(task.root, variable);
|
||||
long end = pos.getStartPosition(task.root, expression);
|
||||
int startLine = (int) lines.getLineNumber(start);
|
||||
int startColumn = (int) lines.getColumnNumber(start);
|
||||
Position startPos = new Position(startLine - 1, startColumn - 1);
|
||||
int endLine = (int) lines.getLineNumber(end);
|
||||
int endColumn = (int) lines.getColumnNumber(end);
|
||||
Position endPos = new Position(endLine - 1, endColumn - 1);
|
||||
Range delete = new Range(startPos, endPos);
|
||||
TextEdit edit = new TextEdit(delete, "");
|
||||
TextEdit[] edits = {edit};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
|
||||
static VariableTree findVariable(ParseTask task, int position) {
|
||||
return new FindVariableAt (task.task).scan(task.root, position);
|
||||
}
|
||||
|
||||
/** https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.8 */
|
||||
static boolean isExpressionStatement(Tree t) {
|
||||
if(t == null) return false;
|
||||
switch (t.getKind()) {
|
||||
case ASSIGNMENT:
|
||||
case PREFIX_INCREMENT:
|
||||
case PREFIX_DECREMENT:
|
||||
case POSTFIX_INCREMENT:
|
||||
case POSTFIX_DECREMENT:
|
||||
case METHOD_INVOCATION:
|
||||
case NEW_CLASS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.visitors.FindMethodCallAt;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class CreateMissingMethod implements Rewrite {
|
||||
final Path file;
|
||||
final int position;
|
||||
|
||||
int argCount = -1;
|
||||
|
||||
public CreateMissingMethod(Path file, int position) {
|
||||
this.file = file;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final FindMethodCallAt methodFinder = new FindMethodCallAt (task.task);
|
||||
final MethodInvocationTree call = methodFinder.scan(task.root(), position);
|
||||
if (call == null) {
|
||||
return CANCELLED;
|
||||
}
|
||||
|
||||
final TreePath path = trees.getPath(task.root(), call);
|
||||
final String returnType = methodFinder.getReturnType();
|
||||
Path sourceFile = file;
|
||||
MethodTree currentMethod = surroundingMethod(path);
|
||||
String insertText = "\n";
|
||||
|
||||
insertText += printMethodHeader(task, call, returnType, methodFinder.isMemberSelect(), (currentMethod.getModifiers().getFlags().contains(Modifier.STATIC) || methodFinder.isStaticAccess())) + " {\n" +
|
||||
" // TODO: Implement this method\n" +
|
||||
" " + createReturnStatement(returnType) + "\n" +
|
||||
"}";
|
||||
|
||||
TextEdit[] edits;
|
||||
if(methodFinder.isMemberSelect()) {
|
||||
// Accessing method from another class
|
||||
final CompilationUnitTree compilationUnit = methodFinder.getEnclosingTreePath().getCompilationUnit();
|
||||
final ClassTree enclosingClass = methodFinder.getEnclosingClass();
|
||||
final int indent = EditHelper.indent(task.task, compilationUnit, enclosingClass) + 4;
|
||||
insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent));
|
||||
insertText = insertText + "\n";
|
||||
final Position insertPoint = EditHelper.insertAtEndOfClass(task.task, compilationUnit, enclosingClass);
|
||||
edits = new TextEdit[]{new TextEdit(new Range (insertPoint, insertPoint), insertText)};
|
||||
sourceFile = Paths.get (compilationUnit.getSourceFile ().toUri ());
|
||||
} else {
|
||||
ClassTree surroundingClass = surroundingClass(path);
|
||||
int indent = EditHelper.indent(task.task, task.root(), surroundingClass) + 4;
|
||||
insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent));
|
||||
insertText = insertText + "\n";
|
||||
Position insertPoint = EditHelper.insertAfter(task.task, task.root(), surroundingMethod(path));
|
||||
edits = new TextEdit[]{new TextEdit(new Range(insertPoint, insertPoint), insertText)};
|
||||
}
|
||||
|
||||
if(file != null)
|
||||
return Collections.singletonMap (sourceFile, edits);
|
||||
else return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String createReturnStatement(String returnType) {
|
||||
if(returnType == null) return "";
|
||||
String value;
|
||||
switch (returnType) {
|
||||
case "int" :
|
||||
case "byte" :
|
||||
case "short" :
|
||||
case "long" :
|
||||
case "char" :
|
||||
value = "0";
|
||||
break;
|
||||
case "float" :
|
||||
value = "0f";
|
||||
break;
|
||||
case "double" :
|
||||
value = "0.0";
|
||||
break;
|
||||
case "boolean" :
|
||||
value = "false";
|
||||
break;
|
||||
|
||||
// Finding type of variable declaration may result in an error
|
||||
// We should then simply return empty return type
|
||||
case "(ERROR)" :
|
||||
return ""; // Directly return empty string
|
||||
default :
|
||||
value = "null";
|
||||
break;
|
||||
|
||||
}
|
||||
return String.format("return %s;", value);
|
||||
}
|
||||
|
||||
private ClassTree surroundingClass(TreePath call) {
|
||||
while (call != null) {
|
||||
if (call.getLeaf() instanceof ClassTree) {
|
||||
return (ClassTree) call.getLeaf();
|
||||
}
|
||||
call = call.getParentPath();
|
||||
}
|
||||
throw new RuntimeException("No surrounding class");
|
||||
}
|
||||
|
||||
private MethodTree surroundingMethod(TreePath call) {
|
||||
while (call != null) {
|
||||
if (call.getLeaf() instanceof MethodTree) {
|
||||
return (MethodTree) call.getLeaf();
|
||||
}
|
||||
call = call.getParentPath();
|
||||
}
|
||||
throw new RuntimeException("No surrounding method");
|
||||
}
|
||||
|
||||
private String printMethodHeader(CompileTask task, MethodInvocationTree call, String type, boolean isMemeberSelect, boolean isStatic) {
|
||||
String methodName = extractMethodName(call.getMethodSelect());
|
||||
String returnType = type == null || "(ERROR)".equals(type) ? "void" : type;
|
||||
LOG.info("Creating missing method with return type: " + returnType);
|
||||
if (returnType.equals(methodName)) {
|
||||
returnType = "_";
|
||||
}
|
||||
String parameters = printParameters(task, call);
|
||||
String modifiers = isMemeberSelect ? "public" : "private";
|
||||
if(isStatic)
|
||||
modifiers += " static";
|
||||
return modifiers + " " + returnType + " " + methodName + "(" + parameters + ")";
|
||||
}
|
||||
|
||||
private String printParameters(CompileTask task, MethodInvocationTree call) {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
StringJoiner join = new StringJoiner(", ");
|
||||
for (int i = 0; i < call.getArguments().size(); i++) {
|
||||
TypeMirror type = trees.getTypeMirror(trees.getPath(task.root(), call.getArguments().get(i)));
|
||||
String name = guessParameterName(call.getArguments().get(i), type);
|
||||
String argType = EditHelper.printType(type);
|
||||
join.add(String.format("final %s %s", argType, name));
|
||||
}
|
||||
return join.toString();
|
||||
}
|
||||
|
||||
private String extractMethodName(ExpressionTree method) {
|
||||
if (method instanceof IdentifierTree) {
|
||||
IdentifierTree id = (IdentifierTree) method;
|
||||
return id.getName().toString();
|
||||
} else if (method instanceof MemberSelectTree) {
|
||||
MemberSelectTree select = (MemberSelectTree) method;
|
||||
return select.getIdentifier().toString();
|
||||
} else {
|
||||
return "extractedMethod";
|
||||
}
|
||||
}
|
||||
|
||||
private String guessParameterName(Tree argument, TypeMirror type) {
|
||||
String fromTree = guessParameterNameFromTree(argument);
|
||||
if (!fromTree.isEmpty()) {
|
||||
return fromTree;
|
||||
}
|
||||
|
||||
String fromType = guessParameterNameFromType(type);
|
||||
if (!fromType.isEmpty()) {
|
||||
return fromType;
|
||||
}
|
||||
|
||||
argCount++;
|
||||
return "param" + argCount;
|
||||
}
|
||||
|
||||
private String guessParameterNameFromTree(Tree argument) {
|
||||
if (argument instanceof IdentifierTree) {
|
||||
IdentifierTree id = (IdentifierTree) argument;
|
||||
return id.getName().toString();
|
||||
} else if (argument instanceof MemberSelectTree) {
|
||||
MemberSelectTree select = (MemberSelectTree) argument;
|
||||
return select.getIdentifier().toString();
|
||||
} else if (argument instanceof MemberReferenceTree) {
|
||||
MemberReferenceTree reference = (MemberReferenceTree) argument;
|
||||
return reference.getName().toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String guessParameterNameFromType(TypeMirror type) {
|
||||
if (type instanceof DeclaredType) {
|
||||
DeclaredType declared = (DeclaredType) type;
|
||||
Name name = declared.asElement().getSimpleName();
|
||||
return "" + Character.toLowerCase(name.charAt(0)) + name.subSequence(1, name.length());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
public class GenerateRecordConstructor implements Rewrite {
|
||||
final String className;
|
||||
|
||||
public GenerateRecordConstructor(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
LOG.info("Generate default constructor for " + className + "...");
|
||||
// TODO this needs to fall back on looking for inner classes and package-private classes
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
TypeElement typeElement = task.task.getElements().getTypeElement(className);
|
||||
ClassTree typeTree = Trees.instance(task.task).getTree(typeElement);
|
||||
List<VariableTree> fields = fieldsNeedingInitialization(typeTree);
|
||||
String parameters = generateParameters(task, fields);
|
||||
String initializers = generateInitializers(fields);
|
||||
StringBuilder buf = new StringBuilder ();
|
||||
buf.append("\n");
|
||||
if (typeTree.getModifiers().getFlags().contains(Modifier.PUBLIC)) {
|
||||
buf.append("public ");
|
||||
}
|
||||
|
||||
buf.append(simpleName(className))
|
||||
.append("(")
|
||||
.append(parameters)
|
||||
.append(") {\n ")
|
||||
.append(initializers)
|
||||
.append("\n}");
|
||||
String string = buf.toString();
|
||||
int indent = EditHelper.indent(task.task, task.root(), typeTree) + 4;
|
||||
string = string.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent));
|
||||
string = string + "\n\n";
|
||||
Position insert = insertPoint(task, typeTree);
|
||||
TextEdit[] edits = {new TextEdit(new Range (insert, insert), string)};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
|
||||
private List<VariableTree> fieldsNeedingInitialization(ClassTree typeTree) {
|
||||
List<VariableTree> fields = new ArrayList<> ();
|
||||
for (Tree member : typeTree.getMembers()) {
|
||||
if (!(member instanceof VariableTree)) continue;
|
||||
VariableTree field = (VariableTree) member;
|
||||
if (field.getInitializer() != null) continue;
|
||||
Set<Modifier> flags = field.getModifiers().getFlags();
|
||||
if (flags.contains(Modifier.STATIC)) continue;
|
||||
if (!flags.contains(Modifier.FINAL)) continue;
|
||||
fields.add(field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private String generateParameters(CompileTask task, List<VariableTree> fields) {
|
||||
StringJoiner join = new StringJoiner(", ");
|
||||
for (VariableTree f : fields) {
|
||||
join.add(extract(task, f.getType()) + " " + f.getName());
|
||||
}
|
||||
return join.toString();
|
||||
}
|
||||
|
||||
private String generateInitializers (List<VariableTree> fields) {
|
||||
StringJoiner join = new StringJoiner("\n ");
|
||||
for (VariableTree f : fields) {
|
||||
join.add("this." + f.getName() + " = " + f.getName() + ";");
|
||||
}
|
||||
return join.toString();
|
||||
}
|
||||
|
||||
private CharSequence extract(CompileTask task, Tree typeTree) {
|
||||
try {
|
||||
CharSequence contents = task.root().getSourceFile().getCharContent(true);
|
||||
SourcePositions pos = Trees.instance(task.task).getSourcePositions();
|
||||
int start = (int) pos.getStartPosition(task.root(), typeTree);
|
||||
int end = (int) pos.getEndPosition(task.root(), typeTree);
|
||||
return contents.subSequence(start, end);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String simpleName(String className) {
|
||||
int dot = className.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
return className.substring(dot + 1);
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
private Position insertPoint(CompileTask task, ClassTree typeTree) {
|
||||
for (Tree member : typeTree.getMembers()) {
|
||||
if (member.getKind() == Tree.Kind.METHOD) {
|
||||
MethodTree method = (MethodTree) member;
|
||||
if (method.getReturnType() == null) continue;
|
||||
LOG.info("...insert constructor before " + method.getName());
|
||||
return EditHelper.insertBefore(task.task, task.root(), method);
|
||||
}
|
||||
}
|
||||
LOG.info("...insert constructor at end of class");
|
||||
return EditHelper.insertAtEndOfClass(task.task, task.root(), typeTree);
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
public class ImplementAbstractMethods implements Rewrite {
|
||||
final Path file;
|
||||
final ClassTree tree;
|
||||
final TreePath path;
|
||||
|
||||
public ImplementAbstractMethods(final Path file, final ClassTree tree, final TreePath path) {
|
||||
Objects.requireNonNull(file);
|
||||
Objects.requireNonNull(tree);
|
||||
Objects.requireNonNull(path);
|
||||
|
||||
this.file = file;
|
||||
this.tree = tree;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
StringJoiner insertText = new StringJoiner ("\n");
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
Elements elements = task.task.getElements();
|
||||
Types types = task.task.getTypes();
|
||||
Trees trees = Trees.instance(task.task);
|
||||
TypeElement thisClass = (TypeElement) trees.getElement(this.path);
|
||||
DeclaredType thisType = (DeclaredType) thisClass.asType();
|
||||
ClassTree thisTree = trees.getTree(thisClass);
|
||||
int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4;
|
||||
for (Element member : elements.getAllMembers(thisClass)) {
|
||||
if (member.getKind() == ElementKind.METHOD && member.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ExecutableElement method = (ExecutableElement) member;
|
||||
MethodTree source = findSource(compiler, task, method);
|
||||
if (source == null) {
|
||||
LOG.warn("...couldn't find source for " + method);
|
||||
}
|
||||
ExecutableType parameterizedType = (ExecutableType) types.asMemberOf(thisType, method);
|
||||
String text = EditHelper.printMethod(method, parameterizedType, source);
|
||||
text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent));
|
||||
insertText.add(text);
|
||||
}
|
||||
}
|
||||
Position insert = EditHelper.insertAtEndOfClass(task.task, task.root(), thisTree);
|
||||
TextEdit[] edits = {new TextEdit(new Range (insert, insert), insertText + "\n")};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
|
||||
private MethodTree findSource(CompilerProvider compiler, CompileTask task, ExecutableElement method) {
|
||||
TypeElement superClass = (TypeElement) method.getEnclosingElement();
|
||||
String superClassName = superClass.getQualifiedName().toString();
|
||||
String methodName = method.getSimpleName().toString();
|
||||
String[] erasedParameterTypes = FindHelper.erasedParameterTypes(task, method);
|
||||
Optional<JavaFileObject> sourceFile = compiler.findAnywhere(superClassName);
|
||||
if (!sourceFile.isPresent ()) {
|
||||
return null;
|
||||
}
|
||||
ParseTask parse = compiler.parse(sourceFile.get());
|
||||
return FindHelper.findMethod(parse, superClassName, methodName, erasedParameterTypes);
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
public class OverrideInheritedMethod implements Rewrite {
|
||||
final String superClassName, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
final Path file;
|
||||
final int insertPosition;
|
||||
|
||||
public OverrideInheritedMethod(
|
||||
String superClassName, String methodName, String[] erasedParameterTypes, Path file, int insertPosition) {
|
||||
this.superClassName = superClassName;
|
||||
this.methodName = methodName;
|
||||
this.erasedParameterTypes = erasedParameterTypes;
|
||||
this.file = file;
|
||||
this.insertPosition = insertPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
Position insertPoint = insertNearCursor(compiler);
|
||||
String insertText = insertText(compiler);
|
||||
TextEdit[] edits = {new TextEdit(new Range (insertPoint, insertPoint), insertText)};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
|
||||
private String insertText(CompilerProvider compiler) {
|
||||
try (final CompileTask task = compiler.compile(file)) {
|
||||
final Types types = task.task.getTypes();
|
||||
final Trees trees = Trees.instance(task.task);
|
||||
final ExecutableElement superMethod = FindHelper.findMethod(task, superClassName, methodName, erasedParameterTypes);
|
||||
final ClassTree thisTree = new FindTypeDeclarationAt (task.task).scan(task.root(), (long) insertPosition);
|
||||
final TreePath thisPath = trees.getPath(task.root(), thisTree);
|
||||
final TypeElement thisClass = (TypeElement) trees.getElement(thisPath);
|
||||
final ExecutableType parameterizedType = (ExecutableType) types.asMemberOf((DeclaredType) thisClass.asType(), superMethod);
|
||||
final int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4;
|
||||
final Optional<JavaFileObject> sourceFile = compiler.findAnywhere(superClassName);
|
||||
if (!sourceFile.isPresent ()) return "";
|
||||
final ParseTask parse = compiler.parse(sourceFile.get());
|
||||
final MethodTree source = FindHelper.findMethod(parse, superClassName, methodName, erasedParameterTypes);
|
||||
String text = EditHelper.printMethod(superMethod, parameterizedType, source);
|
||||
text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent));
|
||||
text = text + "\n\n";
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
private Position insertNearCursor(CompilerProvider compiler) {
|
||||
final ParseTask task = compiler.parse(file);
|
||||
final ClassTree parent = new FindTypeDeclarationAt(task.task).scan(task.root, (long) insertPosition);
|
||||
final Position next = nextMember(task, parent);
|
||||
if (next != Position.NONE) {
|
||||
return next;
|
||||
}
|
||||
return EditHelper.insertAtEndOfClass(task.task, task.root, parent);
|
||||
}
|
||||
|
||||
private Position nextMember(ParseTask task, ClassTree parent) {
|
||||
final SourcePositions pos = Trees.instance(task.task).getSourcePositions();
|
||||
for (Tree member : parent.getMembers()) {
|
||||
long start = pos.getStartPosition(task.root, member);
|
||||
if (start > insertPosition) {
|
||||
int line = (int) task.root.getLineMap().getLineNumber(start);
|
||||
return new Position(line - 1, 0);
|
||||
}
|
||||
}
|
||||
return Position.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class RemoveClass implements Rewrite {
|
||||
final Path file;
|
||||
final int position;
|
||||
|
||||
public RemoveClass(Path file, int position) {
|
||||
this.file = file;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
ParseTask task = compiler.parse(file);
|
||||
final ClassTree type = new FindTypeDeclarationAt (task.task).scan(task.root, (long) position);
|
||||
TextEdit[] edits = {EditHelper.removeTree(task.task, task.root, type)};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
|
||||
public class RemoveException implements Rewrite {
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
final String exceptionType;
|
||||
|
||||
public RemoveException(String className, String methodName, String[] erasedParameterTypes, String exceptionType) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.erasedParameterTypes = erasedParameterTypes;
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes);
|
||||
MethodTree methodTree = Trees.instance(task.task).getTree(methodElement);
|
||||
if (methodTree.getThrows().size() == 1) {
|
||||
TextEdit delete = removeEntireThrows(task.task, task.root(), methodTree);
|
||||
if (delete == TextEdit.NONE) {
|
||||
return CANCELLED;
|
||||
}
|
||||
|
||||
TextEdit[] edits = {delete};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
TextEdit[] edits = {removeSingleException(task.task, task.root(), methodTree)};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern THROWS = Pattern.compile("\\s*\\bthrows\\b");
|
||||
|
||||
private TextEdit removeEntireThrows(JavacTask task, CompilationUnitTree root, MethodTree method) {
|
||||
Trees trees = Trees.instance(task);
|
||||
SourcePositions pos = trees.getSourcePositions();
|
||||
int startMethod = (int) pos.getStartPosition(root, method);
|
||||
CharSequence contents;
|
||||
try {
|
||||
contents = root.getSourceFile().getCharContent(true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Matcher matcher = THROWS.matcher(contents);
|
||||
if (!matcher.find(startMethod)) {
|
||||
return TextEdit.NONE;
|
||||
}
|
||||
|
||||
LineMap lines = root.getLineMap();
|
||||
int start = matcher.start();
|
||||
int startLine = (int) lines.getLineNumber(start);
|
||||
int startColumn = (int) lines.getColumnNumber(start);
|
||||
Position startPos = new Position(startLine - 1, startColumn - 1);
|
||||
ExpressionTree lastException = method.getThrows().get(method.getThrows().size() - 1);
|
||||
int end = (int) pos.getEndPosition(root, lastException);
|
||||
int endLine = (int) lines.getLineNumber(end);
|
||||
int endColumn = (int) lines.getColumnNumber(end);
|
||||
Position endPos = new Position(endLine - 1, endColumn - 1);
|
||||
return new TextEdit(new Range (startPos, endPos), "");
|
||||
}
|
||||
|
||||
private TextEdit removeSingleException(JavacTask task, CompilationUnitTree root, MethodTree method) {
|
||||
int i = findNamedException(task, root, method);
|
||||
if (i == -1) {
|
||||
return TextEdit.NONE;
|
||||
}
|
||||
|
||||
Trees trees = Trees.instance(task);
|
||||
SourcePositions pos = trees.getSourcePositions();
|
||||
ExpressionTree exn = method.getThrows().get(i);
|
||||
long start = pos.getStartPosition(root, exn);
|
||||
long end = pos.getEndPosition(root, exn);
|
||||
if (i == 0) {
|
||||
end = removeTrailingComma(root, end);
|
||||
} else {
|
||||
start = removeLeadingComma(root, start);
|
||||
}
|
||||
|
||||
LineMap lines = root.getLineMap();
|
||||
int startLine = (int) lines.getLineNumber(start);
|
||||
int startColumn = (int) lines.getColumnNumber(start);
|
||||
Position startPos = new Position(startLine - 1, startColumn - 1);
|
||||
int endLine = (int) lines.getLineNumber(end);
|
||||
int endColumn = (int) lines.getColumnNumber(end);
|
||||
Position endPos = new Position(endLine - 1, endColumn - 1);
|
||||
return new TextEdit(new Range(startPos, endPos), "");
|
||||
}
|
||||
|
||||
private int findNamedException(JavacTask task, CompilationUnitTree root, MethodTree method) {
|
||||
Trees trees = Trees.instance(task);
|
||||
for (int i = 0; i < method.getThrows().size(); i++) {
|
||||
ExpressionTree e = method.getThrows().get(i);
|
||||
TreePath path = trees.getPath(root, e);
|
||||
DeclaredType type = (DeclaredType) trees.getTypeMirror(path);
|
||||
TypeElement el = (TypeElement) type.asElement();
|
||||
if (el.getQualifiedName().contentEquals(exceptionType)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int removeLeadingComma(CompilationUnitTree root, long start) {
|
||||
CharSequence contents = contents(root);
|
||||
for (int i = (int) start; i > 0; i--) {
|
||||
if (contents.charAt(i) == ',') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int removeTrailingComma(CompilationUnitTree root, long end) {
|
||||
CharSequence contents = contents(root);
|
||||
for (int i = (int) end; i < contents.length(); i++) {
|
||||
if (contents.charAt(i) == ',') {
|
||||
if (contents.charAt(i + 1) == ' ') {
|
||||
return i + 2;
|
||||
} else {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private CharSequence contents(CompilationUnitTree root) {
|
||||
try {
|
||||
return root.getSourceFile().getCharContent(true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class RemoveMethod implements Rewrite {
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
|
||||
public RemoveMethod(String className, String methodName, String[] erasedParameterTypes) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.erasedParameterTypes = erasedParameterTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
if (file == CompilerProvider.NOT_FOUND) {
|
||||
return CANCELLED;
|
||||
}
|
||||
try (CompileTask task = compiler.compile(file)) {
|
||||
ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes);
|
||||
MethodTree methodTree = Trees.instance(task.task).getTree(methodElement);
|
||||
TextEdit[] edits = {EditHelper.removeTree(task.task, task.root(), methodTree)};
|
||||
return Collections.singletonMap (file, edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,15 @@ import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public interface Rewrite {
|
||||
/** Perform a rewrite across the entire codebase. */
|
||||
TextEdit rewrite(CompilerProvider compiler);
|
||||
Map<Path, TextEdit[]> rewrite(CompilerProvider compiler);
|
||||
/** CANCELLED signals that the rewrite couldn't be completed. */
|
||||
TextEdit CANCELLED = new TextEdit (Range.NONE, "");
|
||||
Map<Path, TextEdit[]> CANCELLED = Collections.emptyMap ();
|
||||
|
||||
Rewrite NOT_SUPPORTED = new RewriteNotSupported();
|
||||
}
|
||||
@@ -20,10 +20,13 @@ package com.itsaky.lsp.java.rewrite;
|
||||
import com.itsaky.lsp.java.CompilerProvider;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
class RewriteNotSupported implements Rewrite {
|
||||
|
||||
@Override
|
||||
public TextEdit rewrite(CompilerProvider compiler) {
|
||||
public Map<Path, TextEdit[]> rewrite(CompilerProvider compiler) {
|
||||
return CANCELLED;
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,8 @@ import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class EditHelper {
|
||||
final JavacTask task;
|
||||
|
||||
public EditHelper(JavacTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
TextEdit removeTree(final CompilationUnitTree root, final Tree remove) {
|
||||
public static TextEdit removeTree(final JavacTask task, final CompilationUnitTree root, final Tree remove) {
|
||||
SourcePositions pos = Trees.instance(task).getSourcePositions();
|
||||
LineMap lines = root.getLineMap();
|
||||
long start = pos.getStartPosition(root, remove);
|
||||
@@ -166,4 +161,12 @@ public class EditHelper {
|
||||
int column = (int) lines.getColumnNumber(end);
|
||||
return new Position(line - 1, column - 2);
|
||||
}
|
||||
|
||||
public static String repeatSpaces (int count) {
|
||||
StringBuilder result = new StringBuilder ();
|
||||
for (int i = 0; i < count; i++) {
|
||||
result.append (" ");
|
||||
}
|
||||
return result.toString ();
|
||||
}
|
||||
}
|
||||
+22
-2
@@ -1,5 +1,25 @@
|
||||
package com.itsaky.lsp.java;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.utils;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.ParseTask;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarationNamed;
|
||||
import com.itsaky.lsp.models.Location;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
@@ -68,7 +88,7 @@ public class FindHelper {
|
||||
}
|
||||
|
||||
public static ClassTree findType(ParseTask task, String className) {
|
||||
return new FindTypeDeclarationNamed().scan(task.root, className);
|
||||
return new FindTypeDeclarationNamed ().scan(task.root, className);
|
||||
}
|
||||
|
||||
public static ExecutableElement findMethod(
|
||||
+21
-4
@@ -1,4 +1,21 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.utils;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.sun.source.tree.Scope;
|
||||
@@ -14,9 +31,9 @@ import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.util.Elements;
|
||||
|
||||
class ScopeHelper {
|
||||
public class ScopeHelper {
|
||||
// TODO is this still necessary? Test speed. We could get rid of the extra static-imports step.
|
||||
static List<Scope> fastScopes (Scope start) {
|
||||
public static List<Scope> fastScopes (Scope start) {
|
||||
List<Scope> scopes = new ArrayList<Scope> ();
|
||||
for (Scope s = start; s != null; s = s.getEnclosingScope ()) {
|
||||
scopes.add (s);
|
||||
@@ -29,7 +46,7 @@ class ScopeHelper {
|
||||
return scopes.subList (0, scopes.size () - 2);
|
||||
}
|
||||
|
||||
static List<Element> scopeMembers (CompileTask task, Scope inner, Predicate<CharSequence> filter) {
|
||||
public static List<Element> scopeMembers (CompileTask task, Scope inner, Predicate<CharSequence> filter) {
|
||||
Trees trees = Trees.instance (task.task);
|
||||
Elements elements = task.task.getElements ();
|
||||
boolean isStatic = false;
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
public class FindVariableAt extends TreeScanner<VariableTree, Integer> {
|
||||
private final SourcePositions pos;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
public FindVariableAt(JavacTask task) {
|
||||
pos = Trees.instance(task).getSourcePositions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VariableTree visitCompilationUnit(CompilationUnitTree t, Integer find) {
|
||||
root = t;
|
||||
return super.visitCompilationUnit(t, find);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VariableTree visitVariable(VariableTree t, Integer find) {
|
||||
VariableTree smaller = super.visitVariable(t, find);
|
||||
if (smaller != null) {
|
||||
return smaller;
|
||||
}
|
||||
if (pos.getStartPosition(root, t) <= find && find < pos.getEndPosition(root, t)) {
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VariableTree reduce(VariableTree r1, VariableTree r2) {
|
||||
if (r1 != null) return r1;
|
||||
return r2;
|
||||
}
|
||||
}
|
||||
+20
-3
@@ -1,4 +1,21 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.CaseTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
@@ -14,11 +31,11 @@ import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
class FindCompletionsAt extends TreePathScanner<TreePath, Long> {
|
||||
public class FindCompletionsAt extends TreePathScanner<TreePath, Long> {
|
||||
private final JavacTask task;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
FindCompletionsAt (JavacTask task) {
|
||||
public FindCompletionsAt (JavacTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
+20
-3
@@ -1,4 +1,21 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
@@ -9,12 +26,12 @@ import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
class FindInvocationAt extends TreePathScanner<TreePath, Long> {
|
||||
public class FindInvocationAt extends TreePathScanner<TreePath, Long> {
|
||||
|
||||
private final JavacTask task;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
FindInvocationAt (JavacTask task) {
|
||||
public FindInvocationAt (JavacTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class FindMethodCallAt extends TreePathScanner<MethodInvocationTree, Integer> {
|
||||
private final Trees trees;
|
||||
private final SourcePositions pos;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
private boolean isMemberSelect;
|
||||
private boolean isStatic;
|
||||
|
||||
private String returnType;
|
||||
private ClassTree enclosingTree;
|
||||
private TreePath enclosingTreePath;
|
||||
private TypeElement declaredInTopLevel;
|
||||
private TypeElement enclosingElement;
|
||||
|
||||
public FindMethodCallAt(JavacTask task) {
|
||||
this.trees = Trees.instance(task);
|
||||
this.pos = trees.getSourcePositions();
|
||||
}
|
||||
|
||||
public String getReturnType(){
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public TypeElement getDeclaringType() {
|
||||
return declaredInTopLevel;
|
||||
}
|
||||
|
||||
public TypeElement getEnclosingElement() {
|
||||
return enclosingElement;
|
||||
}
|
||||
|
||||
public ClassTree getEnclosingClass() {
|
||||
return enclosingTree;
|
||||
}
|
||||
|
||||
public TreePath getEnclosingTreePath() {
|
||||
return enclosingTreePath;
|
||||
}
|
||||
|
||||
public boolean isMemberSelect() {
|
||||
return isMemberSelect;
|
||||
}
|
||||
|
||||
public boolean isStaticAccess() {
|
||||
return isStatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInvocationTree visitCompilationUnit(CompilationUnitTree t, Integer find) {
|
||||
root = t;
|
||||
return super.visitCompilationUnit(t, find);
|
||||
}
|
||||
|
||||
/**
|
||||
* A method invocation
|
||||
* <pre>
|
||||
* foo();
|
||||
* field.foo();
|
||||
* Class.foo();
|
||||
* Class.field.foo();
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
public MethodInvocationTree visitMethodInvocation(MethodInvocationTree t, Integer find) {
|
||||
MethodInvocationTree smaller = super.visitMethodInvocation(t, find);
|
||||
if (smaller != null) {
|
||||
return smaller;
|
||||
}
|
||||
|
||||
if (pos.getStartPosition(root, t) <= find && find < pos.getEndPosition(root, t)) {
|
||||
if(t.getMethodSelect() instanceof MemberSelectTree) {
|
||||
checkForQualifiedName((MemberSelectTree) t.getMethodSelect(), find);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find method invocation while declaring a variable
|
||||
* E.g. <pre> int x = foo(); </pre>
|
||||
*/
|
||||
@Override
|
||||
public MethodInvocationTree visitVariable(VariableTree tree, Integer find) {
|
||||
if(tree != null) {
|
||||
long start = pos.getStartPosition(root, tree);
|
||||
long end = pos.getEndPosition(root, tree);
|
||||
if(start <= find && find <= end && tree.getInitializer() instanceof MethodInvocationTree) {
|
||||
returnType = tree.getType().toString();
|
||||
return visitMethodInvocation((MethodInvocationTree) tree.getInitializer(), find);
|
||||
}
|
||||
}
|
||||
return super.visitVariable(tree, find);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find method invocation while initializing a variable
|
||||
* E.g.
|
||||
* <pre>
|
||||
* int x;
|
||||
* ...
|
||||
* x = foo();
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
public MethodInvocationTree visitAssignment(AssignmentTree tree, Integer find) {
|
||||
if(tree != null) {
|
||||
long start = pos.getStartPosition(root, tree);
|
||||
long end = pos.getEndPosition(root, tree);
|
||||
if(start <= find && find <= end && tree.getExpression() instanceof MethodInvocationTree) {
|
||||
returnType = findType();
|
||||
return visitMethodInvocation((MethodInvocationTree) tree.getExpression(), find);
|
||||
}
|
||||
}
|
||||
return super.visitAssignment(tree, find);
|
||||
|
||||
}
|
||||
|
||||
private String findType() {
|
||||
if(this.trees != null && getCurrentPath() != null) {
|
||||
TypeMirror typeMirror = this.trees.getTypeMirror(getCurrentPath());
|
||||
if(typeMirror != null
|
||||
&& typeMirror.getKind() != TypeKind.NONE
|
||||
&& typeMirror.getKind() != TypeKind.ERROR)
|
||||
return typeMirror.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInvocationTree reduce(MethodInvocationTree r1, MethodInvocationTree r2) {
|
||||
if (r1 != null) return r1;
|
||||
return r2;
|
||||
}
|
||||
|
||||
private void checkForQualifiedName(MemberSelectTree tree, Integer find) {
|
||||
if(tree != null) {
|
||||
long start = pos.getStartPosition(root, tree);
|
||||
long end = pos.getEndPosition(root, tree);
|
||||
if(start <= find && find <= end) {
|
||||
Element element = trees.getElement(trees.getPath(root, tree));
|
||||
|
||||
// Is this a static access?
|
||||
isStatic = element instanceof TypeElement;
|
||||
|
||||
// Find enclosing element to get the TypeElement from which this method is being called
|
||||
this.enclosingElement = enclosingType(element);
|
||||
|
||||
// find top level declaration to get the qualified name of the class
|
||||
this.declaredInTopLevel = enclosingTopLevelType(enclosingElement);
|
||||
|
||||
// Get the tree path of the enclosing element
|
||||
this.enclosingTreePath = trees.getPath(enclosingElement);
|
||||
|
||||
// Get the ClassTree of the enclosing element
|
||||
// Will be needed in CreateMissingMethod.java for EditHelper
|
||||
this.enclosingTree = enclosingClass(this.enclosingTreePath);
|
||||
|
||||
isMemberSelect = enclosingElement != null
|
||||
&& declaredInTopLevel != null
|
||||
&& enclosingTreePath != null
|
||||
&& enclosingTree != null;
|
||||
|
||||
LOG.info(String.format("checkForQualifiedName\nisStatic: %s\nenclosingElement: %s\ndeclaredInTopLevel: %s\nenclosingTreePath: %s\nenclosingTree: %s\nisMemberSelect: %s", "" + isStatic, "" + enclosingElement, "" + declaredInTopLevel, "" + enclosingTreePath, "" + enclosingTree, "" + isMemberSelect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ClassTree enclosingClass(TreePath path) {
|
||||
while(path != null) {
|
||||
if(path.getLeaf() instanceof ClassTree) {
|
||||
return (ClassTree) path.getLeaf();
|
||||
}
|
||||
|
||||
path = path.getParentPath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeElement enclosingTopLevelType (Element element) {
|
||||
while (element != null) {
|
||||
if(element instanceof TypeElement) {
|
||||
TypeElement type = (TypeElement) element;
|
||||
if(type.getNestingKind() == NestingKind.TOP_LEVEL)
|
||||
return type;
|
||||
}
|
||||
|
||||
element = element.getEnclosingElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeElement enclosingType (Element element) {
|
||||
while (element != null) {
|
||||
if(element instanceof TypeElement) {
|
||||
TypeElement type = (TypeElement) element;
|
||||
final ClassTree tree = trees.getTree (type);
|
||||
if(tree != null)
|
||||
return type;
|
||||
}
|
||||
|
||||
element = element.getEnclosingElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance ("main");
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
public class FindMethodDeclarationAt extends TreeScanner<MethodTree, Long> {
|
||||
|
||||
private final SourcePositions pos;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
public FindMethodDeclarationAt(JavacTask task) {
|
||||
pos = Trees.instance(task).getSourcePositions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodTree visitCompilationUnit(CompilationUnitTree t, Long find) {
|
||||
root = t;
|
||||
return super.visitCompilationUnit(t, find);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodTree visitMethod(MethodTree t, Long find) {
|
||||
MethodTree smaller = super.visitMethod(t, find);
|
||||
if (smaller != null) {
|
||||
return smaller;
|
||||
}
|
||||
if (pos.getStartPosition(root, t) <= find && find < pos.getEndPosition(root, t)) {
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodTree reduce(MethodTree r1, MethodTree r2) {
|
||||
if (r1 != null) return r1;
|
||||
return r2;
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -1,5 +1,24 @@
|
||||
package com.itsaky.lsp.java;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.itsaky.lsp.java.CompileTask;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.*;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
public class FindTypeDeclarationAt extends TreePathScanner<ClassTree, Long> {
|
||||
private final SourcePositions pos;
|
||||
private CompilationUnitTree root;
|
||||
|
||||
private TreePath stored;
|
||||
|
||||
public FindTypeDeclarationAt(JavacTask task) {
|
||||
pos = Trees.instance(task).getSourcePositions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree visitCompilationUnit(CompilationUnitTree t, Long find) {
|
||||
root = t;
|
||||
return super.visitCompilationUnit(t, find);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree visitClass(ClassTree t, Long find) {
|
||||
ClassTree smaller = super.visitClass(t, find);
|
||||
if (smaller != null) {
|
||||
return smaller;
|
||||
}
|
||||
if (pos.getStartPosition(root, t) <= find && find < pos.getEndPosition(root, t)) {
|
||||
this.stored = getCurrentPath();
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassTree reduce(ClassTree a, ClassTree b) {
|
||||
if (a != null) return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
public TreePath getStoredTreePath() {
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
+19
-2
@@ -1,4 +1,21 @@
|
||||
package com.itsaky.lsp.java;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
@@ -6,7 +23,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
class FindTypeDeclarationNamed extends TreeScanner<ClassTree, String> {
|
||||
public class FindTypeDeclarationNamed extends TreeScanner<ClassTree, String> {
|
||||
private List<CharSequence> qualifiedName = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FindTypeDeclarations extends TreeScanner<Void, List<String>> {
|
||||
private List<CharSequence> qualifiedName = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree root, List<String> found) {
|
||||
String name = Objects.toString(root.getPackageName(), "");
|
||||
qualifiedName.add(name);
|
||||
return super.visitCompilationUnit(root, found);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClass(ClassTree type, List<String> found) {
|
||||
qualifiedName.add(type.getSimpleName());
|
||||
found.add(String.join(".", qualifiedName));
|
||||
super.visitClass(type, found);
|
||||
qualifiedName.remove(qualifiedName.size() - 1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -1,4 +1,21 @@
|
||||
package com.itsaky.lsp.java.completion;
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.lsp.java.visitors;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
Reference in New Issue
Block a user