diff --git a/common/src/main/java/com/itsaky/androidide/utils/FlashbarActivityUtils.kt b/common/src/main/java/com/itsaky/androidide/utils/FlashbarActivityUtils.kt index e50c6675b..9c70b31d4 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/FlashbarActivityUtils.kt +++ b/common/src/main/java/com/itsaky/androidide/utils/FlashbarActivityUtils.kt @@ -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? = null, action: Consumer) { + 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() } diff --git a/common/src/main/java/com/itsaky/androidide/utils/FlashbarUtils.kt b/common/src/main/java/com/itsaky/androidide/utils/FlashbarUtils.kt index f667d817f..0e468e4e5 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/FlashbarUtils.kt +++ b/common/src/main/java/com/itsaky/androidide/utils/FlashbarUtils.kt @@ -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? = null, action: Consumer) { + withActivity { flashProgress(configure, action) } +} + private fun withActivity(action: Activity.() -> T?): T? { return ActivityUtils.getTopActivity()?.let { it.action() } ?: run { @@ -68,6 +74,7 @@ private fun withActivity(action: Activity.() -> T?): T? { /** The type of flashbar message. */ enum class FlashType { + ERROR, INFO, SUCCESS diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/CommonCompletionProvider.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/CommonCompletionProvider.kt index c5ff5e079..6176b6468 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/CommonCompletionProvider.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/CommonCompletionProvider.kt @@ -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) } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/CompletionCancelChecker.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/CompletionCancelChecker.kt index 5d5914c20..9e2592393 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/CompletionCancelChecker.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/CompletionCancelChecker.kt @@ -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() } } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/IDELanguage.java b/editor/src/main/java/com/itsaky/androidide/editor/language/IDELanguage.java index 98c26f59a..6b7f58b10 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/IDELanguage.java +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/IDELanguage.java @@ -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; } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java index 54f4075c5..bcc3fc249 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java +++ b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java @@ -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 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 -> { + final var params = new DefinitionParams(getFile().toPath(), + getCursorLSPPosition()); - return languageServer.findDefinition(params); - }); + final CompletableFuture 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 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 -> { 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 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(); + }); } /** diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionProvider.java b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionProvider.java index 26c582573..0b225fa69 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionProvider.java +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionProvider.java @@ -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(); } diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java index 2f40083e4..4e1450cc7 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java @@ -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. diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java index 2b1af8cf0..ebd4f2d09 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java @@ -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 diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/BaseJavaServiceProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/BaseJavaServiceProvider.kt index 4899d8829..e52ec8dce 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/BaseJavaServiceProvider.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/BaseJavaServiceProvider.kt @@ -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() } } diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionCancelChecker.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/CancelableServiceProvider.kt similarity index 73% rename from lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionCancelChecker.kt rename to lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/CancelableServiceProvider.kt index 01ddb1f7e..acdd03aa2 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ICompletionCancelChecker.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/CancelableServiceProvider.kt @@ -15,15 +15,14 @@ * along with AndroidIDE. If not, see . */ -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 \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/DefinitionProvider.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/DefinitionProvider.java index 2a46ad16e..ede72705e 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/DefinitionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/DefinitionProvider.java @@ -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 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 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(); diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/ReferenceProvider.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/ReferenceProvider.java index 037562678..b799787b1 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/ReferenceProvider.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/ReferenceProvider.java @@ -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 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 locations; + try { + locations = find(); + } catch (Exception err) { + if (!CancelChecker.isCancelled(err)) { + throw err; + } + locations = new ArrayList<>(); + } + + return new ReferenceResult(locations); } public List 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> 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("")) { 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 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 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 findReferences(CompileTask task) { - Element element = NavigationHelper.findElement(task, file, line, column); + abortIfCancelled(); + Element element = NavigationHelper.findElement(task, file, line, column, this); List paths = new ArrayList<>(); for (CompilationUnitTree root : task.roots) { + abortIfCancelled(); new FindReferences(task.task, element).scan(root, paths); } List locations = new ArrayList<>(); for (TreePath p : paths) { + abortIfCancelled(); locations.add(FindHelper.location(task, p)); } return locations; } -} +} \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/ErroneousDefinitionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/ErroneousDefinitionProvider.kt index 1533cd52a..0a419ea0a 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/ErroneousDefinitionProvider.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/ErroneousDefinitionProvider.kt @@ -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 { val name = element.simpleName ?: return DefinitionProvider.NOT_SUPPORTED @@ -54,6 +55,7 @@ class ErroneousDefinitionProvider( private fun findAllMembers(className: String, memberName: String): List { 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(fileAsSource) } + abortIfCancelled() + return compiler.compile(sources).get { task -> val locations = mutableListOf() 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) } diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/IJavaDefinitionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/IJavaDefinitionProvider.kt index 3617f6c9f..0ff806c78 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/IJavaDefinitionProvider.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/IJavaDefinitionProvider.kt @@ -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) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/LocalDefinitionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/LocalDefinitionProvider.kt index 361660471..49e70e408 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/LocalDefinitionProvider.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/LocalDefinitionProvider.kt @@ -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 { return compiler.compile(file).get { @@ -52,6 +53,7 @@ class LocalDefinitionProvider( name = element.enclosingElement.simpleName } + abortIfCancelled() return@get listOf(FindHelper.location(it, path, name)) } } diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/RemoteDefinitionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/RemoteDefinitionProvider.kt index bf2e17429..ecccf8ad7 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/RemoteDefinitionProvider.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/definition/RemoteDefinitionProvider.kt @@ -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 { // 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) }) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/utils/NavigationHelper.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/utils/NavigationHelper.java index 564798147..cff8a7919 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/utils/NavigationHelper.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/utils/NavigationHelper.java @@ -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; } diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/providers/JavaCompletionProviderTest.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/providers/JavaCompletionProviderTest.kt index ebfc9c84b..955156631 100644 --- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/providers/JavaCompletionProviderTest.kt +++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/providers/JavaCompletionProviderTest.kt @@ -81,7 +81,7 @@ class JavaCompletionProviderTest { private fun completionTitles(pos: com.itsaky.androidide.models.Position): List { return JavaLSPTest.server - .complete(CompletionParams(pos, JavaLSPTest.file!!).apply { prefix = "" }) + .complete(CompletionParams(pos, JavaLSPTest.file!!).apply { prefix = "" }, ) .items .map { it.label } } diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java index bdbf32c3e..e5e13c1a4 100644 --- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java +++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java @@ -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()); } diff --git a/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/CompletionHelperImpl.kt b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/CompletionHelperImpl.kt index 0fc2cea11..7554a36ea 100644 --- a/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/CompletionHelperImpl.kt +++ b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/CompletionHelperImpl.kt @@ -25,7 +25,7 @@ class CompletionHelperImpl : CompletionHelper { override fun complete(transform: (CompletionItem) -> CharSequence): Pair> { 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 } diff --git a/shared/src/main/java/com/itsaky/androidide/progress/ICancelChecker.kt b/shared/src/main/java/com/itsaky/androidide/progress/ICancelChecker.kt index 9bd2a6e22..059c70473 100644 --- a/shared/src/main/java/com/itsaky/androidide/progress/ICancelChecker.kt +++ b/shared/src/main/java/com/itsaky/androidide/progress/ICancelChecker.kt @@ -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() } } } diff --git a/shared/src/main/java/com/itsaky/androidide/progress/ProgressManager.kt b/shared/src/main/java/com/itsaky/androidide/progress/ProgressManager.kt index 50f245d16..921ea0f6e 100644 --- a/shared/src/main/java/com/itsaky/androidide/progress/ProgressManager.kt +++ b/shared/src/main/java/com/itsaky/androidide/progress/ProgressManager.kt @@ -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 /**