mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(lsp): progress dialog for 'find references' action is shown indefinitely
This happens because the 'ReferenceProvider' in the Java LSP tries to start another compilation process while working with the current compilation task. This results in a deadlock where the current task waits for the new compilation request to return and the new compilation request waits for the current task to finish. This commit fixes this issue. Apart from fixing this issue, this commit also makes the 'Go to definition' and 'Find references' actions cancelable. Also, it eliminates the usage of the deprecated 'ProgressDialog' class for these two actions.
This commit is contained in:
@@ -35,6 +35,7 @@ import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.FlashType.ERROR
|
||||
import com.itsaky.androidide.utils.FlashType.INFO
|
||||
import com.itsaky.androidide.utils.FlashType.SUCCESS
|
||||
import java.util.function.Consumer
|
||||
|
||||
const val DURATION_SHORT = 2000L
|
||||
const val DURATION_LONG = 3500L
|
||||
@@ -44,6 +45,7 @@ val COLOR_SUCCESS = Color.parseColor("#4CAF50")
|
||||
val COLOR_ERROR = Color.parseColor("#f44336")
|
||||
const val COLOR_INFO = Color.DKGRAY
|
||||
|
||||
@JvmOverloads
|
||||
fun Activity.flashbarBuilder(
|
||||
gravity: Flashbar.Gravity = TOP,
|
||||
duration: Long = DURATION_SHORT,
|
||||
@@ -101,6 +103,18 @@ fun Activity.flashInfo(@StringRes msg: Int) {
|
||||
flashbarBuilder().infoIcon().message(msg).showOnUiThread()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun Activity.flashProgress(configure: Consumer<Flashbar.Builder>? = null, action: Consumer<Flashbar>) {
|
||||
val builder = flashbarBuilder(gravity = TOP, duration = DURATION_INDEFINITE)
|
||||
.showProgress(Flashbar.ProgressPosition.LEFT)
|
||||
|
||||
configure?.accept(builder)
|
||||
|
||||
val flashbar = builder.build()
|
||||
flashbar.show()
|
||||
action.accept(flashbar)
|
||||
}
|
||||
|
||||
fun Flashbar.Builder.showOnUiThread() {
|
||||
build().showOnUiThread()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.app.Activity
|
||||
import androidx.annotation.StringRes
|
||||
import com.blankj.utilcode.util.ActivityUtils
|
||||
import com.itsaky.androidide.flashbar.Flashbar
|
||||
import java.util.function.Consumer
|
||||
|
||||
fun flashbarBuilder(): Flashbar.Builder? {
|
||||
return withActivity { flashbarBuilder() }
|
||||
@@ -58,6 +59,11 @@ fun flashInfo(@StringRes msg: Int) {
|
||||
withActivity { flashInfo(msg) }
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun flashProgress(configure: Consumer<Flashbar.Builder>? = null, action: Consumer<Flashbar>) {
|
||||
withActivity { flashProgress(configure, action) }
|
||||
}
|
||||
|
||||
private fun <T> withActivity(action: Activity.() -> T?): T? {
|
||||
return ActivityUtils.getTopActivity()?.let { it.action() }
|
||||
?: run {
|
||||
@@ -68,6 +74,7 @@ private fun <T> withActivity(action: Activity.() -> T?): T? {
|
||||
|
||||
/** The type of flashbar message. */
|
||||
enum class FlashType {
|
||||
|
||||
ERROR,
|
||||
INFO,
|
||||
SUCCESS
|
||||
|
||||
+4
-3
@@ -39,7 +39,8 @@ import java.util.function.Predicate
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CommonCompletionProvider(private val server: ILanguageServer) {
|
||||
class CommonCompletionProvider(private val server: ILanguageServer,
|
||||
private val cancelChecker: CompletionCancelChecker) {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
@@ -66,7 +67,7 @@ class CommonCompletionProvider(private val server: ILanguageServer) {
|
||||
CompletionParams(Position(position.line, position.column, position.index), file)
|
||||
params.content = content
|
||||
params.prefix = prefix
|
||||
server.complete(params)
|
||||
server.complete(params, cancelChecker)
|
||||
} catch (e: Throwable) {
|
||||
|
||||
if (e is ProcessCancelledException) {
|
||||
@@ -74,7 +75,7 @@ class CommonCompletionProvider(private val server: ILanguageServer) {
|
||||
}
|
||||
|
||||
// Do not log if completion was interrupted or cancelled
|
||||
if (!(e is ProcessCancelledException || e is CompletionCancelledException)) {
|
||||
if (!(e is ProcessCancelledException || e is CompletionCancelledException)) {
|
||||
if (!server.handleFailure(LSPFailure(COMPLETION, e))) {
|
||||
log.error("Unable to compute completions", e)
|
||||
}
|
||||
|
||||
+4
-2
@@ -17,7 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.editor.language
|
||||
|
||||
import com.itsaky.androidide.lsp.api.ICompletionCancelChecker
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher
|
||||
|
||||
@@ -26,11 +26,13 @@ import io.github.rosemoe.sora.lang.completion.CompletionPublisher
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CompletionCancelChecker(val publisher: CompletionPublisher) : ICompletionCancelChecker {
|
||||
class CompletionCancelChecker(private val publisher: CompletionPublisher) :
|
||||
ICancelChecker.Default() {
|
||||
|
||||
/** Check if the completion is cancelled. */
|
||||
@Throws(CompletionCancelledException::class)
|
||||
override fun abortIfCancelled() {
|
||||
publisher.checkCancelled()
|
||||
super.abortIfCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,25 +17,21 @@
|
||||
package com.itsaky.androidide.editor.language;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.editor.api.IEditor;
|
||||
import com.itsaky.androidide.lookup.Lookup;
|
||||
import com.itsaky.androidide.lsp.api.ICompletionCancelChecker;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferencesKt;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
import io.github.rosemoe.sora.lang.format.Formatter;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base class for language implementations in the IDE.
|
||||
@@ -43,7 +39,7 @@ import io.github.rosemoe.sora.text.ContentReference;
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class IDELanguage implements Language {
|
||||
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("IDELanguage");
|
||||
private Formatter formatter;
|
||||
|
||||
@@ -56,10 +52,10 @@ public abstract class IDELanguage implements Language {
|
||||
throws CompletionCancelledException {
|
||||
try {
|
||||
final var cancelChecker = new CompletionCancelChecker(publisher);
|
||||
Lookup.getDefault().register(ICompletionCancelChecker.class, cancelChecker);
|
||||
doComplete(content, position, publisher, extraArguments);
|
||||
Lookup.getDefault().register(ICancelChecker.class, cancelChecker);
|
||||
doComplete(content, position, publisher, cancelChecker, extraArguments);
|
||||
} finally {
|
||||
Lookup.getDefault().unregister(ICompletionCancelChecker.class);
|
||||
Lookup.getDefault().unregister(ICancelChecker.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +63,7 @@ public abstract class IDELanguage implements Language {
|
||||
final @NonNull ContentReference content,
|
||||
final @NonNull CharPosition position,
|
||||
final @NonNull CompletionPublisher publisher,
|
||||
final @NonNull CompletionCancelChecker cancelChecker,
|
||||
final @NonNull Bundle extraArguments) {
|
||||
final var server = getLanguageServer();
|
||||
if (server == null) {
|
||||
@@ -80,7 +77,7 @@ public abstract class IDELanguage implements Language {
|
||||
return;
|
||||
}
|
||||
|
||||
final var completionProvider = new CommonCompletionProvider(server);
|
||||
final var completionProvider = new CommonCompletionProvider(server, cancelChecker);
|
||||
final var file = Paths.get(path);
|
||||
|
||||
final var completionItems =
|
||||
@@ -122,12 +119,13 @@ public abstract class IDELanguage implements Language {
|
||||
public int getTabSize() {
|
||||
return EditorPreferencesKt.getTabSize();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull final ContentReference content, final int line, final int column) {
|
||||
public int getIndentAdvance(@NonNull final ContentReference content, final int line,
|
||||
final int column) {
|
||||
return getIndentAdvance(content.getLine(line).substring(0, column));
|
||||
}
|
||||
|
||||
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import static com.itsaky.androidide.resources.R.string;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
@@ -33,9 +33,11 @@ import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.editor.R;
|
||||
import com.itsaky.androidide.editor.adapters.CompletionListAdapter;
|
||||
import com.itsaky.androidide.editor.api.IEditor;
|
||||
import com.itsaky.androidide.editor.api.ILspEditor;
|
||||
@@ -56,9 +58,11 @@ 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.flashbar.Flashbar;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageClient;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.models.Command;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionParams;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.lsp.models.ExpandSelectionParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams;
|
||||
@@ -68,10 +72,12 @@ import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.projects.FileManager;
|
||||
import com.itsaky.androidide.syntax.colorschemes.DynamicColorScheme;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
import com.itsaky.androidide.utils.FlashbarActivityUtilsKt;
|
||||
import com.itsaky.androidide.utils.FlashbarUtilsKt;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
@@ -86,6 +92,7 @@ import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
import io.github.rosemoe.sora.widget.snippet.variable.ISnippetVariableResolver;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import kotlin.io.FilesKt;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@@ -492,84 +499,70 @@ public class IDEEditor extends CodeEditor implements IEditor, ILspEditor {
|
||||
* file in this editor, the range specified in the response will be selected.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public void findDefinition() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressDialog pd = ProgressDialog.show(getContext(), null,
|
||||
getContext().getString(string.msg_finding_definition));
|
||||
final var cancelChecker = new ICancelChecker.Default();
|
||||
|
||||
try {
|
||||
final CompletableFuture<DefinitionResult> future = CompletableFuture.supplyAsync(() -> {
|
||||
final var params = new com.itsaky.androidide.lsp.models.DefinitionParams(getFile().toPath(),
|
||||
new Position(getCursor().getLeftLine(),
|
||||
getCursor().getLeftColumn()));
|
||||
final var action = (Consumer<Flashbar>) flashbar -> {
|
||||
final var params = new DefinitionParams(getFile().toPath(),
|
||||
getCursorLSPPosition());
|
||||
|
||||
return languageServer.findDefinition(params);
|
||||
});
|
||||
final CompletableFuture<DefinitionResult> future = CompletableFuture.supplyAsync(
|
||||
() -> languageServer.findDefinition(params, cancelChecker));
|
||||
|
||||
future.whenComplete((result, error) -> {
|
||||
if (result == null || languageClient == null || future.isCancelled() ||
|
||||
future.isCompletedExceptionally()) {
|
||||
LOG.error("An error occurred while finding definition", error);
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
future.whenComplete(
|
||||
(result, error) -> onFindDefinitionResult(flashbar, future, result, error));
|
||||
};
|
||||
|
||||
final var locations = result.getLocations();
|
||||
if (locations.size() == 0) {
|
||||
LOG.error("No definitions found", "Size:", locations.size());
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
if (locations.size() == 1) {
|
||||
var location = locations.get(0);
|
||||
if (DocumentUtils.isSameFile(location.getFile(), getFile().toPath())) {
|
||||
setSelection(location.getRange());
|
||||
return;
|
||||
}
|
||||
languageClient.showDocument(
|
||||
new ShowDocumentParams(location.getFile(), location.getRange()));
|
||||
} else {
|
||||
languageClient.showLocations(locations);
|
||||
}
|
||||
});
|
||||
|
||||
dismissOnUiThread(pd);
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
LOG.error("An error occurred while finding definition", th);
|
||||
showDefinitionNotFound(pd);
|
||||
}
|
||||
FlashbarActivityUtilsKt.flashProgress(((Activity) getContext()),
|
||||
builder -> configureFlashbar(builder, string.msg_finding_definition, cancelChecker),
|
||||
action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the user that no definitions can be found.
|
||||
*
|
||||
* @param pd The {@link ProgressDialog} that was shown when requesting the definitions.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showDefinitionNotFound(final ProgressDialog pd) {
|
||||
//noinspection ConstantConditions
|
||||
private void onFindDefinitionResult(Flashbar flashbar, CompletableFuture<DefinitionResult> future,
|
||||
DefinitionResult result, Throwable error) {
|
||||
LOG.debug("onFindDefinitionsResult");
|
||||
dismissOnUiThread(flashbar);
|
||||
|
||||
if (result == null || languageClient == null || future.isCancelled() ||
|
||||
future.isCompletedExceptionally()) {
|
||||
LOG.error("An error occurred while finding definition", error);
|
||||
notify(string.msg_no_definition);
|
||||
return;
|
||||
}
|
||||
|
||||
final var locations = result.getLocations();
|
||||
if (locations.size() == 0) {
|
||||
LOG.error("No definitions found", "Size:", locations.size());
|
||||
notify(string.msg_no_definition);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
FlashbarUtilsKt.flashError(string.msg_no_definition);
|
||||
pd.dismiss();
|
||||
if (locations.size() == 1) {
|
||||
var location = locations.get(0);
|
||||
if (DocumentUtils.isSameFile(location.getFile(), getFile().toPath())) {
|
||||
setSelection(location.getRange());
|
||||
return;
|
||||
}
|
||||
languageClient.showDocument(
|
||||
new ShowDocumentParams(location.getFile(), location.getRange()));
|
||||
} else {
|
||||
languageClient.showLocations(locations);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the given dialog on the UI thread.
|
||||
* Dismisses the given flashbar on the UI thread.
|
||||
*
|
||||
* @param dialog The dialog to dismiss.
|
||||
* @param flashbar The flashbar to dismiss.
|
||||
*/
|
||||
private void dismissOnUiThread(@NonNull final Dialog dialog) {
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(dialog::dismiss);
|
||||
private void dismissOnUiThread(@NonNull final Flashbar flashbar) {
|
||||
post(flashbar::dismiss);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -585,66 +578,71 @@ public class IDEEditor extends CodeEditor implements IEditor, ILspEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") final ProgressDialog pd = ProgressDialog.show(getContext(),
|
||||
null, getContext().getString(string.msg_finding_references));
|
||||
final var cancelChecker = new ICancelChecker.Default();
|
||||
|
||||
try {
|
||||
final var action = (Consumer<Flashbar>) flashbar -> {
|
||||
final var referenceParams = new ReferenceParams(getFile().toPath(),
|
||||
getCursorLSPPosition(), true);
|
||||
|
||||
final var future = CompletableFuture.supplyAsync(
|
||||
() -> languageServer.findReferences(referenceParams));
|
||||
() -> languageServer.findReferences(referenceParams, cancelChecker));
|
||||
|
||||
future.whenComplete((result, error) -> onFindReferencesResult(pd, future, result, error));
|
||||
} catch (Throwable th) {
|
||||
LOG.error("An error occurred while finding references", th);
|
||||
showReferencesNotFound(pd);
|
||||
}
|
||||
future.whenComplete(
|
||||
(result, error) -> onFindReferencesResult(flashbar, future, result, error));
|
||||
};
|
||||
|
||||
FlashbarActivityUtilsKt.flashProgress(((Activity) getContext()),
|
||||
builder -> configureFlashbar(builder, R.string.msg_finding_references, cancelChecker),
|
||||
action);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void onFindReferencesResult(final ProgressDialog pd,
|
||||
private void onFindReferencesResult(final Flashbar flashbar,
|
||||
final CompletableFuture<ReferenceResult> future,
|
||||
final ReferenceResult result, final Throwable error
|
||||
) {
|
||||
LOG.debug("onFindRefsResult");
|
||||
dismissOnUiThread(flashbar);
|
||||
|
||||
if (result == null || languageClient == null || future.isCancelled() ||
|
||||
future.isCompletedExceptionally()) {
|
||||
LOG.error("An error occurred while finding references", error);
|
||||
showReferencesNotFound(pd);
|
||||
notify(string.msg_no_references);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getLocations().isEmpty()) {
|
||||
showReferencesNotFound(pd);
|
||||
notify(string.msg_no_references);
|
||||
return;
|
||||
} else {
|
||||
if (result.getLocations().size() == 1) {
|
||||
final var loc = result.getLocations().get(0);
|
||||
if (DocumentUtils.isSameFile(loc.getFile(), getFile().toPath())) {
|
||||
setSelection(loc.getRange());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> languageClient.showLocations(result.getLocations()));
|
||||
}
|
||||
|
||||
dismissOnUiThread(pd);
|
||||
if (result.getLocations().size() == 1) {
|
||||
final var loc = result.getLocations().get(0);
|
||||
if (DocumentUtils.isSameFile(loc.getFile(), getFile().toPath())) {
|
||||
setSelection(loc.getRange());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(() -> languageClient.showLocations(result.getLocations()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the user that no references were found for the selected token.
|
||||
* Notify the user that no results were found for a task.
|
||||
*
|
||||
* @param pd The {@link ProgressDialog} that was shown when requesting references.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showReferencesNotFound(final ProgressDialog pd) {
|
||||
private void notify(@StringRes final int message) {
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
FlashbarUtilsKt.flashError(string.msg_no_references);
|
||||
pd.dismiss();
|
||||
});
|
||||
ThreadUtils.runOnUiThread(() -> FlashbarUtilsKt.flashError(message));
|
||||
}
|
||||
|
||||
private void configureFlashbar(Flashbar.Builder builder, @StringRes int message,
|
||||
ICancelChecker cancelChecker) {
|
||||
builder.message(message)
|
||||
.negativeActionText(android.R.string.cancel)
|
||||
.negativeActionTapListener(bar -> {
|
||||
cancelChecker.cancel();
|
||||
bar.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.itsaky.androidide.lsp.models.CompletionResult;
|
||||
import com.itsaky.androidide.lsp.models.CompletionsKt;
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel;
|
||||
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@@ -59,7 +60,7 @@ public interface ICompletionProvider {
|
||||
|
||||
/** Abort the completion process if cancelled. */
|
||||
default void abortCompletionIfCancelled() {
|
||||
final var checker = Lookup.getDefault().lookup(ICompletionCancelChecker.class);
|
||||
final var checker = Lookup.getDefault().lookup(ICancelChecker.class);
|
||||
if (checker != null) {
|
||||
checker.abortIfCancelled();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
|
||||
@@ -113,29 +114,32 @@ public interface ILanguageServer {
|
||||
/**
|
||||
* Compute code completions for the given completion params.
|
||||
*
|
||||
* @param params The completion params.
|
||||
* @param params The completion params.
|
||||
* @param cancelChecker
|
||||
* @return The completion provider.
|
||||
*/
|
||||
@NonNull
|
||||
CompletionResult complete(CompletionParams params);
|
||||
CompletionResult complete(CompletionParams params, ICancelChecker cancelChecker);
|
||||
|
||||
/**
|
||||
* Find references using the given params.
|
||||
*
|
||||
* @param params The params to use for computing references.
|
||||
* @param params The params to use for computing references.
|
||||
* @param cancelChecker
|
||||
* @return The result of the computation.
|
||||
*/
|
||||
@NonNull
|
||||
ReferenceResult findReferences(@NonNull ReferenceParams params);
|
||||
ReferenceResult findReferences(@NonNull ReferenceParams params, ICancelChecker cancelChecker);
|
||||
|
||||
/**
|
||||
* Find definition using the given params.
|
||||
*
|
||||
* @param params The params to use for computing the definition.
|
||||
* @param params The params to use for computing the definition.
|
||||
* @param cancelChecker
|
||||
* @return The result of the computation.
|
||||
*/
|
||||
@NonNull
|
||||
DefinitionResult findDefinition(@NonNull DefinitionParams params);
|
||||
DefinitionResult findDefinition(@NonNull DefinitionParams params, ICancelChecker cancelChecker);
|
||||
|
||||
/**
|
||||
* Request the server to provide an expanded selection range for the current selection.
|
||||
|
||||
@@ -20,7 +20,6 @@ package com.itsaky.androidide.lsp.java;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent;
|
||||
@@ -60,6 +59,7 @@ 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.models.Range;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.projects.FileManager;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
@@ -67,15 +67,13 @@ import com.itsaky.androidide.projects.api.Project;
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.VMUtils;
|
||||
|
||||
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;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
public class JavaLanguageServer implements ILanguageServer {
|
||||
|
||||
@@ -87,7 +85,7 @@ public class JavaLanguageServer implements ILanguageServer {
|
||||
private Path selectedFile;
|
||||
private final AnalyzeTimer timer = new AnalyzeTimer(this::analyzeSelected);
|
||||
private CachedCompletion cachedCompletion;
|
||||
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("JavaLanguageServer");
|
||||
|
||||
public JavaLanguageServer() {
|
||||
@@ -179,14 +177,14 @@ public class JavaLanguageServer implements ILanguageServer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletionResult complete(final CompletionParams params) {
|
||||
public CompletionResult complete(final CompletionParams params, ICancelChecker cancelChecker) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (compiler == null
|
||||
|| !settings.completionsEnabled()
|
||||
|| !this.completionProvider.canComplete(params.getFile())) {
|
||||
return CompletionResult.EMPTY;
|
||||
}
|
||||
|
||||
|
||||
if (diagnosticProvider.isAnalyzing()) {
|
||||
LOG.warn("Cancelling source code analysis due to completion request");
|
||||
diagnosticProvider.cancel();
|
||||
@@ -199,24 +197,26 @@ public class JavaLanguageServer implements ILanguageServer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ReferenceResult findReferences(@NonNull ReferenceParams params) {
|
||||
public ReferenceResult findReferences(@NonNull ReferenceParams params,
|
||||
ICancelChecker cancelChecker) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.referencesEnabled() || compiler == null) {
|
||||
return new ReferenceResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
return new ReferenceProvider(compiler).findReferences(params);
|
||||
return new ReferenceProvider(compiler, cancelChecker).findReferences(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DefinitionResult findDefinition(@NonNull DefinitionParams params) {
|
||||
public DefinitionResult findDefinition(@NonNull DefinitionParams params,
|
||||
ICancelChecker cancelChecker) {
|
||||
final JavaCompilerService compiler = getCompiler(params.getFile());
|
||||
if (!settings.definitionsEnabled() || compiler == null) {
|
||||
return new DefinitionResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
return new DefinitionProvider(compiler, getSettings()).findDefinition(params);
|
||||
return new DefinitionProvider(compiler, getSettings(), cancelChecker).findDefinition(params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
+3
-3
@@ -18,9 +18,9 @@
|
||||
package com.itsaky.androidide.lsp.java.providers
|
||||
|
||||
import com.itsaky.androidide.lookup.Lookup
|
||||
import com.itsaky.androidide.lsp.api.ICompletionCancelChecker
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -34,12 +34,12 @@ abstract class BaseJavaServiceProvider(
|
||||
protected val compiler: JavaCompilerService,
|
||||
protected val settings: IServerSettings
|
||||
) {
|
||||
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
/** Abort the completion if cancelled. */
|
||||
fun abortCompletionIfCancelled() {
|
||||
val checker = Lookup.getDefault().lookup(ICompletionCancelChecker::class.java)
|
||||
val checker = Lookup.getDefault().lookup(ICancelChecker::class.java)
|
||||
checker?.abortIfCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -15,15 +15,14 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.api
|
||||
package com.itsaky.androidide.lsp.java.providers
|
||||
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
|
||||
/**
|
||||
* Checks for cancellation of a process.
|
||||
* A service provider which supports cancellation.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
fun interface ICompletionCancelChecker {
|
||||
|
||||
/** Abort the process if it is cancelled. */
|
||||
fun abortIfCancelled()
|
||||
}
|
||||
abstract class CancelableServiceProvider(cancelChecker: ICancelChecker) :
|
||||
ICancelChecker by cancelChecker
|
||||
+14
-11
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.androidide.lsp.java.providers;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings;
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService;
|
||||
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
|
||||
@@ -33,21 +31,21 @@ import com.itsaky.androidide.lsp.models.DefinitionParams;
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult;
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jdkx.lang.model.element.Element;
|
||||
import jdkx.lang.model.element.TypeElement;
|
||||
import jdkx.lang.model.type.TypeKind;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
|
||||
public class DefinitionProvider {
|
||||
public class DefinitionProvider extends CancelableServiceProvider {
|
||||
|
||||
public static final List<Location> NOT_SUPPORTED = Collections.emptyList();
|
||||
private static final ILogger LOG = ILogger.newInstance("JavaDefinitionProvider");
|
||||
private final JavaCompilerService compiler;
|
||||
@@ -56,7 +54,9 @@ public class DefinitionProvider {
|
||||
private Position position;
|
||||
private int line, column;
|
||||
|
||||
public DefinitionProvider(JavaCompilerService compiler, IServerSettings settings) {
|
||||
public DefinitionProvider(JavaCompilerService compiler, IServerSettings settings,
|
||||
ICancelChecker cancelChecker) {
|
||||
super(cancelChecker);
|
||||
this.compiler = compiler;
|
||||
this.settings = settings;
|
||||
}
|
||||
@@ -76,9 +76,11 @@ public class DefinitionProvider {
|
||||
}
|
||||
|
||||
public List<Location> findDefinition() {
|
||||
abortIfCancelled();
|
||||
final SynchronizedTask compile = compiler.compile(file);
|
||||
abortIfCancelled();
|
||||
final Element element =
|
||||
compile.get(task -> NavigationHelper.findElement(task, file, line, column));
|
||||
compile.get(task -> NavigationHelper.findElement(task, file, line, column, this));
|
||||
|
||||
if (element == null) {
|
||||
LOG.error("Cannot find element at line:", line, "and column:", column);
|
||||
@@ -88,9 +90,9 @@ public class DefinitionProvider {
|
||||
IJavaDefinitionProvider provider = null;
|
||||
|
||||
if (element.asType().getKind() == TypeKind.ERROR) {
|
||||
provider = new ErroneousDefinitionProvider(position, file, compiler, settings);
|
||||
provider = new ErroneousDefinitionProvider(position, file, compiler, settings, this);
|
||||
} else if (NavigationHelper.isLocal(element)) {
|
||||
provider = new LocalDefinitionProvider(position, file, compiler, settings);
|
||||
provider = new LocalDefinitionProvider(position, file, compiler, settings, this);
|
||||
}
|
||||
|
||||
if (provider == null) {
|
||||
@@ -108,10 +110,10 @@ public class DefinitionProvider {
|
||||
|
||||
final JavaFileObject jfo = optional.get();
|
||||
if (DocumentUtils.isSameFile(Paths.get(jfo.toUri()), file)) {
|
||||
provider = new LocalDefinitionProvider(position, file, compiler, settings);
|
||||
provider = new LocalDefinitionProvider(position, file, compiler, settings, this);
|
||||
} else {
|
||||
provider =
|
||||
new RemoteDefinitionProvider(position, file, compiler, settings).setOtherFile(jfo);
|
||||
new RemoteDefinitionProvider(position, file, compiler, settings, this).setOtherFile(jfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +122,7 @@ public class DefinitionProvider {
|
||||
|
||||
private String className(Element element) {
|
||||
while (element != null) {
|
||||
abortIfCancelled();
|
||||
if (element instanceof TypeElement) {
|
||||
TypeElement type = (TypeElement) element;
|
||||
return type.getQualifiedName().toString();
|
||||
|
||||
+69
-26
@@ -18,35 +18,36 @@
|
||||
package com.itsaky.androidide.lsp.java.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.CancelChecker;
|
||||
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.ReferenceParams;
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult;
|
||||
import com.itsaky.androidide.models.Location;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.util.TreePath;
|
||||
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import jdkx.lang.model.element.Element;
|
||||
import jdkx.lang.model.element.TypeElement;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.util.TreePath;
|
||||
|
||||
public class ReferenceProvider {
|
||||
public class ReferenceProvider extends CancelableServiceProvider {
|
||||
|
||||
public static final List<Location> NOT_SUPPORTED = Collections.emptyList();
|
||||
private final CompilerProvider compiler;
|
||||
private Path file;
|
||||
private int line, column;
|
||||
|
||||
public ReferenceProvider(CompilerProvider compiler) {
|
||||
public ReferenceProvider(CompilerProvider compiler, ICancelChecker checker) {
|
||||
super(checker);
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
@@ -57,60 +58,102 @@ public class ReferenceProvider {
|
||||
// 1-based line and column indexes
|
||||
this.line = params.getPosition().getLine() + 1;
|
||||
this.column = params.getPosition().getColumn() + 1;
|
||||
return new ReferenceResult(find());
|
||||
|
||||
List<Location> locations;
|
||||
try {
|
||||
locations = find();
|
||||
} catch (Exception err) {
|
||||
if (!CancelChecker.isCancelled(err)) {
|
||||
throw err;
|
||||
}
|
||||
locations = new ArrayList<>();
|
||||
}
|
||||
|
||||
return new ReferenceResult(locations);
|
||||
}
|
||||
|
||||
public List<Location> find() {
|
||||
abortIfCancelled();
|
||||
final SynchronizedTask synchronizedTask = compiler.compile(file);
|
||||
return synchronizedTask.get(
|
||||
|
||||
// findTypeReferences and findMemberReferences initiate another compilation task
|
||||
// However, initiating a compilation task while another compilation is in progress will result in a deadlock
|
||||
// Therefore, we return a supplier from the current synchronized task and
|
||||
final Supplier<List<Location>> result = synchronizedTask.get(
|
||||
task -> {
|
||||
Element element = NavigationHelper.findElement(task, file, line, column);
|
||||
if (element == null) return NOT_SUPPORTED;
|
||||
if (NavigationHelper.isLocal(element)) {
|
||||
return findReferences(task);
|
||||
abortIfCancelled();
|
||||
Element element = NavigationHelper.findElement(task, file, line, column, this);
|
||||
if (element == null) {
|
||||
return () -> NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (NavigationHelper.isLocal(element)) {
|
||||
// findReferences method here uses the compilation task object
|
||||
// however, finding the references lazily using supplier will leak this task
|
||||
final var references = findReferences(task);
|
||||
return () -> references;
|
||||
}
|
||||
|
||||
if (NavigationHelper.isType(element)) {
|
||||
TypeElement type = (TypeElement) element;
|
||||
String className = type.getQualifiedName().toString();
|
||||
task.close();
|
||||
return findTypeReferences(className);
|
||||
return () -> findTypeReferences(className);
|
||||
}
|
||||
|
||||
if (NavigationHelper.isMember(element)) {
|
||||
TypeElement parentClass = (TypeElement) element.getEnclosingElement();
|
||||
String className = parentClass.getQualifiedName().toString();
|
||||
String memberName = element.getSimpleName().toString();
|
||||
final var parentClass = (TypeElement) element.getEnclosingElement();
|
||||
final var className = parentClass.getQualifiedName().toString();
|
||||
|
||||
var memberName = element.getSimpleName().toString();
|
||||
if (memberName.equals("<init>")) {
|
||||
memberName = parentClass.getSimpleName().toString();
|
||||
}
|
||||
task.close();
|
||||
return findMemberReferences(className, memberName);
|
||||
|
||||
String finalMemberName = memberName;
|
||||
return () -> findMemberReferences(className, finalMemberName);
|
||||
}
|
||||
return NOT_SUPPORTED;
|
||||
|
||||
return () -> NOT_SUPPORTED;
|
||||
});
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private List<Location> findTypeReferences(String className) {
|
||||
abortIfCancelled();
|
||||
Path[] files = compiler.findTypeReferences(className);
|
||||
if (files.length == 0) return Collections.emptyList();
|
||||
if (files.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
abortIfCancelled();
|
||||
return compiler.compile(files).get(this::findReferences);
|
||||
}
|
||||
|
||||
private List<Location> findMemberReferences(String className, String memberName) {
|
||||
Path[] files = compiler.findMemberReferences(className, memberName);
|
||||
if (files.length == 0) return Collections.emptyList();
|
||||
abortIfCancelled();
|
||||
final var files = compiler.findMemberReferences(className, memberName);
|
||||
if (files.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
abortIfCancelled();
|
||||
return compiler.compile(files).get(this::findReferences);
|
||||
}
|
||||
|
||||
private List<Location> findReferences(CompileTask task) {
|
||||
Element element = NavigationHelper.findElement(task, file, line, column);
|
||||
abortIfCancelled();
|
||||
Element element = NavigationHelper.findElement(task, file, line, column, this);
|
||||
List<TreePath> paths = new ArrayList<>();
|
||||
for (CompilationUnitTree root : task.roots) {
|
||||
abortIfCancelled();
|
||||
new FindReferences(task.task, element).scan(root, paths);
|
||||
}
|
||||
List<Location> locations = new ArrayList<>();
|
||||
for (TreePath p : paths) {
|
||||
abortIfCancelled();
|
||||
locations.add(FindHelper.location(task, p));
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -24,6 +24,7 @@ import com.itsaky.androidide.lsp.java.providers.DefinitionProvider
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper
|
||||
import com.itsaky.androidide.models.Location
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import com.itsaky.androidide.utils.DocumentUtils.isSameFile
|
||||
import openjdk.source.util.Trees
|
||||
import java.nio.file.Path
|
||||
@@ -41,8 +42,8 @@ class ErroneousDefinitionProvider(
|
||||
position: Position,
|
||||
completingFile: Path,
|
||||
compiler: JavaCompilerService,
|
||||
settings: IServerSettings,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings) {
|
||||
settings: IServerSettings, cancelChecker: ICancelChecker,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings, cancelChecker) {
|
||||
|
||||
override fun doFindDefinition(element: Element): List<Location> {
|
||||
val name = element.simpleName ?: return DefinitionProvider.NOT_SUPPORTED
|
||||
@@ -54,6 +55,7 @@ class ErroneousDefinitionProvider(
|
||||
|
||||
private fun findAllMembers(className: String, memberName: String): List<Location> {
|
||||
val otherFile = compiler.findAnywhere(className)
|
||||
abortIfCancelled()
|
||||
if (!otherFile.isPresent) {
|
||||
log.error("Cannot find source file for class:", className)
|
||||
return emptyList()
|
||||
@@ -65,16 +67,20 @@ class ErroneousDefinitionProvider(
|
||||
sources = listOf<JavaFileObject>(fileAsSource)
|
||||
}
|
||||
|
||||
abortIfCancelled()
|
||||
|
||||
return compiler.compile(sources).get { task ->
|
||||
val locations = mutableListOf<Location>()
|
||||
val trees = Trees.instance(task.task)
|
||||
val elements = task.task.elements
|
||||
val parentClass = elements.getTypeElement(className)
|
||||
|
||||
abortIfCancelled()
|
||||
for (member in elements.getAllMembers(parentClass)) {
|
||||
if (!member.simpleName.contentEquals(memberName)) continue
|
||||
val path = trees.getPath(member) ?: continue
|
||||
val location = FindHelper.location(task, path, memberName)
|
||||
abortIfCancelled()
|
||||
locations.add(location)
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -23,9 +23,10 @@ import com.itsaky.androidide.lsp.java.providers.BaseJavaServiceProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.DefinitionProvider
|
||||
import com.itsaky.androidide.models.Location
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.nio.file.Path
|
||||
import jdkx.lang.model.element.Element
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Provides definition for a specific symbol in Java source code.
|
||||
@@ -36,8 +37,9 @@ abstract class IJavaDefinitionProvider(
|
||||
protected val position: Position,
|
||||
completingFile: Path,
|
||||
compiler: JavaCompilerService,
|
||||
settings: IServerSettings
|
||||
) : BaseJavaServiceProvider(completingFile, compiler, settings) {
|
||||
settings: IServerSettings,
|
||||
cancelChecker: ICancelChecker
|
||||
) : BaseJavaServiceProvider(completingFile, compiler, settings), ICancelChecker by cancelChecker {
|
||||
|
||||
protected val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
|
||||
+4
-2
@@ -22,6 +22,7 @@ import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper
|
||||
import com.itsaky.androidide.models.Location
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import openjdk.source.util.Trees
|
||||
import java.nio.file.Path
|
||||
import jdkx.lang.model.element.Element
|
||||
@@ -35,8 +36,8 @@ class LocalDefinitionProvider(
|
||||
position: Position,
|
||||
completingFile: Path,
|
||||
compiler: JavaCompilerService,
|
||||
settings: IServerSettings,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings) {
|
||||
settings: IServerSettings, cancelChecker: ICancelChecker,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings, cancelChecker) {
|
||||
|
||||
override fun doFindDefinition(element: Element): List<Location> {
|
||||
return compiler.compile(file).get {
|
||||
@@ -52,6 +53,7 @@ class LocalDefinitionProvider(
|
||||
name = element.enclosingElement.simpleName
|
||||
}
|
||||
|
||||
abortIfCancelled()
|
||||
return@get listOf(FindHelper.location(it, path, name))
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -23,6 +23,7 @@ import com.itsaky.androidide.lsp.java.compiler.SourceFileObject
|
||||
import com.itsaky.androidide.lsp.java.utils.NavigationHelper
|
||||
import com.itsaky.androidide.models.Location
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.progress.ICancelChecker
|
||||
import java.nio.file.Path
|
||||
import jdkx.lang.model.element.Element
|
||||
import jdkx.tools.JavaFileObject
|
||||
@@ -36,8 +37,8 @@ class RemoteDefinitionProvider(
|
||||
position: Position,
|
||||
completingFile: Path,
|
||||
compiler: JavaCompilerService,
|
||||
settings: IServerSettings,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings) {
|
||||
settings: IServerSettings, cancelChecker: ICancelChecker,
|
||||
) : IJavaDefinitionProvider(position, completingFile, compiler, settings, cancelChecker) {
|
||||
|
||||
private lateinit var otherFile: JavaFileObject
|
||||
|
||||
@@ -48,7 +49,7 @@ class RemoteDefinitionProvider(
|
||||
|
||||
override fun doFindDefinition(element: Element): List<Location> {
|
||||
// val task = compiler.compile(listOf(SourceFileObject(file), otherFile))
|
||||
val provider = LocalDefinitionProvider(position, file, compiler, settings)
|
||||
val provider = LocalDefinitionProvider(position, file, compiler, settings, this)
|
||||
return provider.findDefinition(element)
|
||||
// return provider
|
||||
// .findDefinition(task.get { NavigationHelper.findElement(it, file, line, column) })
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.utils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindNameAt;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.source.util.Trees;
|
||||
@@ -47,12 +49,20 @@ import jdkx.lang.model.element.Modifier;
|
||||
|
||||
public class NavigationHelper {
|
||||
|
||||
public static Element findElement(CompileTask task, Path file, int line, int column) {
|
||||
@Nullable
|
||||
public static Element findElement(CompileTask task, Path file, int line, int column, ICancelChecker cancelChecker) {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
for (CompilationUnitTree root : task.roots) {
|
||||
if (cancelChecker != null) {
|
||||
cancelChecker.abortIfCancelled();
|
||||
}
|
||||
|
||||
if (root.getSourceFile().toUri().equals(file.toUri())) {
|
||||
long cursor = root.getLineMap().getPosition(line, column);
|
||||
TreePath path = new FindNameAt(task).scan(root, cursor);
|
||||
if (cancelChecker != null) {
|
||||
cancelChecker.abortIfCancelled();
|
||||
}
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ class JavaCompletionProviderTest {
|
||||
|
||||
private fun completionTitles(pos: com.itsaky.androidide.models.Position): List<CharSequence> {
|
||||
return JavaLSPTest.server
|
||||
.complete(CompletionParams(pos, JavaLSPTest.file!!).apply { prefix = "" })
|
||||
.complete(CompletionParams(pos, JavaLSPTest.file!!).apply { prefix = "" }, )
|
||||
.items
|
||||
.map { it.label }
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.itsaky.androidide.lsp.xml.providers.AdvancedEditProvider;
|
||||
import com.itsaky.androidide.lsp.xml.providers.CodeFormatProvider;
|
||||
import com.itsaky.androidide.lsp.xml.providers.XmlCompletionProvider;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.progress.ICancelChecker;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
@@ -105,7 +106,7 @@ public class XMLLanguageServer implements ILanguageServer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletionResult complete(final CompletionParams params) {
|
||||
public CompletionResult complete(final CompletionParams params, ICancelChecker cancelChecker) {
|
||||
final ICompletionProvider completionProvider;
|
||||
if (!getSettings().completionsEnabled()) {
|
||||
completionProvider = new NoCompletionsProvider();
|
||||
@@ -126,13 +127,15 @@ public class XMLLanguageServer implements ILanguageServer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ReferenceResult findReferences(@NonNull ReferenceParams params) {
|
||||
public ReferenceResult findReferences(@NonNull ReferenceParams params,
|
||||
ICancelChecker cancelChecker) {
|
||||
return new ReferenceResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DefinitionResult findDefinition(@NonNull DefinitionParams params) {
|
||||
public DefinitionResult findDefinition(@NonNull DefinitionParams params,
|
||||
ICancelChecker cancelChecker) {
|
||||
return new DefinitionResult(Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class CompletionHelperImpl : CompletionHelper {
|
||||
override fun complete(transform: (CompletionItem) -> CharSequence): Pair<Boolean, List<CharSequence>> {
|
||||
return XMLLSPTest.run {
|
||||
val createCompletionParams = createCompletionParams()
|
||||
val result = server.complete(createCompletionParams)
|
||||
val result = server.complete(createCompletionParams, )
|
||||
result.isIncomplete to
|
||||
result.items
|
||||
.filter { it.label != null }
|
||||
|
||||
@@ -17,25 +17,48 @@
|
||||
|
||||
package com.itsaky.androidide.progress
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Check whether a process is cancelled.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ICancelChecker {
|
||||
|
||||
/**
|
||||
* Cancel this process.
|
||||
*/
|
||||
fun cancel()
|
||||
|
||||
/**
|
||||
* Check whether this process has been cancelled or not.
|
||||
*
|
||||
* @return Whether the process has been cancelled.
|
||||
*/
|
||||
fun isCancelled(): Boolean
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Throw [ProcessCancelledException] if this process has been cancelled.
|
||||
*/
|
||||
@Throws(ProcessCancelledException::class)
|
||||
fun abortIfCancelled()
|
||||
|
||||
internal class Default(private var cancelled: Boolean = false) : ICancelChecker {
|
||||
open class Default(cancelled: Boolean = false) : ICancelChecker {
|
||||
|
||||
override fun cancel() {
|
||||
cancelled = true
|
||||
}
|
||||
private val cancelled = AtomicBoolean(cancelled)
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return cancelled
|
||||
override fun cancel() {
|
||||
cancelled.set(true)
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return cancelled.get()
|
||||
}
|
||||
|
||||
override fun abortIfCancelled() {
|
||||
if (isCancelled()) {
|
||||
throw ProcessCancelledException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.progress
|
||||
|
||||
import com.itsaky.androidide.progress.ICancelChecker.Companion.Default
|
||||
import com.itsaky.androidide.progress.ICancelChecker.Default
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user