mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
introduce RequestResultProcessor.BulkResultProcessor for poor languages (aka Kotlin) that can't resolve references efficiently one by one
This commit is contained in:
@@ -17,17 +17,25 @@ package com.intellij.psi.search;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.util.Processor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* An occurrence processor for Find Usages functionality. A typical scenario involves invoking
|
||||
* {@link ReferencesSearch.SearchParameters#getOptimizer()} and passing this processor together with search string and some other parameters to
|
||||
* {@link SearchRequestCollector#searchWord(String, SearchScope, short, boolean, String, RequestResultProcessor)}.
|
||||
*
|
||||
* @author peter
|
||||
*/
|
||||
public abstract class RequestResultProcessor {
|
||||
private final Object myEquality;
|
||||
|
||||
/**
|
||||
* @param equality this processor's equals/hashCode will delegate to this object
|
||||
*/
|
||||
protected RequestResultProcessor(@NotNull Object... equality) {
|
||||
myEquality = Arrays.asList(equality);
|
||||
}
|
||||
@@ -47,5 +55,32 @@ public abstract class RequestResultProcessor {
|
||||
return myEquality.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* For every occurrence of the search string in the search scope, the infrastructure invokes this method for every PSI element having that
|
||||
* occurrence inside, from leaf elements up the tree until file element. The implementation is supposed to search for references
|
||||
* inside the given element at the given offset, and feed them to <code>consumer</code>.<p/>
|
||||
*
|
||||
* If you wish to process all offsets in the scope (e.g. file) at once, extend {@link BulkResultProcessor}.
|
||||
*
|
||||
* @return whether the consumer has returned false for any of the references (and thus stopped searching), false otherwise.
|
||||
*/
|
||||
public abstract boolean processTextOccurrence(@NotNull PsiElement element, int offsetInElement, @NotNull Processor<PsiReference> consumer);
|
||||
|
||||
/**
|
||||
* A variant of {@link RequestResultProcessor} that processes all text occurrences at once, e.g. for performance purposes.
|
||||
*/
|
||||
public static abstract class BulkResultProcessor extends RequestResultProcessor {
|
||||
@Override
|
||||
public boolean processTextOccurrence(@NotNull PsiElement element, int offsetInElement, @NotNull Processor<PsiReference> consumer) {
|
||||
return processTextOccurrences(element, new int[]{offsetInElement}, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked for every element of the search scope (e.g. file) with the array of all offsets of search string occurrences in this scope.
|
||||
* Offsets are relative to <code>scope</code> start offset. The implementation is supposed to search for references
|
||||
* inside the given element at the given offsets, and feed them to <code>consumer</code>.<p/>
|
||||
* @return whether the consumer has returned false for any of the references (and thus stopped searching), false otherwise.
|
||||
*/
|
||||
public abstract boolean processTextOccurrences(@NotNull PsiElement scope, int[] offsetsInScope, @NotNull Processor<PsiReference> consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.psi.impl.search;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.text.StringSearcher;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@FunctionalInterface
|
||||
interface BulkOccurrenceProcessor {
|
||||
boolean execute(@NotNull PsiElement scope, int[] offsetsInScope, @NotNull StringSearcher searcher);
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import com.intellij.psi.impl.source.tree.LeafElement;
|
||||
import com.intellij.psi.impl.source.tree.TreeElement;
|
||||
import com.intellij.psi.search.TextOccurenceProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.StringSearcher;
|
||||
@@ -52,7 +53,7 @@ public class LowLevelSearchUtil {
|
||||
private static Boolean processInjectedFile(PsiElement element,
|
||||
final TextOccurenceProcessor processor,
|
||||
final StringSearcher searcher,
|
||||
ProgressIndicator progress,
|
||||
@NotNull ProgressIndicator progress,
|
||||
InjectedLanguageManager injectedLanguageManager) {
|
||||
if (!(element instanceof PsiLanguageInjectionHost)) return null;
|
||||
if (injectedLanguageManager == null) return null;
|
||||
@@ -75,7 +76,7 @@ public class LowLevelSearchUtil {
|
||||
@NotNull StringSearcher searcher,
|
||||
final int offset,
|
||||
final boolean processInjectedPsi,
|
||||
ProgressIndicator progress,
|
||||
@NotNull ProgressIndicator progress,
|
||||
TreeElement lastElement) {
|
||||
if (scope instanceof PsiCompiledElement) {
|
||||
throw new IllegalArgumentException("Scope is compiled, can't scan: "+scope);
|
||||
@@ -115,7 +116,7 @@ public class LowLevelSearchUtil {
|
||||
TreeElement prevNode = null;
|
||||
PsiElement run = null;
|
||||
while (run != scope) {
|
||||
if (progress != null) progress.checkCanceled();
|
||||
progress.checkCanceled();
|
||||
if (useTree) {
|
||||
start += prevNode == null ? 0 : prevNode.getStartOffsetInParent();
|
||||
prevNode = leafNode;
|
||||
@@ -173,12 +174,16 @@ public class LowLevelSearchUtil {
|
||||
return (LeafElement)scopeNode.findLeafElementAt(offsetR);
|
||||
}
|
||||
|
||||
//@RequiresReadAction
|
||||
public static boolean processElementsContainingWordInElement(@NotNull final TextOccurenceProcessor processor,
|
||||
@NotNull final PsiElement scope,
|
||||
@NotNull final StringSearcher searcher,
|
||||
final boolean processInjectedPsi,
|
||||
final ProgressIndicator progress) {
|
||||
boolean processInjectedPsi,
|
||||
@NotNull ProgressIndicator progress) {
|
||||
int[] occurrences = getTextOccurrencesInScope(scope, searcher, progress);
|
||||
return processElementsAtOffsets(scope, searcher, processInjectedPsi, progress, occurrences, processor);
|
||||
}
|
||||
|
||||
static int[] getTextOccurrencesInScope(@NotNull PsiElement scope, @NotNull StringSearcher searcher, ProgressIndicator progress) {
|
||||
if (progress != null) progress.checkCanceled();
|
||||
|
||||
PsiFile file = scope.getContainingFile();
|
||||
@@ -188,25 +193,38 @@ public class LowLevelSearchUtil {
|
||||
TextRange range = scope.getTextRange();
|
||||
if (range == null) {
|
||||
LOG.error("Element " + scope + " of class " + scope.getClass() + " has null range");
|
||||
return true;
|
||||
return ArrayUtil.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
final int scopeStart = range.getStartOffset();
|
||||
final int startOffset = scopeStart;
|
||||
int startOffset = range.getStartOffset();
|
||||
int endOffset = range.getEndOffset();
|
||||
if (endOffset > buffer.length()) {
|
||||
diagnoseInvalidRange(scope, file, viewProvider, buffer, range);
|
||||
return true;
|
||||
return ArrayUtil.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
final Project project = file.getProject();
|
||||
final TreeElement[] lastElement = {null};
|
||||
return processTextOccurrences(buffer, startOffset, endOffset, searcher, progress, offset -> {
|
||||
if (progress != null) progress.checkCanceled();
|
||||
lastElement[0] = processTreeUp(project, processor, scope, searcher, offset - scopeStart, processInjectedPsi, progress,
|
||||
lastElement[0]);
|
||||
return lastElement[0] != null;
|
||||
});
|
||||
int[] offsets = getTextOccurrences(buffer, startOffset, endOffset, searcher, progress);
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
offsets[i] -= startOffset;
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
static boolean processElementsAtOffsets(@NotNull PsiElement scope,
|
||||
@NotNull StringSearcher searcher,
|
||||
boolean processInjectedPsi,
|
||||
@NotNull ProgressIndicator progress,
|
||||
int[] offsetsInScope, @NotNull TextOccurenceProcessor processor) {
|
||||
if (offsetsInScope.length == 0) return true;
|
||||
|
||||
Project project = scope.getProject();
|
||||
TreeElement[] lastElement = {null};
|
||||
for (int offset : offsetsInScope) {
|
||||
progress.checkCanceled();
|
||||
lastElement[0] = processTreeUp(project, processor, scope, searcher, offset, processInjectedPsi, progress, lastElement[0]);
|
||||
if (lastElement[0] == null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void diagnoseInvalidRange(@NotNull PsiElement scope,
|
||||
@@ -239,6 +257,19 @@ public class LowLevelSearchUtil {
|
||||
@NotNull StringSearcher searcher,
|
||||
@Nullable ProgressIndicator progress,
|
||||
@NotNull TIntProcedure processor) {
|
||||
for (int offset : getTextOccurrences(text, startOffset, endOffset, searcher, progress)) {
|
||||
if (!processor.execute(offset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int[] getTextOccurrences(@NotNull CharSequence text,
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
@NotNull StringSearcher searcher,
|
||||
@Nullable ProgressIndicator progress) {
|
||||
if (endOffset > text.length()) {
|
||||
throw new IllegalArgumentException("end: " + endOffset + " > length: "+text.length());
|
||||
}
|
||||
@@ -266,14 +297,15 @@ public class LowLevelSearchUtil {
|
||||
}
|
||||
cachedMap.put(searcher, cachedOccurrences);
|
||||
}
|
||||
TIntArrayList offsets = new TIntArrayList(cachedOccurrences.length - 2);
|
||||
for (int i = 2; i < cachedOccurrences.length; i++) {
|
||||
int occurrence = cachedOccurrences[i];
|
||||
if (occurrence > endOffset - searcher.getPatternLength()) break;
|
||||
if (occurrence >= startOffset && !processor.execute(occurrence)) {
|
||||
return false;
|
||||
if (occurrence >= startOffset) {
|
||||
offsets.add(occurrence);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return offsets.toNativeArray();
|
||||
}
|
||||
|
||||
private static boolean checkJavaIdentifier(@NotNull CharSequence text,
|
||||
|
||||
@@ -157,11 +157,20 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
final short searchContext,
|
||||
@NotNull EnumSet<Options> options,
|
||||
@Nullable String containerName) {
|
||||
return bulkProcessElementsWithWord(searchScope, text, searchContext, options, containerName, (scope, offsetsInScope, searcher) ->
|
||||
LowLevelSearchUtil.processElementsAtOffsets(scope, searcher, options.contains(Options.PROCESS_INJECTED_PSI), getOrCreateIndicator(),
|
||||
offsetsInScope, processor));
|
||||
}
|
||||
|
||||
private boolean bulkProcessElementsWithWord(@NotNull SearchScope searchScope,
|
||||
@NotNull final String text,
|
||||
final short searchContext,
|
||||
@NotNull EnumSet<Options> options,
|
||||
@Nullable String containerName, @NotNull final BulkOccurrenceProcessor processor) {
|
||||
if (text.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot search for elements with empty text");
|
||||
}
|
||||
final ProgressIndicator progress = getOrCreateIndicator();
|
||||
final boolean processInjectedPsi = options.contains(Options.PROCESS_INJECTED_PSI);
|
||||
if (searchScope instanceof GlobalSearchScope) {
|
||||
StringSearcher searcher = new StringSearcher(text, options.contains(Options.CASE_SENSITIVE_SEARCH), true,
|
||||
searchContext == UsageSearchContext.IN_STRINGS,
|
||||
@@ -170,8 +179,8 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
return processElementsWithTextInGlobalScope(processor,
|
||||
(GlobalSearchScope)searchScope,
|
||||
searcher,
|
||||
searchContext, options.contains(Options.CASE_SENSITIVE_SEARCH), containerName, progress,
|
||||
processInjectedPsi);
|
||||
searchContext, options.contains(Options.CASE_SENSITIVE_SEARCH), containerName, progress
|
||||
);
|
||||
}
|
||||
LocalSearchScope scope = (LocalSearchScope)searchScope;
|
||||
PsiElement[] scopeElements = scope.getScope();
|
||||
@@ -190,7 +199,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
LOG.debug("Element " + scopeElement + " of class " + scopeElement.getClass() + " has null range");
|
||||
return true;
|
||||
}
|
||||
return LowLevelSearchUtil.processElementsContainingWordInElement(processor, scopeElement, searcher, processInjectedPsi, progress);
|
||||
return processor.execute(scopeElement, LowLevelSearchUtil.getTextOccurrencesInScope(scopeElement, searcher, progress), searcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,15 +222,14 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Processor<PsiElement> localProcessor(@NotNull final TextOccurenceProcessor processor,
|
||||
private static Processor<PsiElement> localProcessor(@NotNull final BulkOccurrenceProcessor processor,
|
||||
@NotNull final ProgressIndicator progress,
|
||||
final boolean processInjectedPsi,
|
||||
@NotNull final StringSearcher searcher) {
|
||||
return new ReadActionProcessor<PsiElement>() {
|
||||
@Override
|
||||
public boolean processInReadAction(PsiElement scopeElement) {
|
||||
return scopeElement.isValid() &&
|
||||
LowLevelSearchUtil.processElementsContainingWordInElement(processor, scopeElement, searcher, processInjectedPsi, progress);
|
||||
processor.execute(scopeElement, LowLevelSearchUtil.getTextOccurrencesInScope(scopeElement, searcher, progress), searcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,14 +239,13 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean processElementsWithTextInGlobalScope(@NotNull final TextOccurenceProcessor processor,
|
||||
private boolean processElementsWithTextInGlobalScope(@NotNull final BulkOccurrenceProcessor processor,
|
||||
@NotNull final GlobalSearchScope scope,
|
||||
@NotNull final StringSearcher searcher,
|
||||
final short searchContext,
|
||||
final boolean caseSensitively,
|
||||
@Nullable String containerName,
|
||||
@NotNull ProgressIndicator progress,
|
||||
final boolean processInjectedPsi) {
|
||||
@NotNull ProgressIndicator progress) {
|
||||
if (Thread.holdsLock(PsiLock.LOCK)) {
|
||||
throw new AssertionError("You must not run search from within updating PSI activity. Please consider invokeLatering it instead.");
|
||||
}
|
||||
@@ -253,7 +260,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
|
||||
progress.setText(PsiBundle.message("psi.search.for.word.progress", text));
|
||||
|
||||
final Processor<PsiElement> localProcessor = localProcessor(processor, progress, processInjectedPsi, searcher);
|
||||
final Processor<PsiElement> localProcessor = localProcessor(processor, progress, searcher);
|
||||
if (containerName != null) {
|
||||
List<VirtualFile> intersectionWithContainerFiles = new ArrayList<>();
|
||||
// intersectionWithContainerFiles holds files containing words from both `text` and `containerName`
|
||||
@@ -707,18 +714,25 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static TextOccurenceProcessor adaptProcessor(@NotNull PsiSearchRequest singleRequest,
|
||||
@NotNull final Processor<PsiReference> consumer) {
|
||||
private static BulkOccurrenceProcessor adaptProcessor(@NotNull PsiSearchRequest singleRequest,
|
||||
@NotNull Processor<PsiReference> consumer) {
|
||||
final SearchScope searchScope = singleRequest.searchScope;
|
||||
final boolean ignoreInjectedPsi = searchScope instanceof LocalSearchScope && ((LocalSearchScope)searchScope).isIgnoreInjectedPsi();
|
||||
final RequestResultProcessor wrapped = singleRequest.processor;
|
||||
return new TextOccurenceProcessor() {
|
||||
return new BulkOccurrenceProcessor() {
|
||||
@Override
|
||||
public boolean execute(@NotNull PsiElement element, int offsetInElement) {
|
||||
if (ignoreInjectedPsi && element instanceof PsiLanguageInjectionHost) return true;
|
||||
|
||||
public boolean execute(@NotNull PsiElement scope, int[] offsetsInScope, @NotNull StringSearcher searcher) {
|
||||
try {
|
||||
return wrapped.processTextOccurrence(element, offsetInElement, consumer);
|
||||
if (wrapped instanceof RequestResultProcessor.BulkResultProcessor) {
|
||||
return ((RequestResultProcessor.BulkResultProcessor)wrapped).processTextOccurrences(scope, offsetsInScope, consumer);
|
||||
}
|
||||
|
||||
return LowLevelSearchUtil.processElementsAtOffsets(scope, searcher, !ignoreInjectedPsi,
|
||||
getOrCreateIndicator(), offsetsInScope,
|
||||
(element, offsetInElement) -> {
|
||||
if (ignoreInjectedPsi && element instanceof PsiLanguageInjectionHost) return true;
|
||||
return wrapped.processTextOccurrence(element, offsetInElement, consumer);
|
||||
});
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
@@ -852,9 +866,9 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
for (RequestWithProcessor singleRequest : entry.getValue()) {
|
||||
PsiSearchRequest primitive = singleRequest.request;
|
||||
StringSearcher searcher = new StringSearcher(primitive.word, primitive.caseSensitive, true, false);
|
||||
final TextOccurenceProcessor adapted = adaptProcessor(primitive, singleRequest.refProcessor);
|
||||
BulkOccurrenceProcessor adapted = adaptProcessor(primitive, singleRequest.refProcessor);
|
||||
|
||||
Processor<PsiElement> localProcessor = localProcessor(adapted, progress, true, searcher);
|
||||
Processor<PsiElement> localProcessor = localProcessor(adapted, progress, searcher);
|
||||
|
||||
assert !localProcessors.containsKey(singleRequest) || localProcessors.get(singleRequest) == localProcessor;
|
||||
localProcessors.put(singleRequest, localProcessor);
|
||||
@@ -880,8 +894,9 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
if (single.caseSensitive) options.add(Options.CASE_SENSITIVE_SEARCH);
|
||||
if (shouldProcessInjectedPsi(single.searchScope)) options.add(Options.PROCESS_INJECTED_PSI);
|
||||
|
||||
return processElementsWithWord(adaptProcessor(single, consumer), single.searchScope, single.word, single.searchContext, options,
|
||||
single.containerName);
|
||||
return bulkProcessElementsWithWord(single.searchScope, single.word, single.searchContext, options, single.containerName,
|
||||
adaptProcessor(single, consumer)
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
Reference in New Issue
Block a user