minor optimization: do not reallocate lists twice on each highlight visitor instantiation

GitOrigin-RevId: 4f6286ee96d8e2a8bbcb8f1731888088e44e64bd
This commit is contained in:
Alexey Kudravtsev
2024-05-02 17:45:13 +02:00
committed by intellij-monorepo-bot
parent 715ddb123a
commit 164eedcb9d
7 changed files with 65 additions and 47 deletions

View File

@@ -797,6 +797,8 @@ com.intellij.codeInsight.daemon.impl.HighlightRangeExtension
- a:isForceHighlightParents(com.intellij.psi.PsiFile):Z
com.intellij.codeInsight.daemon.impl.HighlightVisitor
- com.intellij.openapi.project.PossiblyDumbAware
- sf:ARRAY_FACTORY:com.intellij.util.ArrayFactory
- sf:EMPTY_ARRAY:com.intellij.codeInsight.daemon.impl.HighlightVisitor[]
- sf:EP_HIGHLIGHT_VISITOR:com.intellij.openapi.extensions.ExtensionPointName
- a:analyze(com.intellij.psi.PsiFile,Z,com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder,java.lang.Runnable):Z
- a:clone():com.intellij.codeInsight.daemon.impl.HighlightVisitor

View File

@@ -92,10 +92,10 @@ public /*sealed */class GeneralHighlightingPass extends ProgressableTextEditorHi
// initial guess to show correct progress in the traffic light icon
setProgressLimit(document.getTextLength()/2); // approx number of PSI elements = file length/2
EditorColorsScheme globalScheme = editor != null ? editor.getColorsScheme() : EditorColorsManager.getInstance().getGlobalScheme();
myHighlightVisitorRunner = new HighlightVisitorRunner(myProject, globalScheme);
myHighlightVisitorRunner = new HighlightVisitorRunner(psiFile, globalScheme);
if (!runVisitors) {
// "do not run visitors" here means "reduce the set of visitors down to DefaultHighlightVisitor", because it reports error elements
myHighlightVisitorRunner.setHighlightVisitorProducer(__ -> List.of(new DefaultHighlightVisitor(psiFile.getProject(), highlightErrorElements, false)));
myHighlightVisitorRunner.setHighlightVisitorProducer(__ -> new HighlightVisitor[]{new DefaultHighlightVisitor(psiFile.getProject(), highlightErrorElements, false)});
}
}

View File

