Skip to content

Commit

Permalink
fix: process element usages under ReadAction
Browse files Browse the repository at this point in the history
Fixes #708
  • Loading branch information
angelozerr committed Dec 18, 2024
1 parent 1200518 commit 174442e
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*******************************************************************************/
package com.redhat.devtools.lsp4ij.usages;

import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
Expand Down Expand Up @@ -56,7 +55,7 @@ private LSPExternalReferencesFinder() {
public static void processExternalReferences(@NotNull PsiFile file,
int offset,
@NotNull Processor<PsiReference> processor) {
processExternalReferences(file, offset, ReadAction.compute(file::getUseScope), processor);
processExternalReferences(file, offset, file.getUseScope(), processor);
}

/**
Expand All @@ -77,7 +76,7 @@ public static void processExternalReferences(@NotNull PsiFile file,
TextRange wordTextRange = document != null ? LSPIJUtils.getWordRangeAt(document, file, offset) : null;
if (wordTextRange != null) {
LSPPsiElement wordElement = new LSPPsiElement(file, wordTextRange);
String wordText = ReadAction.compute(wordElement::getText);
String wordText = wordElement.getText();
if (StringUtil.isNotEmpty(wordText)) {
processExternalReferences(
file,
Expand Down Expand Up @@ -113,7 +112,7 @@ private static void processExternalReferences(@NotNull PsiFile file,
}

Set<String> externalReferenceKeys = new HashSet<>();
ReadAction.run(() -> PsiSearchHelper.getInstance(project).processElementsWithWord(
PsiSearchHelper.getInstance(project).processElementsWithWord(
(element, offsetInElement) -> {
PsiReference originalReference = element.findReferenceAt(offsetInElement);
List<PsiReference> references = originalReference != null ?
Expand Down Expand Up @@ -154,7 +153,7 @@ private static void processExternalReferences(@NotNull PsiFile file,
wordText,
UsageSearchContext.ANY,
caseSensitive
));
);
}

@Nullable
Expand Down
154 changes: 91 additions & 63 deletions src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsageSearcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@
******************************************************************************/
package com.redhat.devtools.lsp4ij.usages;

import com.intellij.codeInsight.codeVision.CodeVisionState;
import com.intellij.find.findUsages.CustomUsageSearcher;
import com.intellij.find.findUsages.FindUsagesOptions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.util.Processor;
import com.intellij.util.ui.EDT;
import com.redhat.devtools.lsp4ij.LSPIJUtils;
import com.redhat.devtools.lsp4ij.LanguageServiceAccessor;
import org.eclipse.lsp4j.Position;
Expand All @@ -33,6 +37,7 @@
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;

import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.waitUntilDone;
Expand All @@ -55,84 +60,106 @@ public class LSPUsageSearcher extends CustomUsageSearcher {

@Override
public void processElementUsages(@NotNull PsiElement element, @NotNull Processor<? super Usage> processor, @NotNull FindUsagesOptions options) {
processElementUsagesUnderReadAction(() -> {
PsiFile file = element.getContainingFile();
if (file == null) {
return null;
}

PsiFile file = element.getContainingFile();
if (file == null) {
return;
}

VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) {
return;
}
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) {
return null;
}

Project project = element.getProject();
if (!LanguageServiceAccessor.getInstance(project).hasAny(
virtualFile,
l -> l.getClientFeatures().getUsageFeature().isSupported(file)
)) {
return;
}
Project project = element.getProject();
if (!LanguageServiceAccessor.getInstance(project).hasAny(
virtualFile,
l -> l.getClientFeatures().getUsageFeature().isSupported(file)
)) {
return null;
}

if (element instanceof LSPUsageTriggeredPsiElement elt) {
if (elt.getLSPReferences() != null) {
elt.getLSPReferences()
.forEach(ref -> {
var psiElement = LSPUsagesManager.toPsiElement(ref.location(), ref.languageServer().getClientFeatures(), LSPUsagePsiElement.UsageKind.references, project);
if (psiElement != null) {
processor.process(ReadAction.compute(() -> new UsageInfo2UsageAdapter(new UsageInfo(psiElement))));
}
});
return;
if (element instanceof LSPUsageTriggeredPsiElement elt) {
if (elt.getLSPReferences() != null) {
elt.getLSPReferences()
.forEach(ref -> {
var psiElement = LSPUsagesManager.toPsiElement(ref.location(), ref.languageServer().getClientFeatures(), LSPUsagePsiElement.UsageKind.references, project);
if (psiElement != null) {
processor.process(new UsageInfo2UsageAdapter(new UsageInfo(psiElement)));
}
});
return null;
}
}
}

// Get position where the "Find Usages" has been triggered
Position position = getPosition(element, file);
if (position == null) {
return;
}
// Collect textDocument/definition, textDocument/references, etc
LSPUsageSupport usageSupport = new LSPUsageSupport(ReadAction.compute(() -> file));
LSPUsageSupport.LSPUsageSupportParams params = new LSPUsageSupport.LSPUsageSupportParams(position);
CompletableFuture<List<LSPUsagePsiElement>> usagesFuture = usageSupport.getFeatureData(params);
try {
// Wait for completion of textDocument/definition, textDocument/references, etc
waitUntilDone(usagesFuture);
if (usagesFuture.isDone()) {
// Show response of textDocument/definition, textDocument/references, etc as usage info.
List<LSPUsagePsiElement> usages = usagesFuture.getNow(null);
if (usages != null) {
for (LSPUsagePsiElement usage : usages) {
processor.process(ReadAction.compute(() -> new UsageInfo2UsageAdapter(new UsageInfo(usage))));
// Get position where the "Find Usages" has been triggered
Position position = getPosition(element, file);
if (position == null) {
return null;
}
// Collect textDocument/definition, textDocument/references, etc
LSPUsageSupport usageSupport = new LSPUsageSupport(file);
LSPUsageSupport.LSPUsageSupportParams params = new LSPUsageSupport.LSPUsageSupportParams(position);
CompletableFuture<List<LSPUsagePsiElement>> usagesFuture = usageSupport.getFeatureData(params);
try {
// Wait for completion of textDocument/definition, textDocument/references, etc
waitUntilDone(usagesFuture);
if (usagesFuture.isDone()) {
// Show response of textDocument/definition, textDocument/references, etc as usage info.
List<LSPUsagePsiElement> usages = usagesFuture.getNow(null);
if (usages != null) {
for (LSPUsagePsiElement usage : usages) {
processor.process(new UsageInfo2UsageAdapter(new UsageInfo(usage)));
}
}
}
} catch (ProcessCanceledException pce) {
throw pce;
} catch (Exception e) {
LOGGER.error("Error while collection LSP Usages", e);
}
} catch (ProcessCanceledException pce) {
throw pce;
} catch (Exception e) {
LOGGER.error("Error while collection LSP Usages", e);
}

// For completeness' sake, also collect external usages to LSP (pseudo-)elements
LSPExternalReferencesFinder.processExternalReferences(
file,
ReadAction.compute(element::getTextOffset),
options.searchScope,
reference -> processor.process(ReadAction.compute(() -> new UsageInfo2UsageAdapter(new UsageInfo(reference))))
);
// For completeness' sake, also collect external usages to LSP (pseudo-)elements
LSPExternalReferencesFinder.processExternalReferences(
file,
element.getTextOffset(),
options.searchScope,
reference -> processor.process(new UsageInfo2UsageAdapter(new UsageInfo(reference)))
);


return null;
}, element);
}

@Nullable
private static Position getPosition(@NotNull PsiElement element, @NotNull PsiFile psiFile) {
if (ApplicationManager.getApplication().isReadAccessAllowed()) {
return doGetPosition(element, psiFile);
private static void processElementUsagesUnderReadAction(@NotNull ThrowableComputable<CodeVisionState, Throwable> computable,
@NotNull PsiElement element) {
if (DumbService.isDumb(element.getProject())) {
return;
}
try {
if (!EDT.isCurrentThreadEdt()) {
ReadAction.computeCancellable(computable);
} else {
// In tests [computeCodeVision] is executed in sync mode on EDT
assert (ApplicationManager.getApplication().isUnitTestMode());
ReadAction.compute(computable);
}
} catch (ReadAction.CannotReadException e) {
// Ignore error
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
// Ignore error
} catch (CancellationException e) {
// Ignore error
} catch (Throwable e) {
// Ignore error
}
return ReadAction.compute(() -> doGetPosition(element, psiFile));
}

@Nullable
private static Position doGetPosition(@NotNull PsiElement element, @NotNull PsiFile psiFile) {
private static Position getPosition(@NotNull PsiElement element, @NotNull PsiFile psiFile) {
VirtualFile file = psiFile.getVirtualFile();
if (file == null) {
return null;
Expand All @@ -144,3 +171,4 @@ private static Position doGetPosition(@NotNull PsiElement element, @NotNull PsiF
return LSPIJUtils.toPosition(Math.min(element.getTextRange().getStartOffset() + 1, element.getTextRange().getEndOffset()), document);
}
}

0 comments on commit 174442e

Please sign in to comment.