mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Integrated the Projects API into LSP
This commit is contained in:
Generated
+1
@@ -13,6 +13,7 @@
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="AndroidIDE.build-tools.builder-api" target="11" />
|
||||
<module name="AndroidIDE.build-tools.builder-model" target="11" />
|
||||
<module name="AndroidIDE.build-tools.eventbus-events.test" target="11" />
|
||||
<module name="AndroidIDE.build-tools.eventbus-models.main" target="11" />
|
||||
<module name="AndroidIDE.build-tools.eventbus-models.test" target="11" />
|
||||
<module name="AndroidIDE.build-tools.project" target="11" />
|
||||
|
||||
@@ -83,7 +83,6 @@ import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.ActivityEditorBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutSearchProjectBinding;
|
||||
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent;
|
||||
import com.itsaky.androidide.fragments.FileTreeFragment;
|
||||
import com.itsaky.androidide.fragments.LogViewFragment;
|
||||
import com.itsaky.androidide.fragments.NonEditableEditorFragment;
|
||||
@@ -98,17 +97,16 @@ import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.lsp.IDELanguageClientImpl;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry;
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer;
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerConfiguration;
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerSettings;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.androidide.lsp.models.InitializeParams;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.xml.XMLLanguageServer;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.managers.ToolsManager;
|
||||
import com.itsaky.androidide.models.ApkMetadata;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.models.SaveResult;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
@@ -135,7 +133,6 @@ import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
@@ -419,9 +416,9 @@ public class EditorActivity extends StudioActivity
|
||||
return openFile(file, null);
|
||||
}
|
||||
|
||||
public CodeEditorView openFile(File file, com.itsaky.androidide.lsp.models.Range selection) {
|
||||
public CodeEditorView openFile(File file, com.itsaky.androidide.models.Range selection) {
|
||||
if (selection == null) {
|
||||
selection = com.itsaky.androidide.lsp.models.Range.NONE;
|
||||
selection = com.itsaky.androidide.models.Range.NONE;
|
||||
}
|
||||
|
||||
int index = openFileAndGetIndex(file, selection);
|
||||
@@ -446,7 +443,7 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public int openFileAndGetIndex(File file, com.itsaky.androidide.lsp.models.Range selection) {
|
||||
public int openFileAndGetIndex(File file, com.itsaky.androidide.models.Range selection) {
|
||||
final var openedFileIndex = findIndexOfEditorByFile(file);
|
||||
|
||||
if (openedFileIndex != -1) {
|
||||
@@ -872,8 +869,6 @@ public class EditorActivity extends StudioActivity
|
||||
ProjectManager.INSTANCE.setupProject(mBuildService.projectProxy);
|
||||
ProjectManager.INSTANCE.notifyProjectUpdate();
|
||||
|
||||
dispatchProjectInitialized();
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (mBinding == null) {
|
||||
@@ -892,12 +887,6 @@ public class EditorActivity extends StudioActivity
|
||||
});
|
||||
}
|
||||
|
||||
protected void dispatchProjectInitialized() {
|
||||
final var event = new ProjectInitializedEvent();
|
||||
event.put(Project.class, ProjectManager.INSTANCE.getRootProject());
|
||||
EventBus.getDefault().post(event);
|
||||
}
|
||||
|
||||
private void initialSetup() {
|
||||
getApp()
|
||||
.getPrefManager()
|
||||
@@ -1083,25 +1072,6 @@ public class EditorActivity extends StudioActivity
|
||||
|
||||
ILanguageServerRegistry.getDefault().register(new JavaLanguageServer());
|
||||
ILanguageServerRegistry.getDefault().register(new XMLLanguageServer());
|
||||
ProjectManager.INSTANCE.setProjectUpdateNotificationConsumer(
|
||||
() -> {
|
||||
try {
|
||||
final var app = Objects.requireNonNull(ProjectManager.INSTANCE.getApp());
|
||||
final var server =
|
||||
ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID);
|
||||
final var classPaths =
|
||||
app.getClassPaths().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(File::toPath)
|
||||
.collect(Collectors.toSet());
|
||||
final var sourceDirs =
|
||||
app.getSourceDirectories().stream().map(File::toPath).collect(Collectors.toSet());
|
||||
final var configuration = new JavaServerConfiguration(classPaths, sourceDirs);
|
||||
server.configurationChanged(configuration);
|
||||
} catch (Throwable err) {
|
||||
LOG.error("Unable to notify configuration change event", err);
|
||||
}
|
||||
});
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) {
|
||||
ProjectManager.INSTANCE.setProjectPath(savedInstanceState.getString(KEY_PROJECT_PATH));
|
||||
}
|
||||
@@ -1186,12 +1156,14 @@ public class EditorActivity extends StudioActivity
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mFileActionsHandler.register();
|
||||
ProjectManager.INSTANCE.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mFileActionsHandler.unregister();
|
||||
ProjectManager.INSTANCE.unregister();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
||||
@@ -16,17 +16,13 @@
|
||||
*/
|
||||
package com.itsaky.androidide.language
|
||||
|
||||
import com.itsaky.androidide.projects.ProjectManager.findModuleForFile
|
||||
import com.itsaky.androidide.projects.api.Project
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.FailureType.COMPLETION
|
||||
import com.itsaky.androidide.lsp.models.LSPFailure
|
||||
import com.itsaky.androidide.lsp.models.Position
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionHelper
|
||||
import io.github.rosemoe.sora.text.CharPosition
|
||||
@@ -56,31 +52,15 @@ class CommonCompletionProvider(private val server: ILanguageServer) {
|
||||
file: Path,
|
||||
position: CharPosition,
|
||||
prefixMatcher: Predicate<Char?>
|
||||
): List<CompletionItem> {
|
||||
): List<com.itsaky.androidide.lsp.models.CompletionItem> {
|
||||
val completionResult =
|
||||
try {
|
||||
val prefix =
|
||||
CompletionHelper.computePrefix(content, position) { t: Char -> prefixMatcher.test(t) }
|
||||
val completer = server.completionProvider
|
||||
if (completer.canComplete(file)) {
|
||||
var fileModule: Project? = null
|
||||
try {
|
||||
fileModule = findModuleForFile(file.toFile())
|
||||
} catch (e: Throwable) {
|
||||
if (e !is InterruptedException) {
|
||||
// This can occur if the completion was cancelled
|
||||
LOG.error("Unable to find module for current file", e)
|
||||
}
|
||||
}
|
||||
val params =
|
||||
CompletionParams(Position(position.line, position.column, position.index), file)
|
||||
params.content = content
|
||||
params.prefix = prefix
|
||||
params.module = fileModule
|
||||
completer.complete(params)
|
||||
} else {
|
||||
CompletionResult.EMPTY
|
||||
}
|
||||
val prefix = CompletionHelper.computePrefix(content, position) { prefixMatcher.test(it) }
|
||||
val params =
|
||||
CompletionParams(Position(position.line, position.column, position.index), file)
|
||||
params.content = content
|
||||
params.prefix = prefix
|
||||
server.complete(params)
|
||||
} catch (e: Throwable) {
|
||||
// Do not log if completion was interrupted or cancelled
|
||||
if (!(e is InterruptedException || e is CompletionCancelledException)) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.models.FormatCodeParams;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
@@ -49,8 +50,7 @@ import com.itsaky.androidide.lsp.models.CodeActionItem;
|
||||
import com.itsaky.androidide.lsp.models.Command;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticResult;
|
||||
import com.itsaky.androidide.lsp.models.Location;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.itsaky.androidide.lsp.util.DiagnosticUtil;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
**************************************************************************************/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import static com.itsaky.androidide.lsp.models.HighlightTokenKind.TYPE_PARAMETER
|
||||
|
||||
import com.itsaky.androidide.lsp.models.HighlightToken;
|
||||
import com.itsaky.androidide.lsp.models.HighlightTokenKind;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
**************************************************************************************/
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
@@ -47,7 +47,7 @@ import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
@@ -375,7 +375,7 @@ public class CodeEditorView extends FrameLayout {
|
||||
}
|
||||
|
||||
private void notifySaved() {
|
||||
binding.editor.didSave();
|
||||
binding.editor.dispatchDocumentSaveEvent();
|
||||
|
||||
if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) {
|
||||
ValuesTableFactory.syncWithFile(getFile());
|
||||
|
||||
@@ -49,7 +49,7 @@ import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.lsp.xml.XMLLanguageServer
|
||||
import io.github.rosemoe.sora.event.HandleStateChangeEvent
|
||||
import io.github.rosemoe.sora.event.ScrollEvent
|
||||
@@ -275,8 +275,8 @@ open class EditorActionsMenu(val editor: IDEEditor) :
|
||||
editor
|
||||
) // For LSP actions, as they cannot access IDEEditor class
|
||||
data.put(File::class.java, editor.file)
|
||||
data.put(DiagnosticItem::class.java, getDiagnosticAtCursor())
|
||||
data.put(Range::class.java, editor.cursorRange)
|
||||
data.put(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java, getDiagnosticAtCursor())
|
||||
data.put(com.itsaky.androidide.models.Range::class.java, editor.cursorRange)
|
||||
data.put(
|
||||
JavaLanguageServer::class.java,
|
||||
StudioApp.getInstance().javaLanguageServer as JavaLanguageServer
|
||||
@@ -290,7 +290,7 @@ open class EditorActionsMenu(val editor: IDEEditor) :
|
||||
|
||||
protected open fun getMenu(): Menu = menu
|
||||
|
||||
private fun getDiagnosticAtCursor(): DiagnosticItem? {
|
||||
private fun getDiagnosticAtCursor(): com.itsaky.androidide.lsp.models.DiagnosticItem? {
|
||||
val start = editor.cursorRange.start
|
||||
return editor.mLanguageClient?.getDiagnosticAt(editor.file, start.line, start.column)
|
||||
}
|
||||
|
||||
@@ -29,23 +29,19 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.eventbus.events.editor.ChangeType;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentSaveEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentSelectedEvent;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.lsp.IDELanguageClientImpl;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.models.ChangeType;
|
||||
import com.itsaky.androidide.lsp.models.Command;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentSaveEvent;
|
||||
import com.itsaky.androidide.lsp.models.ExpandSelectionParams;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.lsp.models.ShowDocumentParams;
|
||||
@@ -53,8 +49,15 @@ import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.androidide.lsp.util.DiagnosticUtil;
|
||||
import com.itsaky.androidide.lsp.util.PathUtils;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -146,13 +149,8 @@ public class IDEEditor extends CodeEditor {
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
|
||||
if (file != null && mLanguageServer != null) {
|
||||
final var documentHandler = mLanguageServer.getDocumentHandler();
|
||||
if (documentHandler.accepts(file.toPath())) {
|
||||
final var text = getText().toString();
|
||||
final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0);
|
||||
documentHandler.onFileOpened(event);
|
||||
}
|
||||
if (file != null) {
|
||||
dispatchDocumentOpenEvent();
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
@@ -188,32 +186,7 @@ public class IDEEditor extends CodeEditor {
|
||||
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
final var documentHandler = mLanguageServer.getDocumentHandler();
|
||||
final var file = getFile().toPath();
|
||||
if (documentHandler.accepts(file)) {
|
||||
var type = ChangeType.INSERT;
|
||||
if (event.getAction() == ContentChangeEvent.ACTION_DELETE) {
|
||||
type = ChangeType.DELETE;
|
||||
} else if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) {
|
||||
type = ChangeType.NEW_TEXT;
|
||||
}
|
||||
|
||||
var changeDelta = type == ChangeType.NEW_TEXT ? 0 : event.getChangedText().length();
|
||||
if (type == ChangeType.DELETE) {
|
||||
changeDelta = -changeDelta;
|
||||
}
|
||||
|
||||
final var start = event.getChangeStart();
|
||||
final var end = event.getChangeEnd();
|
||||
final var changeRange =
|
||||
new Range(
|
||||
new Position(start.line, start.column, start.index),
|
||||
new Position(end.line, end.column, end.index));
|
||||
documentHandler.onContentChange(
|
||||
new DocumentChangeEvent(
|
||||
file, getText().toString(), mFileVersion + 1, type, changeDelta, changeRange));
|
||||
}
|
||||
|
||||
dispatchDocumentChangeEvent(event);
|
||||
checkForSignatureHelp(event);
|
||||
});
|
||||
}
|
||||
@@ -265,6 +238,7 @@ public class IDEEditor extends CodeEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> showSignatureHelp(help));
|
||||
});
|
||||
}
|
||||
@@ -439,7 +413,7 @@ public class IDEEditor extends CodeEditor {
|
||||
final var params =
|
||||
new com.itsaky.androidide.lsp.models.DefinitionParams(
|
||||
getFile().toPath(),
|
||||
new com.itsaky.androidide.lsp.models.Position(
|
||||
new com.itsaky.androidide.models.Position(
|
||||
getCursor().getLeftLine(), getCursor().getLeftColumn()));
|
||||
|
||||
return mLanguageServer.findDefinition(params);
|
||||
@@ -463,6 +437,7 @@ public class IDEEditor extends CodeEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (locations.size() == 1) {
|
||||
@@ -493,6 +468,7 @@ public class IDEEditor extends CodeEditor {
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showDefinitionNotFound(final ProgressDialog pd) {
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
StudioApp.getInstance().toast(R.string.msg_no_definition, Toaster.Type.ERROR);
|
||||
@@ -576,6 +552,7 @@ public class IDEEditor extends CodeEditor {
|
||||
* @param dialog The dialog to dismiss.
|
||||
*/
|
||||
private void dismissOnUiThread(@NonNull final Dialog dialog) {
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(dialog::dismiss);
|
||||
}
|
||||
|
||||
@@ -603,7 +580,7 @@ public class IDEEditor extends CodeEditor {
|
||||
final var referenceParams =
|
||||
new ReferenceParams(
|
||||
getFile().toPath(),
|
||||
new com.itsaky.androidide.lsp.models.Position(
|
||||
new com.itsaky.androidide.models.Position(
|
||||
getCursor().getLeftLine(), getCursor().getLeftColumn()),
|
||||
true);
|
||||
return mLanguageServer.findReferences(referenceParams);
|
||||
@@ -650,6 +627,7 @@ public class IDEEditor extends CodeEditor {
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showReferencesNotFound(final ProgressDialog pd) {
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
StudioApp.getInstance().toast(R.string.msg_no_references, Toaster.Type.ERROR);
|
||||
@@ -657,30 +635,15 @@ public class IDEEditor extends CodeEditor {
|
||||
});
|
||||
}
|
||||
|
||||
/** If any language server is set, notify the server that the file in this editor was saved. */
|
||||
public void didSave() {
|
||||
if (mLanguageServer != null && getFile() != null) {
|
||||
final var documentHandler = mLanguageServer.getDocumentHandler();
|
||||
final var file = getFile().toPath();
|
||||
if (documentHandler.accepts(file)) {
|
||||
documentHandler.onFileSaved(new DocumentSaveEvent(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Notify the language server that the file in this editor is about to be closed. */
|
||||
public void close() {
|
||||
if (mLanguageServer != null && getFile() != null) {
|
||||
final var documentHandler = mLanguageServer.getDocumentHandler();
|
||||
final var file = getFile().toPath();
|
||||
if (documentHandler.accepts(file)) {
|
||||
documentHandler.onFileClosed(new DocumentCloseEvent(file));
|
||||
}
|
||||
LOG.info("'textDocument/didClose' was sent to the language server.");
|
||||
} else {
|
||||
if (getFile() == null) {
|
||||
LOG.info("No language server is available for this file");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchDocumentCloseEvent();
|
||||
|
||||
mActionsPopup.unsubscribeEvents();
|
||||
ensureWindowsDismissed();
|
||||
}
|
||||
@@ -707,10 +670,7 @@ public class IDEEditor extends CodeEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
final var path = getFile().toPath();
|
||||
if (mLanguageServer != null) {
|
||||
mLanguageServer.getDocumentHandler().onFileSelected(path);
|
||||
}
|
||||
dispatchDocumentSelectedEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -764,6 +724,7 @@ public class IDEEditor extends CodeEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> setSelection(range));
|
||||
}));
|
||||
}
|
||||
@@ -841,6 +802,74 @@ public class IDEEditor extends CodeEditor {
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
protected void dispatchDocumentOpenEvent() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var openEvent =
|
||||
new DocumentOpenEvent(getFile().toPath(), getText().toString(), mFileVersion = 0);
|
||||
EventBus.getDefault().post(openEvent);
|
||||
}
|
||||
|
||||
protected void dispatchDocumentSelectedEvent() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var selectedEvent = new DocumentSelectedEvent(getFile().toPath());
|
||||
EventBus.getDefault().post(selectedEvent);
|
||||
}
|
||||
|
||||
protected void dispatchDocumentChangeEvent(final ContentChangeEvent event) {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var file = getFile().toPath();
|
||||
var type = ChangeType.INSERT;
|
||||
if (event.getAction() == ContentChangeEvent.ACTION_DELETE) {
|
||||
type = ChangeType.DELETE;
|
||||
} else if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) {
|
||||
type = ChangeType.NEW_TEXT;
|
||||
}
|
||||
|
||||
var changeDelta = type == ChangeType.NEW_TEXT ? 0 : event.getChangedText().length();
|
||||
if (type == ChangeType.DELETE) {
|
||||
changeDelta = -changeDelta;
|
||||
}
|
||||
|
||||
final var start = event.getChangeStart();
|
||||
final var end = event.getChangeEnd();
|
||||
final var changeRange =
|
||||
new Range(
|
||||
new Position(start.line, start.column, start.index),
|
||||
new Position(end.line, end.column, end.index));
|
||||
|
||||
final var changeEvent =
|
||||
new DocumentChangeEvent(
|
||||
file, getText().toString(), mFileVersion + 1, type, changeDelta, changeRange);
|
||||
EventBus.getDefault().post(changeEvent);
|
||||
}
|
||||
|
||||
protected void dispatchDocumentSaveEvent() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var saveEvent = new DocumentSaveEvent(getFile().toPath());
|
||||
EventBus.getDefault().post(saveEvent);
|
||||
}
|
||||
|
||||
protected void dispatchDocumentCloseEvent() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var closeEvent = new DocumentCloseEvent(getFile().toPath());
|
||||
EventBus.getDefault().post(closeEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDEEditorSearcher getSearcher() {
|
||||
return mSearcher;
|
||||
|
||||
@@ -14,30 +14,38 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
java {
|
||||
sourceCompatibility = rootProject.ext.javaSourceVersion
|
||||
targetCompatibility = rootProject.ext.javaTargetVersion
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation common.jkotlin
|
||||
implementation project (path: ':common')
|
||||
|
||||
api project (path: ':build-tools:eventbus')
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<manifest package="com.itsaky.androidide.eventbus.events"/>
|
||||
+12
-4
@@ -15,8 +15,9 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.models
|
||||
package com.itsaky.androidide.eventbus.events.editor
|
||||
|
||||
import com.itsaky.androidide.models.Range
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -27,11 +28,17 @@ open class DocumentEvent(var file: Path) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Dispatched when an editor is opened for the given file. */
|
||||
data class DocumentOpenEvent(var openedFile: Path, var text: String, var version: Int) :
|
||||
DocumentEvent(openedFile)
|
||||
|
||||
/** Dispatched when the given file is closed. Always dispatched after [DocumentOpenEvent]. */
|
||||
data class DocumentCloseEvent(var closedFile: Path) : DocumentEvent(closedFile)
|
||||
|
||||
/**
|
||||
* Dispatched when the content of the given opened document changes. The change can be either
|
||||
* performed by the user or the IDE itself.
|
||||
*/
|
||||
data class DocumentChangeEvent(
|
||||
var changedFile: Path,
|
||||
var newText: String,
|
||||
@@ -41,12 +48,13 @@ data class DocumentChangeEvent(
|
||||
var changeRange: Range
|
||||
) : DocumentEvent(changedFile)
|
||||
|
||||
/** Dispatched when the given document is saved to disk. */
|
||||
data class DocumentSaveEvent(var savedFile: Path) : DocumentEvent(savedFile)
|
||||
|
||||
data class ShowDocumentParams(var file: Path, var selection: Range)
|
||||
|
||||
data class ShowDocumentResult(var success: Boolean)
|
||||
/** Dispatched when the given opened document is selected and it is visible to the user. */
|
||||
data class DocumentSelectedEvent(var selectedFile: Path) : DocumentEvent(selectedFile)
|
||||
|
||||
/** The type of change in a [DocumentChangeEvent]. */
|
||||
enum class ChangeType {
|
||||
INSERT,
|
||||
DELETE,
|
||||
@@ -31,10 +31,12 @@ android {
|
||||
dependencies {
|
||||
api project (path: ':build-tools:eventbus')
|
||||
api project (path: ':build-tools:eventbus-events')
|
||||
api project (path: ':build-tools:tooling-api')
|
||||
|
||||
implementation project (path: ':build-tools:tooling-api')
|
||||
implementation project (path: ':common')
|
||||
|
||||
implementation google.guava
|
||||
|
||||
testImplementation project (path: ':build-tools:tooling-api-testing')
|
||||
testImplementation tests.junit
|
||||
testImplementation tests.google_truth
|
||||
|
||||
+246
-35
@@ -16,29 +16,43 @@
|
||||
*/
|
||||
package com.itsaky.androidide.projects
|
||||
|
||||
import com.itsaky.androidide.eventbus.events.EventReceiver
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent
|
||||
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.projects.api.ModuleProject
|
||||
import com.itsaky.androidide.projects.api.Project
|
||||
import com.itsaky.androidide.projects.builder.BuildService
|
||||
import com.itsaky.androidide.projects.models.ActiveDocument
|
||||
import com.itsaky.androidide.projects.util.ProjectTransformer
|
||||
import com.itsaky.androidide.tooling.api.IProject
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode.BACKGROUND
|
||||
|
||||
/**
|
||||
* Manages projects in AndroidIDE.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object ProjectManager {
|
||||
object ProjectManager : EventReceiver {
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
lateinit var projectPath: String
|
||||
|
||||
var rootProject: Project? = null
|
||||
var app: AndroidModule? = null
|
||||
|
||||
var projectUpdateNotificationConsumer: Runnable? = null
|
||||
|
||||
fun setupProject(project: IProject) {
|
||||
val caching = CachingProject(project)
|
||||
this.rootProject = ProjectTransformer().transform(caching)
|
||||
@@ -50,17 +64,6 @@ object ProjectManager {
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInitialized() = rootProject != null
|
||||
|
||||
private fun checkInit(): Boolean {
|
||||
if (isInitialized()) {
|
||||
return true
|
||||
}
|
||||
|
||||
log.warn("Project is not initialized yet!")
|
||||
return false
|
||||
}
|
||||
|
||||
fun getProjectDir(): File {
|
||||
return File(getProjectDirPath())
|
||||
}
|
||||
@@ -118,39 +121,247 @@ object ProjectManager {
|
||||
}
|
||||
|
||||
fun notifyProjectUpdate() {
|
||||
log.debug("Notifying language servers about configuration change...")
|
||||
if (app == null) {
|
||||
log.warn("Cannot find application module. Skipping configuration update...")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO Remove this when Events API has been implemented
|
||||
if (projectUpdateNotificationConsumer != null) {
|
||||
projectUpdateNotificationConsumer!!.run()
|
||||
}
|
||||
val event = ProjectInitializedEvent()
|
||||
event.put(Project::class.java, rootProject)
|
||||
EventBus.getDefault().post(event)
|
||||
}
|
||||
|
||||
fun findModuleForFile(file: File): Project? {
|
||||
fun findModuleForFile(file: File): ModuleProject? {
|
||||
if (!checkInit()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val path = file.canonicalPath
|
||||
var longestPath = ""
|
||||
var moduleWithLongestPath: Project? = null
|
||||
return this.rootProject!!.findModuleForFile(file)
|
||||
}
|
||||
|
||||
for (module in rootProject!!.subModules) {
|
||||
val moduleDir = module.projectDir.canonicalPath
|
||||
if (path.startsWith(moduleDir) && longestPath.length < moduleDir.length) {
|
||||
longestPath = moduleDir
|
||||
moduleWithLongestPath = module
|
||||
fun findModuleForFile(file: Path): ModuleProject? {
|
||||
return findModuleForFile(file.toFile())
|
||||
}
|
||||
|
||||
fun isDocumentActive(file: Path): Boolean {
|
||||
if (!checkInit()) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (module in this.rootProject!!.subModules) {
|
||||
if (module !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (module.isActive(file)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (longestPath.isEmpty() || moduleWithLongestPath == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
fun containsSourceFile(file: Path): Boolean {
|
||||
if (!checkInit()) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (module in this.rootProject!!.subModules) {
|
||||
if (module !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
val source = module.compileJavaSourceClasses.findSource(file)
|
||||
if (source != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun getActiveDocument(file: Path): ActiveDocument? {
|
||||
if (!checkInit()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return moduleWithLongestPath
|
||||
for (module in this.rootProject!!.subModules) {
|
||||
if (module !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
val document = module.getActiveDocument(file)
|
||||
if (document != null) {
|
||||
return document
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLastModified(file: Path): Instant {
|
||||
if (!checkInit()) {
|
||||
return getLastModifiedFromDisk(file)
|
||||
}
|
||||
|
||||
if (isDocumentActive(file)) {
|
||||
val project = findModuleForFile(file) ?: return getLastModifiedFromDisk(file)
|
||||
val source = project.compileJavaSourceClasses.findSource(file)
|
||||
if (source != null) {
|
||||
return source.modified
|
||||
}
|
||||
}
|
||||
|
||||
return getLastModifiedFromDisk(file)
|
||||
}
|
||||
|
||||
fun getDocumentContents(file: Path): String {
|
||||
if (!checkInit()) {
|
||||
return getFileContents(file)
|
||||
}
|
||||
|
||||
val document = getActiveDocument(file)
|
||||
if (document != null) {
|
||||
return document.content
|
||||
}
|
||||
|
||||
return getFileContents(file)
|
||||
}
|
||||
|
||||
fun getReader(file: Path): BufferedReader {
|
||||
if (!checkInit()) {
|
||||
return createFileReader(file)
|
||||
}
|
||||
|
||||
if (isDocumentActive(file)) {
|
||||
val document = getActiveDocument(file)
|
||||
if (document != null) {
|
||||
return BufferedReader(StringReader(document.content))
|
||||
}
|
||||
}
|
||||
|
||||
return createFileReader(file)
|
||||
}
|
||||
|
||||
fun getInputStream(file: Path): InputStream {
|
||||
if (!checkInit()) {
|
||||
return createFileInputStream(file)
|
||||
}
|
||||
|
||||
if (isDocumentActive(file)) {
|
||||
val document = getActiveDocument(file)
|
||||
if (document != null) {
|
||||
BufferedInputStream(document.content.byteInputStream())
|
||||
}
|
||||
}
|
||||
|
||||
return createFileInputStream(file)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
////// TODO Subscribe to file creation/deletion/rename events and update source map //////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Subscribe(threadMode = BACKGROUND)
|
||||
@Suppress("unused")
|
||||
fun onDocumentOpen(event: DocumentOpenEvent) {
|
||||
if (!checkInit()) {
|
||||
return
|
||||
}
|
||||
|
||||
log.debug("Dispatching document open event to submodules...")
|
||||
for (subModule in this.rootProject!!.subModules) {
|
||||
if (subModule !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (subModule.onDocumentOpen(event)) {
|
||||
log.debug("DocumentOpenEvent consumed by '${subModule.path}'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = BACKGROUND)
|
||||
@Suppress("unused")
|
||||
fun onDocumentClose(event: DocumentCloseEvent) {
|
||||
if (!checkInit()) {
|
||||
return
|
||||
}
|
||||
|
||||
log.debug("Dispatching document close event to submodules...")
|
||||
for (subModule in this.rootProject!!.subModules) {
|
||||
if (subModule !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (subModule.onDocumentClose(event)) {
|
||||
log.debug("DocumentCloseEvent consumed by '${subModule.path}'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = BACKGROUND)
|
||||
@Suppress("unused")
|
||||
fun onDocumentContentChange(event: DocumentChangeEvent) {
|
||||
if (!checkInit()) {
|
||||
return
|
||||
}
|
||||
|
||||
log.debug("Dispatching document change event to submodules...")
|
||||
for (subModule in this.rootProject!!.subModules) {
|
||||
if (subModule !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (subModule.onDocumentChanged(event)) {
|
||||
log.debug("DocumentChangeEvent consumed by '${subModule.path}'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInitialized() = rootProject != null
|
||||
|
||||
private fun checkInit(): Boolean {
|
||||
if (isInitialized()) {
|
||||
return true
|
||||
}
|
||||
|
||||
log.warn("Project is not initialized yet!")
|
||||
return false
|
||||
}
|
||||
|
||||
private fun createFileReader(file: Path): BufferedReader {
|
||||
return try {
|
||||
Files.newBufferedReader(file)
|
||||
} catch (noFile: java.nio.file.NoSuchFileException) {
|
||||
log.warn("No such file", noFile)
|
||||
BufferedReader(StringReader(""))
|
||||
} catch (other: Throwable) {
|
||||
throw RuntimeException(other)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFileInputStream(file: Path): InputStream {
|
||||
return try {
|
||||
Files.newInputStream(file)
|
||||
} catch (noFile: java.nio.file.NoSuchFileException) {
|
||||
log.warn("No such file", noFile)
|
||||
"".byteInputStream()
|
||||
} catch (other: Throwable) {
|
||||
throw RuntimeException(other)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastModifiedFromDisk(file: Path): Instant {
|
||||
return Files.getLastModifiedTime(file).toInstant()
|
||||
}
|
||||
|
||||
private fun getFileContents(file: Path): String {
|
||||
return try {
|
||||
Files.readAllLines(file).joinToString(separator = "\n")
|
||||
} catch (noFile: java.nio.file.NoSuchFileException) {
|
||||
log.warn("No such file", noFile)
|
||||
return ""
|
||||
} catch (other: Throwable) {
|
||||
throw RuntimeException(other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+185
-35
@@ -17,12 +17,26 @@
|
||||
|
||||
package com.itsaky.androidide.projects.api
|
||||
|
||||
import com.itsaky.androidide.eventbus.events.EventReceiver
|
||||
import android.text.TextUtils
|
||||
import com.google.common.reflect.ClassPath
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.models.ActiveDocument
|
||||
import com.itsaky.androidide.projects.util.ClassTrie
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie.SourceNode
|
||||
import com.itsaky.androidide.tooling.api.model.GradleTask
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.File
|
||||
import java.util.zip.*
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A module project. Base class for [AndroidModule] and [JavaModule].
|
||||
@@ -36,11 +50,10 @@ abstract class ModuleProject(
|
||||
projectDir: File,
|
||||
buildDir: File,
|
||||
buildScript: File,
|
||||
tasks: List<GradleTask>
|
||||
tasks: List<GradleTask>,
|
||||
) :
|
||||
Project(name, description, path, projectDir, buildDir, buildScript, tasks),
|
||||
com.itsaky.androidide.tooling.api.model.ModuleProject,
|
||||
EventReceiver {
|
||||
com.itsaky.androidide.tooling.api.model.ModuleProject {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
@@ -50,8 +63,14 @@ abstract class ModuleProject(
|
||||
const val USAGE_RUNTIME = "java-runtime"
|
||||
}
|
||||
|
||||
val compileSourceClasses = ClassTrie()
|
||||
val compileClasspathClasses = ClassTrie()
|
||||
@JvmField val compileJavaSourceClasses = SourceClassTrie()
|
||||
@JvmField val compileClasspathClasses = ClassTrie()
|
||||
|
||||
/**
|
||||
* Map of documents that are open in the editor. Keys here are the canonical paths of the
|
||||
* documents.
|
||||
*/
|
||||
val activeDocuments: MutableMap<Path, ActiveDocument> = ConcurrentHashMap<Path, ActiveDocument>()
|
||||
|
||||
/**
|
||||
* Get the source directories of this module (non-transitive i.e for this module only).
|
||||
@@ -89,41 +108,172 @@ abstract class ModuleProject(
|
||||
*/
|
||||
abstract fun getCompileModuleProjects(): List<ModuleProject>
|
||||
|
||||
/** Finds the source files from source directories and indexes them. */
|
||||
/**
|
||||
* Called by [com.itsaky.androidide.projects.ProjectManager] when a document is opened.
|
||||
*
|
||||
* @param event The event descriptor.
|
||||
* @return `true` if this module consumed the event. `false` otherwise.
|
||||
*/
|
||||
fun onDocumentOpen(event: DocumentOpenEvent): Boolean {
|
||||
if (!isCacheable(event.openedFile) || !isFromThisModule(event.openedFile)) {
|
||||
return false
|
||||
}
|
||||
log.debug("File ${event.file} opened from module:", path)
|
||||
activeDocuments[event.openedFile.normalize()] = createDocument(event)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by [com.itsaky.androidide.projects.ProjectManager] when a document is changed.
|
||||
*
|
||||
* @param event The event descriptor.
|
||||
* @return `true` if this module consumed the event. `false` otherwise.
|
||||
*/
|
||||
fun onDocumentChanged(event: DocumentChangeEvent): Boolean {
|
||||
if (!isCacheable(event.changedFile) || !isFromThisModule(event.changedFile)) {
|
||||
return false
|
||||
}
|
||||
|
||||
activeDocuments[event.changedFile.normalize()] = createDocument(event)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by [com.itsaky.androidide.projects.ProjectManager] when a document is closed.
|
||||
*
|
||||
* @param event The event descriptor.
|
||||
* @return `true` if this module consumed the event. `false` otherwise.
|
||||
*/
|
||||
fun onDocumentClose(event: DocumentCloseEvent): Boolean {
|
||||
if (!isCacheable(event.closedFile) || !isFromThisModule(event.closedFile)) {
|
||||
return false
|
||||
}
|
||||
|
||||
activeDocuments.remove(event.closedFile.normalize())
|
||||
return true
|
||||
}
|
||||
|
||||
fun isActive(file: Path): Boolean {
|
||||
return this.activeDocuments.containsKey(file.normalize())
|
||||
}
|
||||
|
||||
fun getActiveDocument(file: Path): ActiveDocument? {
|
||||
return this.activeDocuments[file.normalize()]
|
||||
}
|
||||
|
||||
// TODO implement this as replacement of FileStore.javaSources
|
||||
/** Finds the source files and classes from source directories and classpaths and indexes them. */
|
||||
fun indexSourcesAndClasspaths() {
|
||||
log.debug("Starting to index sources and classpaths for project:", path)
|
||||
getCompileSourceDirectories().forEach {
|
||||
val sourceDir = it
|
||||
val sourceDirLength = sourceDir.absolutePath.length
|
||||
val sourceDir = it.toPath()
|
||||
it
|
||||
.walk()
|
||||
.filter { file -> file.isFile && file.exists() }
|
||||
.forEach { file ->
|
||||
// TODO Multiple classes can be defined in a single file
|
||||
// Also, their package names might be different
|
||||
// We need to handle these cases as well
|
||||
val parentPath = file.parentFile!!.absolutePath
|
||||
val path = parentPath.substring(sourceDirLength + 1) + "/" + file.nameWithoutExtension
|
||||
val fullyQualifiedName = path.replace('/', '.')
|
||||
this.compileSourceClasses.append(fullyQualifiedName)
|
||||
}
|
||||
.filter { file -> file.isFile && file.exists() && DocumentUtils.isJavaFile(file.toPath()) }
|
||||
.map { file -> file.toPath() }
|
||||
.forEach { file -> this.compileJavaSourceClasses.append(file, sourceDir) }
|
||||
}
|
||||
log.debug("Sources indexed for project:", path)
|
||||
val urls =
|
||||
getCompileClasspaths().filter { it.exists() }.map { toUrl(it.toPath()) }.toTypedArray()
|
||||
val loader = URLClassLoader(urls, null)
|
||||
val scanner: ClassPath
|
||||
try {
|
||||
scanner = ClassPath.from(loader)
|
||||
} catch (e: IOException) {
|
||||
log.warn("Unable to read classpaths for project:", path)
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
log.debug("Classpaths indexed for project:", path)
|
||||
var count = 0
|
||||
scanner.topLevelClasses.forEach {
|
||||
this.compileClasspathClasses.append(it.name)
|
||||
count++
|
||||
}
|
||||
getCompileClasspaths()
|
||||
.filter { it.exists() }
|
||||
.forEach { classpath ->
|
||||
ZipFile(classpath).use { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
if (!entry.name.endsWith(".class")) {
|
||||
continue
|
||||
}
|
||||
|
||||
val name = entry.name.substringBeforeLast('.').replace('/', '.').replace('$', '.')
|
||||
if (!compileClasspathClasses.contains(name)) {
|
||||
compileClasspathClasses.append(name)
|
||||
} else {
|
||||
log.warn("Duplicate class '$name'")
|
||||
}
|
||||
}
|
||||
log.debug("Found $count classes in classpath of project '${this.path}'")
|
||||
}
|
||||
|
||||
fun getSourceFilesInDir(dir: Path): List<SourceNode> =
|
||||
this.compileJavaSourceClasses.getSourceFilesInDir(dir)
|
||||
|
||||
fun packageNameOrEmpty(file: Path?): String {
|
||||
if (file == null) {
|
||||
return ""
|
||||
}
|
||||
|
||||
val source = this.compileJavaSourceClasses.findSource(file)
|
||||
if (source != null) {
|
||||
return source.packageName
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
fun suggestPackageName(file: Path): String {
|
||||
var dir = file.parent.normalize()
|
||||
while (dir != null) {
|
||||
for (sibling in getSourceFilesInDir(dir)) {
|
||||
if (DocumentUtils.isSameFile(sibling.file, file)) {
|
||||
continue
|
||||
}
|
||||
var packageName: String = packageNameOrEmpty(sibling.file)
|
||||
if (TextUtils.isEmpty(packageName.trim { it <= ' ' })) {
|
||||
continue
|
||||
}
|
||||
val relativePath = dir.relativize(file.parent)
|
||||
val relativePackage = relativePath.toString().replace(File.separatorChar, '.')
|
||||
if (relativePackage.isNotEmpty()) {
|
||||
packageName = "$packageName.$relativePackage"
|
||||
}
|
||||
return packageName
|
||||
}
|
||||
dir = dir.parent.normalize()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fun listClassesFromSourceDirs(packageName: String): List<SourceNode> {
|
||||
return compileJavaSourceClasses
|
||||
.findInPackage(packageName)
|
||||
.filterIsInstance(SourceNode::class.java)
|
||||
}
|
||||
|
||||
protected open fun isCacheable(file: Path): Boolean {
|
||||
// For now, we only cache Java files
|
||||
return DocumentUtils.isJavaFile(file)
|
||||
}
|
||||
|
||||
protected open fun isFromThisModule(file: Path): Boolean {
|
||||
// TODO This can be probably improved
|
||||
return file.startsWith(this.projectDir.toPath())
|
||||
}
|
||||
|
||||
protected open fun createDocument(event: DocumentOpenEvent): ActiveDocument {
|
||||
return ActiveDocument(
|
||||
file = event.openedFile,
|
||||
content = event.text,
|
||||
changeRange = Range.NONE,
|
||||
version = event.version,
|
||||
changDelta = 0
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun createDocument(event: DocumentChangeEvent): ActiveDocument {
|
||||
return ActiveDocument(
|
||||
file = event.changedFile,
|
||||
content = event.newText,
|
||||
changeRange = event.changeRange,
|
||||
version = event.version,
|
||||
changDelta = event.changeDelta
|
||||
)
|
||||
}
|
||||
|
||||
private fun toUrl(path: Path): URL {
|
||||
try {
|
||||
return path.toUri().toURL()
|
||||
} catch (e: MalformedURLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.itsaky.androidide.tooling.api.IProject.Type
|
||||
import com.itsaky.androidide.tooling.api.IProject.Type.Gradle
|
||||
import com.itsaky.androidide.tooling.api.model.GradleTask
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
@@ -118,4 +119,32 @@ open class Project(
|
||||
}
|
||||
return androidModules
|
||||
}
|
||||
|
||||
fun findModuleForFile(file: Path): ModuleProject? {
|
||||
return findModuleForFile(file.toFile())
|
||||
}
|
||||
|
||||
fun findModuleForFile(file: File): ModuleProject? {
|
||||
val path = file.path
|
||||
var longestPath = ""
|
||||
var moduleWithLongestPath: ModuleProject? = null
|
||||
|
||||
for (module in subModules) {
|
||||
if (module !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
val moduleDir = module.projectDir.path
|
||||
if (path.startsWith(moduleDir) && longestPath.length < moduleDir.length) {
|
||||
longestPath = moduleDir
|
||||
moduleWithLongestPath = module
|
||||
}
|
||||
}
|
||||
|
||||
if (longestPath.isEmpty() || moduleWithLongestPath == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return moduleWithLongestPath
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.androidide.projects.models
|
||||
|
||||
import com.itsaky.androidide.models.Range
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* A document that is opened in the editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class ActiveDocument(
|
||||
val file: Path,
|
||||
val content: String,
|
||||
val changeRange: Range,
|
||||
var changDelta: Int,
|
||||
val version: Int
|
||||
)
|
||||
+3
-3
@@ -15,9 +15,9 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.utils;
|
||||
package com.itsaky.androidide.projects.util;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
@@ -42,7 +42,7 @@ public class Cache<K, V> {
|
||||
|
||||
// If key was loaded before file was last modified, it needs to be reloaded
|
||||
Value value = map.get(key);
|
||||
Instant modified = FileStore.modified(file);
|
||||
Instant modified = ProjectManager.INSTANCE.getLastModified(file);
|
||||
// TODO remove all keys associated with file when file changes
|
||||
return value.created.isBefore(modified);
|
||||
}
|
||||
+69
-55
@@ -24,9 +24,7 @@ import java.util.function.*
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ClassTrie {
|
||||
|
||||
val root = Node()
|
||||
open class ClassTrie(val root: Node = Node()) {
|
||||
|
||||
/**
|
||||
* Appends the class name entry to this trie.
|
||||
@@ -34,15 +32,15 @@ class ClassTrie {
|
||||
* @param name The fully qualified class name.
|
||||
* @return The added node.
|
||||
*/
|
||||
fun append(name: String): Node {
|
||||
open fun append(name: String): Node {
|
||||
val segments = segments(name)
|
||||
var node = root
|
||||
for (segment in segments) {
|
||||
for ((index, segment) in segments.withIndex()) {
|
||||
if (segment.isEmpty() || !segment[0].isJavaIdentifierStart()) {
|
||||
continue
|
||||
}
|
||||
|
||||
node = node.createChild(segment)
|
||||
node = createNode(node, segment, segments, index)
|
||||
}
|
||||
node.isClass = true
|
||||
return node
|
||||
@@ -53,7 +51,7 @@ class ClassTrie {
|
||||
*
|
||||
* @param name The fully qualified name of the package segment or class name to remove.
|
||||
*/
|
||||
fun remove(name: String) {
|
||||
open fun remove(name: String) {
|
||||
remove(name, null)
|
||||
}
|
||||
|
||||
@@ -64,7 +62,7 @@ class ClassTrie {
|
||||
* @param name The fully qualified name of the package segment or the class.
|
||||
* @param predicate The predicate which can be used to test which entries should be deleted.
|
||||
*/
|
||||
fun remove(name: String, predicate: Predicate<Node>?) {
|
||||
open fun remove(name: String, predicate: Predicate<Node>?) {
|
||||
val segments = segments(name)
|
||||
val condition = predicate ?: Predicate { it.isClass }
|
||||
var node: Node? = root
|
||||
@@ -92,7 +90,7 @@ class ClassTrie {
|
||||
* @param name The fully qualified package or class name.
|
||||
* @return Whether there is a an entry for the given fully qualified name.
|
||||
*/
|
||||
fun contains(name: String): Boolean {
|
||||
open fun contains(name: String): Boolean {
|
||||
val segments = segments(name)
|
||||
var node: Node? = root
|
||||
for (segment in segments) {
|
||||
@@ -107,14 +105,13 @@ class ClassTrie {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all class names in the given package.
|
||||
* Finds all class nodes in the given package.
|
||||
*
|
||||
* @param packageName The package name to list classes from.
|
||||
* @param packageName The package name to list class nodes from.
|
||||
*/
|
||||
fun findClassNames(packageName: String): List<String> {
|
||||
open fun findInPackage(packageName: String): List<Node> {
|
||||
val segments = segments(packageName)
|
||||
val classes = mutableListOf<String>()
|
||||
val curr = StringBuilder()
|
||||
val classes = mutableListOf<Node>()
|
||||
var node: Node? = root
|
||||
for (segement in segments) {
|
||||
if (node == null || node.children.isEmpty() || !node.children.containsKey(segement)) {
|
||||
@@ -122,16 +119,7 @@ class ClassTrie {
|
||||
}
|
||||
|
||||
if (node.isClass) {
|
||||
var name = "$curr.$segement"
|
||||
if (curr.isEmpty()) {
|
||||
name = segement
|
||||
}
|
||||
classes.add(name)
|
||||
} else {
|
||||
if (curr.isNotEmpty()) {
|
||||
curr.append(".")
|
||||
}
|
||||
curr.append(segement)
|
||||
classes.add(node)
|
||||
}
|
||||
|
||||
node = node.children[segement]
|
||||
@@ -141,10 +129,29 @@ class ClassTrie {
|
||||
return classes
|
||||
}
|
||||
|
||||
node.children.values.forEach { addRecursively(it, curr.toString(), classes) }
|
||||
node.children.values.forEach { addRecursively(it, classes) }
|
||||
return classes
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all class names in the given package.
|
||||
*
|
||||
* @param packageName The package name to list classes from.
|
||||
*/
|
||||
open fun findClassNames(packageName: String): List<String> {
|
||||
return findInPackage(packageName).map { it.qualifiedName }
|
||||
}
|
||||
|
||||
/** Returns all class nodes available in this trie. */
|
||||
fun allClassNodes(): Set<Node> {
|
||||
return root.allClassNodes()
|
||||
}
|
||||
|
||||
/** Returns all classes available in this trie. */
|
||||
fun allClassNames(): Set<String> {
|
||||
return root.allClassNames()
|
||||
}
|
||||
|
||||
fun print() {
|
||||
root.children.forEach { print(it.value, 0) }
|
||||
}
|
||||
@@ -161,32 +168,10 @@ class ClassTrie {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRecursively(node: Node, curr: String, classes: MutableList<String>) {
|
||||
val segement = node.name
|
||||
var packageName = curr
|
||||
if (node.isClass) {
|
||||
var name = "$packageName.$segement"
|
||||
if (packageName.isEmpty()) {
|
||||
name = segement
|
||||
}
|
||||
classes.add(name)
|
||||
} else {
|
||||
packageName =
|
||||
if (curr.isEmpty()) {
|
||||
segement
|
||||
} else {
|
||||
"$packageName.$segement"
|
||||
}
|
||||
}
|
||||
protected open fun createNode(node: Node, segment: String, segments: List<String>, index: Int) =
|
||||
node.createChild(segment, segments.subList(0, index + 1).joinToString(separator = "."))
|
||||
|
||||
if (node.children.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
node.children.values.forEach { addRecursively(it, packageName, classes) }
|
||||
}
|
||||
|
||||
private fun segments(name: String): List<String> {
|
||||
protected open fun segments(name: String): List<String> {
|
||||
return if (name.contains('.')) {
|
||||
name.split(".")
|
||||
} else {
|
||||
@@ -194,16 +179,45 @@ class ClassTrie {
|
||||
}
|
||||
}
|
||||
|
||||
/** A Node can be a package segment or a class name in the package trie. */
|
||||
class Node(val name: String) {
|
||||
private fun addRecursively(node: Node, classes: MutableList<Node>) {
|
||||
if (node.isClass) {
|
||||
classes.add(node)
|
||||
}
|
||||
|
||||
internal constructor() : this("")
|
||||
if (node.children.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
node.children.values.forEach { addRecursively(it, classes) }
|
||||
}
|
||||
|
||||
/** A Node can be a package segment or a class name in the package trie. */
|
||||
open class Node(val name: String, val qualifiedName: String) {
|
||||
|
||||
internal constructor() : this("", "")
|
||||
|
||||
val children: MutableMap<String, Node> = mutableMapOf()
|
||||
var isClass = false
|
||||
|
||||
fun createChild(name: String): Node {
|
||||
return children.computeIfAbsent(name) { Node(it) }
|
||||
open fun createChild(name: String, qualifiedName: String): Node {
|
||||
return children.computeIfAbsent(name) { Node(it, qualifiedName) }
|
||||
}
|
||||
|
||||
open fun allClassNames(): Set<String> {
|
||||
return this.allClassNodes().map { it.qualifiedName }.toSet()
|
||||
}
|
||||
|
||||
open fun allClassNodes(): Set<Node> {
|
||||
val all = mutableSetOf<Node>()
|
||||
|
||||
if (this.isClass) {
|
||||
all.add(this)
|
||||
}
|
||||
|
||||
if (children.isNotEmpty()) {
|
||||
children.values.forEach { all.addAll(it.allClassNodes()) }
|
||||
}
|
||||
return all
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.androidide.projects.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DocumentUtils {
|
||||
|
||||
public static boolean isJavaFile(Path file) {
|
||||
String name = file.getFileName().toString();
|
||||
return name.endsWith(".java")
|
||||
&& Files.exists(file)
|
||||
&& !Files.isDirectory(file)
|
||||
&& !name.equals("module-info.java");
|
||||
}
|
||||
|
||||
public static boolean isXmlFile(Path file) {
|
||||
String name = file.getFileName().toString();
|
||||
return name.endsWith(".xml") && Files.exists(file) && !Files.isDirectory(file);
|
||||
}
|
||||
|
||||
public static boolean isSameFile(Path first, Path second) {
|
||||
try {
|
||||
return Files.isSameFile(first, second);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-13
@@ -41,19 +41,24 @@ class ProjectTransformer {
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
fun transform(project: IProject): Project? {
|
||||
val path = project.projectPath.get()
|
||||
val root =
|
||||
Project(
|
||||
name = project.name.get(),
|
||||
description = project.description.get() ?: "",
|
||||
path = path,
|
||||
projectDir = project.projectDir.get(),
|
||||
buildDir = project.buildDir.get(),
|
||||
buildScript = project.buildScript.get(),
|
||||
tasks = project.tasks.get() ?: listOf()
|
||||
)
|
||||
(root.subModules as MutableList).addAll(transform(project.listModules().get(), project))
|
||||
return root
|
||||
try {
|
||||
val path = project.projectPath.get()
|
||||
val root =
|
||||
Project(
|
||||
name = project.name.get(),
|
||||
description = project.description.get() ?: "",
|
||||
path = path,
|
||||
projectDir = project.projectDir.get(),
|
||||
buildDir = project.buildDir.get(),
|
||||
buildScript = project.buildScript.get(),
|
||||
tasks = project.tasks.get() ?: listOf()
|
||||
)
|
||||
(root.subModules as MutableList).addAll(transform(project.listModules().get(), project))
|
||||
return root
|
||||
} catch (error: Throwable) {
|
||||
log.error("Unable to transform project", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun transform(project: com.itsaky.androidide.tooling.api.model.AndroidModule): AndroidModule {
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.androidide.projects.util
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Instant
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
/**
|
||||
* A [ClassTrie] implementation to store information about java source files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class SourceClassTrie(root: SourcePackageNode = SourcePackageNode()) : ClassTrie(root) {
|
||||
|
||||
override fun append(name: String): Node {
|
||||
throw UnsupportedOperationException("Method not supported!")
|
||||
}
|
||||
|
||||
open fun append(path: Path, sourceDir: Path): SourceNode {
|
||||
val modified = Files.getLastModifiedTime(path).toInstant()
|
||||
val packageName = StringSearch.packageName(path)
|
||||
val segments = segments(packageName)
|
||||
var node: SourcePackageNode = root as SourcePackageNode
|
||||
var currentPath = sourceDir
|
||||
for ((index, segment) in segments.withIndex()) {
|
||||
if (segment.isEmpty() || !segment[0].isJavaIdentifierStart()) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentPath = currentPath.resolve(segment)
|
||||
node = createNode(currentPath, node, segment, segments, index)
|
||||
}
|
||||
node.isClass = false
|
||||
|
||||
val name = path.nameWithoutExtension
|
||||
val klass = SourceNode(path, packageName, modified, name)
|
||||
klass.isClass = true
|
||||
node.children[name] = klass
|
||||
return klass
|
||||
}
|
||||
|
||||
open fun createNode(dir: Path, node: SourcePackageNode, segment: String, segments: List<String>, index: Int) =
|
||||
node.createChild(dir, segment, segments.subList(0, index + 1).joinToString(separator = "."))
|
||||
|
||||
override fun createNode(node: Node, segment: String, segments: List<String>, index: Int): Node {
|
||||
throw UnsupportedOperationException("Method not supported")
|
||||
}
|
||||
|
||||
open fun allSources(): List<SourceNode> {
|
||||
return allClassNodes().filterIsInstance(SourceNode::class.java)
|
||||
}
|
||||
|
||||
fun findSource(file: Path): SourceNode? {
|
||||
return findSourceInNode(file, root)
|
||||
}
|
||||
|
||||
fun findDirNode(dir: Path, node: Node): SourcePackageNode? {
|
||||
var dirNode: SourcePackageNode? = null
|
||||
for (child in node.children.values) {
|
||||
if (
|
||||
child !is SourceNode &&
|
||||
child is SourcePackageNode &&
|
||||
DocumentUtils.isSameFile(child.dir, dir)
|
||||
) {
|
||||
dirNode = child
|
||||
}
|
||||
|
||||
dirNode = dirNode ?: findDirNode(dir, child)
|
||||
|
||||
if (dirNode != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return dirNode
|
||||
}
|
||||
|
||||
fun getSourceFilesInDir(dir: Path): List<SourceNode> {
|
||||
val dirNode = findDirNode(dir, root) ?: return emptyList()
|
||||
return dirNode.children.values.filterIsInstance(SourceNode::class.java)
|
||||
}
|
||||
|
||||
private fun findSourceInNode(file: Path, node: Node): SourceNode? {
|
||||
|
||||
if (node is SourceNode && DocumentUtils.isSameFile(file, node.file)) {
|
||||
return node
|
||||
}
|
||||
|
||||
for (child in node.children.values) {
|
||||
if (child is SourceNode && DocumentUtils.isSameFile(file, child.file)) {
|
||||
return child
|
||||
}
|
||||
|
||||
val childResult = findSourceInNode(file, child)
|
||||
if (childResult != null) {
|
||||
return childResult
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
open class SourceNode(
|
||||
val file: Path,
|
||||
val packageName: String,
|
||||
val modified: Instant,
|
||||
name: String
|
||||
) : Node(name, "$packageName.$name") {
|
||||
|
||||
override fun createChild(name: String, qualifiedName: String): Node {
|
||||
throw UnsupportedOperationException("Method not supported!")
|
||||
}
|
||||
}
|
||||
|
||||
open class SourcePackageNode(val dir: Path, name: String, qualifiedName: String) :
|
||||
Node(name, qualifiedName) {
|
||||
|
||||
internal constructor() : this(Paths.get(""), "", "")
|
||||
|
||||
override fun createChild(name: String, qualifiedName: String): Node {
|
||||
throw UnsupportedOperationException("Method not suported!")
|
||||
}
|
||||
|
||||
open fun createChild(dir: Path, name: String, qualifiedName: String): SourcePackageNode {
|
||||
return this.children.computeIfAbsent(name) { SourcePackageNode(dir, name, qualifiedName) }
|
||||
as SourcePackageNode
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
-13
@@ -1,6 +1,24 @@
|
||||
package com.itsaky.androidide.lsp.java.utils;
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
package com.itsaky.androidide.projects.util;
|
||||
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.models.ActiveDocument;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -96,7 +114,7 @@ public class StringSearch {
|
||||
}
|
||||
}
|
||||
|
||||
int next(String text) {
|
||||
public int next(String text) {
|
||||
return next(text.getBytes());
|
||||
}
|
||||
|
||||
@@ -144,7 +162,7 @@ public class StringSearch {
|
||||
return startsWord(text, offset) && endsWord(text, offset + pattern.length - 1);
|
||||
}
|
||||
|
||||
int nextWord(String text) {
|
||||
public int nextWord(String text) {
|
||||
return nextWord(text.getBytes());
|
||||
}
|
||||
|
||||
@@ -181,10 +199,24 @@ public class StringSearch {
|
||||
|
||||
private static final ByteBuffer SEARCH_BUFFER = ByteBuffer.allocateDirect(1024 * 1024);
|
||||
|
||||
private static String tryGetActiveDocContent(Path file) {
|
||||
if (ProjectManager.INSTANCE.isDocumentActive(file)) {
|
||||
final ActiveDocument document = ProjectManager.INSTANCE.getActiveDocument(file);
|
||||
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return document.getContent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO cache the progress made by searching shorter queries
|
||||
public static boolean containsWordMatching(Path java, String query) {
|
||||
if (FileStore.activeDocuments().contains(java)) {
|
||||
String text = FileStore.contents(java);
|
||||
String text = tryGetActiveDocContent(java);
|
||||
if (text != null) {
|
||||
return matchesTitleCase(text, query);
|
||||
}
|
||||
try (FileChannel channel = FileChannel.open(java)) {
|
||||
@@ -206,9 +238,9 @@ public class StringSearch {
|
||||
|
||||
public static boolean containsWord(Path java, String query) {
|
||||
StringSearch search = new StringSearch(query);
|
||||
if (FileStore.activeDocuments().contains(java)) {
|
||||
byte[] text = FileStore.contents(java).getBytes();
|
||||
return search.nextWord(text) != -1;
|
||||
String text = tryGetActiveDocContent(java);
|
||||
if (text != null) {
|
||||
return search.nextWord(text.getBytes()) != -1;
|
||||
}
|
||||
try (FileChannel channel = FileChannel.open(java)) {
|
||||
// Read up to 1 MB of data from file
|
||||
@@ -228,9 +260,9 @@ public class StringSearch {
|
||||
|
||||
private static boolean containsString(Path java, String query) {
|
||||
StringSearch search = new StringSearch(query);
|
||||
if (FileStore.activeDocuments().contains(java)) {
|
||||
byte[] text = FileStore.contents(java).getBytes();
|
||||
return search.next(text) != -1;
|
||||
String text = tryGetActiveDocContent(java);
|
||||
if (text != null) {
|
||||
return search.next(text.getBytes()) != -1;
|
||||
}
|
||||
try (FileChannel channel = FileChannel.open(java)) {
|
||||
// Read up to 1 MB of data from file
|
||||
@@ -345,7 +377,7 @@ public class StringSearch {
|
||||
public static String packageName(Path file) {
|
||||
Pattern packagePattern = Pattern.compile("^package +(.*);");
|
||||
Pattern startOfClass = Pattern.compile("^[\\w ]*class +\\w+");
|
||||
try (BufferedReader lines = FileStore.lines(file)) {
|
||||
try (BufferedReader lines = ProjectManager.INSTANCE.getReader(file)) {
|
||||
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
|
||||
if (startOfClass.matcher(line).find()) return "";
|
||||
Matcher matchPackage = packagePattern.matcher(line);
|
||||
@@ -368,6 +400,20 @@ public class StringSearch {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Convert from line/column (1-based) to offset (0-based) */
|
||||
public static int offset(String contents, int line, int column) {
|
||||
line--;
|
||||
column--;
|
||||
int cursor = 0;
|
||||
while (line > 0) {
|
||||
if (contents.charAt(cursor) == '\n') {
|
||||
line--;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
return cursor + column;
|
||||
}
|
||||
|
||||
private static class Slice {
|
||||
private final byte[] target;
|
||||
private int from, until;
|
||||
+32
-4
@@ -19,6 +19,7 @@ package com.itsaky.androidide.projects.api
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie.SourceNode
|
||||
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
|
||||
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
@@ -46,6 +47,8 @@ class ModuleProjectTest {
|
||||
.get()
|
||||
ProjectManager.setupProject(project)
|
||||
|
||||
verifyProjectManagerAPIs()
|
||||
|
||||
val root = ProjectManager.rootProject
|
||||
assertThat(root).isNotNull()
|
||||
assertThat(root!!.name).isEqualTo("TestApp")
|
||||
@@ -152,13 +155,38 @@ class ModuleProjectTest {
|
||||
|
||||
app.indexSourcesAndClasspaths()
|
||||
|
||||
val classNames = app.compileSourceClasses.findClassNames("com.itsaky")
|
||||
assertThat(classNames).isNotNull()
|
||||
assertThat(classNames).isNotEmpty()
|
||||
assertThat(classNames)
|
||||
val classes = app.compileJavaSourceClasses.findInPackage("com.itsaky")
|
||||
assertThat(classes).isNotNull()
|
||||
assertThat(classes).isNotEmpty()
|
||||
assertThat(classes.map { it.qualifiedName })
|
||||
.containsExactly(
|
||||
"com.itsaky.androidide.tooling.test.Main",
|
||||
"com.itsaky.test.app.MainActivity"
|
||||
)
|
||||
assertThat(classes.map { it.isClass }).containsExactly(true, true)
|
||||
assertThat(classes.map { it::class.java })
|
||||
.containsExactly(SourceNode::class.java, SourceNode::class.java)
|
||||
}
|
||||
|
||||
private fun verifyProjectManagerAPIs() {
|
||||
val root = ProjectManager.rootProject
|
||||
assertThat(root).isNotNull()
|
||||
assertThat(root!!.path).isEqualTo(":")
|
||||
|
||||
val source =
|
||||
root.projectDir.toPath().resolve("app/src/main/java/com/itsaky/test/app/MainActivity.java")
|
||||
val moduleForFile = root.findModuleForFile(source)
|
||||
assertThat(moduleForFile).isNotNull()
|
||||
assertThat(moduleForFile!!.path).isEqualTo(":app")
|
||||
assertThat(moduleForFile.projectDir.path).isEqualTo(File(root.projectDir, "app").path)
|
||||
|
||||
val sourceNode = moduleForFile.compileJavaSourceClasses.findSource(source)
|
||||
assertThat(sourceNode).isNotNull()
|
||||
assertThat(sourceNode!!.isClass).isTrue()
|
||||
assertThat(sourceNode.name).isEqualTo("MainActivity")
|
||||
assertThat(sourceNode.packageName).isEqualTo("com.itsaky.test.app")
|
||||
assertThat(sourceNode.qualifiedName).isEqualTo("com.itsaky.test.app.MainActivity")
|
||||
assertThat(sourceNode.children).isEmpty()
|
||||
assertThat(Files.isSameFile(source, sourceNode.file)).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-8
@@ -32,17 +32,19 @@ class ClassTrieTest {
|
||||
fun testAppend() {
|
||||
val trie = ClassTrie()
|
||||
trie.append("java.lang.String")
|
||||
trie.append("java.lang.String.Inner")
|
||||
trie.append("java.lang.Object")
|
||||
trie.append("java.util.List")
|
||||
trie.append("java.io.File")
|
||||
trie.append("java.nio.file.Path")
|
||||
|
||||
assertThat(trie.findClassNames("java.lang"))
|
||||
.containsExactly("java.lang.String", "java.lang.Object")
|
||||
.containsExactly("java.lang.String", "java.lang.String.Inner", "java.lang.Object")
|
||||
assertThat(trie.findInPackage("java.lang").map { it.isClass }).containsExactly(true, true, true)
|
||||
assertThat(trie.findClassNames("java.util")).containsExactly("java.util.List")
|
||||
assertThat(trie.findClassNames("java.nio")).containsExactly("java.nio.file.Path")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testRemoval() {
|
||||
val trie = ClassTrie()
|
||||
@@ -51,19 +53,30 @@ class ClassTrieTest {
|
||||
trie.append("java.util.List")
|
||||
trie.append("java.io.File")
|
||||
trie.append("java.nio.file.Path")
|
||||
|
||||
|
||||
assertThat(trie.allClassNames())
|
||||
.containsExactly(
|
||||
"java.lang.String",
|
||||
"java.lang.Object",
|
||||
"java.util.List",
|
||||
"java.io.File",
|
||||
"java.nio.file.Path"
|
||||
)
|
||||
|
||||
assertThat(trie.contains("java.io.File")).isTrue()
|
||||
assertThat(trie.contains("java.nio.file.Path")).isTrue()
|
||||
|
||||
|
||||
trie.remove("java.lang.String")
|
||||
trie.remove("java.nio.file.Path")
|
||||
|
||||
|
||||
assertThat(trie.allClassNames())
|
||||
.containsExactly("java.lang.Object", "java.util.List", "java.io.File")
|
||||
|
||||
assertThat(trie.contains("java.io.File")).isTrue()
|
||||
assertThat(trie.contains("java.nio.file.Path")).isFalse()
|
||||
assertThat(trie.contains("java.lang.String")).isFalse()
|
||||
|
||||
assertThat(trie.findClassNames("java.lang"))
|
||||
.containsExactly("java.lang.Object")
|
||||
|
||||
assertThat(trie.findClassNames("java.lang")).containsExactly("java.lang.Object")
|
||||
assertThat(trie.findClassNames("java.util")).containsExactly("java.util.List")
|
||||
assertThat(trie.findClassNames("java.nio")).isEmpty()
|
||||
}
|
||||
|
||||
Binary file not shown.
+1
-2
@@ -15,7 +15,7 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.models
|
||||
package com.itsaky.androidide.models
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -203,4 +203,3 @@ open class Range(var start: Position, var end: Position) : Comparable<Range> {
|
||||
}
|
||||
}
|
||||
|
||||
data class ExpandSelectionParams(var file: Path, var selection: Range)
|
||||
@@ -46,6 +46,7 @@ android {
|
||||
dependencies {
|
||||
implementation common.editor
|
||||
implementation project (':build-tools:fuzzysearch')
|
||||
implementation project (':build-tools:eventbus-events')
|
||||
|
||||
implementation androidx.appcompat
|
||||
implementation androidx.ktx
|
||||
@@ -53,7 +54,9 @@ dependencies {
|
||||
implementation common.utilcode
|
||||
implementation google.material
|
||||
|
||||
compileOnly project (':build-tools:projects')
|
||||
api project (':build-tools:projects')
|
||||
api project (':lsp:models')
|
||||
|
||||
compileOnly project (':xml-inflater')
|
||||
compileOnly project (':actions')
|
||||
compileOnly project (':common')
|
||||
|
||||
+25
@@ -20,6 +20,13 @@ package com.itsaky.androidide.lsp.api;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
@@ -37,6 +44,9 @@ public class DefaultLanguageServerRegistry extends ILanguageServerRegistry {
|
||||
|
||||
@Override
|
||||
public void register(@NonNull final ILanguageServer server) {
|
||||
if (!EventBus.getDefault().isRegistered(this)) {
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
final var old = mRegister.put(server.getServerId(), server);
|
||||
@@ -64,6 +74,7 @@ public class DefaultLanguageServerRegistry extends ILanguageServerRegistry {
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
EventBus.getDefault().unregister(this);
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
for (var server : mRegister.values()) {
|
||||
@@ -91,4 +102,18 @@ public class DefaultLanguageServerRegistry extends ILanguageServerRegistry {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||
@SuppressWarnings("unused")
|
||||
public void onProjectInitialized(ProjectInitializedEvent event) {
|
||||
final var project = event.get(Project.class);
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Dispatching ProjectInitializedEvent to language servers...");
|
||||
for (final var server : mRegister.values()) {
|
||||
server.setupWithProject(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem;
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams;
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult;
|
||||
import com.itsaky.androidide.lsp.models.CompletionsKt;
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@@ -51,7 +52,7 @@ import java.nio.file.Path;
|
||||
*/
|
||||
public interface ICompletionProvider {
|
||||
|
||||
int MIN_MATCH_RATIO = 59;
|
||||
int MIN_MATCH_RATIO = CompletionsKt.MIN_MATCH_RATIO;
|
||||
|
||||
default boolean canComplete(Path file) {
|
||||
return file != null && Files.exists(file) && !Files.isDirectory(file);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* 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.androidide.lsp.api;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentSaveEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Handles documents of a language server implementations.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IDocumentHandler {
|
||||
|
||||
/**
|
||||
* Check if this document handler can accept the given file or not.
|
||||
*
|
||||
* @param file The file to check.
|
||||
* @return <code>true</code> if the document handler can accept this file, <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
boolean accepts(Path file);
|
||||
|
||||
/**
|
||||
* Notification sent to this handler to notify the file open event. This event tells the document
|
||||
* handler that the client is now handling the contents of this file.
|
||||
*
|
||||
* @param event The document open event data.
|
||||
*/
|
||||
void onFileOpened(DocumentOpenEvent event);
|
||||
|
||||
/**
|
||||
* Notify the language server that file's content has been changed. Handler implementations should
|
||||
* update the contents and last modified timestamp of this file.
|
||||
*
|
||||
* @param event The document change event data.
|
||||
*/
|
||||
void onContentChange(DocumentChangeEvent event);
|
||||
|
||||
/**
|
||||
* Notify the document handler that the given file's contents were saved. Document handlers can
|
||||
* optionally run a lint check and provide diagnostics for the file.
|
||||
*
|
||||
* @param event The document save event data.
|
||||
*/
|
||||
void onFileSaved(DocumentSaveEvent event);
|
||||
|
||||
/**
|
||||
* Notify the document handler that the given file was closed and the client is no longer handling
|
||||
* this file. Document handlers should remove any caches or data stored about this file and
|
||||
* optionally clear the diagnostics of this file.
|
||||
*
|
||||
* @param event The document close event data.
|
||||
*/
|
||||
void onFileClosed(DocumentCloseEvent event);
|
||||
|
||||
/**
|
||||
* Notify the document handler that the user has selected the given file for editing. Usually, the
|
||||
* given file is visible to user.
|
||||
*
|
||||
* @param path The file that was selected.
|
||||
*/
|
||||
void onFileSelected(@NonNull Path path);
|
||||
}
|
||||
@@ -36,6 +36,8 @@ package com.itsaky.androidide.lsp.api;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams;
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionParams;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticResult;
|
||||
@@ -43,12 +45,14 @@ import com.itsaky.androidide.lsp.models.ExpandSelectionParams;
|
||||
import com.itsaky.androidide.lsp.models.FormatCodeParams;
|
||||
import com.itsaky.androidide.lsp.models.InitializeParams;
|
||||
import com.itsaky.androidide.lsp.models.LSPFailure;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.lsp.models.ServerCapabilities;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@@ -131,12 +135,22 @@ public interface ILanguageServer {
|
||||
void configurationChanged(Object newConfiguration);
|
||||
|
||||
/**
|
||||
* Get the completion provider associated with this language server. Should never be null.
|
||||
* Setup this language server with the given project. Servers are not expected to keep a reference
|
||||
* to the provided project. Instead, use {@link ProjectManager#getRootProject()} to obtain the
|
||||
* project instance.
|
||||
*
|
||||
* @param project The initialized project.
|
||||
*/
|
||||
void setupWithProject(@NonNull Project project);
|
||||
|
||||
/**
|
||||
* Compute code completions for the given completion params.
|
||||
*
|
||||
* @param params The completion params.
|
||||
* @return The completion provider.
|
||||
*/
|
||||
@NonNull
|
||||
ICompletionProvider getCompletionProvider();
|
||||
CompletionResult complete(CompletionParams params);
|
||||
|
||||
/**
|
||||
* Find references using the given params.
|
||||
@@ -195,14 +209,6 @@ public interface ILanguageServer {
|
||||
return params.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* The document handler associated with this language server instance.
|
||||
*
|
||||
* @return The document handler. Must not be null.
|
||||
*/
|
||||
@NonNull
|
||||
IDocumentHandler getDocumentHandler();
|
||||
|
||||
/**
|
||||
* Handle failure caused by LSP
|
||||
*
|
||||
|
||||
+21
-11
@@ -17,11 +17,9 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.internal.model
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.Position
|
||||
import java.nio.file.Files
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
@@ -30,14 +28,24 @@ import java.nio.file.Paths
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CachedCompletion
|
||||
private constructor(val params: CompletionParams, val result: CompletionResult) {
|
||||
private constructor(
|
||||
val params: com.itsaky.androidide.lsp.models.CompletionParams,
|
||||
val result: com.itsaky.androidide.lsp.models.CompletionResult
|
||||
) {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
companion object {
|
||||
|
||||
/** Empty cached completion. Could be used to represent "no cache available". */
|
||||
@JvmField
|
||||
val EMPTY = cache(CompletionParams(Position.NONE, Paths.get("")), CompletionResult.EMPTY)
|
||||
val EMPTY =
|
||||
cache(
|
||||
com.itsaky.androidide.lsp.models.CompletionParams(
|
||||
com.itsaky.androidide.models.Position.NONE,
|
||||
Paths.get("")
|
||||
),
|
||||
com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates cached version of the result from the given params and result.
|
||||
@@ -47,19 +55,21 @@ private constructor(val params: CompletionParams, val result: CompletionResult)
|
||||
* @param result The result of the completion to cache.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cache(_params: CompletionParams, result: CompletionResult): CachedCompletion {
|
||||
fun cache(
|
||||
_params: com.itsaky.androidide.lsp.models.CompletionParams,
|
||||
result: com.itsaky.androidide.lsp.models.CompletionResult
|
||||
): CachedCompletion {
|
||||
val params =
|
||||
CompletionParams(_params.position, _params.file).apply {
|
||||
com.itsaky.androidide.lsp.models.CompletionParams(_params.position, _params.file).apply {
|
||||
prefix = _params.prefix ?: ""
|
||||
content = ""
|
||||
module = null
|
||||
}
|
||||
|
||||
return CachedCompletion(params, result)
|
||||
}
|
||||
}
|
||||
|
||||
fun canUseCache(params: CompletionParams): Boolean {
|
||||
fun canUseCache(params: com.itsaky.androidide.lsp.models.CompletionParams): Boolean {
|
||||
val partial = params.requirePrefix()
|
||||
val position = this.params.position
|
||||
val file = this.params.file
|
||||
@@ -87,7 +97,7 @@ private constructor(val params: CompletionParams, val result: CompletionResult)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!Files.isSameFile(file, params.file)) {
|
||||
if (!DocumentUtils.isSameFile(file, params.file)) {
|
||||
log.info("...no cache available for current file")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ package com.itsaky.androidide.lsp.util;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,5 +23,5 @@ import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class NoCompletionsProvider : ICompletionProvider {
|
||||
override fun complete(params: CompletionParams?): CompletionResult = CompletionResult.EMPTY
|
||||
override fun complete(params: com.itsaky.androidide.lsp.models.CompletionParams?): com.itsaky.androidide.lsp.models.CompletionResult = com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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.androidide.lsp.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.api.IDocumentHandler;
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentSaveEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class NoDocumentHandler implements IDocumentHandler {
|
||||
|
||||
@Override
|
||||
public boolean accepts(Path file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileOpened(DocumentOpenEvent event) {}
|
||||
|
||||
@Override
|
||||
public void onContentChange(DocumentChangeEvent event) {}
|
||||
|
||||
@Override
|
||||
public void onFileSaved(DocumentSaveEvent event) {}
|
||||
|
||||
@Override
|
||||
public void onFileClosed(DocumentCloseEvent event) {}
|
||||
|
||||
@Override
|
||||
public void onFileSelected(@NonNull Path path) {}
|
||||
}
|
||||
@@ -17,8 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.util
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils
|
||||
import java.nio.file.Path
|
||||
|
||||
/** @author Akash Yadav */
|
||||
@@ -26,11 +25,7 @@ class PathUtils {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun isSameFile(first: Path, second: Path): Boolean {
|
||||
return try {
|
||||
Files.isSameFile(first, second)
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
return DocumentUtils.isSameFile(first, second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
package com.itsaky.androidide.lsp.java;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.CharSequenceInputStream;
|
||||
import com.itsaky.androidide.utils.CharSequenceReader;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.java.utils.StringSearch;
|
||||
import com.itsaky.androidide.lsp.java.utils.TypeUtils;
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.util.PathUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
public class FileStore {
|
||||
|
||||
private static final Set<Path> workspaceRoots = new HashSet<>();
|
||||
private static final Map<Path, VersionedContent> activeDocuments = new HashMap<>();
|
||||
|
||||
/** javaSources[file] is the javaSources time of a .java source file. */
|
||||
// TODO organize by package name for speed of list(...)
|
||||
private static final TreeMap<Path, Info> javaSources = new TreeMap<>();
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("FileStore");
|
||||
|
||||
public static void shutdown() {
|
||||
workspaceRoots.clear();
|
||||
activeDocuments.clear();
|
||||
javaSources.clear();
|
||||
}
|
||||
|
||||
public static void configurationChanged(Set<Path> newWorkspaceRoots) {
|
||||
javaSources.clear();
|
||||
workspaceRoots.clear();
|
||||
setWorkspaceRoots(newWorkspaceRoots);
|
||||
}
|
||||
|
||||
public static void setWorkspaceRoots(Set<Path> newRoots) {
|
||||
newRoots.removeIf(it -> !Files.exists(it) || !Files.isReadable(it));
|
||||
for (Path root : workspaceRoots) {
|
||||
if (!newRoots.contains(root)) {
|
||||
workspaceRoots.removeIf(f -> f.startsWith(root));
|
||||
}
|
||||
}
|
||||
for (Path root : newRoots) {
|
||||
if (!workspaceRoots.contains(root)) {
|
||||
addFiles(root);
|
||||
}
|
||||
}
|
||||
workspaceRoots.clear();
|
||||
workspaceRoots.addAll(newRoots);
|
||||
}
|
||||
|
||||
private static void addFiles(Path root) {
|
||||
if (!Files.isReadable(root) || !Files.isWritable(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.walkFileTree(root, new FindJavaSources());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Path> all() {
|
||||
return javaSources.keySet();
|
||||
}
|
||||
|
||||
public static boolean contains(Path file) {
|
||||
return isJavaFile(file) && javaSources.containsKey(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.
|
||||
// This becomes evident when javac starts recompiling *way too much* on each task,
|
||||
// because it doesn't realize there are already up-to-date .class files.
|
||||
// The better solution would be for java-language server to detect the presence of
|
||||
// module-info.java,
|
||||
// and go into its own "module mode" where it infers a module source path and a module class
|
||||
// path.
|
||||
return name.endsWith(".java") && !Files.isDirectory(file) && !name.equals("module-info.java");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// If we've never checked before, look up modified time on disk
|
||||
if (!javaSources.containsKey(file)) {
|
||||
readInfoFromDisk(file);
|
||||
}
|
||||
// Look up modified time from cache
|
||||
return Objects.requireNonNull(javaSources.get(file)).modified;
|
||||
}
|
||||
|
||||
private static void readInfoFromDisk(Path file) {
|
||||
try {
|
||||
Instant time = Files.getLastModifiedTime(file).toInstant();
|
||||
String packageName = StringSearch.packageName(file);
|
||||
javaSources.put(file, new Info(time, packageName));
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.warn(e);
|
||||
javaSources.remove(file);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String suggestedPackageName(Path file) {
|
||||
// Look in each parent directory of file
|
||||
for (Path dir = file.getParent(); dir != null; dir = dir.getParent()) {
|
||||
// Try to find a sibling with a package declaration
|
||||
for (Path sibling : javaSourcesIn(dir)) {
|
||||
if (PathUtils.isSameFile(sibling, file)) {
|
||||
continue;
|
||||
}
|
||||
String packageName = packageName(sibling);
|
||||
if (TextUtils.isEmpty(packageName.trim())) {
|
||||
continue;
|
||||
}
|
||||
Path relativePath = dir.relativize(file.getParent());
|
||||
String relativePackage = relativePath.toString().replace(File.separatorChar, '.');
|
||||
if (!relativePackage.isEmpty()) {
|
||||
packageName = packageName + "." + relativePackage;
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static List<Path> javaSourcesIn(Path dir) {
|
||||
Map<Path, Info> tail = javaSources.tailMap(dir, false);
|
||||
List<Path> list = new ArrayList<>();
|
||||
for (Path file : tail.keySet()) {
|
||||
if (!file.startsWith(dir)) {
|
||||
break;
|
||||
}
|
||||
list.add(file);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static String packageName(Path file) {
|
||||
// If we've never checked before, look up package name on disk
|
||||
if (!javaSources.containsKey(file)) {
|
||||
readInfoFromDisk(file);
|
||||
}
|
||||
// Look up package name from cache
|
||||
return Objects.requireNonNull(javaSources.get(file)).packageName;
|
||||
}
|
||||
|
||||
public static void externalCreate(Path file) {
|
||||
readInfoFromDisk(file);
|
||||
}
|
||||
|
||||
public static void externalChange(Path file) {
|
||||
readInfoFromDisk(file);
|
||||
}
|
||||
|
||||
public static void externalDelete(Path file) {
|
||||
javaSources.remove(file);
|
||||
}
|
||||
|
||||
public static void open(DocumentOpenEvent params) {
|
||||
Path document = params.getOpenedFile();
|
||||
if (!isJavaFile(document)) {
|
||||
LOG.error("Opened file is not a java file. Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
activeDocuments.put(
|
||||
document, new VersionedContent(params.getText(), params.getVersion(), Range.NONE));
|
||||
}
|
||||
|
||||
public static void change(DocumentChangeEvent params) {
|
||||
final Path document = params.getChangedFile();
|
||||
if (!isJavaFile(document)) {
|
||||
LOG.error("Changed file is not a java file. Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
// We do not support range changes
|
||||
String newText = params.getNewText();
|
||||
activeDocuments.put(
|
||||
document, new VersionedContent(newText, params.getVersion(), params.getChangeRange()));
|
||||
}
|
||||
|
||||
public static void close(DocumentCloseEvent params) {
|
||||
final Path document = params.getClosedFile();
|
||||
if (!isJavaFile(document)) {
|
||||
LOG.error("Closed file is not a java file. Ignoring...");
|
||||
return;
|
||||
}
|
||||
activeDocuments.remove(document);
|
||||
}
|
||||
|
||||
public static Set<Path> activeDocuments() {
|
||||
return activeDocuments.keySet();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VersionedContent versionedContent(Path file) {
|
||||
return activeDocuments.get(file);
|
||||
}
|
||||
|
||||
public static String contents(Path file) {
|
||||
if (!isJavaFile(file)) {
|
||||
throw new RuntimeException(file + " is not a java file");
|
||||
}
|
||||
|
||||
if (activeDocuments.containsKey(file)) {
|
||||
return Objects.requireNonNull(activeDocuments.get(file)).content;
|
||||
}
|
||||
|
||||
try (final BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream inputStream(Path file) {
|
||||
if (activeDocuments.containsKey(file)) {
|
||||
CharSequence content = Objects.requireNonNull(activeDocuments.get(file)).content;
|
||||
return new CharSequenceInputStream(content, "UTF-8");
|
||||
}
|
||||
|
||||
try {
|
||||
return Files.newInputStream(file);
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.warn(e);
|
||||
byte[] bs = {};
|
||||
return new ByteArrayInputStream(bs);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedReader lines(Path file) {
|
||||
return bufferedReader(file);
|
||||
}
|
||||
|
||||
public static BufferedReader bufferedReader(Path file) {
|
||||
if (activeDocuments.containsKey(file)) {
|
||||
CharSequence string = Objects.requireNonNull(activeDocuments.get(file)).content;
|
||||
return new BufferedReader(new CharSequenceReader(string));
|
||||
}
|
||||
|
||||
try {
|
||||
return Files.newBufferedReader(file);
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.warn(e);
|
||||
return new BufferedReader(new StringReader(""));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert from line/column (1-based) to offset (0-based) */
|
||||
public static int offset(String contents, int line, int column) {
|
||||
line--;
|
||||
column--;
|
||||
int cursor = 0;
|
||||
while (line > 0) {
|
||||
if (contents.charAt(cursor) == '\n') {
|
||||
line--;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
return cursor + column;
|
||||
}
|
||||
|
||||
public static Optional<Path> findDeclaringFile(@NonNull TypeElement el) {
|
||||
String qualifiedName = el.getQualifiedName().toString();
|
||||
String packageName = StringSearch.mostName(qualifiedName);
|
||||
String className = StringSearch.lastName(qualifiedName);
|
||||
// Fast path: look for text `class Foo` in file Foo.java
|
||||
for (Path f : list(packageName)) {
|
||||
if (f.getFileName().toString().equals(className) && TypeUtils.containsType(f, el)) {
|
||||
return Optional.of(f);
|
||||
}
|
||||
}
|
||||
// Slow path: look for text `class Foo` in any file in package
|
||||
for (Path f : list(packageName)) {
|
||||
if (TypeUtils.containsType(f, el)) {
|
||||
return Optional.of(f);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
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)) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static class FindJavaSources extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes _attrs) {
|
||||
if (isJavaFile(file)) {
|
||||
readInfoFromDisk(file);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Info {
|
||||
final Instant modified;
|
||||
final String packageName;
|
||||
|
||||
Info(Instant modified, String packageName) {
|
||||
this.modified = modified;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
}
|
||||
|
||||
static class VersionedContent {
|
||||
final String content;
|
||||
final int version;
|
||||
final Range modifiedRange;
|
||||
final Instant modified = Instant.now();
|
||||
|
||||
VersionedContent(String content, int version, final Range modifiedRange) {
|
||||
this.modifiedRange = modifiedRange;
|
||||
Objects.requireNonNull(content, "content is null");
|
||||
this.content = content;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.androidide.lsp.java;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
|
||||
/**
|
||||
* Provides {@link JavaCompilerService} instances for different {@link ModuleProject}s.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class JavaCompilerProvider {
|
||||
|
||||
private static JavaCompilerProvider sInstance;
|
||||
private volatile JavaCompilerService compilerService;
|
||||
|
||||
private JavaCompilerProvider() {}
|
||||
|
||||
public static JavaCompilerProvider getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new JavaCompilerProvider();
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static JavaCompilerService get(ModuleProject module) {
|
||||
return JavaCompilerProvider.getInstance().forModule(module);
|
||||
}
|
||||
|
||||
public synchronized JavaCompilerService forModule(ModuleProject module) {
|
||||
// A module instance is set to the compiler only in case the project is initialized or
|
||||
// this method was called with other mdoule instance.
|
||||
if (compilerService == null || compilerService.getModule() != module) {
|
||||
compilerService = new JavaCompilerService(module);
|
||||
}
|
||||
|
||||
return compilerService;
|
||||
}
|
||||
|
||||
public synchronized void destory() {
|
||||
if (compilerService != null) {
|
||||
compilerService.destroy();
|
||||
}
|
||||
compilerService = null;
|
||||
}
|
||||
}
|
||||
@@ -19,18 +19,16 @@ package com.itsaky.androidide.lsp.java;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.api.ICompletionProvider;
|
||||
import com.itsaky.androidide.lsp.api.IDocumentHandler;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentSelectedEvent;
|
||||
import com.itsaky.androidide.javac.services.CancelAbort;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageClient;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings;
|
||||
import com.itsaky.androidide.lsp.internal.model.CachedCompletion;
|
||||
import com.itsaky.androidide.lsp.java.actions.JavaCodeActionsMenu;
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService;
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerConfiguration;
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerSettings;
|
||||
import com.itsaky.androidide.lsp.java.providers.CodeFormatProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.CompletionProvider;
|
||||
@@ -40,34 +38,38 @@ import com.itsaky.androidide.lsp.java.providers.JavaSelectionProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.ReferenceProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.SignatureProvider;
|
||||
import com.itsaky.androidide.lsp.java.utils.AnalyzeTimer;
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams;
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionParams;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticResult;
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.androidide.lsp.models.DocumentSaveEvent;
|
||||
import com.itsaky.androidide.lsp.models.ExpandSelectionParams;
|
||||
import com.itsaky.androidide.lsp.models.FormatCodeParams;
|
||||
import com.itsaky.androidide.lsp.models.InitializeParams;
|
||||
import com.itsaky.androidide.lsp.models.LSPFailure;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.lsp.models.ServerCapabilities;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.androidide.lsp.util.LSPEditorActions;
|
||||
import com.itsaky.androidide.lsp.util.NoCompletionsProvider;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import com.itsaky.androidide.javac.services.CancelAbort;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
public class JavaLanguageServer implements ILanguageServer {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("JavaLanguageServer");
|
||||
public static final String SERVER_ID = "java";
|
||||
@@ -77,17 +79,12 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
private ILanguageClient client;
|
||||
private IServerSettings settings;
|
||||
private Path selectedFile;
|
||||
private JavaCompilerService compilerService;
|
||||
private JavaServerConfiguration configuration;
|
||||
private CachedCompletion cachedCompletion;
|
||||
private ServerCapabilities capabilities;
|
||||
private boolean initialized;
|
||||
private boolean createCompiler;
|
||||
|
||||
public JavaLanguageServer() {
|
||||
this.initialized = false;
|
||||
this.createCompiler = true;
|
||||
this.configuration = new JavaServerConfiguration();
|
||||
this.completionProvider = new CompletionProvider();
|
||||
this.diagnosticProvider = new JavaDiagnosticProvider(this.completionProvider::isCompleting);
|
||||
this.cachedCompletion = CachedCompletion.EMPTY;
|
||||
@@ -117,6 +114,24 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected JavaCompilerService getCompiler(Path file) {
|
||||
if (!DocumentUtils.isJavaFile(file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Project root = ProjectManager.INSTANCE.getRootProject();
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ModuleProject module = root.findModuleForFile(file);
|
||||
if (module == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JavaCompilerProvider.get(module);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerId() {
|
||||
return SERVER_ID;
|
||||
@@ -129,8 +144,6 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
throw new AlreadyInitializedException();
|
||||
}
|
||||
|
||||
FileStore.setWorkspaceRoots(params.getWorkspaceRoots());
|
||||
|
||||
capabilities = new ServerCapabilities();
|
||||
capabilities.setCompletionsAvailable(true);
|
||||
capabilities.setCodeActionsAvailable(true);
|
||||
@@ -141,7 +154,9 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
capabilities.setSmartSelectionsEnabled(true);
|
||||
|
||||
LSPEditorActions.ensureActionsMenuRegistered(JavaCodeActionsMenu.class);
|
||||
|
||||
if (EventBus.getDefault().isRegistered(this)) {
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@@ -158,13 +173,8 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (compilerService != null) {
|
||||
compilerService.close();
|
||||
compilerService = null;
|
||||
createCompiler = true;
|
||||
}
|
||||
|
||||
FileStore.shutdown();
|
||||
JavaCompilerProvider.getInstance().destory();
|
||||
EventBus.getDefault().unregister(this);
|
||||
timer.shutdown();
|
||||
initialized = false;
|
||||
}
|
||||
@@ -186,101 +196,79 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurationChanged(Object newConfiguration) {
|
||||
if (!(newConfiguration instanceof JavaServerConfiguration)) {
|
||||
LOG.error("Invalid configuration passed to server.", newConfiguration);
|
||||
LOG.error("Configuration change event will be ignored.");
|
||||
return;
|
||||
}
|
||||
public void configurationChanged(Object newConfiguration) {}
|
||||
|
||||
this.configuration = (JavaServerConfiguration) newConfiguration;
|
||||
LOG.info("Java language server configuration changed.");
|
||||
LOG.info(
|
||||
this.configuration.getClassPaths().size(),
|
||||
"class paths and",
|
||||
this.configuration.getSourceDirs().size(),
|
||||
"source directories were provided in the configuration");
|
||||
// Compiler must be recreated on a configuration change
|
||||
this.createCompiler = true;
|
||||
|
||||
FileStore.configurationChanged(this.configuration.getSourceDirs());
|
||||
}
|
||||
@Override
|
||||
public void setupWithProject(@NonNull final Project project) {}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ICompletionProvider getCompletionProvider() {
|
||||
if (!settings.completionsEnabled()) {
|
||||
return new NoCompletionsProvider();
|
||||
public CompletionResult complete(final CompletionParams params) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (compiler == null
|
||||
|| !settings.completionsEnabled()
|
||||
|| !this.completionProvider.canComplete(params.getFile())) {
|
||||
return CompletionResult.EMPTY;
|
||||
}
|
||||
|
||||
return this.completionProvider.reset(
|
||||
getCompiler(), this.settings, this.cachedCompletion, this::updateCachedCompletion);
|
||||
this.completionProvider.reset(
|
||||
compiler, this.settings, this.cachedCompletion, this::updateCachedCompletion);
|
||||
return this.completionProvider.complete(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ReferenceResult findReferences(@NonNull ReferenceParams params) {
|
||||
if (!settings.referencesEnabled()) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.referencesEnabled() || compiler == null) {
|
||||
return new ReferenceResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
return new ReferenceProvider(getCompiler()).findReferences(params);
|
||||
return new ReferenceProvider(compiler).findReferences(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DefinitionResult findDefinition(@NonNull DefinitionParams params) {
|
||||
if (!settings.definitionsEnabled()) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.definitionsEnabled() || compiler == null) {
|
||||
return new DefinitionResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
return new DefinitionProvider(getCompiler()).findDefinition(params);
|
||||
return new DefinitionProvider(compiler).findDefinition(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Range expandSelection(@NonNull ExpandSelectionParams params) {
|
||||
if (!settings.smartSelectionsEnabled()) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.smartSelectionsEnabled() || compiler == null) {
|
||||
return params.getSelection();
|
||||
}
|
||||
|
||||
return new JavaSelectionProvider(getCompiler()).expandSelection(params);
|
||||
return new JavaSelectionProvider(compiler).expandSelection(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SignatureHelp signatureHelp(@NonNull SignatureHelpParams params) {
|
||||
if (!settings.signatureHelpEnabled()) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.signatureHelpEnabled() || compiler == null) {
|
||||
return new SignatureHelp(Collections.emptyList(), -1, -1);
|
||||
}
|
||||
|
||||
return new SignatureProvider(getCompiler()).signatureHelp(params);
|
||||
return new SignatureProvider(compiler).signatureHelp(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DiagnosticResult analyze(@NonNull Path file) {
|
||||
if (!settings.codeAnalysisEnabled()) {
|
||||
final JavaCompilerService compiler = getCompiler(file);
|
||||
if (!settings.codeAnalysisEnabled() || compiler == null) {
|
||||
return DiagnosticResult.NO_UPDATE;
|
||||
}
|
||||
|
||||
return this.diagnosticProvider.analyze(getCompiler(), file);
|
||||
}
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
public JavaCompilerService getCompiler() {
|
||||
if (createCompiler) {
|
||||
LOG.info("Creating new compiler instance...");
|
||||
compilerService = createCompiler();
|
||||
createCompiler = false;
|
||||
}
|
||||
|
||||
return compilerService;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private JavaCompilerService createCompiler() {
|
||||
return new JavaCompilerService(configuration.getClassPaths(), Collections.emptySet());
|
||||
return this.diagnosticProvider.analyze(compiler, file);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -289,55 +277,11 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
return new CodeFormatProvider(getSettings()).format(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public IDocumentHandler getDocumentHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateCachedCompletion(CachedCompletion cachedCompletion) {
|
||||
Objects.requireNonNull(cachedCompletion);
|
||||
this.cachedCompletion = cachedCompletion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accepts(Path file) {
|
||||
return FileStore.isJavaFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileOpened(DocumentOpenEvent event) {
|
||||
onFileSelected(event.getOpenedFile());
|
||||
FileStore.open(event);
|
||||
startOrRestartAnalyzeTimer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChange(@NonNull DocumentChangeEvent event) {
|
||||
// If a file's content is changed, it is definitely visible to user.
|
||||
onFileSelected(event.getChangedFile());
|
||||
FileStore.change(event);
|
||||
if (compilerService != null) {
|
||||
compilerService.onContentChanged(event);
|
||||
}
|
||||
startOrRestartAnalyzeTimer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileSaved(DocumentSaveEvent event) {
|
||||
// TODO Run a lint check (or a simple compilation)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileClosed(DocumentCloseEvent event) {
|
||||
FileStore.close(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileSelected(@NonNull Path path) {
|
||||
this.selectedFile = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleFailure(final LSPFailure failure) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
@@ -348,15 +292,25 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (compilerService != null) {
|
||||
compilerService.close();
|
||||
}
|
||||
JavaCompilerProvider.getInstance().destory();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@SuppressWarnings("unused")
|
||||
public void onContentChange(@NonNull DocumentChangeEvent event) {
|
||||
startOrRestartAnalyzeTimer();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@SuppressWarnings("unused")
|
||||
public void onFileSelected(@NonNull DocumentSelectedEvent event) {
|
||||
this.selectedFile = event.getSelectedFile();
|
||||
}
|
||||
|
||||
private void startOrRestartAnalyzeTimer() {
|
||||
if (!this.timer.isStarted()) {
|
||||
this.timer.start();
|
||||
|
||||
@@ -22,8 +22,13 @@ import android.graphics.drawable.Drawable
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.lsp.java.FileStore
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@@ -41,6 +46,8 @@ abstract class BaseCodeAction : ActionItem {
|
||||
|
||||
protected abstract val titleTextRes: Int
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
|
||||
if (
|
||||
@@ -55,7 +62,7 @@ abstract class BaseCodeAction : ActionItem {
|
||||
}
|
||||
|
||||
val file = requireFile(data)
|
||||
visible = FileStore.isJavaFile(file.toPath())
|
||||
visible = DocumentUtils.isJavaFile(file.toPath())
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
@@ -77,4 +84,15 @@ abstract class BaseCodeAction : ActionItem {
|
||||
val method = klass.getDeclaredMethod("newMaterialDialogBuilder", Context::class.java)
|
||||
return method.invoke(null, data.get(Context::class.java)!!) as MaterialAlertDialogBuilder
|
||||
}
|
||||
|
||||
fun performCodeAction(data: ActionData, result: Rewrite) {
|
||||
val server = ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val client = server.client!!
|
||||
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(compiler, label))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ package com.itsaky.androidide.lsp.java.actions
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.Tree.Kind.VARIABLE
|
||||
@@ -47,7 +48,11 @@ abstract class FieldBasedAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, Range::class.java, CodeEditor::class.java)) {
|
||||
if (
|
||||
!visible ||
|
||||
!hasRequiredData(data, com.itsaky.androidide.models.Range::class.java, CodeEditor::class.java) ||
|
||||
ProjectManager.rootProject == null
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
@@ -57,11 +62,11 @@ abstract class FieldBasedAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val range = data[Range::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = requirePath(data)
|
||||
val module = ProjectManager.findModuleForFile(file) ?: return Any()
|
||||
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return JavaCompilerProvider.get(module).compile(file).get { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val type = triple.second
|
||||
val fields = triple.third
|
||||
@@ -76,7 +81,7 @@ abstract class FieldBasedAction : BaseCodeAction() {
|
||||
protected fun findFields(
|
||||
task: CompileTask,
|
||||
file: Path,
|
||||
range: Range
|
||||
range: com.itsaky.androidide.models.Range
|
||||
): Triple<FindTypeDeclarationAt, ClassTree, MutableList<VariableTree>> {
|
||||
// 1-based line and column index
|
||||
val startLine = range.start.line + 1
|
||||
|
||||
+42
-13
@@ -18,16 +18,19 @@ package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.google.common.collect.Iterables.toArray
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddImport
|
||||
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import javax.tools.Diagnostic
|
||||
import javax.tools.JavaFileObject
|
||||
|
||||
@@ -44,18 +47,26 @@ class AddImportAction() : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code || diagnostic.extra !is Diagnostic<*>) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val server = data.get(JavaLanguageServer::class.java)!!
|
||||
val file = requireFile(data)
|
||||
val module =
|
||||
ProjectManager.findModuleForFile(file)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val jcDiagnostic =
|
||||
@@ -67,7 +78,7 @@ class AddImportAction() : BaseCodeAction() {
|
||||
|
||||
var found = false
|
||||
val simpleName = jcDiagnostic.args[1]
|
||||
for (name in server.compiler.publicTopLevelTypes()) {
|
||||
for (name in compiler.publicTopLevelTypes()) {
|
||||
var klass = name
|
||||
|
||||
// This will be true in a test environment
|
||||
@@ -90,14 +101,22 @@ class AddImportAction() : BaseCodeAction() {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val diagnostic =
|
||||
JavaDiagnosticUtils.asUnwrapper(
|
||||
data.get(DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
)!!
|
||||
val server = data.get(JavaLanguageServer::class.java)!!
|
||||
val file = requireFile(data)
|
||||
val module =
|
||||
ProjectManager.findModuleForFile(file)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return Any()
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
|
||||
val titles = mutableListOf<String>()
|
||||
val rewrites = mutableListOf<AddImport>()
|
||||
val simpleName = diagnostic.d.args[1]
|
||||
for (name in server.compiler.publicTopLevelTypes()) {
|
||||
for (name in compiler.publicTopLevelTypes()) {
|
||||
var klass = name
|
||||
if (klass.contains('/')) {
|
||||
klass = klass.replace('/', '.')
|
||||
@@ -125,17 +144,27 @@ class AddImportAction() : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
val server = data.get(JavaLanguageServer::class.java)!!
|
||||
val client = server.client ?: return
|
||||
val file = requireFile(data)
|
||||
val actions = mutableListOf<CodeActionItem>()
|
||||
val module =
|
||||
ProjectManager.findModuleForFile(file)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
val server =
|
||||
ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
|
||||
as JavaLanguageServer
|
||||
val client = server.client ?: return
|
||||
val actions = mutableListOf<com.itsaky.androidide.lsp.models.CodeActionItem>()
|
||||
val titles = result.first as List<String>
|
||||
val rewrites = result.second as List<Rewrite>
|
||||
|
||||
for (index in rewrites.indices) {
|
||||
val name = titles[index]
|
||||
val rewrite = rewrites[index]
|
||||
val action = rewrite.asCodeActions(server.compiler, name)
|
||||
val action = rewrite.asCodeActions(compiler, name)
|
||||
actions.add(action)
|
||||
}
|
||||
|
||||
|
||||
+10
-12
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddException
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AddThrowsAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class AddThrowsAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,10 +52,11 @@ class AddThrowsAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return compiler.compile(file).get { task ->
|
||||
val needsThrow = CodeActionUtils.findMethod(task, diagnostic.range)
|
||||
val exceptionName = CodeActionUtils.extractExceptionName(diagnostic.message)
|
||||
return@get AddException(
|
||||
@@ -72,10 +74,6 @@ class AddThrowsAction : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.CreateMissingMethod
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CreateMissingMethodAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class CreateMissingMethodAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,10 +52,11 @@ class CreateMissingMethodAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
return server.compiler.compile(file).get {
|
||||
return compiler.compile(file).get {
|
||||
CreateMissingMethod(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
@@ -64,11 +66,7 @@ class CreateMissingMethodAction : BaseCodeAction() {
|
||||
log.warn("Unable to create missing method")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ConvertFieldToBlock
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FieldToBlockAction : BaseCodeAction() {
|
||||
@@ -43,12 +44,12 @@ class FieldToBlockAction : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -56,11 +57,12 @@ class FieldToBlockAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val file = requirePath(data)
|
||||
|
||||
return server.compiler.compile(file).get {
|
||||
return compiler.compile(file).get {
|
||||
ConvertFieldToBlock(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
@@ -70,11 +72,7 @@ class FieldToBlockAction : BaseCodeAction() {
|
||||
log.warn("Unable to convert field to block")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-19
@@ -17,15 +17,13 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ImplementAbstractMethods
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import java.io.File
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import javax.tools.Diagnostic
|
||||
import javax.tools.JavaFileObject
|
||||
|
||||
@@ -46,12 +44,12 @@ class ImplementAbstractMethodsAction : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code || diagnostic.extra !is Diagnostic<*>) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -71,7 +69,7 @@ class ImplementAbstractMethodsAction : BaseCodeAction() {
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic =
|
||||
JavaDiagnosticUtils.asJCDiagnostic(
|
||||
data.get(DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
)
|
||||
return ImplementAbstractMethods(diagnostic!!)
|
||||
}
|
||||
@@ -81,17 +79,7 @@ class ImplementAbstractMethodsAction : BaseCodeAction() {
|
||||
log.warn("Unable to perform action. Invalid result from execAction(..)")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data.get(JavaLanguageServer::class.java)!!
|
||||
val client = server.client
|
||||
if (client == null) {
|
||||
log.error("No client set to java language server")
|
||||
return
|
||||
}
|
||||
|
||||
client.performCodeAction(
|
||||
data.get(File::class.java)!!,
|
||||
result.asCodeActions(server.compiler, label)
|
||||
)
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveClass
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveClassAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class RemoveClassAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,11 +52,12 @@ class RemoveClassAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
|
||||
return server.compiler.compile(file).get {
|
||||
return compiler.compile(file).get {
|
||||
RemoveClass(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
@@ -65,11 +67,7 @@ class RemoveClassAction : BaseCodeAction() {
|
||||
log.warn("Unable to remove class")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler!!, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveMethod
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findMethod
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveMethodAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class RemoveMethodAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,11 +52,12 @@ class RemoveMethodAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
|
||||
return server.compiler.compile(file).get {
|
||||
return compiler.compile(file).get {
|
||||
val unusedMethod = findMethod(it, diagnostic.range)
|
||||
RemoveMethod(
|
||||
unusedMethod.className,
|
||||
@@ -70,11 +72,7 @@ class RemoveMethodAction : BaseCodeAction() {
|
||||
log.warn("Unable to remove method")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler!!, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveException
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveUnusedThrowsAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class RemoveUnusedThrowsAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,10 +52,11 @@ class RemoveUnusedThrowsAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val d = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val d = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return compiler.compile(file).get { task ->
|
||||
val notThrown = CodeActionUtils.extractNotThrownExceptionName(d.message)
|
||||
val methodWithExtraThrow = CodeActionUtils.findMethod(task, d.range)
|
||||
|
||||
@@ -72,11 +74,7 @@ class RemoveUnusedThrowsAction : BaseCodeAction() {
|
||||
log.warn("Unable to remove unused throws")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddSuppressWarningAnnotation
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SuppressUncheckedWarningAction : BaseCodeAction() {
|
||||
@@ -38,12 +39,12 @@ class SuppressUncheckedWarningAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -51,10 +52,11 @@ class SuppressUncheckedWarningAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return compiler.compile(file).get { task ->
|
||||
val warnedMethod = CodeActionUtils.findMethod(task, diagnostic.range)
|
||||
return@get AddSuppressWarningAnnotation(
|
||||
warnedMethod.className,
|
||||
@@ -70,10 +72,6 @@ class SuppressUncheckedWarningAction : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-12
@@ -17,15 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ConvertVariableToStatement
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import java.io.File
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class VariableToStatementAction : BaseCodeAction() {
|
||||
@@ -44,12 +44,12 @@ class VariableToStatementAction : BaseCodeAction() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -60,9 +60,9 @@ class VariableToStatementAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val compiler = server.compiler!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val path = requirePath(data)
|
||||
|
||||
return compiler.compile(path).get {
|
||||
@@ -75,10 +75,7 @@ class VariableToStatementAction : BaseCodeAction() {
|
||||
log.warn("Unable to convert variable to statement")
|
||||
return
|
||||
}
|
||||
|
||||
val file = data[File::class.java]
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -24,14 +24,15 @@ import com.github.javaparser.ast.Modifier
|
||||
import com.github.javaparser.ast.body.ConstructorDeclaration
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.StopWatch
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R.string
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.VariableTree
|
||||
@@ -69,11 +70,12 @@ class GenerateConstructorAction : FieldBasedAction() {
|
||||
}
|
||||
|
||||
private fun generateConstructor(data: ActionData, selected: MutableSet<String>) {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val range = data[Range::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = requirePath(data)
|
||||
|
||||
server.compiler.compile(file).run { task ->
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
|
||||
+10
-12
@@ -17,14 +17,15 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.GenerateRecordConstructor
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class GenerateMissingConstructorAction : BaseCodeAction() {
|
||||
@@ -37,12 +38,12 @@ class GenerateMissingConstructorAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
@@ -50,10 +51,11 @@ class GenerateMissingConstructorAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return compiler.compile(file).get { task ->
|
||||
val needsConstructor =
|
||||
CodeActionUtils.findClassNeedingConstructor(task, diagnostic.range) ?: return@get false
|
||||
return@get GenerateRecordConstructor(needsConstructor)
|
||||
@@ -65,11 +67,7 @@ class GenerateMissingConstructorAction : BaseCodeAction() {
|
||||
log.warn("Unable to generate constructor")
|
||||
return
|
||||
}
|
||||
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val client = server.client!!
|
||||
val file = requireFile(data)
|
||||
|
||||
client.performCodeAction(file, result.asCodeActions(server.compiler, label))
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -24,15 +24,16 @@ import com.github.javaparser.ast.expr.SimpleName
|
||||
import com.github.javaparser.ast.type.VoidType
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
|
||||
import com.itsaky.androidide.lsp.java.utils.TypeUtils.toType
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.util.TreePath
|
||||
@@ -79,11 +80,12 @@ class GenerateSettersAndGettersAction : FieldBasedAction() {
|
||||
}
|
||||
|
||||
private fun generateForFields(data: ActionData, names: MutableSet<String>) {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val range = data[Range::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = requirePath(data)
|
||||
|
||||
server.compiler.compile(file).run { task ->
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
|
||||
+7
-5
@@ -24,14 +24,15 @@ import com.github.javaparser.ast.body.MethodDeclaration
|
||||
import com.github.javaparser.ast.stmt.ReturnStmt
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.R.string
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.sun.source.tree.ClassTree
|
||||
@@ -79,11 +80,12 @@ class GenerateToStringMethodAction : FieldBasedAction() {
|
||||
}
|
||||
|
||||
private fun generateToString(data: ActionData, selected: MutableSet<String>) {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val range = data[Range::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = requirePath(data)
|
||||
|
||||
server.compiler.compile(file).run { task ->
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
|
||||
+18
-14
@@ -20,8 +20,7 @@ import android.content.Context
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseCodeAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
@@ -33,8 +32,10 @@ import com.itsaky.androidide.lsp.java.utils.FindHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
|
||||
import com.itsaky.androidide.lsp.java.utils.MethodPtr
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.lsp.models.Position
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.sun.source.tree.MethodTree
|
||||
import com.sun.source.util.Trees
|
||||
@@ -64,7 +65,7 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !hasRequiredData(data, Range::class.java, CodeEditor::class.java)) {
|
||||
if (!visible || !hasRequiredData(data, com.itsaky.androidide.models.Range::class.java, CodeEditor::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
@@ -74,11 +75,12 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val range = data[Range::class.java]!!
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return Any())
|
||||
val file = requirePath(data)
|
||||
|
||||
return server.compiler.compile(file).get { task ->
|
||||
return compiler.compile(file).get { task ->
|
||||
// 1-based line and column index
|
||||
val startLine = range.start.line + 1
|
||||
val startColumn = range.start.column + 1
|
||||
@@ -190,10 +192,11 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
}
|
||||
|
||||
private fun overrideMethods(data: ActionData, checkedMethods: MutableList<MethodPtr>) {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val file = requirePath(data)
|
||||
|
||||
server.compiler.compile(file).run { task ->
|
||||
compiler.compile(file).run { task ->
|
||||
val types = task.task.types
|
||||
val trees = Trees.instance(task.task)
|
||||
val sb = StringBuilder()
|
||||
@@ -217,7 +220,7 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
|
||||
val thisDeclaredType = thisClass.asType() as DeclaredType
|
||||
val executableType = types.asMemberOf(thisDeclaredType, superMethod) as ExecutableType
|
||||
val source = findSource(server.compiler, task, superMethod)
|
||||
val source = findSource(compiler, task, superMethod)
|
||||
val method =
|
||||
if (source != null) {
|
||||
JavaParserUtils.printMethod(superMethod, executableType, source)
|
||||
@@ -253,9 +256,10 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
data: ActionData,
|
||||
sb: StringBuilder,
|
||||
imports: MutableSet<String>,
|
||||
position: Position,
|
||||
position: com.itsaky.androidide.models.Position,
|
||||
) {
|
||||
val server = data[JavaLanguageServer::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(requireFile(data)) ?: return)
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
val file = requirePath(data)
|
||||
val text = editor.text
|
||||
@@ -266,7 +270,7 @@ class OverrideSuperclassMethodsAction : BaseCodeAction() {
|
||||
|
||||
for (name in imports) {
|
||||
val rewrite = AddImport(file, name)
|
||||
val edits = rewrite.rewrite(server.compiler)[file]
|
||||
val edits = rewrite.rewrite(compiler)[file]
|
||||
if (edits == null || edits.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,14 +21,16 @@ import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableCompiler;
|
||||
import com.itsaky.androidide.javac.services.partial.DiagnosticListenerImpl;
|
||||
import com.itsaky.androidide.lsp.java.parser.Parser;
|
||||
import com.itsaky.androidide.lsp.java.visitors.MethodRangeScanner;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.StopWatch;
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.lsp.java.parser.Parser;
|
||||
import com.itsaky.androidide.javac.services.partial.DiagnosticListenerImpl;
|
||||
import com.itsaky.androidide.lsp.java.visitors.MethodRangeScanner;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
@@ -180,7 +182,7 @@ public class CompileBatch implements AutoCloseable {
|
||||
|
||||
private String errorText(javax.tools.Diagnostic<? extends javax.tools.JavaFileObject> err) {
|
||||
Path file = Paths.get(err.getSource().toUri());
|
||||
CharSequence contents = FileStore.contents(file);
|
||||
CharSequence contents = ProjectManager.INSTANCE.getDocumentContents(file);
|
||||
int begin = (int) err.getStartPosition();
|
||||
int end = (int) err.getEndPosition();
|
||||
return substring(contents, begin, end);
|
||||
@@ -210,11 +212,30 @@ public class CompileBatch implements AutoCloseable {
|
||||
|
||||
private String packageName(javax.tools.Diagnostic<? extends javax.tools.JavaFileObject> err) {
|
||||
Path file = Paths.get(err.getSource().toUri());
|
||||
return FileStore.packageName(file);
|
||||
ModuleProject module = ProjectManager.INSTANCE.findModuleForFile(file);
|
||||
if (module == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
SourceClassTrie.SourceNode node = module.compileJavaSourceClasses.findSource(file);
|
||||
if (node == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return node.getPackageName();
|
||||
}
|
||||
|
||||
private Path findPackagePrivateClass(String packageName, String className) {
|
||||
for (Path file : FileStore.list(packageName)) {
|
||||
final List<SourceClassTrie.SourceNode> classes =
|
||||
parent.module != null
|
||||
? parent.module.listClassesFromSourceDirs(packageName)
|
||||
: Collections.emptyList();
|
||||
if (classes.isEmpty()) {
|
||||
return FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
for (SourceClassTrie.SourceNode node : classes) {
|
||||
final Path file = node.getFile();
|
||||
Parser parse = Parser.parseFile(file);
|
||||
for (Name candidate : parse.packagePrivateClasses()) {
|
||||
if (candidate.contentEquals(className)) {
|
||||
|
||||
@@ -34,14 +34,10 @@ import javax.tools.JavaFileObject;
|
||||
public interface CompilerProvider {
|
||||
Path NOT_FOUND = Paths.get("");
|
||||
|
||||
Set<String> imports();
|
||||
|
||||
List<String> publicTopLevelTypes();
|
||||
|
||||
List<String> packagePrivateTopLevelTypes(String packageName);
|
||||
|
||||
Iterable<Path> search(String query);
|
||||
|
||||
Optional<JavaFileObject> findAnywhere(String className);
|
||||
|
||||
Path findTypeDeclaration(String className);
|
||||
|
||||
+76
-66
@@ -22,22 +22,24 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableCompiler;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.StopWatch;
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.parser.Parser;
|
||||
import com.itsaky.androidide.javac.services.partial.CompilationInfo;
|
||||
import com.itsaky.androidide.javac.services.partial.PartialReparser;
|
||||
import com.itsaky.androidide.javac.services.partial.PartialReparserImpl;
|
||||
import com.itsaky.androidide.lsp.java.utils.Cache;
|
||||
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.parser.Parser;
|
||||
import com.itsaky.androidide.lsp.java.utils.Extractors;
|
||||
import com.itsaky.androidide.lsp.java.utils.ScanClassPath;
|
||||
import com.itsaky.androidide.lsp.java.utils.StringSearch;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarations;
|
||||
import com.itsaky.androidide.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.models.ActiveDocument;
|
||||
import com.itsaky.androidide.projects.util.Cache;
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie;
|
||||
import com.itsaky.androidide.projects.util.StringSearch;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.StopWatch;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
@@ -53,15 +55,14 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
@@ -74,33 +75,36 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
private static final ILogger LOG = ILogger.newInstance("JavaCompilerService");
|
||||
|
||||
protected final ScanClassPath classPathScanner = new ScanClassPath();
|
||||
protected final Set<Path> classPath, docPath;
|
||||
protected final Set<Path> classPath;
|
||||
protected final Set<String> jdkClasses = classPathScanner.jdkTopLevelClasses(), classPathClasses;
|
||||
protected final List<Diagnostic<? extends JavaFileObject>> diagnostics = new ArrayList<>();
|
||||
protected final Map<JavaFileObject, Long> cachedModified = new HashMap<>();
|
||||
protected final Cache<Void, List<String>> cacheFileImports = new Cache<>();
|
||||
protected final SynchronizedTask synchronizedTask = new SynchronizedTask();
|
||||
protected final SourceFileManager fileManager;
|
||||
protected final ModuleProject module;
|
||||
|
||||
public ReusableCompiler compiler = new ReusableCompiler();
|
||||
|
||||
private CompileBatch cachedCompile;
|
||||
private int changeDelta = 0;
|
||||
|
||||
public JavaCompilerService(Set<Path> classPath, Set<Path> docPath) {
|
||||
this.classPath = Collections.unmodifiableSet(classPath);
|
||||
this.docPath = Collections.unmodifiableSet(docPath);
|
||||
// The module project must not be null
|
||||
// It is marked as nullable just to work with tests
|
||||
public JavaCompilerService(@Nullable ModuleProject module) {
|
||||
this.module = module;
|
||||
if (module == null) {
|
||||
this.classPath = Collections.unmodifiableSet(Collections.emptySet());
|
||||
} else {
|
||||
this.classPath =
|
||||
Collections.unmodifiableSet(
|
||||
module.getCompileClasspaths().stream().map(File::toPath).collect(Collectors.toSet()));
|
||||
}
|
||||
this.classPathClasses = classPathScanner.classPathTopLevelClasses(classPath);
|
||||
this.fileManager = new SourceFileManager();
|
||||
this.fileManager = new SourceFileManager(module);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> imports() {
|
||||
HashSet<String> all = new HashSet<>();
|
||||
for (Path f : FileStore.all()) {
|
||||
all.addAll(readImports(f));
|
||||
}
|
||||
return all;
|
||||
public ModuleProject getModule() {
|
||||
return module;
|
||||
}
|
||||
|
||||
private List<String> readImports(Path file) {
|
||||
@@ -110,11 +114,15 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
return cacheFileImports.get(file, null);
|
||||
}
|
||||
|
||||
private String packageNameOrEmpty(Path file) {
|
||||
return module != null ? module.packageNameOrEmpty(file) : "";
|
||||
}
|
||||
|
||||
private void loadImports(Path file) {
|
||||
List<String> list = new ArrayList<>();
|
||||
Pattern importClass = Pattern.compile("^import +([\\w\\.]+\\.\\w+);");
|
||||
Pattern importStar = Pattern.compile("^import +([\\w\\.]+\\.\\*);");
|
||||
try (BufferedReader lines = FileStore.lines(file)) {
|
||||
Pattern importClass = Pattern.compile("^import +([\\w.]+\\.\\w+);");
|
||||
Pattern importStar = Pattern.compile("^import +([\\w.]+\\.\\*);");
|
||||
try (BufferedReader lines = ProjectManager.INSTANCE.getReader(file)) {
|
||||
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
|
||||
// If we reach a class declaration, stop looking for imports
|
||||
// TODO This could be a little more specific
|
||||
@@ -141,17 +149,10 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
@Override
|
||||
public List<String> publicTopLevelTypes() {
|
||||
List<String> all = new ArrayList<>();
|
||||
for (Path file : FileStore.all()) {
|
||||
String fileName = file.getFileName().toString();
|
||||
if (!fileName.endsWith(".java")) {
|
||||
continue;
|
||||
}
|
||||
String className = fileName.substring(0, fileName.length() - ".java".length());
|
||||
String packageName = FileStore.packageName(file);
|
||||
if (!packageName.isEmpty()) {
|
||||
className = packageName + "." + className;
|
||||
}
|
||||
all.add(className);
|
||||
List<SourceClassTrie.SourceNode> sourceClasses =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceClasses) {
|
||||
all.add(node.getQualifiedName());
|
||||
}
|
||||
all.addAll(classPathClasses);
|
||||
all.addAll(jdkClasses);
|
||||
@@ -163,12 +164,6 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Path> search(String query) {
|
||||
Predicate<Path> test = f -> StringSearch.containsWordMatching(f, query);
|
||||
return () -> FileStore.all().stream().filter(test).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<JavaFileObject> findAnywhere(String className) {
|
||||
Path fromSource = findTypeDeclaration(className);
|
||||
@@ -189,9 +184,12 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
// optimization.
|
||||
String packageName = Extractors.packageName(className);
|
||||
String simpleName = Extractors.simpleName(className);
|
||||
for (Path f : FileStore.list(packageName)) {
|
||||
if (containsWord(f, simpleName) && containsType(f, className)) {
|
||||
return f;
|
||||
List<SourceClassTrie.SourceNode> classes =
|
||||
module != null ? module.listClassesFromSourceDirs(packageName) : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : classes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, simpleName) && containsType(path, className)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
@@ -202,11 +200,14 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
String packageName = Extractors.packageName(className);
|
||||
String simpleName = Extractors.simpleName(className);
|
||||
List<Path> candidates = new ArrayList<>();
|
||||
for (Path f : FileStore.all()) {
|
||||
if (containsWord(f, packageName)
|
||||
&& containsImport(f, className)
|
||||
&& containsWord(f, simpleName)) {
|
||||
candidates.add(f);
|
||||
List<SourceClassTrie.SourceNode> sourceNodes =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceNodes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, packageName)
|
||||
&& containsImport(path, className)
|
||||
&& containsWord(path, simpleName)) {
|
||||
candidates.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +223,7 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
|
||||
private boolean containsImport(Path file, String className) {
|
||||
String packageName = Extractors.packageName(className);
|
||||
if (FileStore.packageName(file).equals(packageName)) {
|
||||
if (packageNameOrEmpty(file).equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
String star = packageName + ".*";
|
||||
@@ -237,9 +238,12 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
@Override
|
||||
public Path[] findMemberReferences(String className, String memberName) {
|
||||
List<Path> candidates = new ArrayList<>();
|
||||
for (Path f : FileStore.all()) {
|
||||
if (containsWord(f, memberName)) {
|
||||
candidates.add(f);
|
||||
List<SourceClassTrie.SourceNode> sourceNodes =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceNodes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, memberName)) {
|
||||
candidates.add(path);
|
||||
}
|
||||
}
|
||||
return candidates.toArray(new Path[0]);
|
||||
@@ -285,7 +289,8 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (f.getLastModified() != cachedModified.get(f)) {
|
||||
final Long modified = cachedModified.get(f);
|
||||
if (modified != null && f.getLastModified() != modified) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -338,6 +343,13 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
final MethodTree methodTree = (MethodTree) currentMethod.second.getLeaf();
|
||||
LOG.debug("Trying to reparse method:", methodTree.getName());
|
||||
|
||||
final int changeDelta;
|
||||
final ActiveDocument document = ProjectManager.INSTANCE.getActiveDocument(file.toPath());
|
||||
if (document != null) {
|
||||
changeDelta = document.getChangDelta();
|
||||
} else {
|
||||
changeDelta = 0;
|
||||
}
|
||||
final CompilationInfo info =
|
||||
new CompilationInfo(
|
||||
cachedCompile.task, cachedCompile.diagnosticListener, cachedCompile.roots.get(0));
|
||||
@@ -345,7 +357,7 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
final SourcePositions sourcePositions = Trees.instance(cachedCompile.task).getSourcePositions();
|
||||
final int start = (int) sourcePositions.getStartPosition(info.cu, methodTree.getBody());
|
||||
final int end =
|
||||
(int) sourcePositions.getEndPosition(info.cu, methodTree.getBody()) + this.changeDelta;
|
||||
(int) sourcePositions.getEndPosition(info.cu, methodTree.getBody()) + changeDelta;
|
||||
watch.lapFromLast("Found start and end positions of current method");
|
||||
final PartialReparser reparser = new PartialReparserImpl();
|
||||
|
||||
@@ -354,7 +366,7 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
"Cannot reparse. Invalid change delta. end:",
|
||||
end,
|
||||
"changeDelta:",
|
||||
this.changeDelta,
|
||||
changeDelta,
|
||||
"content.length:",
|
||||
request.partialRequest.contents.length());
|
||||
recompile(request);
|
||||
@@ -375,7 +387,10 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
LOG.info("Successfully reparsed method", methodTree.getName());
|
||||
updateModificationCache(request);
|
||||
cachedCompile.updatePositions(info.cu, true);
|
||||
this.changeDelta = 0;
|
||||
|
||||
if (document != null) {
|
||||
document.setChangDelta(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -405,7 +420,6 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
close();
|
||||
cachedCompile = performCompilation(request.sources);
|
||||
updateModificationCache(request);
|
||||
this.changeDelta = 0;
|
||||
}
|
||||
|
||||
private void updateModificationCache(final CompilationRequest request) {
|
||||
@@ -440,10 +454,6 @@ public class JavaCompilerService implements CompilerProvider {
|
||||
return new CompileBatch(this, moreSources);
|
||||
}
|
||||
|
||||
public void onContentChanged(@NonNull DocumentChangeEvent event) {
|
||||
this.changeDelta += event.getChangeDelta();
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (cachedCompile != null && !cachedCompile.closed) {
|
||||
cachedCompile.close();
|
||||
|
||||
+33
-10
@@ -17,8 +17,9 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.lsp.java.utils.StringSearch;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.util.SourceClassTrie;
|
||||
import com.itsaky.androidide.projects.util.StringSearch;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
|
||||
import java.io.File;
|
||||
@@ -26,6 +27,8 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
@@ -42,9 +45,19 @@ import javax.tools.StandardLocation;
|
||||
public class SourceFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger("main");
|
||||
private ModuleProject module;
|
||||
|
||||
public SourceFileManager() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public SourceFileManager(final ModuleProject module) {
|
||||
super(createDelegateFileManager());
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
public void setModule(final ModuleProject module) {
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
private static StandardJavaFileManager createDelegateFileManager() {
|
||||
@@ -57,29 +70,36 @@ public class SourceFileManager extends ForwardingJavaFileManager<StandardJavaFil
|
||||
LOG.warning(error.getMessage(null));
|
||||
}
|
||||
|
||||
private String packageNameOrEmpty(Path file) {
|
||||
return this.module != null ? module.packageNameOrEmpty(file) : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<JavaFileObject> list(
|
||||
Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse)
|
||||
throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
if (this.module == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Stream<JavaFileObject> stream =
|
||||
FileStore.list(packageName).stream().map(this::asJavaFileObject);
|
||||
module.listClassesFromSourceDirs(packageName).stream().map(this::asJavaFileObject);
|
||||
return stream::iterator;
|
||||
} else {
|
||||
return super.list(location, packageName, kinds, recurse);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaFileObject asJavaFileObject(Path file) {
|
||||
private JavaFileObject asJavaFileObject(SourceClassTrie.SourceNode node) {
|
||||
// TODO erase method bodies of files that are not open
|
||||
return new SourceFileObject(file);
|
||||
return new SourceFileObject(node.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inferBinaryName(Location location, JavaFileObject file) {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
SourceFileObject source = (SourceFileObject) file;
|
||||
String packageName = FileStore.packageName(source.path);
|
||||
String packageName = packageNameOrEmpty(source.path);
|
||||
String className = removeExtension(source.path.getFileName().toString());
|
||||
if (!packageName.isEmpty()) className = packageName + "." + className;
|
||||
return className;
|
||||
@@ -105,9 +125,12 @@ public class SourceFileManager extends ForwardingJavaFileManager<StandardJavaFil
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
String packageName = StringSearch.mostName(className);
|
||||
String simpleClassName = StringSearch.lastName(className);
|
||||
for (Path f : FileStore.list(packageName)) {
|
||||
if (f.getFileName().toString().equals(simpleClassName + kind.extension)) {
|
||||
return new SourceFileObject(f);
|
||||
List<SourceClassTrie.SourceNode> classes =
|
||||
module != null ? module.listClassesFromSourceDirs(packageName) : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : classes) {
|
||||
final Path path = node.getFile();
|
||||
if (path.getFileName().toString().equals(simpleClassName + kind.extension)) {
|
||||
return new SourceFileObject(path);
|
||||
}
|
||||
}
|
||||
// Fall through to disk in case we have .jar or .zip files on the source path
|
||||
@@ -128,7 +151,7 @@ public class SourceFileManager extends ForwardingJavaFileManager<StandardJavaFil
|
||||
public boolean contains(Location location, FileObject file) throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
SourceFileObject source = (SourceFileObject) file;
|
||||
return FileStore.contains(source.path);
|
||||
return module.compileJavaSourceClasses.findSource(source.path) != null;
|
||||
} else {
|
||||
return super.contains(location, file);
|
||||
}
|
||||
|
||||
+7
-6
@@ -17,8 +17,9 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.lsp.util.PathUtils;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.util.DocumentUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -47,7 +48,7 @@ public class SourceFileObject implements JavaFileObject {
|
||||
}
|
||||
|
||||
public SourceFileObject(Path path, String contents, Instant modified) {
|
||||
if (!FileStore.isJavaFile(path)) throw new RuntimeException(path + " is not a java source");
|
||||
if (!DocumentUtils.isJavaFile(path)) throw new RuntimeException(path + " is not a java source");
|
||||
this.path = path;
|
||||
this.contents = contents;
|
||||
this.modified = modified;
|
||||
@@ -116,7 +117,7 @@ public class SourceFileObject implements JavaFileObject {
|
||||
byte[] bytes = contents.getBytes();
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
return FileStore.inputStream(path);
|
||||
return ProjectManager.INSTANCE.getInputStream(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,7 +130,7 @@ public class SourceFileObject implements JavaFileObject {
|
||||
if (contents != null) {
|
||||
return new StringReader(contents);
|
||||
}
|
||||
return FileStore.bufferedReader(path);
|
||||
return ProjectManager.INSTANCE.getReader(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +138,7 @@ public class SourceFileObject implements JavaFileObject {
|
||||
if (contents != null) {
|
||||
return contents;
|
||||
}
|
||||
return FileStore.contents(path);
|
||||
return ProjectManager.INSTANCE.getDocumentContents(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,7 +151,7 @@ public class SourceFileObject implements JavaFileObject {
|
||||
if (contents != null) {
|
||||
return modified.toEpochMilli();
|
||||
}
|
||||
return FileStore.modified(path).toEpochMilli();
|
||||
return ProjectManager.INSTANCE.getLastModified(this.path).toEpochMilli();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -36,10 +36,10 @@ class ClassImportEditHandler(val imports: Set<String>, file: Path) : IJavaEditHa
|
||||
override fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: CompletionItem
|
||||
completionItem: com.itsaky.androidide.lsp.models.CompletionItem
|
||||
) {
|
||||
val className = completionItem.data!!.className
|
||||
val edits = EditHelper.addImportIfNeeded(compiler, file, imports, className)
|
||||
RewriteHelper.performEdits(edits, editor)
|
||||
com.itsaky.androidide.lsp.util.RewriteHelper.performEdits(edits, editor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.edits
|
||||
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.edits.IEditHandler
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -32,11 +32,10 @@ import java.nio.file.Path
|
||||
*/
|
||||
abstract class IJavaEditHandler(protected val file: Path) : IEditHandler {
|
||||
|
||||
override fun performEdits(editor: CodeEditor, completionItem: CompletionItem) {
|
||||
val languageServer =
|
||||
ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
|
||||
if (languageServer != null && languageServer is JavaLanguageServer) {
|
||||
performEdits(languageServer.compiler, editor, completionItem)
|
||||
override fun performEdits(editor: CodeEditor, completionItem: com.itsaky.androidide.lsp.models.CompletionItem) {
|
||||
val compiler = JavaCompilerProvider.get(ProjectManager.findModuleForFile(file) ?: return)
|
||||
if (compiler != null) {
|
||||
performEdits(compiler, editor, completionItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +50,6 @@ abstract class IJavaEditHandler(protected val file: Path) : IEditHandler {
|
||||
abstract fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: CompletionItem
|
||||
completionItem: com.itsaky.androidide.lsp.models.CompletionItem
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -44,9 +44,9 @@ class MultipleClassImportEditHandler(
|
||||
override fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: CompletionItem
|
||||
completionItem: com.itsaky.androidide.lsp.models.CompletionItem
|
||||
) {
|
||||
val edits = mutableListOf<TextEdit>()
|
||||
val edits = mutableListOf<com.itsaky.androidide.lsp.models.TextEdit>()
|
||||
for (className in classes) {
|
||||
try {
|
||||
edits.addAll(EditHelper.addImportIfNeeded(compiler, file, imported, className))
|
||||
@@ -54,6 +54,6 @@ class MultipleClassImportEditHandler(
|
||||
log.error("Unable to compute edits to perform import for class:", className)
|
||||
}
|
||||
}
|
||||
RewriteHelper.performEdits(edits, editor)
|
||||
com.itsaky.androidide.lsp.util.RewriteHelper.performEdits(edits, editor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,52 +17,36 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.parser;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.java.FileStore;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SourceFileManager;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SourceFileObject;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.lsp.util.StringUtils;
|
||||
import com.sun.source.tree.BlockTree;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ErroneousTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.StatementTree;
|
||||
import com.sun.source.tree.SwitchTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
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.TreeScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
@@ -97,6 +81,7 @@ public class Parser {
|
||||
|
||||
/** Create a task that compiles a single file */
|
||||
private static JavacTask singleFileTask(JavaFileObject file) {
|
||||
FILE_MANAGER.setModule(ProjectManager.INSTANCE.findModuleForFile(Paths.get(file.toUri())));
|
||||
return (JavacTask)
|
||||
COMPILER.getTask(
|
||||
null,
|
||||
@@ -250,60 +235,6 @@ public class Parser {
|
||||
return "";
|
||||
}
|
||||
|
||||
static String describeTree(Tree leaf) {
|
||||
if (leaf instanceof MethodTree) {
|
||||
MethodTree method = (MethodTree) leaf;
|
||||
StringJoiner params = new StringJoiner(", ");
|
||||
for (VariableTree p : method.getParameters()) {
|
||||
params.add(p.getType() + " " + p.getName());
|
||||
}
|
||||
return method.getName() + "(" + params + ")";
|
||||
}
|
||||
if (leaf instanceof ClassTree) {
|
||||
ClassTree cls = (ClassTree) leaf;
|
||||
return "class " + cls.getSimpleName();
|
||||
}
|
||||
if (leaf instanceof BlockTree) {
|
||||
BlockTree block = (BlockTree) leaf;
|
||||
return String.format(Locale.ROOT, "{ ...%d lines... }", block.getStatements().size());
|
||||
}
|
||||
return leaf.toString();
|
||||
}
|
||||
|
||||
static Optional<Path> declaringFile(Element e) {
|
||||
// Find top-level type surrounding `to`
|
||||
LOG.info(String.format("...looking up declaring file of `%s`...", e));
|
||||
Optional<TypeElement> top = topLevelDeclaration(e);
|
||||
if (!top.isPresent()) {
|
||||
LOG.warn("...no top-level type!");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Find file by looking at package and class name
|
||||
LOG.info(String.format("...top-level type is %s", top.get()));
|
||||
Optional<Path> file = FileStore.findDeclaringFile(top.get());
|
||||
if (!file.isPresent()) {
|
||||
LOG.info("...couldn't find declaring file for type");
|
||||
return Optional.empty();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
private static Optional<TypeElement> topLevelDeclaration(Element e) {
|
||||
if (e == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Element parent = e;
|
||||
TypeElement result = null;
|
||||
while (parent.getEnclosingElement() != null) {
|
||||
if (parent instanceof TypeElement) {
|
||||
result = (TypeElement) parent;
|
||||
}
|
||||
parent = parent.getEnclosingElement();
|
||||
}
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
public Set<Name> packagePrivateClasses() {
|
||||
Set<Name> result = new HashSet<>();
|
||||
for (Tree t : root.getTypeDecls()) {
|
||||
@@ -317,160 +248,4 @@ public class Parser {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> accessibleClasses(String partialName, String fromPackage) {
|
||||
String toPackage = Objects.toString(root.getPackageName(), "");
|
||||
boolean samePackage = fromPackage.equals(toPackage) || toPackage.isEmpty();
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Tree t : root.getTypeDecls()) {
|
||||
if (!(t instanceof ClassTree)) {
|
||||
continue;
|
||||
}
|
||||
ClassTree cls = (ClassTree) t;
|
||||
// If class is not accessible, skip it
|
||||
boolean isPublic = cls.getModifiers().getFlags().contains(Modifier.PUBLIC);
|
||||
if (!samePackage && !isPublic) {
|
||||
continue;
|
||||
}
|
||||
// If class doesn't match partialName, skip it
|
||||
String name = cls.getSimpleName().toString();
|
||||
if (!StringUtils.matchesPartialName(name, partialName)) {
|
||||
continue;
|
||||
}
|
||||
if (root.getPackageName() != null) {
|
||||
name = root.getPackageName() + "." + name;
|
||||
}
|
||||
result.add(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String prune(long cursor) {
|
||||
SourcePositions pos = Trees.instance(task).getSourcePositions();
|
||||
StringBuilder buffer = new StringBuilder(contents);
|
||||
long[] cursors = {cursor};
|
||||
return prune(root, pos, buffer, cursors, true);
|
||||
}
|
||||
|
||||
private static String prune(
|
||||
CompilationUnitTree root,
|
||||
SourcePositions pos,
|
||||
StringBuilder buffer,
|
||||
long[] offsets,
|
||||
boolean eraseAfterCursor) {
|
||||
class Scan extends TreeScanner<Void, Void> {
|
||||
boolean erasedAfterCursor = !eraseAfterCursor;
|
||||
|
||||
@Override
|
||||
public Void visitImport(ImportTree node, Void __) {
|
||||
// Erase 'static' keyword so autocomplete works better
|
||||
if (containsCursor(node) && node.isStatic()) {
|
||||
int start = (int) pos.getStartPosition(root, node);
|
||||
start = buffer.indexOf("static", start);
|
||||
int end = start + "static".length();
|
||||
erase(buffer, start, end);
|
||||
}
|
||||
return super.visitImport(node, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBlock(BlockTree node, Void __) {
|
||||
if (containsCursor(node)) {
|
||||
super.visitBlock(node, null);
|
||||
// When we find the deepest block that includes the cursor
|
||||
if (!erasedAfterCursor) {
|
||||
long start = lastCursorIn(node);
|
||||
long end = pos.getEndPosition(root, node);
|
||||
if (end >= buffer.length()) {
|
||||
end = buffer.length() - 1;
|
||||
}
|
||||
// Find the next line
|
||||
while (start < end && buffer.charAt((int) start) != '\n') start++;
|
||||
// Find the end of the block
|
||||
while (end > start && buffer.charAt((int) end) != '}') end--;
|
||||
// Erase from next line to end of block
|
||||
erase(buffer, start, end - 1);
|
||||
erasedAfterCursor = true;
|
||||
}
|
||||
} else if (!node.getStatements().isEmpty()) {
|
||||
StatementTree first = node.getStatements().get(0);
|
||||
StatementTree last = node.getStatements().get(node.getStatements().size() - 1);
|
||||
long start = pos.getStartPosition(root, first);
|
||||
long end = pos.getEndPosition(root, last);
|
||||
if (end >= buffer.length()) {
|
||||
end = buffer.length() - 1;
|
||||
}
|
||||
erase(buffer, start, end);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
long lastCursorIn(Tree node) {
|
||||
long start = pos.getStartPosition(root, node);
|
||||
long end = pos.getEndPosition(root, node);
|
||||
long last = -1;
|
||||
for (long cursor : offsets) {
|
||||
if (start <= cursor && cursor <= end) {
|
||||
last = cursor;
|
||||
}
|
||||
}
|
||||
if (last == -1) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"No cursor in %s is between %d and %d", Arrays.toString(offsets), start, end));
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSwitch(SwitchTree node, Void __) {
|
||||
if (containsCursor(node)) {
|
||||
// Prevent the enclosing block from erasing the closing } of the switch
|
||||
erasedAfterCursor = true;
|
||||
}
|
||||
return super.visitSwitch(node, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitErroneous(ErroneousTree node, Void nothing) {
|
||||
return super.scan(node.getErrorTrees(), nothing);
|
||||
}
|
||||
|
||||
boolean anyContainsCursor(Collection<? extends Tree> nodes) {
|
||||
for (Tree n : nodes) {
|
||||
if (containsCursor(n)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean containsCursor(Tree node) {
|
||||
long start = pos.getStartPosition(root, node);
|
||||
long end = pos.getEndPosition(root, node);
|
||||
for (long cursor : offsets) {
|
||||
if (start <= cursor && cursor <= end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
new Scan().scan(root, null);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static void erase(StringBuilder buffer, long start, long end) {
|
||||
for (int i = (int) start; i < end; i++) {
|
||||
switch (buffer.charAt(i)) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
break;
|
||||
default:
|
||||
buffer.setCharAt(i, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings;
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerSettings;
|
||||
import com.itsaky.androidide.lsp.models.FormatCodeParams;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
+1
-1
@@ -19,6 +19,7 @@ package com.itsaky.androidide.lsp.java.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
@@ -28,7 +29,6 @@ import com.itsaky.androidide.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.androidide.lsp.java.utils.NavigationHelper;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionParams;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.lsp.models.Location;
|
||||
import com.itsaky.androidide.lsp.util.PathUtils;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
+45
-25
@@ -16,17 +16,17 @@
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.providers
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode.EMPTY_BLOCK
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode.UNUSED_THROWS
|
||||
import com.itsaky.androidide.lsp.java.visitors.DiagnosticVisitor
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticSeverity
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticSeverity.WARNING
|
||||
import com.itsaky.androidide.lsp.models.Position
|
||||
import com.itsaky.androidide.lsp.models.Range
|
||||
import com.itsaky.androidide.lsp.util.PathUtils.Companion.isSameFile
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.sun.source.tree.BlockTree
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.CompilationUnitTree
|
||||
@@ -61,7 +61,10 @@ object DiagnosticsProvider {
|
||||
* @return The list of diagnostics retrieved from the task. Never null.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun findDiagnostics(task: CompileTask, file: Path?): List<DiagnosticItem> {
|
||||
fun findDiagnostics(
|
||||
task: CompileTask,
|
||||
file: Path?
|
||||
): List<DiagnosticItem> {
|
||||
val result = mutableListOf<DiagnosticItem>()
|
||||
var root: CompilationUnitTree? = null
|
||||
for (tree in task.roots) {
|
||||
@@ -102,7 +105,11 @@ object DiagnosticsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun warnEmptyBlock(task: CompileTask, path: TreePath, name: String): DiagnosticItem {
|
||||
private fun warnEmptyBlock(
|
||||
task: CompileTask,
|
||||
path: TreePath,
|
||||
name: String
|
||||
): DiagnosticItem {
|
||||
val trees = Trees.instance(task.task)
|
||||
val thisTree = path.leaf
|
||||
val code = EMPTY_BLOCK
|
||||
@@ -118,10 +125,11 @@ object DiagnosticsProvider {
|
||||
message = "'$name' statement has empty body",
|
||||
severity = WARNING,
|
||||
range =
|
||||
Range(getPosition(start, lines), getPosition(end, lines)).apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
}
|
||||
Range(getPosition(start, lines), getPosition(end, lines))
|
||||
.apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -141,7 +149,11 @@ object DiagnosticsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun warnNotThrown(task: CompileTask, name: String?, path: TreePath): DiagnosticItem {
|
||||
private fun warnNotThrown(
|
||||
task: CompileTask,
|
||||
name: String?,
|
||||
path: TreePath
|
||||
): DiagnosticItem {
|
||||
val trees = Trees.instance(task.task)
|
||||
val pos = trees.sourcePositions
|
||||
val root = path.compilationUnit
|
||||
@@ -151,17 +163,21 @@ object DiagnosticsProvider {
|
||||
return DiagnosticItem(
|
||||
message = String.format("'%s' is not thrown in the body of the method", name),
|
||||
range =
|
||||
Range(getPosition(start, lines), getPosition(end, lines)).apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
},
|
||||
code = DiagnosticCode.UNUSED_THROWS.id,
|
||||
Range(getPosition(start, lines), getPosition(end, lines))
|
||||
.apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
},
|
||||
code = UNUSED_THROWS.id,
|
||||
severity = DiagnosticSeverity.INFO,
|
||||
source = ""
|
||||
)
|
||||
}
|
||||
|
||||
private fun warnUnused(task: CompileTask, unusedEl: Element): DiagnosticItem {
|
||||
private fun warnUnused(
|
||||
task: CompileTask,
|
||||
unusedEl: Element
|
||||
): DiagnosticItem {
|
||||
val trees = Trees.instance(task.task)
|
||||
val path = trees.getPath(unusedEl) ?: throw RuntimeException("$unusedEl has no path")
|
||||
val root = path.compilationUnit
|
||||
@@ -176,7 +192,7 @@ object DiagnosticsProvider {
|
||||
}
|
||||
}
|
||||
val file = Paths.get(root.sourceFile.toUri())
|
||||
val contents = FileStore.contents(file)
|
||||
val contents = ProjectManager.getDocumentContents(file)
|
||||
var name = unusedEl.simpleName
|
||||
if (name.contentEquals("<init>")) {
|
||||
name = unusedEl.enclosingElement.simpleName
|
||||
@@ -240,10 +256,11 @@ object DiagnosticsProvider {
|
||||
code = code,
|
||||
severity = severity,
|
||||
range =
|
||||
Range(getPosition(start, root.lineMap), getPosition(end, root.lineMap)).apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
},
|
||||
Range(getPosition(start, root.lineMap), getPosition(end, root.lineMap))
|
||||
.apply {
|
||||
this.start.index = start.toInt()
|
||||
this.end.index = end.toInt()
|
||||
},
|
||||
source = ""
|
||||
)
|
||||
}
|
||||
@@ -275,19 +292,22 @@ object DiagnosticsProvider {
|
||||
return Range(start, end)
|
||||
}
|
||||
|
||||
private fun getPosition(position: Long, lines: LineMap): Position {
|
||||
private fun getPosition(position: Long, lines: LineMap): com.itsaky.androidide.models.Position {
|
||||
// decrement the numbers
|
||||
// to convert 1-based indexes to 0-based
|
||||
val line = (lines.getLineNumber(position) - 1).toInt()
|
||||
val column = (lines.getColumnNumber(position) - 1).toInt()
|
||||
return Position(line, column)
|
||||
return com.itsaky.androidide.models.Position(line, column)
|
||||
}
|
||||
|
||||
private fun severityFor(kind: Diagnostic.Kind): DiagnosticSeverity {
|
||||
private fun severityFor(
|
||||
kind: Diagnostic.Kind
|
||||
): DiagnosticSeverity {
|
||||
return when (kind) {
|
||||
Diagnostic.Kind.ERROR -> DiagnosticSeverity.ERROR
|
||||
Diagnostic.Kind.WARNING,
|
||||
Diagnostic.Kind.MANDATORY_WARNING -> DiagnosticSeverity.WARNING
|
||||
Diagnostic.Kind.MANDATORY_WARNING ->
|
||||
WARNING
|
||||
Diagnostic.Kind.NOTE -> DiagnosticSeverity.INFO
|
||||
Diagnostic.Kind.OTHER -> DiagnosticSeverity.HINT
|
||||
else -> DiagnosticSeverity.HINT
|
||||
|
||||
+16
-12
@@ -16,18 +16,16 @@
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.providers
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.lsp.java.CompilationCancellationException.Companion.isCancelled
|
||||
import com.itsaky.androidide.lsp.java.FileStore
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.providers.DiagnosticsProvider.findDiagnostics
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticResult
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.*
|
||||
import java.util.function.*
|
||||
import com.itsaky.androidide.javac.services.CancelAbort
|
||||
|
||||
/**
|
||||
* Code analyzer for java source code.
|
||||
@@ -38,19 +36,22 @@ class JavaDiagnosticProvider(private val completionChecker: Supplier<Boolean>) {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val analyzeTimestamps = mutableMapOf<Path, Instant>()
|
||||
private var cachedDiagnostics = DiagnosticResult.NO_UPDATE
|
||||
private var cachedDiagnostics = com.itsaky.androidide.lsp.models.DiagnosticResult.NO_UPDATE
|
||||
private var analyzing = AtomicBoolean(false)
|
||||
|
||||
fun analyze(compiler: JavaCompilerService, file: Path): DiagnosticResult {
|
||||
fun analyze(
|
||||
compiler: JavaCompilerService,
|
||||
file: Path
|
||||
): com.itsaky.androidide.lsp.models.DiagnosticResult {
|
||||
|
||||
log.debug("Analyzing:", file)
|
||||
|
||||
if (completionChecker.get()) {
|
||||
log.warn("Completion is in progress. Skipping analyze...")
|
||||
return DiagnosticResult.NO_UPDATE
|
||||
return com.itsaky.androidide.lsp.models.DiagnosticResult.NO_UPDATE
|
||||
}
|
||||
|
||||
val modifiedAt = FileStore.modified(file)
|
||||
val modifiedAt = ProjectManager.getLastModified(file)
|
||||
val analyzedAt = analyzeTimestamps[file]
|
||||
|
||||
if (analyzedAt?.isAfter(modifiedAt) == true) {
|
||||
@@ -60,7 +61,7 @@ class JavaDiagnosticProvider(private val completionChecker: Supplier<Boolean>) {
|
||||
|
||||
if (analyzing.get()) {
|
||||
log.warn("Another analyze process is in progress...")
|
||||
return DiagnosticResult.NO_UPDATE
|
||||
return com.itsaky.androidide.lsp.models.DiagnosticResult.NO_UPDATE
|
||||
}
|
||||
|
||||
analyzing.set(true)
|
||||
@@ -76,7 +77,7 @@ class JavaDiagnosticProvider(private val completionChecker: Supplier<Boolean>) {
|
||||
log.warn("Unable to analyze file", err)
|
||||
|
||||
compiler.destroy()
|
||||
DiagnosticResult.NO_UPDATE
|
||||
com.itsaky.androidide.lsp.models.DiagnosticResult.NO_UPDATE
|
||||
}
|
||||
.also {
|
||||
cachedDiagnostics = it
|
||||
@@ -85,13 +86,16 @@ class JavaDiagnosticProvider(private val completionChecker: Supplier<Boolean>) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAnalyze(file: Path, task: CompileTask): DiagnosticResult {
|
||||
private fun doAnalyze(
|
||||
file: Path,
|
||||
task: CompileTask
|
||||
): com.itsaky.androidide.lsp.models.DiagnosticResult {
|
||||
return if (!isTaskValid(task)) {
|
||||
// Do not use Collections.emptyList ()
|
||||
// The returned list is accessed and the list returned by Collections.emptyList()
|
||||
// throws exception when trying to access.
|
||||
cachedDiagnostics
|
||||
} else DiagnosticResult(file, findDiagnostics(task, file))
|
||||
} else com.itsaky.androidide.lsp.models.DiagnosticResult(file, findDiagnostics(task, file))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindBiggerRange;
|
||||
import com.itsaky.androidide.lsp.models.ExpandSelectionParams;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -25,9 +25,9 @@ import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.androidide.lsp.java.utils.NavigationHelper;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindReferences;
|
||||
import com.itsaky.androidide.lsp.models.Location;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
|
||||
|
||||
+3
-3
@@ -49,8 +49,8 @@ class ClassNamesCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
val packageName = Objects.toString(root.packageName, "")
|
||||
val uniques: MutableSet<String> = HashSet()
|
||||
val previousSize: Int = list.size
|
||||
@@ -108,6 +108,6 @@ class ClassNamesCompletionProvider(
|
||||
|
||||
log.info("...found " + (list.size - previousSize) + " class names")
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-37
@@ -22,7 +22,6 @@ import com.itsaky.androidide.lsp.api.IServerSettings
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.edits.ClassImportEditHandler
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.models.Command
|
||||
import com.itsaky.androidide.lsp.models.CompletionData
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
@@ -83,7 +82,7 @@ abstract class IJavaCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val root = task.root(completingFile)
|
||||
filePackage = root.`package`.packageName.toString()
|
||||
fileImports = root.imports.map { it.qualifiedIdentifier.toString() }.toSet()
|
||||
@@ -105,10 +104,10 @@ abstract class IJavaCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult
|
||||
|
||||
protected open fun matchLevel(candidate: CharSequence, partial: CharSequence): MatchLevel {
|
||||
return CompletionItem.matchLevel(candidate.toString(), partial.toString())
|
||||
protected open fun matchLevel(candidate: CharSequence, partial: CharSequence): com.itsaky.androidide.lsp.models.MatchLevel {
|
||||
return com.itsaky.androidide.lsp.models.CompletionItem.matchLevel(candidate.toString(), partial.toString())
|
||||
}
|
||||
|
||||
protected open fun putMethod(
|
||||
@@ -126,13 +125,13 @@ abstract class IJavaCompletionProvider(
|
||||
keyword: String,
|
||||
partialName: CharSequence,
|
||||
matchRatio: Int,
|
||||
): CompletionItem {
|
||||
val item = CompletionItem()
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
val item = com.itsaky.androidide.lsp.models.CompletionItem()
|
||||
item.setLabel(keyword)
|
||||
item.kind = KEYWORD
|
||||
item.detail = "keyword"
|
||||
item.sortText = keyword
|
||||
item.matchLevel = CompletionItem.matchLevel(keyword, partialName.toString())
|
||||
item.matchLevel = com.itsaky.androidide.lsp.models.CompletionItem.matchLevel(keyword, partialName.toString())
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -140,12 +139,12 @@ abstract class IJavaCompletionProvider(
|
||||
task: CompileTask,
|
||||
overloads: List<ExecutableElement>,
|
||||
addParens: Boolean,
|
||||
matchLevel: MatchLevel,
|
||||
): CompletionItem {
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel,
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
val first = overloads[0]
|
||||
val item = CompletionItem()
|
||||
val item = com.itsaky.androidide.lsp.models.CompletionItem()
|
||||
item.setLabel(first.simpleName.toString())
|
||||
item.kind = CompletionItemKind.METHOD
|
||||
item.kind = com.itsaky.androidide.lsp.models.CompletionItemKind.METHOD
|
||||
item.detail = first.returnType.toString() + " " + first
|
||||
item.sortText = item.label.toString()
|
||||
item.matchLevel = matchLevel
|
||||
@@ -156,7 +155,7 @@ abstract class IJavaCompletionProvider(
|
||||
item.insertText = first.simpleName.toString() + "()$0"
|
||||
} else {
|
||||
item.insertText = first.simpleName.toString() + "($0)"
|
||||
item.command = Command("Trigger Parameter Hints", Command.TRIGGER_PARAMETER_HINTS)
|
||||
item.command = com.itsaky.androidide.lsp.models.Command("Trigger Parameter Hints", com.itsaky.androidide.lsp.models.Command.TRIGGER_PARAMETER_HINTS)
|
||||
}
|
||||
item.insertTextFormat = SNIPPET // Snippet
|
||||
}
|
||||
@@ -166,10 +165,10 @@ abstract class IJavaCompletionProvider(
|
||||
protected open fun item(
|
||||
task: CompileTask,
|
||||
element: Element,
|
||||
matchLevel: MatchLevel
|
||||
): CompletionItem {
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
if (element.kind == METHOD) throw RuntimeException("method")
|
||||
val item = CompletionItem()
|
||||
val item = com.itsaky.androidide.lsp.models.CompletionItem()
|
||||
item.setLabel(element.simpleName.toString())
|
||||
item.kind = kind(element)
|
||||
item.detail = element.toString()
|
||||
@@ -182,8 +181,8 @@ abstract class IJavaCompletionProvider(
|
||||
protected open fun classItem(
|
||||
className: String,
|
||||
partialName: String,
|
||||
matchLevel: MatchLevel
|
||||
): CompletionItem {
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
return classItem(emptySet(), null, className, partialName, matchLevel)
|
||||
}
|
||||
|
||||
@@ -192,15 +191,15 @@ abstract class IJavaCompletionProvider(
|
||||
file: Path?,
|
||||
className: String,
|
||||
partialName: String,
|
||||
matchLevel: MatchLevel,
|
||||
): CompletionItem {
|
||||
val item = CompletionItem()
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel,
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
val item = com.itsaky.androidide.lsp.models.CompletionItem()
|
||||
item.setLabel(simpleName(className).toString())
|
||||
item.kind = CompletionItemKind.CLASS
|
||||
item.kind = com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
|
||||
item.detail = className
|
||||
item.sortText = item.label.toString()
|
||||
item.matchLevel = matchLevel
|
||||
val data = CompletionData()
|
||||
val data = com.itsaky.androidide.lsp.models.CompletionData()
|
||||
data.className = className
|
||||
item.data = data
|
||||
item.additionalEditHandler = ClassImportEditHandler(imports, file!!)
|
||||
@@ -215,40 +214,40 @@ abstract class IJavaCompletionProvider(
|
||||
protected open fun packageItem(
|
||||
name: String,
|
||||
partialName: String,
|
||||
matchLevel: MatchLevel
|
||||
): CompletionItem =
|
||||
CompletionItem().apply {
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem =
|
||||
com.itsaky.androidide.lsp.models.CompletionItem().apply {
|
||||
setLabel(name)
|
||||
this.kind = MODULE
|
||||
this.sortText = name
|
||||
this.matchLevel = matchLevel
|
||||
}
|
||||
|
||||
protected open fun kind(e: Element): CompletionItemKind {
|
||||
protected open fun kind(e: Element): com.itsaky.androidide.lsp.models.CompletionItemKind {
|
||||
return when (e.kind) {
|
||||
ANNOTATION_TYPE -> CompletionItemKind.ANNOTATION_TYPE
|
||||
CLASS -> CompletionItemKind.CLASS
|
||||
CONSTRUCTOR -> CompletionItemKind.CONSTRUCTOR
|
||||
ENUM -> CompletionItemKind.ENUM
|
||||
ANNOTATION_TYPE -> com.itsaky.androidide.lsp.models.CompletionItemKind.ANNOTATION_TYPE
|
||||
CLASS -> com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
|
||||
CONSTRUCTOR -> com.itsaky.androidide.lsp.models.CompletionItemKind.CONSTRUCTOR
|
||||
ENUM -> com.itsaky.androidide.lsp.models.CompletionItemKind.ENUM
|
||||
ENUM_CONSTANT -> ENUM_MEMBER
|
||||
EXCEPTION_PARAMETER,
|
||||
PARAMETER, -> PROPERTY
|
||||
FIELD -> CompletionItemKind.FIELD
|
||||
FIELD -> com.itsaky.androidide.lsp.models.CompletionItemKind.FIELD
|
||||
STATIC_INIT,
|
||||
INSTANCE_INIT, -> FUNCTION
|
||||
INTERFACE -> CompletionItemKind.INTERFACE
|
||||
INTERFACE -> com.itsaky.androidide.lsp.models.CompletionItemKind.INTERFACE
|
||||
LOCAL_VARIABLE,
|
||||
RESOURCE_VARIABLE, -> VARIABLE
|
||||
METHOD -> CompletionItemKind.METHOD
|
||||
METHOD -> com.itsaky.androidide.lsp.models.CompletionItemKind.METHOD
|
||||
PACKAGE -> MODULE
|
||||
TYPE_PARAMETER -> CompletionItemKind.TYPE_PARAMETER
|
||||
TYPE_PARAMETER -> com.itsaky.androidide.lsp.models.CompletionItemKind.TYPE_PARAMETER
|
||||
OTHER -> NONE
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun data(task: CompileTask, element: Element, overloads: Int): CompletionData? {
|
||||
val data = CompletionData()
|
||||
protected open fun data(task: CompileTask, element: Element, overloads: Int): com.itsaky.androidide.lsp.models.CompletionData? {
|
||||
val data = com.itsaky.androidide.lsp.models.CompletionData()
|
||||
when {
|
||||
element is TypeElement -> data.className = element.qualifiedName.toString()
|
||||
element.kind == FIELD -> {
|
||||
|
||||
+4
-4
@@ -38,8 +38,8 @@ class IdentifierCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
|
||||
val scopeMembers =
|
||||
ScopeCompletionProvider(completingFile, cursor, compiler, settings)
|
||||
@@ -57,7 +57,7 @@ class IdentifierCompletionProvider(
|
||||
.complete(task, path, partial, endsWithParen)
|
||||
list.addAll(staticImports.items)
|
||||
|
||||
if (CompletionResult.TRIM_TO_MAX && list.size < CompletionResult.MAX_ITEMS) {
|
||||
if (com.itsaky.androidide.lsp.models.CompletionResult.TRIM_TO_MAX && list.size < com.itsaky.androidide.lsp.models.CompletionResult.MAX_ITEMS) {
|
||||
val allLower: Boolean = settings.shouldMatchAllLowerCase()
|
||||
if (allLower || partial.isNotEmpty() && Character.isUpperCase(partial[0])) {
|
||||
val classNames =
|
||||
@@ -78,6 +78,6 @@ class IdentifierCompletionProvider(
|
||||
.complete(task, path, partial, endsWithParen)
|
||||
list.addAll(keywords.items)
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -46,11 +46,11 @@ class ImportCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
log.info("...complete import")
|
||||
|
||||
val names: MutableSet<String> = HashSet()
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
for (className in compiler.publicTopLevelTypes()) {
|
||||
val matchLevel = matchLevel(className, partial)
|
||||
if (matchLevel == NO_MATCH) {
|
||||
@@ -79,6 +79,6 @@ class ImportCompletionProvider(
|
||||
}
|
||||
}
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ class KeywordCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val level: Tree = findKeywordLevel(path)
|
||||
var keywords = arrayOf<String>()
|
||||
when (level) {
|
||||
@@ -56,7 +56,7 @@ class KeywordCompletionProvider(
|
||||
is MethodTree -> keywords = METHOD_BODY_KEYWORDS
|
||||
}
|
||||
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
for (k in keywords) {
|
||||
val matchLevel = matchLevel(k, partial)
|
||||
if (matchLevel == NO_MATCH) {
|
||||
@@ -66,7 +66,7 @@ class KeywordCompletionProvider(
|
||||
list.add(keyword(k, partial, 100))
|
||||
}
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
|
||||
private fun findKeywordLevel(treePath: TreePath): Tree {
|
||||
|
||||
+12
-12
@@ -54,7 +54,7 @@ class MemberReferenceCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val trees = Trees.instance(task.task)
|
||||
val select = path.leaf as MemberReferenceTree
|
||||
log.info("...complete methods of " + select.qualifierExpression)
|
||||
@@ -67,20 +67,20 @@ class MemberReferenceCompletionProvider(
|
||||
is ArrayType -> completeArrayMemberReference(isStatic, partial)
|
||||
is TypeVariable -> completeTypeVariableMemberReference(task, scope, type, isStatic, partial)
|
||||
is DeclaredType -> completeDeclaredTypeMemberReference(task, scope, type, isStatic, partial)
|
||||
else -> CompletionResult.EMPTY
|
||||
else -> com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeArrayMemberReference(
|
||||
isStatic: Boolean,
|
||||
partialName: CharSequence,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
return if (isStatic) {
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
list.add(keyword("new", partialName, 100))
|
||||
CompletionResult(list)
|
||||
com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
} else {
|
||||
CompletionResult.EMPTY
|
||||
com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class MemberReferenceCompletionProvider(
|
||||
type: TypeVariable,
|
||||
isStatic: Boolean,
|
||||
partial: String,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
return when (type.upperBound) {
|
||||
is DeclaredType ->
|
||||
completeDeclaredTypeMemberReference(
|
||||
@@ -108,7 +108,7 @@ class MemberReferenceCompletionProvider(
|
||||
isStatic,
|
||||
partial
|
||||
)
|
||||
else -> CompletionResult.EMPTY
|
||||
else -> com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,12 +118,12 @@ class MemberReferenceCompletionProvider(
|
||||
type: DeclaredType,
|
||||
isStatic: Boolean,
|
||||
partial: String,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val trees = Trees.instance(task.task)
|
||||
val typeElement = type.asElement() as TypeElement
|
||||
val list: MutableList<CompletionItem> = ArrayList()
|
||||
val list: MutableList<com.itsaky.androidide.lsp.models.CompletionItem> = ArrayList()
|
||||
val methods: MutableMap<String, MutableList<ExecutableElement>> = mutableMapOf()
|
||||
val matchLevels: MutableMap<String, MatchLevel> = HashMap()
|
||||
val matchLevels: MutableMap<String, com.itsaky.androidide.lsp.models.MatchLevel> = HashMap()
|
||||
for (member in task.task.elements.getAllMembers(typeElement)) {
|
||||
val matchLevel = matchLevel(member.simpleName, partial)
|
||||
if (matchLevel == NO_MATCH) {
|
||||
@@ -163,6 +163,6 @@ class MemberReferenceCompletionProvider(
|
||||
list.add(keyword("new", partial, 100))
|
||||
}
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -56,7 +56,7 @@ class MemberSelectCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val trees = Trees.instance(task.task)
|
||||
val select = path.leaf as MemberSelectTree
|
||||
log.info("...complete members of " + select.expression)
|
||||
@@ -70,20 +70,20 @@ class MemberSelectCompletionProvider(
|
||||
completeTypeVariableMemberSelect(task, scope, type, isStatic, partial, endsWithParen)
|
||||
is DeclaredType ->
|
||||
completeDeclaredTypeMemberSelect(task, scope, type, isStatic, partial, endsWithParen)
|
||||
else -> CompletionResult.EMPTY
|
||||
else -> com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeArrayMemberSelect(
|
||||
isStatic: Boolean,
|
||||
partialName: CharSequence
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
return if (isStatic) {
|
||||
CompletionResult.EMPTY
|
||||
com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
} else {
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
list.add(keyword("length", partialName, 100))
|
||||
CompletionResult(list)
|
||||
com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class MemberSelectCompletionProvider(
|
||||
isStatic: Boolean,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
return when (type.upperBound) {
|
||||
is DeclaredType ->
|
||||
completeDeclaredTypeMemberSelect(
|
||||
@@ -114,7 +114,7 @@ class MemberSelectCompletionProvider(
|
||||
partial,
|
||||
endsWithParen
|
||||
)
|
||||
else -> CompletionResult.EMPTY
|
||||
else -> com.itsaky.androidide.lsp.models.CompletionResult.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,12 +125,12 @@ class MemberSelectCompletionProvider(
|
||||
isStatic: Boolean,
|
||||
partialName: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val trees = Trees.instance(task.task)
|
||||
val typeElement = type.asElement() as TypeElement
|
||||
val list: MutableList<CompletionItem> = ArrayList()
|
||||
val list: MutableList<com.itsaky.androidide.lsp.models.CompletionItem> = ArrayList()
|
||||
val methods = mutableMapOf<String, MutableList<ExecutableElement>>()
|
||||
val matchLevels: MutableMap<String, MatchLevel> = mutableMapOf()
|
||||
val matchLevels: MutableMap<String, com.itsaky.androidide.lsp.models.MatchLevel> = mutableMapOf()
|
||||
for (member in task.task.elements.getAllMembers(typeElement)) {
|
||||
if (member.kind == CONSTRUCTOR) {
|
||||
continue
|
||||
@@ -173,7 +173,7 @@ class MemberSelectCompletionProvider(
|
||||
list.add(keyword("super", partialName, 100))
|
||||
}
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
|
||||
private fun isEnclosingClass(type: DeclaredType, start: Scope): Boolean {
|
||||
|
||||
+13
-15
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.lsp.java.providers.completion
|
||||
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings
|
||||
import com.itsaky.androidide.lsp.java.FileStore
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.edits.MultipleClassImportEditHandler
|
||||
@@ -29,12 +28,9 @@ import com.itsaky.androidide.lsp.java.utils.FindHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaPoetUtils.Companion.buildMethod
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaPoetUtils.Companion.print
|
||||
import com.itsaky.androidide.lsp.java.utils.ScopeHelper
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.lsp.models.CompletionItemKind
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.InsertTextFormat.SNIPPET
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.squareup.javapoet.MethodSpec.Builder
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.MethodTree
|
||||
@@ -70,9 +66,9 @@ class ScopeCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val trees = Trees.instance(task.task)
|
||||
val list: MutableList<CompletionItem> = ArrayList()
|
||||
val list: MutableList<com.itsaky.androidide.lsp.models.CompletionItem> = ArrayList()
|
||||
val scope = trees.getScope(path)
|
||||
val filter =
|
||||
Predicate<CharSequence?> {
|
||||
@@ -107,7 +103,7 @@ class ScopeCompletionProvider(
|
||||
|
||||
log.info("...found " + list.size + " scope members")
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,8 +120,8 @@ class ScopeCompletionProvider(
|
||||
parentPath: TreePath,
|
||||
method: ExecutableElement,
|
||||
endsWithParen: Boolean,
|
||||
matchLevel: MatchLevel,
|
||||
): CompletionItem {
|
||||
matchLevel: com.itsaky.androidide.lsp.models.MatchLevel,
|
||||
): com.itsaky.androidide.lsp.models.CompletionItem {
|
||||
if (parentPath.leaf.kind != CLASS) {
|
||||
// Can only override if the cursor is directly in a class declaration
|
||||
return method(task, listOf(method), !endsWithParen, matchLevel)
|
||||
@@ -154,7 +150,8 @@ class ScopeCompletionProvider(
|
||||
|
||||
// Print the method details and the annotations
|
||||
// Print the method details and the annotations
|
||||
val indent = EditHelper.indent(FileStore.contents(completingFile), cursor.toInt())
|
||||
val indent =
|
||||
EditHelper.indent(ProjectManager.getDocumentContents(completingFile), cursor.toInt())
|
||||
val builder: Builder
|
||||
try {
|
||||
builder = buildMethod(method, types, type)
|
||||
@@ -168,9 +165,9 @@ class ScopeCompletionProvider(
|
||||
var insertText = print(methodSpec, imports, false)
|
||||
insertText = insertText.replace("\n", "\n${repeatSpaces(indent)}")
|
||||
|
||||
val item = CompletionItem()
|
||||
val item = com.itsaky.androidide.lsp.models.CompletionItem()
|
||||
item.setLabel(methodSpec.name)
|
||||
item.kind = CompletionItemKind.METHOD
|
||||
item.kind = com.itsaky.androidide.lsp.models.CompletionItemKind.METHOD
|
||||
item.detail = method.returnType.toString() + " " + method
|
||||
item.sortText = item.label.toString()
|
||||
item.insertText = insertText
|
||||
@@ -182,10 +179,11 @@ class ScopeCompletionProvider(
|
||||
}
|
||||
|
||||
imports.removeIf { "java.lang." == it || fileImports.contains(it) || filePackage == it }
|
||||
item.additionalEditHandler = MultipleClassImportEditHandler(imports, fileImports, completingFile)
|
||||
item.additionalEditHandler =
|
||||
MultipleClassImportEditHandler(imports, fileImports, completingFile)
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
private fun findSource(
|
||||
compiler: CompilerProvider,
|
||||
task: CompileTask,
|
||||
|
||||
+4
-4
@@ -55,11 +55,11 @@ class StaticImportCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
val list = mutableListOf<CompletionItem>()
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
||||
val trees = Trees.instance(task.task)
|
||||
val methods = mutableMapOf<String, MutableList<ExecutableElement>>()
|
||||
val matchRatios: MutableMap<String, MatchLevel> = mutableMapOf()
|
||||
val matchRatios: MutableMap<String, com.itsaky.androidide.lsp.models.MatchLevel> = mutableMapOf()
|
||||
val previousSize: Int = list.size
|
||||
outer@ for (i in root.imports) {
|
||||
if (!i.isStatic) {
|
||||
@@ -111,7 +111,7 @@ class StaticImportCompletionProvider(
|
||||
|
||||
log.info("...found " + (list.size - previousSize) + " static imports")
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
|
||||
private fun importMatchesPartial(staticImport: Name, partial: String): Boolean {
|
||||
|
||||
+3
-3
@@ -49,7 +49,7 @@ class SwitchConstantCompletionProvider(
|
||||
path: TreePath,
|
||||
partial: String,
|
||||
endsWithParen: Boolean,
|
||||
): CompletionResult {
|
||||
): com.itsaky.androidide.lsp.models.CompletionResult {
|
||||
val switchTree = path.leaf as SwitchTree
|
||||
val exprPath = TreePath(path, switchTree.expression)
|
||||
val type = Trees.instance(task.task).getTypeMirror(exprPath)
|
||||
@@ -73,7 +73,7 @@ class SwitchConstantCompletionProvider(
|
||||
|
||||
log.info("...complete constants of type $type")
|
||||
|
||||
val list: MutableList<CompletionItem> = ArrayList()
|
||||
val list: MutableList<com.itsaky.androidide.lsp.models.CompletionItem> = ArrayList()
|
||||
for (member in task.task.elements.getAllMembers(element)) {
|
||||
if (member.kind != ENUM_CONSTANT) {
|
||||
continue
|
||||
@@ -87,7 +87,7 @@ class SwitchConstantCompletionProvider(
|
||||
list.add(item(task, member, matchLevel))
|
||||
}
|
||||
|
||||
return CompletionResult(list)
|
||||
return com.itsaky.androidide.lsp.models.CompletionResult(list)
|
||||
}
|
||||
|
||||
private fun completeIdentifier(
|
||||
|
||||
+8
-4
@@ -17,13 +17,13 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.providers.completion
|
||||
|
||||
import com.itsaky.androidide.lsp.java.FileStore
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.lsp.models.CompletionItemKind.SNIPPET
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.InsertTextFormat
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel.CASE_INSENSITIVE_EQUAL
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.sun.source.tree.CompilationUnitTree
|
||||
import com.sun.source.tree.Tree.Kind.ERRONEOUS
|
||||
import java.nio.file.Path
|
||||
@@ -36,7 +36,10 @@ class TopLevelSnippetsProvider {
|
||||
if (!hasTypeDeclaration(task.root)) {
|
||||
result.add(classSnippet(file))
|
||||
if (task.root.getPackage() == null) {
|
||||
result.add(packageSnippet(file))
|
||||
val packageSnippet = packageSnippet(file)
|
||||
if (packageSnippet != null) {
|
||||
result.add(packageSnippet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,8 +59,9 @@ class TopLevelSnippetsProvider {
|
||||
return snippetItem("class $name", "class $name {\n $0\n}")
|
||||
}
|
||||
|
||||
private fun packageSnippet(file: Path): CompletionItem {
|
||||
val name = FileStore.suggestedPackageName(file)
|
||||
private fun packageSnippet(file: Path): CompletionItem? {
|
||||
val module = ProjectManager.findModuleForFile(file) ?: return null
|
||||
val name = module.suggestPackageName(file)
|
||||
return snippetItem("package $name", "package $name;\n\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
@@ -22,8 +22,8 @@ import androidx.annotation.RestrictTo;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindVariableAtCursor;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindMethodCallAt;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ import com.itsaky.androidide.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@ import com.itsaky.androidide.lsp.java.visitors.FindAnonymousTypeDeclaration;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem;
|
||||
import com.itsaky.androidide.lsp.models.Command;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
|
||||
@@ -20,8 +20,8 @@ package com.itsaky.androidide.lsp.java.rewrite;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.itsaky.androidide.lsp.java.rewrite.Rewrite;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindMethodDeclarationAt;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
|
||||
@@ -21,8 +21,8 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddImport;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
|
||||
@@ -20,9 +20,9 @@ package com.itsaky.androidide.lsp.java.utils;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationNamed;
|
||||
import com.itsaky.androidide.lsp.models.Location;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.sun.source.tree.ArrayTypeTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.utils;
|
||||
|
||||
import static com.itsaky.androidide.lsp.java.utils.StringSearch.containsClass;
|
||||
import static com.itsaky.androidide.lsp.java.utils.StringSearch.containsInterface;
|
||||
import static com.itsaky.androidide.projects.util.StringSearch.containsClass;
|
||||
import static com.itsaky.androidide.projects.util.StringSearch.containsInterface;
|
||||
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.NodeList;
|
||||
|
||||
@@ -21,8 +21,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.lsp.models.Position;
|
||||
import com.itsaky.androidide.lsp.models.Range;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user