@@ -6,9 +6,12 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.PossiblyDumbAware;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayFactory;
import org.jetbrains.annotations.NotNull;
public interface HighlightVisitor extends PossiblyDumbAware {
HighlightVisitor [] EMPTY_ARRAY = new HighlightVisitor[0];
ArrayFactory<HighlightVisitor> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new HighlightVisitor[count];
ExtensionPointName<HighlightVisitor> EP_HIGHLIGHT_VISITOR = new ExtensionPointName<>("com.intellij.highlightVisitor");
boolean suitableForFile(@NotNull PsiFile file);

View File

@@ -19,6 +19,7 @@ import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.serviceContainer.AlreadyDisposedException;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.TriConsumer;
@@ -30,72 +31,72 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntFunction;
import java.util.function.Function;
import java.util.function.Supplier;
class HighlightVisitorRunner {
private final Project myProject;
private final PsiFile myPsiFile;
private @Nullable final TextAttributesScheme myColorsScheme;
HighlightVisitorRunner(@NotNull Project project, @Nullable TextAttributesScheme scheme) {
myProject = project;
HighlightVisitorRunner(@NotNull PsiFile psiFile, @Nullable TextAttributesScheme scheme) {
myPsiFile = psiFile;
myColorsScheme = scheme;
}
void setHighlightVisitorProducer(@NotNull IntFunction<? extends @NotNull List<HighlightVisitor>> highlightVisitorProducer) {
void setHighlightVisitorProducer(@NotNull Function<? super Boolean, ? extends @NotNull HighlightVisitor @NotNull []> highlightVisitorProducer) {
myHighlightVisitorProducer = highlightVisitorProducer;
}
private static final PassRunningAssert HIGHLIGHTING_PERFORMANCE_ASSERT =
new PassRunningAssert("the expensive method should not be called inside the highlighting pass");
private volatile @NotNull IntFunction<? extends @NotNull List<HighlightVisitor>> myHighlightVisitorProducer = this::cloneHighlightVisitors;
private volatile @NotNull Function<? super Boolean, ? extends @NotNull HighlightVisitor @NotNull []> myHighlightVisitorProducer = this::cloneAndFilterHighlightVisitors;
static void assertHighlightingPassNotRunning() {
HIGHLIGHTING_PERFORMANCE_ASSERT.assertPassNotRunning();
}
private static final Key<AtomicInteger> HIGHLIGHT_VISITOR_INSTANCE_COUNT = new Key<>("HIGHLIGHT_VISITOR_INSTANCE_COUNT");
private @NotNull List<HighlightVisitor> cloneHighlightVisitors(int oldCount) {
List<HighlightVisitor> highlightVisitors = HighlightVisitor.EP_HIGHLIGHT_VISITOR.getExtensionList(myProject);
if (oldCount != 0) {
HighlightVisitor[] clones = new HighlightVisitor[highlightVisitors.size()];
for (int i = 0; i < highlightVisitors.size(); i++) {
HighlightVisitor highlightVisitor = highlightVisitors.get(i);
HighlightVisitor cloned = highlightVisitor.clone();
assert cloned.getClass() == highlightVisitor.getClass() : highlightVisitor.getClass()+".clone() must return a copy of "+highlightVisitor.getClass()+"; but got: "+cloned+" of "+cloned.getClass();
clones[i] = cloned;
}
// List.of will copy the array - do not use it
return Arrays.asList(clones);
}
return highlightVisitors;
}
private HighlightVisitor @NotNull [] filterVisitors(@NotNull List<? extends HighlightVisitor> highlightVisitors, @NotNull PsiFile psiFile) {
List<HighlightVisitor> visitors = new ArrayList<>(highlightVisitors.size());
for (HighlightVisitor visitor : DumbService.getInstance(myProject).filterByDumbAwareness(highlightVisitors)) {
if (visitor instanceof RainbowVisitor
&& !RainbowHighlighter.isRainbowEnabledWithInheritance(myColorsScheme, psiFile.getLanguage())) {
private @NotNull HighlightVisitor @NotNull [] cloneAndFilterHighlightVisitors(boolean mustClone) {
Project project = myPsiFile.getProject();
HighlightVisitor[] visitors = HighlightVisitor.EP_HIGHLIGHT_VISITOR.getExtensions(project);
DumbService dumbService = DumbService.getInstance(project);
int o = 0;
HighlightVisitor[] clones = new HighlightVisitor[visitors.length];
for (HighlightVisitor visitor : visitors) {
if (!dumbService.isUsableInCurrentContext(visitor)) {
continue;
}
if (visitor.suitableForFile(psiFile)) {
visitors.add(visitor);
if (visitor instanceof RainbowVisitor
&& !RainbowHighlighter.isRainbowEnabledWithInheritance(myColorsScheme, myPsiFile.getLanguage())) {
continue;
}
if (visitor.suitableForFile(myPsiFile)) {
HighlightVisitor cloned;
if (mustClone) {
cloned = visitor.clone();
assert cloned.getClass() == visitor.getClass() : visitor.getClass()+".clone() must return a copy of "+visitor.getClass()+"; but got: "+cloned+" of "+cloned.getClass();
}
else {
cloned = visitor;
}
clones[o++] = cloned;
}
}
if (visitors.isEmpty()) {
GeneralHighlightingPass.LOG.error("No visitors registered. list=" + highlightVisitors + "; all visitors are:" + HighlightVisitor.EP_HIGHLIGHT_VISITOR.getExtensionList(myProject));
if (o == 0) {
GeneralHighlightingPass.LOG.error("No visitors registered. all visitors:" + Arrays.toString(visitors));
}
return visitors.toArray(new HighlightVisitor[0]);
return ArrayUtil.realloc(clones, o, HighlightVisitor.ARRAY_FACTORY);
}
void createHighlightVisitorsFor(@NotNull PsiFile psiFile, @NotNull Consumer<? super HighlightVisitor[]> consumer) {
AtomicInteger count = myProject.getUserData(HIGHLIGHT_VISITOR_INSTANCE_COUNT);
Project project = myPsiFile.getProject();
AtomicInteger count = project.getUserData(HIGHLIGHT_VISITOR_INSTANCE_COUNT);
if (count == null) {
count = ((UserDataHolderEx)myProject).putUserDataIfAbsent(HIGHLIGHT_VISITOR_INSTANCE_COUNT, new AtomicInteger(0));
count = ((UserDataHolderEx)project).putUserDataIfAbsent(HIGHLIGHT_VISITOR_INSTANCE_COUNT, new AtomicInteger(0));
}
int oldCount = count.getAndIncrement();
HighlightVisitor[] filtered = filterVisitors(myHighlightVisitorProducer.apply(oldCount), psiFile);
// first ever queried HighlightVisitor can be used as is, but all further HighlightVisitors queried while the previous HighlightVisitor haven't finished, should be cloned to avoid reentrancy issues
HighlightVisitor[] filtered = myHighlightVisitorProducer.apply(oldCount != 0);
try {
consumer.consume(filtered);
}

View File

@@ -2646,6 +2646,7 @@ a:com.intellij.openapi.project.DumbService
- a:isDumb():Z
- sf:isDumb(com.intellij.openapi.project.Project):Z
- sf:isDumbAware(java.lang.Object):Z
- f:isUsableInCurrentContext(java.lang.Object):Z
- f:makeDumbAware(javax.swing.JComponent,com.intellij.openapi.Disposable):V
- a:queueTask(com.intellij.openapi.project.DumbModeTask):V
- f:repeatUntilPassesInSmartMode(java.lang.Runnable):V

View File

@@ -416,6 +416,13 @@ abstract class DumbService {
@ApiStatus.Internal
abstract fun unsafeRunWhenSmart(runnable: Runnable)
/**
* return true if [thing] can be used in current dumb context, i.e., either the [thing] is [isDumbAware] or the current context is smart; return false otherwise
*/
fun isUsableInCurrentContext(thing: Any) : Boolean {
return !isDumb || isDumbAware(thing)
}
companion object {
@JvmField
@Topic.ProjectLevel

View File

@@ -49,8 +49,9 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli
private final ProperTextRange myPriorityRange;
private final @NotNull EditorColorsScheme myGlobalScheme;
private final List<HighlightInfo> myHighlights = new ArrayList<>(); // guarded by myHighlights
private final HighlightVisitorRunner myHighlightVisitorRunner;
private final boolean myRunAnnotators;
private final boolean myRunVisitors;
private final boolean myHighlightErrorElements;
private final HighlightInfoUpdater myHighlightInfoUpdater;
InjectedGeneralHighlightingPass(@NotNull PsiFile psiFile,
@@ -67,13 +68,10 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli
myUpdateAll = updateAll;
myPriorityRange = priorityRange;
myGlobalScheme = editor != null ? editor.getColorsScheme() : EditorColorsManager.getInstance().getGlobalScheme();
myHighlightVisitorRunner = new HighlightVisitorRunner(psiFile.getProject(), myGlobalScheme);
myRunAnnotators = runAnnotators;
myRunVisitors = runVisitors;
myHighlightErrorElements = highlightErrorElements;
myHighlightInfoUpdater = highlightInfoUpdater;
if (!runVisitors) {
// "do not run visitors" here means "reduce the set of visitors down to DefaultHighlightVisitor", because it reports error elements
myHighlightVisitorRunner.setHighlightVisitorProducer(__ -> List.of(new DefaultHighlightVisitor(psiFile.getProject(), highlightErrorElements, false)));
}
}
@Override
@@ -197,9 +195,15 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli
List<? extends @NotNull PsiElement> inside = dividedElements.inside();
LongList insideRanges = dividedElements.insideRanges();
BooleanSupplier runnable = () -> {
myHighlightVisitorRunner.createHighlightVisitorsFor(injectedPsi, visitors -> {
HighlightVisitorRunner highlightVisitorRunner = new HighlightVisitorRunner(injectedPsi, myGlobalScheme);
if (!myRunVisitors) {
// "do not run visitors" here means "reduce the set of visitors down to DefaultHighlightVisitor", because it reports error elements
highlightVisitorRunner.setHighlightVisitorProducer(__ -> new HighlightVisitor[]{ new DefaultHighlightVisitor(myProject, myHighlightErrorElements, false) });
}
highlightVisitorRunner.createHighlightVisitorsFor(injectedPsi, visitors -> {
int chunkSize = Math.max(1, inside.size() / 100); // one percent precision is enough
myHighlightVisitorRunner.runVisitors(injectedPsi, injectedPsi.getTextRange(), inside,
highlightVisitorRunner.runVisitors(injectedPsi, injectedPsi.getTextRange(), inside,
insideRanges, List.of(), LongList.of(), visitors, false, chunkSize, true,
() -> createInfoHolder(injectedPsi), (toolId, psiElement, infos) -> {
// convert injected infos to host