From 39dfe81ccc9abad83c0b0052d1d7968fedf74c63 Mon Sep 17 00:00:00 2001 From: Emmanuel Lebeaupin <9947807+elebeaup@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:36:29 +0200 Subject: [PATCH] Injects the editor in the executor context Injects the editor in ExecutorContext instead of create it for each lsp command --- server/build.gradle.kts | 7 +- .../ideals/server/MyTextDocumentService.java | 96 +++++----- .../ideals/server/codeactions/ActionData.java | 2 +- .../server/codeactions/CodeActionService.java | 170 ++++++++---------- .../server/commands/ExecutorContext.java | 24 ++- .../ideals/server/commands/LspCommand.java | 51 ++---- .../server/completions/CompletionService.java | 90 ++++------ .../server/formatting/FormattingCommand.java | 2 +- .../formatting/OnTypeFormattingCommand.java | 43 ++--- .../references/DocumentHighlightCommand.java | 24 +-- .../references/FindDefinitionCommand.java | 4 - .../references/FindDefinitionCommandBase.java | 65 +++---- .../references/FindImplementationCommand.java | 4 - .../references/FindTypeDefinitionCommand.java | 4 - .../server/references/FindUsagesCommand.java | 103 +++++------ .../ideals/server/rename/RenameCommand.java | 33 ++-- .../signature/SignatureHelpService.java | 66 +++---- .../server/symbol/DocumentSymbolService.java | 47 ++--- .../rri/ideals/server/util/AsyncExecutor.java | 105 +++++++++++ .../rri/ideals/server/util/EditorUtil.java | 2 + .../org/rri/ideals/server/util/MiscUtil.java | 16 +- .../codeactions/CodeActionServiceTest.java | 57 +++--- .../completions/CompletionServiceTest.java | 61 ++++--- .../formatting/FormattingCommandTest.java | 3 +- .../references/DefinitionCommandTest.java | 9 +- .../DefinitionFromLibrarySourcesTest.java | 2 +- .../DocumentHighlightCommandTest.java | 5 +- .../references/FindUsagesCommandTest.java | 9 +- .../references/ReferencesCommandTestBase.java | 7 +- .../references/TypeDefinitionCommandTest.java | 9 +- .../server/rename/RenameCommandTestBase.java | 2 +- .../signature/SignatureHelpServiceTest.java | 22 ++- .../symbol/DocumentSymbolServiceTest.java | 21 ++- 33 files changed, 587 insertions(+), 578 deletions(-) create mode 100644 server/src/main/java/org/rri/ideals/server/util/AsyncExecutor.java diff --git a/server/build.gradle.kts b/server/build.gradle.kts index c80b46c5..7b853f46 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -16,6 +16,8 @@ repositories { dependencies { implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.17.0") implementation("io.github.furstenheim:copy_down:1.1") + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") } // Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin @@ -39,11 +41,6 @@ tasks.register("plainIdea") { tasks { test { - dependencies { - testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") - testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") - } - jvmArgs = listOf( "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", diff --git a/server/src/main/java/org/rri/ideals/server/MyTextDocumentService.java b/server/src/main/java/org/rri/ideals/server/MyTextDocumentService.java index e55fa876..20969ebc 100644 --- a/server/src/main/java/org/rri/ideals/server/MyTextDocumentService.java +++ b/server/src/main/java/org/rri/ideals/server/MyTextDocumentService.java @@ -1,5 +1,6 @@ package org.rri.ideals.server; +import com.google.gson.GsonBuilder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.util.concurrency.AppExecutorUtil; @@ -8,6 +9,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.TextDocumentService; import org.jetbrains.annotations.NotNull; +import org.rri.ideals.server.codeactions.ActionData; import org.rri.ideals.server.codeactions.CodeActionService; import org.rri.ideals.server.completions.CompletionService; import org.rri.ideals.server.diagnostics.DiagnosticsService; @@ -17,12 +19,12 @@ import org.rri.ideals.server.rename.RenameCommand; import org.rri.ideals.server.signature.SignatureHelpService; import org.rri.ideals.server.symbol.DocumentSymbolService; +import org.rri.ideals.server.util.AsyncExecutor; import org.rri.ideals.server.util.Metrics; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import java.util.stream.Collectors; public class MyTextDocumentService implements TextDocumentService { @@ -32,6 +34,7 @@ public class MyTextDocumentService implements TextDocumentService { public MyTextDocumentService(@NotNull LspSession session) { this.session = session; } + @Override public void didOpen(DidOpenTextDocumentParams params) { final var textDocument = params.getTextDocument(); @@ -79,60 +82,69 @@ public void didSave(DidSaveTextDocumentParams params) { @Override public CompletableFuture, List>> definition(DefinitionParams params) { - return new FindDefinitionCommand(params.getPosition()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new FindDefinitionCommand() + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } @Override public CompletableFuture, List>> typeDefinition(TypeDefinitionParams params) { - return new FindTypeDefinitionCommand(params.getPosition()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new FindTypeDefinitionCommand() + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } @Override public CompletableFuture, List>> implementation(ImplementationParams params) { - return new FindImplementationCommand(params.getPosition()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new FindImplementationCommand() + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } @Override public CompletableFuture> references(ReferenceParams params) { - return new FindUsagesCommand(params.getPosition()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new FindUsagesCommand() + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } @Override public CompletableFuture> documentHighlight(DocumentHighlightParams params) { - return new DocumentHighlightCommand(params.getPosition()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new DocumentHighlightCommand() + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } @SuppressWarnings("deprecation") @Override public CompletableFuture>> documentSymbol(DocumentSymbolParams params) { - final var path = LspPath.fromLspUri(params.getTextDocument().getUri()); - return CompletableFutures.computeAsync( - AppExecutorUtil.getAppExecutorService(), - (cancelChecker) -> - documentSymbols().computeDocumentSymbols(path, cancelChecker) - ); + final var client = AsyncExecutor.>>builder() + .cancellable(true) + .executorContext(session.getProject(), params.getTextDocument().getUri(), null) + .build(); + + return client.compute((executorContext -> documentSymbols().computeDocumentSymbols(executorContext))); } @Override public CompletableFuture>> codeAction(CodeActionParams params) { - return CompletableFuture.completedFuture( - codeActions().getCodeActions( - LspPath.fromLspUri(params.getTextDocument().getUri()), - params.getRange() - ).stream().map((Function>) Either::forRight).collect(Collectors.toList()) + final var client = AsyncExecutor.>>builder() + .executorContext(session.getProject(), params.getTextDocument().getUri(), params.getRange().getStart()) + .build(); + + return client.compute(executorContext -> + codeActions().getCodeActions(params.getRange(), executorContext).stream() + .map((Function>) Either::forRight) + .toList() ); } @Override public CompletableFuture resolveCodeAction(CodeAction unresolved) { - return CompletableFuture.supplyAsync(() -> { - var edit = codeActions().applyCodeAction(unresolved); + final var actionData = new GsonBuilder().create() + .fromJson(unresolved.getData().toString(), ActionData.class); + final var client = AsyncExecutor.builder() + .executorContext(session.getProject(), actionData.getUri(), actionData.getRange().getStart()) + .build(); + + return client.compute(executorContext -> { + var edit = codeActions().applyCodeAction(actionData, unresolved.getTitle(), executorContext); unresolved.setEdit(edit); return unresolved; }); @@ -186,46 +198,48 @@ public CompletableFuture resolveCompletionItem(@NotNull Completi @Override @NotNull public CompletableFuture, CompletionList>> completion(@NotNull CompletionParams params) { - final var path = LspPath.fromLspUri(params.getTextDocument().getUri()); - return CompletableFutures.computeAsync( - AppExecutorUtil.getAppExecutorService(), - (cancelChecker) -> - Either.forLeft(completions().computeCompletions(path, params.getPosition(), cancelChecker)) - ); + final var client = AsyncExecutor., CompletionList>>builder() + .cancellable(true) + .executorContext(session.getProject(), params.getTextDocument().getUri(), params.getPosition()) + .build(); + + return client.compute((executorContext -> Either.forLeft(completions().computeCompletions(executorContext)))); } @Override @NotNull public CompletableFuture signatureHelp(SignatureHelpParams params) { - final var path = LspPath.fromLspUri(params.getTextDocument().getUri()); - return CompletableFutures.computeAsync(AppExecutorUtil.getAppExecutorService(), - cancelChecker -> signature().computeSignatureHelp(path, params.getPosition(), cancelChecker)); + final var client = AsyncExecutor.builder() + .cancellable(true) + .executorContext(session.getProject(), params.getTextDocument().getUri(), params.getPosition()) + .build(); + final var signature = signature(); + + return client.compute((signature::computeSignatureHelp)); } @Override public CompletableFuture> formatting(@NotNull DocumentFormattingParams params) { return new FormattingCommand(null, params.getOptions()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + .runAsync(session.getProject(), params.getTextDocument()); } @Override public CompletableFuture> rangeFormatting(@NotNull DocumentRangeFormattingParams params) { return new FormattingCommand(params.getRange(), params.getOptions()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + .runAsync(session.getProject(), params.getTextDocument()); } @Override public CompletableFuture> onTypeFormatting(DocumentOnTypeFormattingParams params) { - return new OnTypeFormattingCommand(params.getPosition(), params.getOptions(), - params.getCh().charAt(0)).runAsync( - session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri()) - ); + return new OnTypeFormattingCommand(params.getPosition(), params.getOptions(), params.getCh().charAt(0)) + .runAsync(session.getProject(), params.getTextDocument()); } @Override public CompletableFuture rename(RenameParams params) { - return new RenameCommand(params.getPosition(), params.getNewName()) - .runAsync(session.getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + return new RenameCommand(params.getNewName()) + .runAsync(session.getProject(), params.getTextDocument(), params.getPosition()); } } diff --git a/server/src/main/java/org/rri/ideals/server/codeactions/ActionData.java b/server/src/main/java/org/rri/ideals/server/codeactions/ActionData.java index 64cf677d..850b4170 100644 --- a/server/src/main/java/org/rri/ideals/server/codeactions/ActionData.java +++ b/server/src/main/java/org/rri/ideals/server/codeactions/ActionData.java @@ -5,7 +5,7 @@ import java.util.Objects; -final class ActionData { +public final class ActionData { private String uri; private Range range; diff --git a/server/src/main/java/org/rri/ideals/server/codeactions/CodeActionService.java b/server/src/main/java/org/rri/ideals/server/codeactions/CodeActionService.java index ee3adcad..45b676e8 100644 --- a/server/src/main/java/org/rri/ideals/server/codeactions/CodeActionService.java +++ b/server/src/main/java/org/rri/ideals/server/codeactions/CodeActionService.java @@ -1,6 +1,5 @@ package org.rri.ideals.server.codeactions; -import com.google.gson.GsonBuilder; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass; import com.intellij.openapi.application.ApplicationManager; @@ -10,9 +9,7 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; @@ -22,8 +19,8 @@ import org.eclipse.lsp4j.WorkspaceEdit; import org.jetbrains.annotations.NotNull; import org.rri.ideals.server.LspPath; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.diagnostics.DiagnosticsService; -import org.rri.ideals.server.util.EditorUtil; import org.rri.ideals.server.util.MiscUtil; import org.rri.ideals.server.util.TextUtil; @@ -61,113 +58,88 @@ private static Predicate distinctByKey(@NotNull Function ke } @NotNull - public List getCodeActions(@NotNull LspPath path, @NotNull Range range) { - - var result = new Ref>(); - ApplicationManager.getApplication().invokeAndWait( - () -> MiscUtil.invokeWithPsiFileInReadAction(project, path, (file) -> { - final var disposable = Disposer.newDisposable(); - - try { - EditorUtil.withEditor(disposable, file, range.getStart(), (editor) -> { - final var actionInfo = ShowIntentionsPass.getActionsToShow(editor, file, true); - - final var quickFixes = diagnostics().getQuickFixes(path, range).stream() - .map(it -> toCodeAction(path, range, it, CodeActionKind.QuickFix)); - - final var intentionActions = Stream.of( - actionInfo.errorFixesToShow, - actionInfo.inspectionFixesToShow, - actionInfo.intentionsToShow) - .flatMap(Collection::stream) - .map(it -> toCodeAction(path, range, it, CodeActionKind.Refactor)); - - final var actions = Stream.concat(quickFixes, intentionActions) - .filter(distinctByKey(CodeAction::getTitle)) - .collect(Collectors.toList()); - - result.set(actions); - }); - } finally { - Disposer.dispose(disposable); - } - })); + public List getCodeActions(@NotNull Range range, @NotNull ExecutorContext executorContext) { - return Optional.ofNullable(result.get()).orElse(Collections.emptyList()); - } + final var result = new Ref>(); + final var file = executorContext.getPsiFile(); + final var path = LspPath.fromVirtualFile(file.getVirtualFile()); + final var editor = executorContext.getEditor(); + assert editor != null; - @NotNull - public WorkspaceEdit applyCodeAction(@NotNull CodeAction codeAction) { - final var actionData = new GsonBuilder().create() - .fromJson(codeAction.getData().toString(), ActionData.class); + final var actionInfo = MiscUtil.computeInEDTAndWait(() -> + ShowIntentionsPass.getActionsToShow(editor, file, true) + ); - final var path = LspPath.fromLspUri(actionData.getUri()); - final var result = new WorkspaceEdit(); + final var quickFixes = diagnostics().getQuickFixes(path, range).stream() + .map(it -> toCodeAction(path, range, it, CodeActionKind.QuickFix)); - var disposable = Disposer.newDisposable(); + final var intentionActions = Stream.of( + actionInfo.errorFixesToShow, + actionInfo.inspectionFixesToShow, + actionInfo.intentionsToShow) + .flatMap(Collection::stream) + .map(it -> toCodeAction(path, range, it, CodeActionKind.Refactor)); - try { - final var psiFile = MiscUtil.resolvePsiFile(project, path); + final var actions = Stream.concat(quickFixes, intentionActions) + .filter(distinctByKey(CodeAction::getTitle)) + .collect(Collectors.toList()); - if (psiFile == null) { - LOG.error("couldn't find PSI file: " + path); - return result; - } + result.set(actions); + + return Optional.ofNullable(result.get()).orElse(Collections.emptyList()); + } - final var oldCopy = ((PsiFile) psiFile.copy()); + @NotNull + public WorkspaceEdit applyCodeAction(@NotNull ActionData actionData, String title, ExecutorContext executorContext) { + final var result = new WorkspaceEdit(); + final var editor = executorContext.getEditor(); + assert editor != null; + final var psiFile = executorContext.getPsiFile(); + final var path = LspPath.fromVirtualFile(psiFile.getVirtualFile()); + final var oldCopy = ((PsiFile) psiFile.copy()); + + ApplicationManager.getApplication().invokeAndWait(() -> { + final var quickFixes = diagnostics().getQuickFixes(path, actionData.getRange()); + final var actionInfo = ShowIntentionsPass.getActionsToShow(editor, psiFile, true); + + var actionFound = Stream.of( + quickFixes, + actionInfo.errorFixesToShow, + actionInfo.inspectionFixesToShow, + actionInfo.intentionsToShow) + .flatMap(Collection::stream) + .map(HighlightInfo.IntentionActionDescriptor::getAction) + .filter(it -> it.getText().equals(title)) + .findFirst() + .orElse(null); + + if (actionFound == null) { + LOG.warn("No action descriptor found: " + title); + return; + } - ApplicationManager.getApplication().invokeAndWait(() -> { - final var editor = EditorUtil.createEditor(disposable, psiFile, actionData.getRange().getStart()); + CommandProcessor.getInstance().executeCommand(project, () -> { + if (actionFound.startInWriteAction()) { + WriteAction.run(() -> actionFound.invoke(project, editor, psiFile)); + } else { + actionFound.invoke(project, editor, psiFile); + } + }, title, null); + }); - final var quickFixes = diagnostics().getQuickFixes(path, actionData.getRange()); - final var actionInfo = ShowIntentionsPass.getActionsToShow(editor, psiFile, true); + final var oldDoc = ReadAction.compute(() -> MiscUtil.getDocument(oldCopy)); + final var newDoc = editor.getDocument(); - var actionFound = Stream.of( - quickFixes, - actionInfo.errorFixesToShow, - actionInfo.inspectionFixesToShow, - actionInfo.intentionsToShow) - .flatMap(Collection::stream) - .map(HighlightInfo.IntentionActionDescriptor::getAction) - .filter(it -> it.getText().equals(codeAction.getTitle())) - .findFirst() - .orElse(null); + final var edits = TextUtil.textEditFromDocs(oldDoc, newDoc); - if (actionFound == null) { - LOG.warn("No action descriptor found: " + codeAction.getTitle()); - return; - } + WriteCommandAction.runWriteCommandAction(project, () -> { + newDoc.setText(oldDoc.getText()); + PsiDocumentManager.getInstance(project).commitDocument(newDoc); + }); - CommandProcessor.getInstance().executeCommand(project, () -> { - if (actionFound.startInWriteAction()) { - WriteAction.run(() -> actionFound.invoke(project, editor, psiFile)); - } else { - actionFound.invoke(project, editor, psiFile); - } - }, codeAction.getTitle(), null); - }); - - final var oldDoc = new Ref(); - final var newDoc = new Ref(); - - ReadAction.run(() -> { - oldDoc.set(Objects.requireNonNull(MiscUtil.getDocument(oldCopy))); - newDoc.set(Objects.requireNonNull(MiscUtil.getDocument(psiFile))); - }); - - final var edits = TextUtil.textEditFromDocs(oldDoc.get(), newDoc.get()); - - WriteCommandAction.runWriteCommandAction(project, () -> { - newDoc.get().setText(oldDoc.get().getText()); - PsiDocumentManager.getInstance(project).commitDocument(newDoc.get()); - }); - - if (!edits.isEmpty()) { - diagnostics().haltDiagnostics(path); // all cached quick fixes are no longer valid - result.setChanges(Map.of(actionData.getUri(), edits)); - } - } finally { - ApplicationManager.getApplication().invokeAndWait(() -> Disposer.dispose(disposable)); + if (!edits.isEmpty()) { + diagnostics().haltDiagnostics(path); // all cached quick fixes are no longer valid + result.setChanges(Map.of(actionData.getUri(), edits)); } diagnostics().launchDiagnostics(path); diff --git a/server/src/main/java/org/rri/ideals/server/commands/ExecutorContext.java b/server/src/main/java/org/rri/ideals/server/commands/ExecutorContext.java index 74399b2e..97a90ed2 100644 --- a/server/src/main/java/org/rri/ideals/server/commands/ExecutorContext.java +++ b/server/src/main/java/org/rri/ideals/server/commands/ExecutorContext.java @@ -1,6 +1,7 @@ package org.rri.ideals.server.commands; -import com.intellij.openapi.project.Project; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiFile; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.jetbrains.annotations.NotNull; @@ -11,13 +12,16 @@ final public class ExecutorContext { @NotNull private final PsiFile file; @NotNull - private final Project project; + private final Disposable disposable; @Nullable private final CancelChecker cancelToken; + @Nullable + private final Editor editor; - public ExecutorContext(@NotNull PsiFile file, @NotNull Project project, @Nullable CancelChecker cancelToken) { + public ExecutorContext(@NotNull PsiFile file, @NotNull Disposable disposable, @Nullable Editor editor, @Nullable CancelChecker cancelToken) { this.file = file; - this.project = project; + this.editor = editor; + this.disposable = disposable; this.cancelToken = cancelToken; } @@ -25,11 +29,15 @@ public ExecutorContext(@NotNull PsiFile file, @NotNull Project project, @Nullabl return file; } - public @NotNull Project getProject() { - return project; - } - public @Nullable CancelChecker getCancelToken() { return cancelToken; } + + public @Nullable Editor getEditor() { + return editor; + } + + public @NotNull Disposable getDisposable() { + return disposable; + } } diff --git a/server/src/main/java/org/rri/ideals/server/commands/LspCommand.java b/server/src/main/java/org/rri/ideals/server/commands/LspCommand.java index 3a82d316..04e98a23 100644 --- a/server/src/main/java/org/rri/ideals/server/commands/LspCommand.java +++ b/server/src/main/java/org/rri/ideals/server/commands/LspCommand.java @@ -1,19 +1,14 @@ package org.rri.ideals.server.commands; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.util.concurrency.AppExecutorUtil; -import org.eclipse.lsp4j.jsonrpc.CancelChecker; -import org.eclipse.lsp4j.jsonrpc.CompletableFutures; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.rri.ideals.server.LspPath; -import org.rri.ideals.server.util.MiscUtil; +import org.rri.ideals.server.util.AsyncExecutor; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; public abstract class LspCommand { @@ -26,34 +21,22 @@ public abstract class LspCommand { protected abstract R execute(@NotNull ExecutorContext ctx); - public @NotNull CompletableFuture<@Nullable R> runAsync(@NotNull Project project, @NotNull LspPath path) { - final var virtualFile = path.findVirtualFile(); - if (virtualFile == null) { - LOG.info("File not found: " + path); - // todo mb need to throw excep - return CompletableFuture.completedFuture(null); - } + public @NotNull CompletableFuture<@Nullable R> runAsync(@NotNull Project project, @NotNull TextDocumentIdentifier textDocumentIdentifier) { + return runAsync(project, textDocumentIdentifier.getUri(), null); + } - LOG.info(getMessageSupplier().get()); - Executor executor = AppExecutorUtil.getAppExecutorService(); - if (isCancellable()) { - return CompletableFutures.computeAsync(executor, cancelToken -> getResult(path, project, cancelToken)); - } else { - return CompletableFuture.supplyAsync(() -> getResult(path, project, null), executor); - } + public @NotNull CompletableFuture<@Nullable R> runAsync(@NotNull Project project, @NotNull TextDocumentIdentifier textDocumentIdentifier, @Nullable Position position) { + return runAsync(project, textDocumentIdentifier.getUri(), position); } - private @Nullable R getResult(@NotNull LspPath path, - @NotNull Project project, - @Nullable CancelChecker cancelToken) { - final AtomicReference ref = new AtomicReference<>(); - ApplicationManager.getApplication() - .invokeAndWait( - () -> ref.set(MiscUtil.produceWithPsiFileInReadAction( - project, - path, - (psiFile) -> execute(new ExecutorContext(psiFile, project, cancelToken)) - ))); - return ref.get(); + public @NotNull CompletableFuture<@Nullable R> runAsync(@NotNull Project project, @NotNull String uri, @Nullable Position position) { + LOG.info(getMessageSupplier().get()); + var client = AsyncExecutor.builder() + .executorContext(project, uri, position) + .cancellable(isCancellable()) + .runInEDT(true) + .build(); + + return client.compute(this::execute); } } diff --git a/server/src/main/java/org/rri/ideals/server/completions/CompletionService.java b/server/src/main/java/org/rri/ideals/server/completions/CompletionService.java index b75e564e..fdaa6163 100644 --- a/server/src/main/java/org/rri/ideals/server/completions/CompletionService.java +++ b/server/src/main/java/org/rri/ideals/server/completions/CompletionService.java @@ -39,7 +39,7 @@ import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.jetbrains.annotations.NotNull; -import org.rri.ideals.server.LspPath; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.completions.util.IconUtil; import org.rri.ideals.server.completions.util.TextEditRearranger; import org.rri.ideals.server.completions.util.TextEditWithOffsets; @@ -72,20 +72,17 @@ public void dispose() { } @NotNull - public List computeCompletions( - @NotNull LspPath path, - @NotNull Position position, - @NotNull CancelChecker cancelChecker) { + public List computeCompletions(@NotNull ExecutorContext executorContext) { LOG.info("start completion"); + final var cancelChecker = executorContext.getCancelToken(); + assert cancelChecker != null; try { - var virtualFile = path.findVirtualFile(); - if (virtualFile == null) { - LOG.warn("file not found: " + path); - return List.of(); - } - final var psiFile = MiscUtil.resolvePsiFile(project, path); - assert psiFile != null; - return doComputeCompletions(psiFile, position, cancelChecker); + LOG.info("start signature help"); + final var editor = executorContext.getEditor(); + assert editor != null; + final var psiFile = executorContext.getPsiFile(); + + return doComputeCompletions(psiFile, editor, cancelChecker); } finally { cancelChecker.checkCanceled(); } @@ -287,7 +284,7 @@ private static CompletionItem createLspCompletionItem(@NotNull LookupElement loo private @NotNull List doComputeCompletions(@NotNull PsiFile psiFile, - @NotNull Position position, + @NotNull Editor editor, @NotNull CancelChecker cancelChecker) { VoidCompletionProcess process = new VoidCompletionProcess(); Ref> resultRef = new Ref<>(); @@ -298,46 +295,37 @@ private static CompletionItem createLspCompletionItem(@NotNull LookupElement loo var completionDataVersionRef = new Ref(); // invokeAndWait is necessary for editor creation and completion call ProgressManager.getInstance().runProcess(() -> - ApplicationManager.getApplication().invokeAndWait( - () -> EditorUtil.withEditor(process, psiFile, - position, - (editor) -> { - var compInfo = new CompletionInfo(editor, project); - var ideaCompService = com.intellij.codeInsight.completion.CompletionService.getCompletionService(); - assert ideaCompService != null; - - ideaCompService.performCompletion(compInfo.getParameters(), - (result) -> { - compInfo.getLookup().addItem(result.getLookupElement(), result.getPrefixMatcher()); - compInfo.getArranger().addElement(result); - }); - - var elementsWithMatcher = compInfo.getArranger().getElementsWithMatcher(); - lookupElementsWithMatcherRef.set(elementsWithMatcher); - - var document = MiscUtil.getDocument(psiFile); - assert document != null; - - // version and data manipulations here are thread safe because they are done inside invokeAndWait - int newVersion = 1 + cachedDataRef.get().version; - completionDataVersionRef.set(newVersion); - - cachedDataRef.set( - new CompletionData( - elementsWithMatcher, - newVersion, - position, - document.getText(), - psiFile.getLanguage() - )); - } - ) + ApplicationManager.getApplication().invokeAndWait(() -> { + var compInfo = new CompletionInfo(editor, project); + var ideaCompService = com.intellij.codeInsight.completion.CompletionService.getCompletionService(); + assert ideaCompService != null; + + ideaCompService.performCompletion(compInfo.getParameters(), + (result) -> { + compInfo.getLookup().addItem(result.getLookupElement(), result.getPrefixMatcher()); + compInfo.getArranger().addElement(result); + }); + + var elementsWithMatcher = compInfo.getArranger().getElementsWithMatcher(); + lookupElementsWithMatcherRef.set(elementsWithMatcher); + + // version and data manipulations here are thread safe because they are done inside invokeAndWait + int newVersion = 1 + cachedDataRef.get().version; + completionDataVersionRef.set(newVersion); + + cachedDataRef.set( + new CompletionData( + elementsWithMatcher, + newVersion, + MiscUtil.offsetToPosition(editor.getDocument(), editor.getCaretModel().getOffset()), + editor.getDocument().getText(), + psiFile.getLanguage() + )); + } ), new LspProgressIndicator(cancelChecker)); ReadAction.run(() -> { - var document = MiscUtil.getDocument(psiFile); - assert document != null; resultRef.set(convertLookupElementsWithMatcherToCompletionItems( - lookupElementsWithMatcherRef.get(), document, position, completionDataVersionRef.get())); + lookupElementsWithMatcherRef.get(), editor.getDocument(), MiscUtil.offsetToPosition(editor.getDocument(), editor.getCaretModel().getOffset()), completionDataVersionRef.get())); }); } finally { WriteCommandAction.runWriteCommandAction(project, () -> Disposer.dispose(process)); diff --git a/server/src/main/java/org/rri/ideals/server/formatting/FormattingCommand.java b/server/src/main/java/org/rri/ideals/server/formatting/FormattingCommand.java index 5a56ed94..3c2690b9 100644 --- a/server/src/main/java/org/rri/ideals/server/formatting/FormattingCommand.java +++ b/server/src/main/java/org/rri/ideals/server/formatting/FormattingCommand.java @@ -52,7 +52,7 @@ void reformatPsiFile(@NotNull ExecutorContext context, @NotNull PsiFile psiFile) CommandProcessor .getInstance() .executeCommand( - context.getProject(), + psiFile.getProject(), () -> doWithTemporaryCodeStyleSettingsForFile( psiFile, () -> doReformat(psiFile, getConfiguredTextRange(psiFile))), diff --git a/server/src/main/java/org/rri/ideals/server/formatting/OnTypeFormattingCommand.java b/server/src/main/java/org/rri/ideals/server/formatting/OnTypeFormattingCommand.java index 4b14d8c9..a2423b84 100644 --- a/server/src/main/java/org/rri/ideals/server/formatting/OnTypeFormattingCommand.java +++ b/server/src/main/java/org/rri/ideals/server/formatting/OnTypeFormattingCommand.java @@ -59,32 +59,27 @@ protected List execute(@NotNull ExecutorContext ctx) { } void typeAndReformatIfNeededInFile(@NotNull PsiFile psiFile) { - var disposable = Disposer.newDisposable(); - try { - EditorUtil.withEditor(disposable, psiFile, position, editor -> { - var doc = MiscUtil.getDocument(psiFile); - assert doc != null; - ApplicationManager.getApplication().runWriteAction(() -> { - if (!deleteTypedChar(editor, doc)) { - return; - } - PsiDocumentManager.getInstance(psiFile.getProject()).commitDocument(doc); + EditorUtil.withEditor(Disposer.newDisposable(), psiFile, position, editor -> { + var doc = MiscUtil.getDocument(psiFile); + assert doc != null; + ApplicationManager.getApplication().runWriteAction(() -> { + if (!deleteTypedChar(editor, doc)) { + return; + } + PsiDocumentManager.getInstance(psiFile.getProject()).commitDocument(doc); - if (editor instanceof EditorEx) { - ((EditorEx) editor).setHighlighter( - HighlighterFactory.createHighlighter(psiFile.getProject(), psiFile.getFileType())); - } - doWithTemporaryCodeStyleSettingsForFile( - psiFile, - () -> TypedAction.getInstance().actionPerformed( - editor, - triggerCharacter, - com.intellij.openapi.editor.ex.util.EditorUtil.getEditorDataContext(editor))); - }); + if (editor instanceof EditorEx) { + ((EditorEx) editor).setHighlighter( + HighlighterFactory.createHighlighter(psiFile.getProject(), psiFile.getFileType())); + } + doWithTemporaryCodeStyleSettingsForFile( + psiFile, + () -> TypedAction.getInstance().actionPerformed( + editor, + triggerCharacter, + com.intellij.openapi.editor.ex.util.EditorUtil.getEditorDataContext(editor))); }); - } finally { - Disposer.dispose(disposable); - } + }); } private boolean deleteTypedChar(@NotNull Editor editor, @NotNull Document doc) { diff --git a/server/src/main/java/org/rri/ideals/server/references/DocumentHighlightCommand.java b/server/src/main/java/org/rri/ideals/server/references/DocumentHighlightCommand.java index 211e817d..70532c5b 100644 --- a/server/src/main/java/org/rri/ideals/server/references/DocumentHighlightCommand.java +++ b/server/src/main/java/org/rri/ideals/server/references/DocumentHighlightCommand.java @@ -14,7 +14,6 @@ import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; @@ -28,7 +27,6 @@ import com.intellij.util.containers.ContainerUtil; import org.eclipse.lsp4j.DocumentHighlight; import org.eclipse.lsp4j.DocumentHighlightKind; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,12 +41,6 @@ import java.util.stream.Stream; public class DocumentHighlightCommand extends LspCommand> { - @NotNull - private final Position pos; - - public DocumentHighlightCommand(@NotNull Position pos) { - this.pos = pos; - } @Override protected @NotNull Supplier<@NotNull String> getMessageSupplier() { @@ -62,17 +54,13 @@ protected boolean isCancellable() { @Override protected @NotNull List execute(@NotNull ExecutorContext ctx) { - var disposable = Disposer.newDisposable(); + final var editor = ctx.getEditor(); + assert editor != null; + try { - return EditorUtil.computeWithEditor(disposable, ctx.getPsiFile(), pos, editor -> { - try { - return findHighlights(ctx.getProject(), editor, ctx.getPsiFile()); - } catch (IndexNotReadyException e) { - return List.of(); - } - }); - } finally { - Disposer.dispose(disposable); + return findHighlights(ctx.getPsiFile().getProject(), editor, ctx.getPsiFile()); + } catch (IndexNotReadyException e) { + return List.of(); } } diff --git a/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommand.java b/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommand.java index 925eea9b..36816e99 100644 --- a/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommand.java +++ b/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommand.java @@ -3,7 +3,6 @@ import com.intellij.codeInsight.TargetElementUtil; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; -import org.eclipse.lsp4j.Position; import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -11,9 +10,6 @@ public class FindDefinitionCommand extends FindDefinitionCommandBase { - public FindDefinitionCommand(@NotNull Position pos) { - super(pos); - } @Override protected @NotNull Supplier<@NotNull String> getMessageSupplier() { diff --git a/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommandBase.java b/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommandBase.java index d9d25627..48e1aafc 100644 --- a/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommandBase.java +++ b/server/src/main/java/org/rri/ideals/server/references/FindDefinitionCommandBase.java @@ -11,10 +11,8 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.jetbrains.annotations.NotNull; @@ -22,7 +20,6 @@ import org.rri.ideals.server.LspPath; import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.commands.LspCommand; -import org.rri.ideals.server.util.EditorUtil; import org.rri.ideals.server.util.MiscUtil; import java.util.List; @@ -34,13 +31,6 @@ abstract class FindDefinitionCommandBase extends LspCommand EDITOR_FILE_SWAPPER_EP_NAME = new ExtensionPointName<>("com.intellij.editorFileSwapper"); - @NotNull - protected final Position pos; - - protected FindDefinitionCommandBase(@NotNull Position pos) { - this.pos = pos; - } - @Override protected boolean isCancellable() { return false; @@ -48,39 +38,34 @@ protected boolean isCancellable() { @Override protected @NotNull Either, @NotNull List> execute(@NotNull ExecutorContext ctx) { - PsiFile file = ctx.getPsiFile(); - Document doc = MiscUtil.getDocument(file); - if (doc == null) { - return Either.forRight(List.of()); - } - - var offset = MiscUtil.positionToOffset(doc, pos); + final var editor = ctx.getEditor(); + assert editor != null; + final var file = ctx.getPsiFile(); + final var doc = editor.getDocument(); + final var offset = editor.getCaretModel().getOffset(); + PsiElement originalElem = file.findElementAt(offset); Range originalRange = MiscUtil.getPsiElementRange(doc, originalElem); - var disposable = Disposer.newDisposable(); - try { - var definitions = EditorUtil.computeWithEditor(disposable, file, pos, - editor -> findDefinitions(editor, offset)) - .filter(Objects::nonNull) - .map(targetElem -> { - if (targetElem.getContainingFile() == null) { return null; } - final var loc = findSourceLocation(ctx.getProject(), targetElem); - if (loc != null) { - return new LocationLink(loc.getUri(), loc.getRange(), loc.getRange(), originalRange); - } else { - Document targetDoc = targetElem.getContainingFile().equals(file) - ? doc : MiscUtil.getDocument(targetElem.getContainingFile()); - return MiscUtil.psiElementToLocationLink(targetElem, targetDoc, originalRange); - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - return Either.forRight(definitions); - } finally { - Disposer.dispose(disposable); - } + var definitions = findDefinitions(editor, offset) + .filter(Objects::nonNull) + .map(targetElem -> { + if (targetElem.getContainingFile() == null) { + return null; + } + final var loc = findSourceLocation(file.getProject(), targetElem); + if (loc != null) { + return new LocationLink(loc.getUri(), loc.getRange(), loc.getRange(), originalRange); + } else { + Document targetDoc = targetElem.getContainingFile().equals(file) + ? doc : MiscUtil.getDocument(targetElem.getContainingFile()); + return MiscUtil.psiElementToLocationLink(targetElem, targetDoc, originalRange); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return Either.forRight(definitions); } /** diff --git a/server/src/main/java/org/rri/ideals/server/references/FindImplementationCommand.java b/server/src/main/java/org/rri/ideals/server/references/FindImplementationCommand.java index e67a8840..82a4fe10 100644 --- a/server/src/main/java/org/rri/ideals/server/references/FindImplementationCommand.java +++ b/server/src/main/java/org/rri/ideals/server/references/FindImplementationCommand.java @@ -5,7 +5,6 @@ import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; -import org.eclipse.lsp4j.Position; import org.jetbrains.annotations.NotNull; import org.rri.ideals.server.util.MiscUtil; @@ -13,9 +12,6 @@ import java.util.stream.Stream; public class FindImplementationCommand extends FindDefinitionCommandBase { - public FindImplementationCommand(@NotNull Position pos) { - super(pos); - } @Override protected @NotNull Supplier<@NotNull String> getMessageSupplier() { diff --git a/server/src/main/java/org/rri/ideals/server/references/FindTypeDefinitionCommand.java b/server/src/main/java/org/rri/ideals/server/references/FindTypeDefinitionCommand.java index b41cc4a2..4c525f08 100644 --- a/server/src/main/java/org/rri/ideals/server/references/FindTypeDefinitionCommand.java +++ b/server/src/main/java/org/rri/ideals/server/references/FindTypeDefinitionCommand.java @@ -3,7 +3,6 @@ import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; -import org.eclipse.lsp4j.Position; import org.jetbrains.annotations.NotNull; import org.rri.ideals.server.util.MiscUtil; @@ -11,9 +10,6 @@ import java.util.stream.Stream; public class FindTypeDefinitionCommand extends FindDefinitionCommandBase { - public FindTypeDefinitionCommand(@NotNull Position pos) { - super(pos); - } @Override protected @NotNull Supplier<@NotNull String> getMessageSupplier() { diff --git a/server/src/main/java/org/rri/ideals/server/references/FindUsagesCommand.java b/server/src/main/java/org/rri/ideals/server/references/FindUsagesCommand.java index 4ea586ec..b490e416 100644 --- a/server/src/main/java/org/rri/ideals/server/references/FindUsagesCommand.java +++ b/server/src/main/java/org/rri/ideals/server/references/FindUsagesCommand.java @@ -10,9 +10,9 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Disposer; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiInvalidElementAccessException; @@ -32,31 +32,21 @@ import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import org.eclipse.lsp4j.Location; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.commands.LspCommand; -import org.rri.ideals.server.util.EditorUtil; +import org.rri.ideals.server.util.LspProgressIndicator; import org.rri.ideals.server.util.MiscUtil; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.CancellationException; import java.util.function.Supplier; import java.util.stream.Collectors; public class FindUsagesCommand extends LspCommand> { private static final Logger LOG = Logger.getInstance(FindUsagesCommand.class); - @NotNull - private final Position pos; - - public FindUsagesCommand(@NotNull Position pos) { - this.pos = pos; - } @Override protected @NotNull Supplier<@NotNull String> getMessageSupplier() { @@ -70,23 +60,17 @@ protected boolean isCancellable() { @Override protected @NotNull List execute(@NotNull ExecutorContext ctx) { + final var editor = ctx.getEditor(); + assert editor != null; PsiFile file = ctx.getPsiFile(); Document doc = MiscUtil.getDocument(file); if (doc == null) { return List.of(); } - var disposable = Disposer.newDisposable(); - try { - var target = EditorUtil.computeWithEditor(disposable, file, pos, - editor -> TargetElementUtil.findTargetElement(editor, TargetElementUtil.getInstance().getAllAccepted())); - if (target == null) { - return List.of(); - } - return findUsages(ctx.getProject(), target, ctx.getCancelToken()); - } finally { - Disposer.dispose(disposable); - } + return Optional.ofNullable(TargetElementUtil.findTargetElement(editor, TargetElementUtil.getInstance().getAllAccepted())) + .map(target -> findUsages(file.getProject(), target, ctx.getCancelToken())) + .orElse(List.of()); } private static @NotNull List<@NotNull Location> findUsages(@NotNull Project project, @@ -94,41 +78,46 @@ protected boolean isCancellable() { @Nullable CancelChecker cancelToken) { var manager = ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager(); var handler = manager.getFindUsagesHandler(target, FindUsagesHandlerFactory.OperationMode.USAGES_WITH_DEFAULT_OPTIONS); - List result; - if (handler != null) { - var dialog = handler.getFindUsagesDialog(false, false, false); - dialog.close(DialogWrapper.OK_EXIT_CODE); - var options = dialog.calcFindUsagesOptions(); - PsiElement[] primaryElements = handler.getPrimaryElements(); - PsiElement[] secondaryElements = handler.getSecondaryElements(); - UsageSearcher searcher = createUsageSearcher(primaryElements, secondaryElements, handler, options, project); - Set saver = ContainerUtil.newConcurrentSet(); - searcher.generate(usage -> { - if (cancelToken != null) { - try { - cancelToken.checkCanceled(); - } catch (CancellationException e) { - return false; + + return ProgressManager.getInstance().runProcess(() -> { + List result; + + if (handler != null) { + var dialog = handler.getFindUsagesDialog(false, false, false); + dialog.close(DialogWrapper.OK_EXIT_CODE); + var options = dialog.calcFindUsagesOptions(); + PsiElement[] primaryElements = handler.getPrimaryElements(); + PsiElement[] secondaryElements = handler.getSecondaryElements(); + UsageSearcher searcher = createUsageSearcher(primaryElements, secondaryElements, handler, options, project); + Set saver = ContainerUtil.newConcurrentSet(); + searcher.generate(usage -> { + if (cancelToken != null) { + try { + cancelToken.checkCanceled(); + } catch (CancellationException e) { + return false; + } } - } - if (usage instanceof final UsageInfo2UsageAdapter ui2ua && !ui2ua.isNonCodeUsage()) { - var elem = ui2ua.getElement(); - var loc = MiscUtil.psiElementToLocation(elem); - if (loc != null) { - saver.add(loc); + if (usage instanceof final UsageInfo2UsageAdapter ui2ua && !ui2ua.isNonCodeUsage()) { + var elem = ui2ua.getElement(); + var loc = MiscUtil.psiElementToLocation(elem); + if (loc != null) { + saver.add(loc); + } } - } - return true; - }); - result = new ArrayList<>(saver); - } else { - result = ReferencesSearch.search(target).findAll().stream() - .map(PsiReference::getElement) - .map(MiscUtil::psiElementToLocation) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - return result; + return true; + }); + result = new ArrayList<>(saver); + } else { + result = ReferencesSearch.search(target).findAll().stream() + .map(PsiReference::getElement) + .map(MiscUtil::psiElementToLocation) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + return result; + }, new LspProgressIndicator(cancelToken)); } // Took this function from com.intellij.find.findUsages.FindUsagesManager. diff --git a/server/src/main/java/org/rri/ideals/server/rename/RenameCommand.java b/server/src/main/java/org/rri/ideals/server/rename/RenameCommand.java index 78c153e2..766ce156 100644 --- a/server/src/main/java/org/rri/ideals/server/rename/RenameCommand.java +++ b/server/src/main/java/org/rri/ideals/server/rename/RenameCommand.java @@ -2,7 +2,6 @@ import com.intellij.codeInsight.TargetElementUtil; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Segment; @@ -17,7 +16,6 @@ import org.rri.ideals.server.LspPath; import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.commands.LspCommand; -import org.rri.ideals.server.util.EditorUtil; import org.rri.ideals.server.util.MiscUtil; import java.util.*; @@ -26,11 +24,9 @@ import java.util.stream.Stream; public class RenameCommand extends LspCommand { - private final Position pos; private final String newName; - public RenameCommand(Position pos, String newName) { - this.pos = pos; + public RenameCommand(String newName) { this.newName = newName; } @@ -47,27 +43,22 @@ protected boolean isCancellable() { @Override protected @Nullable WorkspaceEdit execute(@NotNull ExecutorContext ctx) { final var file = ctx.getPsiFile(); + final var editor = ctx.getEditor(); + assert editor != null; Document doc = MiscUtil.getDocument(file); if (doc == null) { return null; } var elementRef = new Ref(); - final var disposable = Disposer.newDisposable(); - try { - EditorUtil.withEditor(disposable, file, pos, editor -> { - var elementToRename = TargetElementUtil.findTargetElement(editor, TargetElementUtil.getInstance().getAllAccepted()); - if (elementToRename != null) { - final var processor = RenamePsiElementProcessor.forElement(elementToRename); - final var newElementToRename = processor.substituteElementToRename(elementToRename, editor); - if (newElementToRename != null) { - elementToRename = newElementToRename; - } - } - elementRef.set(elementToRename); - }); - } finally { - Disposer.dispose(disposable); + var elementToRename = TargetElementUtil.findTargetElement(editor, TargetElementUtil.getInstance().getAllAccepted()); + if (elementToRename != null) { + final var processor = RenamePsiElementProcessor.forElement(elementToRename); + final var newElementToRename = processor.substituteElementToRename(elementToRename, editor); + if (newElementToRename != null) { + elementToRename = newElementToRename; + } } + elementRef.set(elementToRename); if (elementRef.get() == null) { return null; @@ -75,7 +66,7 @@ protected boolean isCancellable() { final var elemToName = new LinkedHashMap(); elemToName.put(elementRef.get(), newName); - final var renamer = new RenameProcessor(ctx.getProject(), elementRef.get(), newName, false, false); + final var renamer = new RenameProcessor(file.getProject(), elementRef.get(), newName, false, false); renamer.prepareRenaming(elementRef.get(), newName, elemToName); elemToName.forEach(renamer::addElement); diff --git a/server/src/main/java/org/rri/ideals/server/signature/SignatureHelpService.java b/server/src/main/java/org/rri/ideals/server/signature/SignatureHelpService.java index c11037e5..40a22e64 100644 --- a/server/src/main/java/org/rri/ideals/server/signature/SignatureHelpService.java +++ b/server/src/main/java/org/rri/ideals/server/signature/SignatureHelpService.java @@ -14,21 +14,17 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiUtilCore; import org.eclipse.lsp4j.ParameterInformation; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureInformation; -import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.messages.Tuple; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import org.rri.ideals.server.LspPath; -import org.rri.ideals.server.util.EditorUtil; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.util.LspProgressIndicator; import org.rri.ideals.server.util.MiscUtil; @@ -52,48 +48,34 @@ public void dispose() { } @Nullable - public SignatureHelp computeSignatureHelp(@NotNull LspPath path, - @NotNull Position position, - @NotNull CancelChecker cancelChecker) { + public SignatureHelp computeSignatureHelp(@NotNull ExecutorContext executorContext) { LOG.info("start signature help"); - var disposable = Disposer.newDisposable(); - try { - var virtualFile = path.findVirtualFile(); - if (virtualFile == null) { - LOG.warn("file not found: " + path); - return null; - } - final var psiFile = MiscUtil.resolvePsiFile(project, path); - assert psiFile != null; - final var doc = ReadAction.compute(() -> MiscUtil.getDocument(psiFile)); - assert doc != null; - final var offset = MiscUtil.positionToOffset(doc, position); - - final Language language = ReadAction.compute(() -> PsiUtilCore.getLanguageAtOffset(psiFile, offset)); - // This assignment came from ShowParameterInfoHandler, IDEA 203.5981.155 - @SuppressWarnings("unchecked") final ParameterInfoHandler[] handlers = - ShowParameterInfoHandler.getHandlers(project, language, psiFile.getViewProvider().getBaseLanguage()); + final var editor = executorContext.getEditor(); + assert editor != null; + final var psiFile = executorContext.getPsiFile(); + final var offset = ReadAction.compute(() -> editor.getCaretModel().getOffset()); + final var cancelChecker = executorContext.getCancelToken(); + assert cancelChecker != null; - var editor = WriteAction.computeAndWait(() -> EditorUtil.createEditor(disposable, psiFile, position)); + final Language language = ReadAction.compute(() -> PsiUtilCore.getLanguageAtOffset(psiFile, offset)); + // This assignment came from ShowParameterInfoHandler, IDEA 203.5981.155 + @SuppressWarnings("unchecked") final ParameterInfoHandler[] handlers = + ShowParameterInfoHandler.getHandlers(project, language, psiFile.getViewProvider().getBaseLanguage()); - final ShowParameterInfoContext context = new ShowParameterInfoContext( - editor, project, psiFile, offset, -1, false, false); + final ShowParameterInfoContext context = new ShowParameterInfoContext( + editor, project, psiFile, offset, -1, false, false); - boolean isHandled = findAndUseValidHandler(handlers, context); - if (!isHandled) { - return MiscUtil.with(new SignatureHelp(), - signatureHelp -> signatureHelp.setSignatures(new ArrayList<>())); - } - WriteAction.runAndWait(() -> PsiDocumentManager.getInstance(project).commitAllDocuments()); - if (ApplicationManager.getApplication().isUnitTestMode() && flushRunnable != null) { - flushRunnable.run(); - } - return ProgressManager.getInstance().runProcess( - SignatureHelpService::createSignatureHelpFromListener, new LspProgressIndicator(cancelChecker)); - } finally { - WriteAction.runAndWait(() -> Disposer.dispose(disposable)); - cancelChecker.checkCanceled(); + boolean isHandled = findAndUseValidHandler(handlers, context); + if (!isHandled) { + return MiscUtil.with(new SignatureHelp(), + signatureHelp -> signatureHelp.setSignatures(new ArrayList<>())); + } + WriteAction.runAndWait(() -> PsiDocumentManager.getInstance(project).commitAllDocuments()); + if (ApplicationManager.getApplication().isUnitTestMode() && flushRunnable != null) { + flushRunnable.run(); } + return ProgressManager.getInstance().runProcess( + SignatureHelpService::createSignatureHelpFromListener, new LspProgressIndicator(cancelChecker)); } private static boolean findAndUseValidHandler( diff --git a/server/src/main/java/org/rri/ideals/server/symbol/DocumentSymbolService.java b/server/src/main/java/org/rri/ideals/server/symbol/DocumentSymbolService.java index be798511..ccedc93d 100644 --- a/server/src/main/java/org/rri/ideals/server/symbol/DocumentSymbolService.java +++ b/server/src/main/java/org/rri/ideals/server/symbol/DocumentSymbolService.java @@ -22,11 +22,10 @@ import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; -import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.rri.ideals.server.LspPath; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.symbol.util.SymbolUtil; import org.rri.ideals.server.util.LspProgressIndicator; import org.rri.ideals.server.util.MiscUtil; @@ -48,35 +47,27 @@ public DocumentSymbolService(@NotNull Project project) { @SuppressWarnings("deprecation") public @NotNull List> computeDocumentSymbols( - @NotNull LspPath path, - @NotNull CancelChecker cancelChecker) { + @NotNull ExecutorContext executorContext) { LOG.info("document symbol start"); - final var psiFile = MiscUtil.resolvePsiFile(project, path); - if (psiFile == null) { - return List.of(); - } - var disposable = Disposer.newDisposable(); - try { - return ProgressManager.getInstance().runProcess(() -> { + final var psiFile = executorContext.getPsiFile(); + final var cancelChecker = executorContext.getCancelToken(); + assert cancelChecker != null; + return ProgressManager.getInstance().runProcess(() -> { - StructureViewTreeElement root = getViewTreeElement(psiFile, disposable); - if (root == null) { - return List.of(); - } - Document document = ReadAction.compute(() -> MiscUtil.getDocument(psiFile)); - assert document != null; + StructureViewTreeElement root = getViewTreeElement(psiFile, executorContext.getDisposable()); + if (root == null) { + return List.of(); + } + Document document = ReadAction.compute(() -> MiscUtil.getDocument(psiFile)); + assert document != null; - var rootSymbol = processTree(root, psiFile, document); - if (rootSymbol == null) { - return List.of(); - } - rootSymbol.setKind(SymbolKind.File); - return List.of(Either.forRight(rootSymbol)); - }, new LspProgressIndicator(cancelChecker)); - } finally { - WriteCommandAction.runWriteCommandAction(project, null, null, - () -> Disposer.dispose(disposable), psiFile); - } + var rootSymbol = processTree(root, psiFile, document); + if (rootSymbol == null) { + return List.of(); + } + rootSymbol.setKind(SymbolKind.File); + return List.of(Either.forRight(rootSymbol)); + }, new LspProgressIndicator(cancelChecker)); } @Nullable diff --git a/server/src/main/java/org/rri/ideals/server/util/AsyncExecutor.java b/server/src/main/java/org/rri/ideals/server/util/AsyncExecutor.java new file mode 100644 index 00000000..6fdfc431 --- /dev/null +++ b/server/src/main/java/org/rri/ideals/server/util/AsyncExecutor.java @@ -0,0 +1,105 @@ +package org.rri.ideals.server.util; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.psi.PsiFile; +import com.intellij.util.concurrency.AppExecutorUtil; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.eclipse.lsp4j.jsonrpc.CompletableFutures; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.rri.ideals.server.LspPath; +import org.rri.ideals.server.commands.ExecutorContext; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; + +public class AsyncExecutor { + private final boolean cancellable; + private final boolean runInEDT; + private final Executor executor = AppExecutorUtil.getAppExecutorService(); + @Nullable + private final PsiFile psiFile; + private final Project project; + @Nullable + private final Position position; + + private AsyncExecutor(@NotNull Builder builder) { + this.cancellable = builder.cancellable; + this.psiFile = builder.psiFile; + this.project = builder.project; + this.position = builder.position; + this.runInEDT = builder.runInEDT; + } + + public static Builder builder() { + return new AsyncExecutor.Builder(); + } + + public @NotNull CompletableFuture<@Nullable R> compute(@NotNull Function action) { + if (cancellable) { + return CompletableFutures.computeAsync(executor, cancelToken -> getResult(action, cancelToken)); + } else { + return CompletableFuture.supplyAsync(() -> getResult(action, null), executor); + } + } + + private @Nullable R getResult(@NotNull Function action, + @Nullable CancelChecker cancelToken) { + if (psiFile == null) { + return null; + } + + final var disposable = Disposer.newDisposable(); + final var editor = Optional.ofNullable(position) + .map(pos -> MiscUtil.computeInEDTAndWait(() -> EditorUtil.createEditor(disposable, psiFile, pos))) + .orElse(null); + final var context = new ExecutorContext(psiFile, disposable, editor, cancelToken); + + try { + if (runInEDT) { + return MiscUtil.computeInEDTAndWait(() -> action.apply(context)); + } else { + return action.apply(context); + } + } finally { + ApplicationManager.getApplication().invokeAndWait(() -> Disposer.dispose(disposable)); + if (cancelToken != null) { + cancelToken.checkCanceled(); + } + } + } + + public static class Builder { + private boolean cancellable = false; + private boolean runInEDT = false; + private Project project; + private Position position; + private PsiFile psiFile; + + public Builder cancellable(boolean cancellable) { + this.cancellable = cancellable; + return this; + } + + public Builder executorContext(@NotNull Project project, @NotNull String uri, @Nullable Position position) { + this.project = project; + this.psiFile = MiscUtil.resolvePsiFile(project, LspPath.fromLspUri(uri)); + this.position = position; + return this; + } + + public Builder runInEDT(boolean runInEDT) { + this.runInEDT = runInEDT; + return this; + } + + public AsyncExecutor build() { + return new AsyncExecutor<>(this); + } + } +} diff --git a/server/src/main/java/org/rri/ideals/server/util/EditorUtil.java b/server/src/main/java/org/rri/ideals/server/util/EditorUtil.java index 4d325a41..dd9f40d6 100644 --- a/server/src/main/java/org/rri/ideals/server/util/EditorUtil.java +++ b/server/src/main/java/org/rri/ideals/server/util/EditorUtil.java @@ -61,6 +61,8 @@ public static T computeWithEditor(@NotNull Disposable context, return callback.apply(editor); } catch (Exception e) { throw MiscUtil.wrap(e); + } finally { + Disposer.dispose(context); } } diff --git a/server/src/main/java/org/rri/ideals/server/util/MiscUtil.java b/server/src/main/java/org/rri/ideals/server/util/MiscUtil.java index 5654b338..868cbbed 100644 --- a/server/src/main/java/org/rri/ideals/server/util/MiscUtil.java +++ b/server/src/main/java/org/rri/ideals/server/util/MiscUtil.java @@ -7,7 +7,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.Segment; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; @@ -22,6 +25,7 @@ import java.util.Arrays; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -89,6 +93,16 @@ public static void invokeWithPsiFileInReadAction(@NotNull Project project, @NotN }); } + public static T computeInEDTAndWait(@NotNull Supplier action) { + final var ref = new AtomicReference(); + + ApplicationManager.getApplication().invokeAndWait(() -> { + ref.set(action.get()); + }); + + return ref.get(); + } + @Nullable public static Document getDocument(@NotNull PsiFile file) { var virtualFile = file.getVirtualFile(); diff --git a/server/src/test/java/org/rri/ideals/server/codeactions/CodeActionServiceTest.java b/server/src/test/java/org/rri/ideals/server/codeactions/CodeActionServiceTest.java index 1e493222..30248a4a 100644 --- a/server/src/test/java/org/rri/ideals/server/codeactions/CodeActionServiceTest.java +++ b/server/src/test/java/org/rri/ideals/server/codeactions/CodeActionServiceTest.java @@ -1,8 +1,6 @@ package org.rri.ideals.server.codeactions; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; +import com.intellij.openapi.util.Disposer; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiManager; import org.eclipse.lsp4j.CodeAction; @@ -13,7 +11,9 @@ import org.junit.runners.JUnit4; import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.diagnostics.DiagnosticsTestBase; +import org.rri.ideals.server.util.EditorUtil; import java.util.stream.Stream; @@ -46,23 +46,24 @@ public static void main() { final var file = myFixture.configureByText("test.java", text); final var orExpressionRange = TestUtil.newRange(2, 8, 2, 8); - - var path = LspPath.fromVirtualFile(file.getVirtualFile()); - final var codeActionService = getProject().getService(CodeActionService.class); + final var disposable = Disposer.newDisposable(); - final var codeActionsBeforeDiagnostic = codeActionService.getCodeActions(path, orExpressionRange); + EditorUtil.withEditor(disposable, file, orExpressionRange.getStart(), editor -> { + final var executorContext = new ExecutorContext(file, disposable, editor, null); + final var codeActionsBeforeDiagnostic = codeActionService.getCodeActions(orExpressionRange, executorContext); - Assert.assertTrue(codeActionsBeforeDiagnostic.stream().allMatch(it -> it.getKind().equals(CodeActionKind.Refactor))); - Assert.assertEquals(expectedIntentions, codeActionsBeforeDiagnostic.stream().map(CodeAction::getTitle).sorted().toList()); + Assert.assertTrue(codeActionsBeforeDiagnostic.stream().allMatch(it -> it.getKind().equals(CodeActionKind.Refactor))); + Assert.assertEquals(expectedIntentions, codeActionsBeforeDiagnostic.stream().map(CodeAction::getTitle).sorted().toList()); - runAndGetDiagnostics(file); + runAndGetDiagnostics(file); - final var quickFixes = codeActionService.getCodeActions(path, orExpressionRange); - quickFixes.removeAll(codeActionsBeforeDiagnostic); + final var quickFixes = codeActionService.getCodeActions(orExpressionRange, executorContext); + quickFixes.removeAll(codeActionsBeforeDiagnostic); - Assert.assertTrue(quickFixes.stream().allMatch(it -> it.getKind().equals(CodeActionKind.QuickFix))); - Assert.assertEquals(expectedQuickFixes, quickFixes.stream().map(CodeAction::getTitle).sorted().toList()); + Assert.assertTrue(quickFixes.stream().allMatch(it -> it.getKind().equals(CodeActionKind.QuickFix))); + Assert.assertEquals(expectedQuickFixes, quickFixes.stream().map(CodeAction::getTitle).sorted().toList()); + }); } @@ -82,30 +83,27 @@ class A { final var actionTitle = "Change field 'x' type to 'String'"; - final var file = myFixture.configureByText("test.java", before); - final var xVariableRange = TestUtil.newRange(1, 13, 1, 13); - var path = LspPath.fromVirtualFile(file.getVirtualFile()); - final var codeActionService = getProject().getService(CodeActionService.class); + final var disposable = Disposer.newDisposable(); - runAndGetDiagnostics(file); + EditorUtil.withEditor(disposable, file, xVariableRange.getStart(), editor -> { + final var executorContext = new ExecutorContext(file, disposable, editor, null); - final var codeActions = codeActionService.getCodeActions(path, xVariableRange); + runAndGetDiagnostics(file); - var action = codeActions.stream() - .filter(it -> it.getTitle().equals(actionTitle)) - .findFirst() - .orElseThrow(() -> new AssertionError("action not found")); + final var codeActions = codeActionService.getCodeActions(xVariableRange, executorContext); - Gson gson = new GsonBuilder().create(); - action.setData(gson.fromJson(gson.toJson(action.getData()), JsonObject.class)); + var action = codeActions.stream() + .filter(it -> it.getTitle().equals(actionTitle)) + .findFirst() + .orElseThrow(() -> new AssertionError("action not found")); - final var edit = codeActionService.applyCodeAction(action); - - Assert.assertEquals(after, TestUtil.applyEdits(file.getText(), edit.getChanges().get(path.toLspUri()))); + final var edit = codeActionService.applyCodeAction((ActionData) action.getData(), actionTitle, executorContext); + Assert.assertEquals(after, TestUtil.applyEdits(file.getText(), edit.getChanges().get(path.toLspUri()))); + }); // checking the quick fix doesn't actually change the file final var reloaded = PsiManager.getInstance(getProject()).findFile(file.getVirtualFile()); @@ -114,5 +112,6 @@ class A { final var reloadedDoc = PsiDocumentManager.getInstance(getProject()).getDocument(reloaded); Assert.assertNotNull(reloadedDoc); Assert.assertEquals(before, reloadedDoc.getText()); + } } diff --git a/server/src/test/java/org/rri/ideals/server/completions/CompletionServiceTest.java b/server/src/test/java/org/rri/ideals/server/completions/CompletionServiceTest.java index 31358d9b..59b07c5e 100644 --- a/server/src/test/java/org/rri/ideals/server/completions/CompletionServiceTest.java +++ b/server/src/test/java/org/rri/ideals/server/completions/CompletionServiceTest.java @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiFile; import com.intellij.testFramework.TestModeFlags; @@ -19,10 +20,13 @@ import org.rri.ideals.server.LspLightBasePlatformTestCase; import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; +import org.rri.ideals.server.commands.ExecutorContext; import org.rri.ideals.server.completions.generators.CompletionTestGenerator; import org.rri.ideals.server.engine.IdeaTestFixture; import org.rri.ideals.server.engine.TestEngine; import org.rri.ideals.server.generator.IdeaOffsetPositionConverter; +import org.rri.ideals.server.util.EditorUtil; +import org.rri.ideals.server.util.MiscUtil; import java.util.ArrayList; import java.util.List; @@ -260,31 +264,34 @@ private void testWithEngine(@NotNull CompletionTestParams completionTestParams) final var test = completionTest.get(0); final var params = test.params(); final var expectedText = test.expected(); - var cs = getProject().getService(CompletionService.class); - var completionItems = cs.computeCompletions( - LspPath.fromLspUri(params.getTextDocument().getUri()), params.getPosition(), - new TestUtil.DumbCancelChecker()); - if (completionTestParams.finder != null) { - var compItem = completionItems.stream().filter(completionTestParams.finder).findFirst().orElseThrow(); - compItem.setData(gson.fromJson(gson.toJson(compItem.getData()), JsonObject.class)); - var resolved = cs.resolveCompletion(compItem, new TestUtil.DumbCancelChecker()); - assertNotNull(expectedText); - assertNotNull(test.getSourceText()); - var allEdits = new ArrayList(); - allEdits.add(resolved.getTextEdit().getLeft()); - allEdits.addAll(resolved.getAdditionalTextEdits()); - assertEquals(expectedText, TestUtil.applyEdits(test.getSourceText(), allEdits)); - - if (completionTestParams.documentation != null) { - assertEquals(completionTestParams.documentation, compItem.getDocumentation().getRight()); - } else { - assertNull(compItem.getDocumentation()); + final var cs = getProject().getService(CompletionService.class); + final var disposable = Disposer.newDisposable(); + final var psiFile = MiscUtil.resolvePsiFile(getProject(), LspPath.fromLspUri(params.getTextDocument().getUri())); + + EditorUtil.withEditor(disposable, psiFile, params.getPosition(), editor -> { + var completionItems = cs.computeCompletions(new ExecutorContext(psiFile, disposable, editor, new TestUtil.DumbCancelChecker())); + if (completionTestParams.finder != null) { + var compItem = completionItems.stream().filter(completionTestParams.finder).findFirst().orElseThrow(); + compItem.setData(gson.fromJson(gson.toJson(compItem.getData()), JsonObject.class)); + var resolved = cs.resolveCompletion(compItem, new TestUtil.DumbCancelChecker()); + assertNotNull(expectedText); + assertNotNull(test.getSourceText()); + var allEdits = new ArrayList(); + allEdits.add(resolved.getTextEdit().getLeft()); + allEdits.addAll(resolved.getAdditionalTextEdits()); + assertEquals(expectedText, TestUtil.applyEdits(test.getSourceText(), allEdits)); + + if (completionTestParams.documentation != null) { + assertEquals(completionTestParams.documentation, compItem.getDocumentation().getRight()); + } else { + assertNull(compItem.getDocumentation()); + } } - } - if (completionTestParams.expectedItems != null) { - assertEquals(completionTestParams.expectedItems(), - completionItems.stream().map(CompletionServiceTestUtil::removeResolveInfo).collect(Collectors.toSet())); - } + if (completionTestParams.expectedItems != null) { + assertEquals(completionTestParams.expectedItems(), + completionItems.stream().map(CompletionServiceTestUtil::removeResolveInfo).collect(Collectors.toSet())); + } + }); } @Test @@ -344,8 +351,10 @@ public void checkCanceled() { private List<@NotNull CompletionItem> getCompletionListAtPosition(@NotNull PsiFile file, @NotNull Position position, @NotNull CancelChecker cancelChecker) { - return getProject().getService(CompletionService.class).computeCompletions( - LspPath.fromVirtualFile(file.getVirtualFile()), position, cancelChecker); + final var disposable = Disposer.newDisposable(); + return EditorUtil.computeWithEditor(disposable, file, position, editor -> + getProject().getService(CompletionService.class).computeCompletions( + new ExecutorContext(file, disposable, editor, cancelChecker))); } static private void runWithTemplateFlags(@NotNull Runnable action) { diff --git a/server/src/test/java/org/rri/ideals/server/formatting/FormattingCommandTest.java b/server/src/test/java/org/rri/ideals/server/formatting/FormattingCommandTest.java index 4dddf230..a16ac6da 100644 --- a/server/src/test/java/org/rri/ideals/server/formatting/FormattingCommandTest.java +++ b/server/src/test/java/org/rri/ideals/server/formatting/FormattingCommandTest.java @@ -1,6 +1,7 @@ package org.rri.ideals.server.formatting; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.util.Disposer; import com.jetbrains.python.PythonFileType; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -172,7 +173,7 @@ public void testTextRangeFormatInSelectFormatting() { @Nullable Range lspRange) { final var actualPsiFile = myFixture.configureByText(fileType, actualText); - var context = new ExecutorContext(actualPsiFile, getProject(), new TestUtil.DumbCancelChecker()); + var context = new ExecutorContext(actualPsiFile, Disposer.newDisposable(), null, new TestUtil.DumbCancelChecker()); var command = new FormattingCommand(lspRange, FormattingTestUtil.defaultOptions()); return TextUtil.differenceAfterAction(actualPsiFile, (copy) -> { diff --git a/server/src/test/java/org/rri/ideals/server/references/DefinitionCommandTest.java b/server/src/test/java/org/rri/ideals/server/references/DefinitionCommandTest.java index ef06f2f4..57e61c79 100644 --- a/server/src/test/java/org/rri/ideals/server/references/DefinitionCommandTest.java +++ b/server/src/test/java/org/rri/ideals/server/references/DefinitionCommandTest.java @@ -7,7 +7,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; import org.rri.ideals.server.engine.TestEngine; import org.rri.ideals.server.generator.IdeaOffsetPositionConverter; @@ -18,7 +17,7 @@ @RunWith(JUnit4.class) -public class DefinitionCommandTest extends ReferencesCommandTestBase { +public class DefinitionCommandTest extends ReferencesCommandTestBase { @Test public void definitionJavaTest() { checkReferencesByDirectory("java/project-definition"); @@ -37,10 +36,8 @@ public void definitionPythonTest() { @Override @Nullable - protected Set getActuals(@NotNull Object params) { - final DefinitionParams defParams = (DefinitionParams) params; - final var path = LspPath.fromLspUri(defParams.getTextDocument().getUri()); - final var future = new FindDefinitionCommand(defParams.getPosition()).runAsync(getProject(), path); + protected Set getActuals(@NotNull DefinitionParams params) { + final var future = new FindDefinitionCommand().runAsync(getProject(), params.getTextDocument(), params.getPosition()); var actual = TestUtil.getNonBlockingEdt(future, 50000); if (actual == null) { return null; diff --git a/server/src/test/java/org/rri/ideals/server/references/DefinitionFromLibrarySourcesTest.java b/server/src/test/java/org/rri/ideals/server/references/DefinitionFromLibrarySourcesTest.java index 66185159..497a0b2e 100644 --- a/server/src/test/java/org/rri/ideals/server/references/DefinitionFromLibrarySourcesTest.java +++ b/server/src/test/java/org/rri/ideals/server/references/DefinitionFromLibrarySourcesTest.java @@ -48,7 +48,7 @@ public void definitionJavaTest() { LspPath.fromLocalPath( Paths.get(getTestDataPath()).resolve("src/DefinitionFromJar.java")); - final var future = new FindDefinitionCommand(new Position(3, 8)).runAsync(getProject(), path); + final var future = new FindDefinitionCommand().runAsync(getProject(), path.toLspUri(), new Position(3, 8)); var actual = TestUtil.getNonBlockingEdt(future, 5000); assertNotNull(actual); diff --git a/server/src/test/java/org/rri/ideals/server/references/DocumentHighlightCommandTest.java b/server/src/test/java/org/rri/ideals/server/references/DocumentHighlightCommandTest.java index c4693a6d..47054d6e 100644 --- a/server/src/test/java/org/rri/ideals/server/references/DocumentHighlightCommandTest.java +++ b/server/src/test/java/org/rri/ideals/server/references/DocumentHighlightCommandTest.java @@ -3,6 +3,7 @@ import org.eclipse.lsp4j.DocumentHighlight; import org.eclipse.lsp4j.DocumentHighlightKind; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentPositionParams; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Test; @@ -72,7 +73,7 @@ public void testDocumentHighlightPython() { private void checkHighlight(@NotNull Set<@NotNull DocumentHighlight> answers, @NotNull Position pos, @NotNull LspPath path) { - final var future = new DocumentHighlightCommand(pos).runAsync(getProject(), path); + final var future = new DocumentHighlightCommand().runAsync(getProject(), path.toLspUri(), pos); final var lst = TestUtil.getNonBlockingEdt(future, 50000); assertNotNull(lst); assertEquals(answers, new HashSet<>(lst)); @@ -87,7 +88,7 @@ protected TestGenerator getGenerator(@NotNull Test } @Override - protected @Nullable Object getActuals(@NotNull Object params) { + protected @Nullable Object getActuals(@NotNull TextDocumentPositionParams params) { return null; } } diff --git a/server/src/test/java/org/rri/ideals/server/references/FindUsagesCommandTest.java b/server/src/test/java/org/rri/ideals/server/references/FindUsagesCommandTest.java index 5601c235..0b47e4de 100644 --- a/server/src/test/java/org/rri/ideals/server/references/FindUsagesCommandTest.java +++ b/server/src/test/java/org/rri/ideals/server/references/FindUsagesCommandTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; import org.rri.ideals.server.engine.TestEngine; import org.rri.ideals.server.generator.IdeaOffsetPositionConverter; @@ -15,7 +14,7 @@ import java.util.HashSet; @RunWith(JUnit4.class) -public class FindUsagesCommandTest extends ReferencesCommandTestBase { +public class FindUsagesCommandTest extends ReferencesCommandTestBase { @Test public void testFindUsagesJava() { checkReferencesByDirectory("java/project-find-usages"); @@ -32,10 +31,8 @@ public void testFindUsagesPython() { } @Override - protected @Nullable Object getActuals(@NotNull Object params) { - final ReferenceParams refParams = (ReferenceParams) params; - final var path = LspPath.fromLspUri(refParams.getTextDocument().getUri()); - final var future = new FindUsagesCommand(refParams.getPosition()).runAsync(getProject(), path); + protected @Nullable Object getActuals(@NotNull ReferenceParams params) { + final var future = new FindUsagesCommand().runAsync(getProject(), params.getTextDocument(), params.getPosition()); return new HashSet<>(TestUtil.getNonBlockingEdt(future, 50000)); } } diff --git a/server/src/test/java/org/rri/ideals/server/references/ReferencesCommandTestBase.java b/server/src/test/java/org/rri/ideals/server/references/ReferencesCommandTestBase.java index 8833e3bc..43f9f047 100644 --- a/server/src/test/java/org/rri/ideals/server/references/ReferencesCommandTestBase.java +++ b/server/src/test/java/org/rri/ideals/server/references/ReferencesCommandTestBase.java @@ -1,5 +1,6 @@ package org.rri.ideals.server.references; +import org.eclipse.lsp4j.TextDocumentPositionParams; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.rri.ideals.server.LspLightBasePlatformTestCase; @@ -10,7 +11,7 @@ import java.nio.file.Paths; public abstract class ReferencesCommandTestBase> extends LspLightBasePlatformTestCase { + extends TestGenerator.Test>, T extends TextDocumentPositionParams> extends LspLightBasePlatformTestCase { @Override protected String getTestDataPath() { return Paths.get("test-data/references").toAbsolutePath().toString(); @@ -18,7 +19,7 @@ protected String getTestDataPath() { protected abstract @NotNull E getGenerator(@NotNull final TestEngine engine); - protected abstract @Nullable Object getActuals(@NotNull final Object params); + protected abstract @Nullable Object getActuals(@NotNull final T params); protected void checkReferencesByDirectory(@NotNull String testProjectRelativePath) { final var engine = new TestEngine(new IdeaTestFixture(myFixture)); @@ -29,7 +30,7 @@ protected void checkReferencesByDirectory(@NotNull String testProjectRelativePat final var params = test.params(); final var expected = test.expected(); - final var actual = getActuals(params); + final var actual = getActuals((T) params); assertEquals(expected, actual); } diff --git a/server/src/test/java/org/rri/ideals/server/references/TypeDefinitionCommandTest.java b/server/src/test/java/org/rri/ideals/server/references/TypeDefinitionCommandTest.java index 3f341f47..382e1013 100644 --- a/server/src/test/java/org/rri/ideals/server/references/TypeDefinitionCommandTest.java +++ b/server/src/test/java/org/rri/ideals/server/references/TypeDefinitionCommandTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; import org.rri.ideals.server.engine.TestEngine; import org.rri.ideals.server.generator.IdeaOffsetPositionConverter; @@ -16,7 +15,7 @@ import java.util.Set; @RunWith(JUnit4.class) -public class TypeDefinitionCommandTest extends ReferencesCommandTestBase { +public class TypeDefinitionCommandTest extends ReferencesCommandTestBase { @Test public void testTypeDefinitionJava() { checkReferencesByDirectory("java/project-type-definition"); @@ -33,10 +32,8 @@ public void testTypeDefinitionPython() { } @Override - protected @Nullable Set getActuals(@NotNull Object params) { - final TypeDefinitionParams typeDefParams = (TypeDefinitionParams) params; - final var path = LspPath.fromLspUri(typeDefParams.getTextDocument().getUri()); - final var future = new FindTypeDefinitionCommand(typeDefParams.getPosition()).runAsync(getProject(), path); + protected @Nullable Set getActuals(@NotNull TypeDefinitionParams params) { + final var future = new FindTypeDefinitionCommand().runAsync(getProject(), params.getTextDocument(), params.getPosition()); var actual = TestUtil.getNonBlockingEdt(future, 50000); if (actual == null) { return null; diff --git a/server/src/test/java/org/rri/ideals/server/rename/RenameCommandTestBase.java b/server/src/test/java/org/rri/ideals/server/rename/RenameCommandTestBase.java index 35552969..daeecb87 100644 --- a/server/src/test/java/org/rri/ideals/server/rename/RenameCommandTestBase.java +++ b/server/src/test/java/org/rri/ideals/server/rename/RenameCommandTestBase.java @@ -48,7 +48,7 @@ protected void checkRename(@NotNull List<@NotNull TextDocumentEdit> edits, @NotN answer = new WorkspaceEdit(Stream.concat(changedEdits.stream(), changedOperations.stream()).toList()); } - final var future = new RenameCommand(pos, newName).runAsync(getProject(), renameTestPath); + final var future = new RenameCommand(newName).runAsync(getProject(), renameTestPath.toLspUri(), pos); final var result = TestUtil.getNonBlockingEdt(future, 50000); assertNotNull(result); diff --git a/server/src/test/java/org/rri/ideals/server/signature/SignatureHelpServiceTest.java b/server/src/test/java/org/rri/ideals/server/signature/SignatureHelpServiceTest.java index a694ce6b..ba6e880c 100644 --- a/server/src/test/java/org/rri/ideals/server/signature/SignatureHelpServiceTest.java +++ b/server/src/test/java/org/rri/ideals/server/signature/SignatureHelpServiceTest.java @@ -3,6 +3,7 @@ import com.intellij.codeInsight.hint.ParameterInfoListener; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.util.Disposer; import com.jetbrains.python.PythonFileType; import org.eclipse.lsp4j.ParameterInformation; import org.eclipse.lsp4j.Position; @@ -15,8 +16,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.rri.ideals.server.LspLightBasePlatformTestCase; -import org.rri.ideals.server.LspPath; import org.rri.ideals.server.TestUtil; +import org.rri.ideals.server.commands.ExecutorContext; +import org.rri.ideals.server.util.EditorUtil; import java.util.List; @@ -196,15 +198,17 @@ private void testSignatureHelp(@NotNull String text, @NotNull LanguageFileType f @NotNull CancelChecker cancelChecker, @NotNull List expected) { final var file = myFixture.configureByText(fileType, text); - var signatureHelpService = getProject().getService(SignatureHelpService.class); + final var signatureHelpService = getProject().getService(SignatureHelpService.class); signatureHelpService.setEdtFlushRunnable(defaultFlushRunnable()); - - var signatureHelp = - signatureHelpService.computeSignatureHelp( - LspPath.fromVirtualFile(file.getVirtualFile()), pos, cancelChecker); - assertNotNull(signatureHelp); - assertEquals(activeSignature, signatureHelp.getActiveSignature()); - assertEquals(expected, signatureHelp.getSignatures()); + final var disposable = Disposer.newDisposable(); + + EditorUtil.withEditor(disposable, file, pos, editor -> { + var signatureHelp = + signatureHelpService.computeSignatureHelp(new ExecutorContext(file, disposable, editor, cancelChecker)); + assertNotNull(signatureHelp); + assertEquals(activeSignature, signatureHelp.getActiveSignature()); + assertEquals(expected, signatureHelp.getSignatures()); + }); } @NotNull diff --git a/server/src/test/java/org/rri/ideals/server/symbol/DocumentSymbolServiceTest.java b/server/src/test/java/org/rri/ideals/server/symbol/DocumentSymbolServiceTest.java index 5a69e7f7..97442a53 100644 --- a/server/src/test/java/org/rri/ideals/server/symbol/DocumentSymbolServiceTest.java +++ b/server/src/test/java/org/rri/ideals/server/symbol/DocumentSymbolServiceTest.java @@ -1,6 +1,8 @@ package org.rri.ideals.server.symbol; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiManager; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolKind; @@ -11,7 +13,8 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.rri.ideals.server.LspLightBasePlatformTestCase; -import org.rri.ideals.server.LspPath; +import org.rri.ideals.server.TestUtil; +import org.rri.ideals.server.commands.ExecutorContext; import java.lang.String; import java.nio.file.Paths; @@ -202,10 +205,18 @@ public void testDocumentSymbolKotlin() { private void checkDocumentSymbols(@NotNull List<@NotNull DocumentSymbol> answers, @Nullable VirtualFile virtualFile) { assertNotNull(virtualFile); - var service = getProject().getService(DocumentSymbolService.class); - var actual = service.computeDocumentSymbols(LspPath.fromVirtualFile(virtualFile), () -> { - }).stream().map(Either::getRight).toList(); - assertEquals(answers, actual); + final var service = getProject().getService(DocumentSymbolService.class); + final var disposable = Disposer.newDisposable(); + try { + final var psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile); + assertNotNull(psiFile); + var actual = service.computeDocumentSymbols( + new ExecutorContext(psiFile, disposable, null, new TestUtil.DumbCancelChecker())) + .stream().map(Either::getRight).toList(); + assertEquals(answers, actual); + } finally { + Disposer.dispose(disposable); + } } @NotNull