From 6bea9cfe0477c662f90de885f3a76bc51a260e27 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 14 Oct 2024 15:20:15 +0200 Subject: [PATCH] optimization: do not iterate all range markers in the document, use the visible area only (part of IJPL-162151 Scrolling is slow) GitOrigin-RevId: e0fbaf8757364a3ad0e823facee0def50cd7f49c --- .../quickfix/JavaIntentionsOrderProvider.kt | 2 +- .../daemon/impl/ImportHelperTest.java | 23 +- .../analysis-impl/api-dump-unreviewed.txt | 1 + .../daemon/impl/AnnotatorRunner.java | 4 +- .../daemon/impl/HighlightInfo.java | 269 +++++++++--------- .../daemon/impl/HighlightInfoB.java | 20 +- .../daemon/impl/HighlightingSessionImpl.java | 6 +- .../impl/CleanupIntentionMenuContributor.java | 2 +- ...howInspectionIntentionMenuContributor.java | 2 +- ...rNotificationIntentionMenuContributor.java | 2 +- .../impl/InjectedGeneralHighlightingPass.java | 13 +- .../daemon/impl/ShowIntentionsPass.java | 5 +- ...nresolvedReferenceQuickFixUpdaterImpl.java | 141 ++++----- ...veHintsTogglingIntentionMenuContributor.kt | 4 +- .../intention/impl/CachedIntentions.java | 2 +- .../intention/impl/IntentionListStep.java | 6 +- ...uggestedRefactoringIntentionContributor.kt | 2 +- .../convertBinaryExpression.kt | 2 +- .../convertBinaryExpression.kt.after | 2 +- ...ourceBundleEditorShowQuickFixesAction.java | 2 +- 20 files changed, 257 insertions(+), 253 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/JavaIntentionsOrderProvider.kt b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/JavaIntentionsOrderProvider.kt index 188cb7b3a86b..dd79f4b422c0 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/JavaIntentionsOrderProvider.kt +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/JavaIntentionsOrderProvider.kt @@ -17,7 +17,7 @@ class JavaIntentionsOrderProvider : IntentionsOrderProvider { } private fun isCompilationFix(context: CachedIntentions, intention: IntentionActionWithTextCaching): Boolean { - if (intention.fixRange?.contains(context.offset) == false) return false + if (intention.fixRange?.containsOffset(context.offset) == false) return false val isInspectionHighlighting = context.highlightInfoType?.isInspectionHighlightInfoType == true return context.errorFixes.contains(intention) && !isInspectionHighlighting } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/ImportHelperTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/ImportHelperTest.java index 525994e825d5..671ecfbdd49c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/ImportHelperTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/ImportHelperTest.java @@ -29,6 +29,7 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.util.PingProgress; import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; @@ -57,6 +58,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; @@ -629,18 +631,23 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase { UIUtil.dispatchAllInvocationEvents(); CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = false; DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); - @NotNull Editor editor = getEditor(); + Editor editor = getEditor(); TextRange visibleRange = editor.calculateVisibleRange(); assertTrue(visibleRange.toString(), visibleRange.getStartOffset() > 5000 && visibleRange.getEndOffset() < 10_000); // sanity check that visible range has been indeed changed - List errors = highlightErrors(); - assertNotEmpty(errors); + List errors = ContainerUtil.sorted(highlightErrors(), Segment.BY_START_OFFSET_THEN_END_OFFSET); + assertSize(1000, errors); UnresolvedReferenceQuickFixUpdaterImpl updater = (UnresolvedReferenceQuickFixUpdaterImpl)UnresolvedReferenceQuickFixUpdater.getInstance(getProject()); - for (HighlightInfo error : errors) { - updater.waitForBackgroundJobIfStartedInTests(error); - List hints = ShowAutoImportPass.extractHints(error); - String message = error + " hasHints: " + error.hasHint() + "; hints:" + hints + "; visibleRange:" + visibleRange + "; contains: " + visibleRange.contains(error); - assertEquals(message, error.hasHint(), visibleRange.contains(error)); + for (int i = 0; i < errors.size(); i++) { + HighlightInfo error = errors.get(i); + if (visibleRange.contains(error)) { // we care only for visible errors; invisible ones may or may not be computed + updater.waitForBackgroundJobIfStartedInTests(error, 60, TimeUnit.SECONDS); + if (!error.hasHint()) { + List hints = ShowAutoImportPass.extractHints(error); + String message = error + ": " + i + " hasHints: "+error.hasHint() + "; hints:" + hints + "; visibleRange:" + visibleRange + "; contains: " + visibleRange.contains(error); + fail(message); + } + } } } } diff --git a/platform/analysis-impl/api-dump-unreviewed.txt b/platform/analysis-impl/api-dump-unreviewed.txt index 673cbb227632..715385f86ee4 100644 --- a/platform/analysis-impl/api-dump-unreviewed.txt +++ b/platform/analysis-impl/api-dump-unreviewed.txt @@ -656,6 +656,7 @@ com.intellij.codeInsight.daemon.impl.HighlightInfo$Builder - a:unescapedToolTip(java.lang.String):com.intellij.codeInsight.daemon.impl.HighlightInfo$Builder c:com.intellij.codeInsight.daemon.impl.HighlightInfo$IntentionActionDescriptor - (com.intellij.codeInsight.intention.IntentionAction,java.util.List,java.lang.String,javax.swing.Icon,com.intellij.codeInsight.daemon.HighlightDisplayKey,com.intellij.lang.annotation.ProblemGroup,com.intellij.lang.annotation.HighlightSeverity):V +- (com.intellij.codeInsight.intention.IntentionAction,java.util.List,java.lang.String,javax.swing.Icon,com.intellij.codeInsight.daemon.HighlightDisplayKey,com.intellij.lang.annotation.ProblemGroup,com.intellij.lang.annotation.HighlightSeverity,com.intellij.openapi.util.Segment):V - equals(java.lang.Object):Z - getAction():com.intellij.codeInsight.intention.IntentionAction - getDisplayName():java.lang.String diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java index 1e44bb9e2152..b5b01f5bc3c5 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java @@ -187,14 +187,16 @@ final class AnnotatorRunner { false, 0, injectedInfo.getProblemGroup(), injectedInfo.toolId, injectedInfo.getGutterIconRenderer(), injectedInfo.getGroup(), injectedInfo.unresolvedReference); patched.setHint(injectedInfo.hasHint()); + List quickFixes = new ArrayList<>(); injectedInfo.findRegisteredQuickFix((descriptor, quickfixTextRange) -> { List editableQF = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, quickfixTextRange); for (TextRange editableRange : editableQF) { TextRange hostEditableRange = documentWindow.injectedToHost(editableRange); - patched.registerFix(descriptor.getAction(), descriptor.myOptions, descriptor.getDisplayName(), hostEditableRange, descriptor.myKey); + quickFixes.add(descriptor.withFixRange(hostEditableRange)); } return null; }); + patched.registerFixes(quickFixes); patched.markFromInjection(); outHostInfos.add(patched); } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java index 31a4dd197890..46c9ee5a3bcc 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java @@ -1,4 +1,4 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/* Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. */ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.Pass; @@ -7,6 +7,7 @@ import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.actions.DisableHighlightingIntentionAction; import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption; import com.intellij.codeInsight.intention.*; +import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; import com.intellij.codeInspection.*; import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; import com.intellij.codeInspection.ex.InspectionToolWrapper; @@ -56,7 +57,6 @@ import static com.intellij.openapi.util.NlsContexts.Tooltip; @ApiStatus.NonExtendable public class HighlightInfo implements Segment { private static final Logger LOG = Logger.getInstance(HighlightInfo.class); - /** * Short name of the {@link com.intellij.codeInsight.daemon.impl.HighlightVisitorBasedInspection} tool, which needs to be treated differently from other inspections: * it doesn't have "disable" or "suppress" quickfixes @@ -70,7 +70,7 @@ public class HighlightInfo implements Segment { private static final byte AFTER_END_OF_LINE_MASK = 0x4; private static final byte FILE_LEVEL_ANNOTATION_MASK = 0x8; private static final byte NEEDS_UPDATE_ON_TYPING_MASK = 0x10; - /** true if this HighlightInfo was created as an error for some unresolved reference, so there likely will be some "Import" quickfixes after {@link com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider} being asked about em */ + /** true if this HighlightInfo was created as an error for some unresolved reference, so there likely will be some "Import" quickfixes after {@link UnresolvedReferenceQuickFixProvider} being asked about em */ private static final byte UNRESOLVED_REFERENCE_QUICK_FIXES_COMPUTED_MASK = 0x20; @MagicConstant(intValues = {HAS_HINT_MASK, FROM_INJECTION_MASK, AFTER_END_OF_LINE_MASK, FILE_LEVEL_ANNOTATION_MASK, NEEDS_UPDATE_ON_TYPING_MASK, UNRESOLVED_REFERENCE_QUICK_FIXES_COMPUTED_MASK}) @@ -87,13 +87,17 @@ public class HighlightInfo implements Segment { * @deprecated use {@link #findRegisteredQuickFix(BiFunction)} instead */ @Deprecated + @Unmodifiable public List> quickFixActionRanges; /** * @deprecated use {@link #findRegisteredQuickFix(BiFunction)} instead */ @Deprecated + @Unmodifiable public List> quickFixActionMarkers; + private @Unmodifiable List myIntentionActionDescriptors = List.of(); + private final @DetailedDescription String description; private final @Tooltip String toolTip; private final @NotNull HighlightSeverity severity; @@ -105,9 +109,9 @@ public class HighlightInfo implements Segment { * Quick fix text range: the range within which the Alt-Enter should open the quick fix popup. * Might be bigger than (getStartOffset(), getEndOffset()) when it's deemed more usable, * e.g., when "import class" fix wanted to be Alt-Entered from everywhere at the same line. - * */ private long fixRange; + private @Nullable("null means it's the same as highlighter") RangeMarker fixMarker; /** * @see FlagConstant for allowed values */ @@ -117,10 +121,9 @@ public class HighlightInfo implements Segment { private @Nullable Object fileLevelComponentsStorage; - private @Nullable("null means it the same as highlighter") RangeMarker fixMarker; private volatile RangeHighlighterEx highlighter; /** - * in case this HighlightInfo is created to highlight unresolved reference, store this reference here to be able to call {@link com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider} later + * in case this HighlightInfo is created to highlight unresolved reference, store this reference here to be able to call {@link UnresolvedReferenceQuickFixProvider} later */ final PsiReference unresolvedReference; @@ -171,37 +174,20 @@ public class HighlightInfo implements Segment { } /** - * Find the quickfix (among ones added by {@link #registerFix}) selected by returning non-null value from the {@code predicate} + * Find the quickfix (among ones added by {@link #registerFixes}) selected by returning non-null value from the {@code predicate} * and return that value, or null if the quickfix was not found. */ - public T findRegisteredQuickFix(@NotNull BiFunction predicate) { + public synchronized T findRegisteredQuickFix(@NotNull BiFunction predicate) { Set processed = new HashSet<>(); - List> markers; - List> ranges; - synchronized (this) { - markers = quickFixActionMarkers; - ranges = quickFixActionRanges; - } - // prefer range markers as having more actual offsets - T result = find(markers, processed, predicate); - if (result != null) return result; - return find(ranges, processed, predicate); - } - - private static @Nullable T find(@Nullable List> markers, - @NotNull Set processed, - @NotNull BiFunction predicate) { - if (markers != null) { - for (Pair pair : markers) { - Segment segment = pair.second; - TextRange range = segment instanceof RangeMarker ? ((RangeMarker)segment).isValid() ? ((RangeMarker)segment).getTextRange() : null : (TextRange)segment; - if (range == null) continue; - IntentionActionDescriptor descriptor = pair.first; - if (!processed.add(descriptor)) continue; - T result = predicate.apply(descriptor, range); - if (result != null) { - return result; - } + for (IntentionActionDescriptor descriptor : myIntentionActionDescriptors) { + TextRange fixRange = descriptor.getFixRange(); + if (fixRange == null) { + fixRange = TextRange.create(this); + } + if (!processed.add(descriptor)) continue; + T result = predicate.apply(descriptor, fixRange); + if (result != null) { + return result; } } return null; @@ -500,10 +486,8 @@ public class HighlightInfo implements Segment { if (getDescription() != null) s += ", description='" + getDescription() + "'"; s += "; severity=" + getSeverity(); synchronized (this) { - if (quickFixActionRanges != null) { - s += "; quickFixes: " + StringUtil.join( - quickFixActionRanges, q -> ReportingClassSubstitutor.getClassToReport(q.getFirst().myAction).getName(), ", "); - } + s += "; quickFixes: " + StringUtil.join( + myIntentionActionDescriptors, q -> ReportingClassSubstitutor.getClassToReport(q.myAction).getName(), ", "); } if (gutterIconRenderer != null) { s += "; gutter: " + gutterIconRenderer; @@ -514,6 +498,9 @@ public class HighlightInfo implements Segment { if (forcedTextAttributesKey != null) { s += "; forcedTextAttributesKey: " + forcedTextAttributesKey; } + if (unresolvedReference != null) { + s += "; unresolvedReference: " + unresolvedReference; + } return s; } @@ -633,11 +620,12 @@ public class HighlightInfo implements Segment { List fixes = batchMode ? annotation.getBatchFixes() : annotation.getQuickFixes(); if (fixes != null) { - for (Annotation.QuickFixInfo quickFixInfo : fixes) { - TextRange range = quickFixInfo.textRange; - HighlightDisplayKey k = quickFixInfo.key != null ? quickFixInfo.key : HighlightDisplayKey.find(ANNOTATOR_INSPECTION_SHORT_NAME); - info.registerFix(quickFixInfo.quickFix, null, HighlightDisplayKey.getDisplayNameByKey(k), range, k); - } + List qfrs = ContainerUtil.map(fixes, af -> { + TextRange range = af.textRange; + HighlightDisplayKey k = af.key != null ? af.key : HighlightDisplayKey.find(ANNOTATOR_INSPECTION_SHORT_NAME); + return new IntentionActionDescriptor(af.quickFix, null, HighlightDisplayKey.getDisplayNameByKey(k), null, k, annotation.getProblemGroup(), info.getSeverity(), range); + }); + info.registerFixes(qfrs); } return info; @@ -705,15 +693,23 @@ public class HighlightInfo implements Segment { public static class IntentionActionDescriptor { private final IntentionAction myAction; - volatile List myOptions; + volatile List myOptions; // null means not initialized yet final @Nullable HighlightDisplayKey myKey; private final ProblemGroup myProblemGroup; private final HighlightSeverity mySeverity; private final @Nls String myDisplayName; private final Icon myIcon; private Boolean myCanCleanup; - private TextRange myFixRange; + /** + * either {@link TextRange} (when the info is just created) or {@link RangeMarker} (when the info is bound to the document) + * maybe null or empty, in which case it's considered to be equal to the info's range + */ + private Segment myFixRange; + /** + * @deprecated use {@link #IntentionActionDescriptor(IntentionAction, List, String, Icon, HighlightDisplayKey, ProblemGroup, HighlightSeverity, Segment)} + */ + @Deprecated public IntentionActionDescriptor(@NotNull IntentionAction action, @Nullable List options, @Nullable @Nls String displayName, @@ -721,6 +717,17 @@ public class HighlightInfo implements Segment { @Nullable HighlightDisplayKey key, @Nullable ProblemGroup problemGroup, @Nullable HighlightSeverity severity) { + this(action, options, displayName, icon, key, problemGroup, severity, null); + } + + public IntentionActionDescriptor(@NotNull IntentionAction action, + @Nullable List options, + @Nullable @Nls String displayName, + @Nullable Icon icon, + @Nullable HighlightDisplayKey key, + @Nullable ProblemGroup problemGroup, + @Nullable HighlightSeverity severity, + @Nullable Segment fixRange) { myAction = action; myOptions = options; myDisplayName = displayName; @@ -728,9 +735,11 @@ public class HighlightInfo implements Segment { myKey = key; myProblemGroup = problemGroup; mySeverity = severity; + myFixRange = fixRange; } - @Nullable IntentionActionDescriptor copyWithEmptyAction() { + @Nullable + IntentionActionDescriptor withEmptyAction() { if (myKey == null || myKey.getID().equals(ANNOTATOR_INSPECTION_SHORT_NAME)) { // No need to show "Inspection 'Annotator' options" quick fix, it wouldn't be actionable. return null; @@ -739,7 +748,11 @@ public class HighlightInfo implements Segment { String displayName = HighlightDisplayKey.getDisplayNameByKey(myKey); if (displayName == null) return null; return new IntentionActionDescriptor(new EmptyIntentionAction(displayName), myOptions, myDisplayName, myIcon, - myKey, myProblemGroup, mySeverity); + myKey, myProblemGroup, mySeverity, myFixRange); + } + @NotNull + IntentionActionDescriptor withFixRange(@NotNull TextRange fixRange) { + return new IntentionActionDescriptor(myAction, myOptions, myDisplayName, myIcon, myKey, myProblemGroup, mySeverity, fixRange); } public @NotNull IntentionAction getAction() { @@ -887,11 +900,8 @@ public class HighlightInfo implements Segment { * Used to check intention's availability at given offset */ public TextRange getFixRange() { - return myFixRange; - } - - void setFixRange(@NotNull TextRange fixRange) { - myFixRange = fixRange; + Segment range = myFixRange; + return range instanceof TextRange tr ? tr : range instanceof RangeMarker marker ? marker.getTextRange() : null; } } @@ -927,61 +937,71 @@ public class HighlightInfo implements Segment { /** * @deprecated Use {@link Builder#registerFix(IntentionAction, List, String, TextRange, HighlightDisplayKey)} instead + * Invoking this method might lead to disappearing/flickering quick fixes, due to inherent data races because of the unrestricted call context. */ @Deprecated - public synchronized // synchronized to avoid concurrent access to quickFix* fields; TODO rework to lock-free - void registerFix(@Nullable IntentionAction action, + public + void registerFix(@NotNull IntentionAction action, @Nullable List options, @Nullable @Nls String displayName, @Nullable TextRange fixRange, @Nullable HighlightDisplayKey key) { - if (action == null) return; - if (fixRange == null) fixRange = new TextRange(getActualStartOffset(), getActualEndOffset()); - if (quickFixActionRanges == null) { - quickFixActionRanges = ContainerUtil.createLockFreeCopyOnWriteList(); + registerFixes(List.of(new IntentionActionDescriptor(action, options, displayName, null, key, myProblemGroup, getSeverity(), fixRange))); + } + + synchronized // synchronized to avoid concurrent access to quickFix* fields; TODO rework to lock-free + void registerFixes(@NotNull List fixes) { + if (fixes.isEmpty()) { + return; } - IntentionActionDescriptor desc = - new IntentionActionDescriptor(action, options, displayName, null, key, getProblemGroup(), getSeverity()); - quickFixActionRanges.add(Pair.create(desc, fixRange)); - if (fixMarker != null && fixMarker.isValid()) { - this.fixRange = TextRangeScalarUtil.toScalarRange(fixMarker); - } - else { - RangeMarker fixMarker = this.fixMarker; - RangeHighlighterEx highlighter = this.highlighter; - Document document = fixMarker != null ? fixMarker.getDocument() : - highlighter != null ? highlighter.getDocument() : - quickFixActionMarkers != null && !quickFixActionMarkers.isEmpty() && quickFixActionMarkers.get(0).getSecond() != null ? quickFixActionMarkers.get(0).getSecond().getDocument() : - null; - if (document != null) { - // coerce fixRange inside document - int newEnd = Math.min(document.getTextLength(), TextRangeScalarUtil.endOffset(this.fixRange)); - int newStart = Math.min(newEnd, TextRangeScalarUtil.startOffset(this.fixRange)); - this.fixRange = TextRangeScalarUtil.toScalarRange(newStart, newEnd); + Document document = null; + List result = new ArrayList<>(myIntentionActionDescriptors.size() + fixes.size()); + result.addAll(myIntentionActionDescriptors); + for (IntentionActionDescriptor descriptor : fixes) { + if (descriptor.myAction instanceof HintAction) { + setHint(true); } + if (descriptor.myFixRange instanceof RangeMarker marker) { + document = marker.getDocument(); + } + TextRange fixRange = descriptor.getFixRange(); + if (fixRange == null) { + fixRange = TextRange.create(this); + descriptor = descriptor.withFixRange(fixRange); + } + result.add(descriptor); + this.fixRange = TextRangeScalarUtil.union(this.fixRange, TextRangeScalarUtil.toScalarRange(fixRange)); } - this.fixRange = TextRangeScalarUtil.union(this.fixRange, TextRangeScalarUtil.toScalarRange(fixRange)); - if (action instanceof HintAction) { - setHint(true); + myIntentionActionDescriptors = List.copyOf(result); + RangeHighlighterEx highlighter = this.highlighter; + if (document == null) { + RangeMarker fixMarker = this.fixMarker; + document = fixMarker != null ? fixMarker.getDocument() : + highlighter != null ? highlighter.getDocument() : + null; } - RangeHighlighterEx myHighlighter = highlighter; - if (myHighlighter != null && myHighlighter.isValid()) { - // highlighter already has been created, we need to update quickFixActionMarkers - long highlighterRange = TextRangeScalarUtil.toScalarRange(myHighlighter); - Long2ObjectMap cache = reuseRangeMarkerCacheIfCreated(highlighterRange); - updateQuickFixFields(myHighlighter.getDocument(), cache, highlighterRange); + if (document != null) { + // coerce fixRange inside document + int newEnd = Math.min(document.getTextLength(), TextRangeScalarUtil.endOffset(this.fixRange)); + int newStart = Math.min(newEnd, TextRangeScalarUtil.startOffset(this.fixRange)); + this.fixRange = TextRangeScalarUtil.toScalarRange(newStart, newEnd); + } + if (highlighter != null && highlighter.isValid()) { + //highlighter already has been created, we need to update quickFixActionMarkers + long highlighterRange = TextRangeScalarUtil.toScalarRange(highlighter); + Long2ObjectMap cache = getRangeMarkerCache(); + updateQuickFixFields(highlighter.getDocument(), cache, highlighterRange); } } - private @NotNull Long2ObjectMap reuseRangeMarkerCacheIfCreated(long targetRange) { + @NotNull + private Long2ObjectMap getRangeMarkerCache() { Long2ObjectMap cache = new Long2ObjectOpenHashMap<>(); - if (quickFixActionMarkers != null) { - for (Pair pair : quickFixActionMarkers) { - RangeMarker marker = pair.getSecond(); - if (marker.isValid() && TextRangeScalarUtil.toScalarRange(marker) == targetRange) { - cache.put(targetRange, marker); - break; - } + for (IntentionActionDescriptor pair : myIntentionActionDescriptors) { + Segment fixRange = pair.myFixRange; + if (fixRange instanceof RangeMarker marker && marker.isValid()) { + cache.put(TextRangeScalarUtil.toScalarRange(marker), marker); + break; } } return cache; @@ -989,20 +1009,16 @@ public class HighlightInfo implements Segment { public synchronized //TODO rework to lock-free void unregisterQuickFix(@NotNull Condition condition) { - if (quickFixActionRanges != null) { - quickFixActionRanges.removeIf(pair -> condition.value(pair.first.getAction())); - } - if (quickFixActionMarkers != null) { - quickFixActionMarkers.removeIf(pair -> condition.value(pair.first.getAction())); - } + myIntentionActionDescriptors = List.copyOf(ContainerUtil.filter(myIntentionActionDescriptors, descriptor -> !condition.value(descriptor.getAction()))); } public synchronized IntentionAction getSameFamilyFix(@NotNull IntentionActionWithFixAllOption action) { - if (quickFixActionRanges == null) return null; - for (Pair range : quickFixActionRanges) { - IntentionAction other = IntentionActionDelegate.unwrap(range.first.myAction); + for (IntentionActionDescriptor descriptor : myIntentionActionDescriptors) { + IntentionAction other = IntentionActionDelegate.unwrap(descriptor.getAction()); if (other instanceof IntentionActionWithFixAllOption && - action.belongsToMyFamily((IntentionActionWithFixAllOption)other)) return other; + action.belongsToMyFamily((IntentionActionWithFixAllOption)other)) { + return other; + } } return null; } @@ -1038,25 +1054,11 @@ public class HighlightInfo implements Segment { synchronized void updateQuickFixFields(@NotNull Document document, @NotNull Long2ObjectMap range2markerCache, long finalHighlighterRange) { - if (quickFixActionMarkers != null && quickFixActionRanges != null && quickFixActionRanges.size() == quickFixActionMarkers.size() +1) { - // markers already created, make quickFixRanges <-> quickFixMarkers consistent by adding new marker to the quickFixMarkers if necessary - Pair last = ContainerUtil.getLastItem(quickFixActionRanges); - Segment textRange = last.getSecond(); - if (textRange.getEndOffset() <= document.getTextLength()) { - RangeMarker marker = getOrCreate(document, range2markerCache, TextRangeScalarUtil.toScalarRange(textRange)); - quickFixActionMarkers.add(Pair.create(last.getFirst(), marker)); + for (IntentionActionDescriptor descriptor : myIntentionActionDescriptors) { + Segment fixRange = descriptor.myFixRange; + if (fixRange instanceof TextRange tr) { + descriptor.myFixRange = getOrCreate(document, range2markerCache, TextRangeScalarUtil.toScalarRange(tr)); } - return; - } - if (quickFixActionRanges != null && quickFixActionMarkers == null) { - List> list = new ArrayList<>(quickFixActionRanges.size()); - for (Pair pair : quickFixActionRanges) { - TextRange textRange = pair.second; - if (textRange.getEndOffset() > document.getTextLength()) continue; - RangeMarker marker = getOrCreate(document, range2markerCache, TextRangeScalarUtil.toScalarRange(textRange)); - list.add(Pair.create(pair.first, marker)); - } - quickFixActionMarkers = ContainerUtil.createLockFreeCopyOnWriteList(list); } if (fixRange == finalHighlighterRange) { fixMarker = null; // null means it the same as highlighter's range @@ -1093,32 +1095,19 @@ public class HighlightInfo implements Segment { boolean isInjectionRelated() { return HighlightInfoUpdaterImpl.isInjectionRelated(toolId); } + static @NotNull HighlightInfo createComposite(@NotNull List infos) { - // derive composite's offsets from an result with tooltip, if present + // derive composite's offsets from an info with tooltip, if present HighlightInfo anchorInfo = ContainerUtil.find(infos, info -> info.getToolTip() != null); if (anchorInfo == null) anchorInfo = infos.get(0); - HighlightInfo result = new HighlightInfo(null, null, anchorInfo.type, anchorInfo.startOffset, anchorInfo.endOffset, + HighlightInfo info = new HighlightInfo(null, null, anchorInfo.type, anchorInfo.startOffset, anchorInfo.endOffset, createCompositeDescription(infos), createCompositeTooltip(infos), anchorInfo.type.getSeverity(null), false, null, false, 0, anchorInfo.getProblemGroup(), null, anchorInfo.getGutterIconRenderer(), anchorInfo.getGroup(), null); - result.highlighter = anchorInfo.getHighlighter(); - //result.myIntentionActionDescriptors = ContainerUtil.concat(ContainerUtil.map(infos, i->((HighlightInfo)i).myIntentionActionDescriptors)); - List> markers = ContainerUtil.emptyList(); - List> ranges = ContainerUtil.emptyList(); - for (HighlightInfo info : infos) { - if (info.quickFixActionMarkers != null) { - if (markers == ContainerUtil.>emptyList()) markers = new ArrayList<>(); - markers.addAll(info.quickFixActionMarkers); - } - if (info.quickFixActionRanges != null) { - if (ranges == ContainerUtil.>emptyList()) ranges = new ArrayList<>(); - ranges.addAll(info.quickFixActionRanges); - } - } - result.quickFixActionMarkers = ContainerUtil.createLockFreeCopyOnWriteList(markers); - result.quickFixActionRanges = ContainerUtil.createLockFreeCopyOnWriteList(ranges); - return result; + info.highlighter = anchorInfo.getHighlighter(); + info.myIntentionActionDescriptors = ContainerUtil.concat(ContainerUtil.map(infos, i->((HighlightInfo)i).myIntentionActionDescriptors)); + return info; } private static @Nullable @NlsSafe String createCompositeDescription(@NotNull List infos) { StringBuilder description = new StringBuilder(); diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoB.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoB.java index 8e8aae6f942f..f1a3f2df5e34 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoB.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoB.java @@ -16,6 +16,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,7 @@ final class HighlightInfoB implements HighlightInfo.Builder { private Object toolId; private PsiElement psiElement; private int group; - private final List fixes = new ArrayList<>(); + private final List fixes = new ArrayList<>(); private boolean created; private PsiReference unresolvedReference; @@ -222,13 +223,6 @@ final class HighlightInfoB implements HighlightInfo.Builder { unresolvedReference = ref; } - private record FixInfo(@NotNull IntentionAction action, - @Nullable List options, - @Nls @Nullable String displayName, - @Nullable TextRange fixRange, - @Nullable HighlightDisplayKey key) { - } - @Override public HighlightInfo.@NotNull Builder registerFix(@NotNull IntentionAction action, @Nullable List options, @@ -236,7 +230,8 @@ final class HighlightInfoB implements HighlightInfo.Builder { @Nullable TextRange fixRange, @Nullable HighlightDisplayKey key) { assertNotCreated(); - fixes.add(new FixInfo(action, options, displayName, fixRange, key)); + // both problemGroup and severity are null here since they might haven't been set yet; we'll pass actual values later, in createUnconditionally() + fixes.add(new HighlightInfo.IntentionActionDescriptor(action, options, displayName, null, key, null, null, fixRange)); return this; } @@ -262,9 +257,10 @@ final class HighlightInfoB implements HighlightInfo.Builder { escapedToolTip, severity, isAfterEndOfLine, myNeedsUpdateOnTyping, isFileLevelAnnotation, navigationShift, problemGroup, toolId, gutterIconRenderer, group, unresolvedReference); - for (FixInfo fix : fixes) { - info.registerFix(fix.action(), fix.options(), fix.displayName(), fix.fixRange(), fix.key()); - } + // fill IntentionActionDescriptor.problemGroup and IntentionActionDescriptor.severity - they can be null because .registerFix() might have been called before .problemGroup() and .severity() + List iads = ContainerUtil.map(fixes, fixInfo -> new HighlightInfo.IntentionActionDescriptor( + fixInfo.getAction(), fixInfo.myOptions, fixInfo.getDisplayName(), fixInfo.getIcon(), fixInfo.myKey, problemGroup, severity, fixInfo.getFixRange())); + info.registerFixes(iads); return info; } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java index 4ed8fe3e396b..1650c4fae7e6 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java @@ -26,6 +26,7 @@ import com.intellij.openapi.util.TextRangeScalarUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.injected.InjectedFileViewProvider; +import com.intellij.util.ExceptionUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.ThreeState; import com.intellij.util.concurrency.EdtExecutorService; @@ -300,7 +301,10 @@ public final class HighlightingSessionImpl implements HighlightingSession { return "HighlightingSessionImpl: " + "myVisibleRange:"+myVisibleRange+ "; myPsiFile: "+myPsiFile+ " (" + myPsiFile.getClass() + ")"+ - (myIsEssentialHighlightingOnly ? "; essentialHighlightingOnly":""); + (myIsEssentialHighlightingOnly ? "; essentialHighlightingOnly":"") + + (isCanceled() ? "; canceled" : "") + + (myProgressIndicator.isCanceled() ? "indicator: "+ myProgressIndicator : "") + ; } // compute additional stuff in background thread diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CleanupIntentionMenuContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CleanupIntentionMenuContributor.java index 45c0eb789755..81f22027ad64 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CleanupIntentionMenuContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CleanupIntentionMenuContributor.java @@ -29,7 +29,7 @@ public class CleanupIntentionMenuContributor implements IntentionMenuContributor actionDescriptors.add(new HighlightInfo.IntentionActionDescriptor(manager.createCleanupAllIntention(), manager.getCleanupIntentionOptions(), InspectionsBundle.message("action.description.code.cleanup.options"), - null, null, null, null)); + null, null, null, null, null)); return true; } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DoNotShowInspectionIntentionMenuContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DoNotShowInspectionIntentionMenuContributor.java index e29a583b8020..7e945fccce2a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DoNotShowInspectionIntentionMenuContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DoNotShowInspectionIntentionMenuContributor.java @@ -151,7 +151,7 @@ final class DoNotShowInspectionIntentionMenuContributor implements IntentionMenu String displayName = displayNames.get(shortName); HighlightInfo.IntentionActionDescriptor actionDescriptor = new HighlightInfo.IntentionActionDescriptor(intentionAction, null, displayName, null, - key, null, HighlightSeverity.INFORMATION); + key, null, HighlightSeverity.INFORMATION, range); (problemDescriptor.getHighlightType() == ProblemHighlightType.ERROR ? outIntentions.errorFixesToShow : outIntentions.intentionsToShow).add(actionDescriptor); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/EditorNotificationIntentionMenuContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/EditorNotificationIntentionMenuContributor.java index e827ee90c654..57dc20ba24ff 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/EditorNotificationIntentionMenuContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/EditorNotificationIntentionMenuContributor.java @@ -26,7 +26,7 @@ final class EditorNotificationIntentionMenuContributor implements IntentionMenuC List actions = EditorNotifications.getInstance(project).getStoredFileLevelIntentions(fileEditor); for (IntentionActionWithOptions action : actions) { intentions.notificationActionsToShow.add(new HighlightInfo.IntentionActionDescriptor( - action, action.getOptions(), null, null, null, null, null)); + action, action.getOptions(), null, null, null, null, null, null)); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java index c58d65e9f466..0e531f67508a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java @@ -243,7 +243,7 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli } resultSink.accept(InjectedLanguageManagerImpl.INJECTION_BACKGROUND_TOOL_ID, injectedPsi, result); } - + @NotNull private static List createPatchedInfos(@NotNull HighlightInfo info, @NotNull PsiFile injectedPsi, @NotNull DocumentWindow documentWindow, @@ -276,10 +276,13 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli info.findRegisteredQuickFix((descriptor, quickfixTextRange) -> { List editableQF = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, quickfixTextRange); - for (TextRange editableRange : editableQF) { - TextRange hostEditableRange = documentWindow.injectedToHost(editableRange); - patched.registerFix(descriptor.getAction(), descriptor.myOptions, descriptor.getDisplayName(), hostEditableRange, descriptor.myKey); - } + List fixes = + ContainerUtil.map(editableQF, editableRange -> + { + TextRange patchedFixRange = documentWindow.injectedToHost(editableRange); + return descriptor.withFixRange(patchedFixRange); + }); + patched.registerFixes(fixes); return null; }); patched.markFromInjection(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java index 741dad18c3a0..3f7620a7c33d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java @@ -152,7 +152,7 @@ public final class ShowIntentionsPass extends TextEditorHighlightingPass impleme }); if (!hasAvailableAction[0] && unavailableAction[0] != null) { - HighlightInfo.IntentionActionDescriptor emptyActionDescriptor = unavailableAction[0].copyWithEmptyAction(); + HighlightInfo.IntentionActionDescriptor emptyActionDescriptor = unavailableAction[0].withEmptyAction(); if (emptyActionDescriptor != null) { outList.add(emptyActionDescriptor); } @@ -326,7 +326,6 @@ public final class ShowIntentionsPass extends TextEditorHighlightingPass impleme boolean added = false; for (HighlightInfo.IntentionActionDescriptor fix : additionalFixes) { if (!ContainerUtil.exists(fixes, descriptor -> descriptor.getAction().getText().equals(fix.getAction().getText()))) { - fix.setFixRange(info.getFixTextRange()); fixes.add(fix); added = true; } @@ -391,7 +390,7 @@ public final class ShowIntentionsPass extends TextEditorHighlightingPass impleme enableDisableIntentionAction.add(new AssignShortcutToIntentionAction(action)); } HighlightInfo.IntentionActionDescriptor descriptor = - new HighlightInfo.IntentionActionDescriptor(action, enableDisableIntentionAction, null, null, null, null, null); + new HighlightInfo.IntentionActionDescriptor(action, enableDisableIntentionAction, null, null, null, null, null, null); if (!currentFixes.contains(descriptor)) { intentions.intentionsToShow.add(descriptor); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UnresolvedReferenceQuickFixUpdaterImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UnresolvedReferenceQuickFixUpdaterImpl.java index d995b266f076..df7dcb8fc290 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UnresolvedReferenceQuickFixUpdaterImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UnresolvedReferenceQuickFixUpdaterImpl.java @@ -2,8 +2,6 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.TextEditorHighlightingPass; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; @@ -25,13 +23,11 @@ import com.intellij.openapi.util.*; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; -import com.intellij.util.concurrency.AppScheduledExecutorService; import org.jetbrains.annotations.*; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; /** * Manages execution of {@link UnresolvedReferenceQuickFixUpdater#registerQuickFixesLater(PsiReference, HighlightInfo.Builder)} in background. @@ -57,20 +53,24 @@ public final class UnresolvedReferenceQuickFixUpdaterImpl implements UnresolvedR for (HighlightInfo info : infos) { PsiReference reference = info.unresolvedReference; if (reference == null) continue; - if (info.isUnresolvedReferenceQuickFixesComputed()) continue; + Future job; PsiElement refElement = ReadAction.compute(() -> reference.getElement()); - Future job = refElement.getUserData(JOB); - if (job == null) { - CompletableFuture newFuture = new CompletableFuture<>(); - job = ((UserDataHolderEx)refElement).putUserDataIfAbsent(JOB, newFuture); - if (job == newFuture) { - try { - ReadAction.run(() -> registerReferenceFixes(info, editor, file, reference, new ProperTextRange(file.getTextRange()))); - newFuture.complete(null); - } - catch (Throwable t) { - newFuture.completeExceptionally(t); - } + CompletableFuture newFuture = null; + synchronized (info) { + if (info.isUnresolvedReferenceQuickFixesComputed()) continue; + job = refElement.getUserData(JOB); + if (job == null) { + newFuture = new CompletableFuture<>(); + job = ((UserDataHolderEx)refElement).putUserDataIfAbsent(JOB, newFuture); + } + } + if (job == newFuture) { + try { + ReadAction.run(() -> registerReferenceFixes(info, editor, file, reference, new ProperTextRange(file.getTextRange()))); + newFuture.complete(null); + } + catch (Throwable t) { + newFuture.completeExceptionally(t); } } try { @@ -96,30 +96,22 @@ public final class UnresolvedReferenceQuickFixUpdaterImpl implements UnresolvedR public void startComputingNextQuickFixes(@NotNull PsiFile file, @NotNull Editor editor, @NotNull ProperTextRange visibleRange) { ApplicationManager.getApplication().assertIsNonDispatchThread(); ApplicationManager.getApplication().assertReadAccessAllowed(); - int offset = editor.getCaretModel().getOffset(); Project project = file.getProject(); Document document = editor.getDocument(); - AtomicInteger unresolvedInfosProcessed = new AtomicInteger(); + // compute unresolved refs suggestions from the caret to two pages down (in case the user is scrolling down, which is often the case) + int startOffset = Math.max(0, visibleRange.getStartOffset()); + int endOffset = Math.min(document.getTextLength(), visibleRange.getEndOffset()+visibleRange.getLength()); // first, compute quick fixes close to the caret - DaemonCodeAnalyzerEx.processHighlights(document, project, HighlightSeverity.ERROR, offset, - document.getTextLength(), info -> { - if (!info.isUnresolvedReference()) { - return true; - } - startUnresolvedRefsJob(info, editor, file, visibleRange); - // start no more than two jobs - return unresolvedInfosProcessed.incrementAndGet() <= 2; - }); - // then, compute quickfixes inside the entire visible area, to show import hints for all unresolved references in vicinity if enabled - if (DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled() && - DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(file)) { - DaemonCodeAnalyzerEx.processHighlights(document, project, HighlightSeverity.ERROR, visibleRange.getStartOffset(), - visibleRange.getEndOffset(), info -> { - if (info.isUnresolvedReference()) { - startUnresolvedRefsJob(info, editor, file, visibleRange); - } - return true; - }); + List unresolvedInfos = new ArrayList<>(); + DaemonCodeAnalyzerEx.processHighlights(document, project, HighlightSeverity.ERROR, startOffset, endOffset, info -> { + if (!info.isUnresolvedReference()) { + return true; + } + unresolvedInfos.add(info); + return true; + }); + for (HighlightInfo info : unresolvedInfos) { + startUnresolvedRefsJob(info, editor, file, visibleRange); } } @@ -133,19 +125,25 @@ public final class UnresolvedReferenceQuickFixUpdaterImpl implements UnresolvedR PsiReference reference = info.unresolvedReference; if (reference == null) return; PsiElement refElement = reference.getElement(); - Future job = refElement.getUserData(JOB); - if (job != null) { - return; - } - if (info.isUnresolvedReferenceQuickFixesComputed()) return; - job = ForkJoinPool.commonPool().submit(ThreadContext.captureThreadContext(() -> - ((ApplicationImpl)ApplicationManager.getApplication()).executeByImpatientReader( - () -> ProgressIndicatorUtils.runInReadActionWithWriteActionPriority( + synchronized (info) { + Future job = refElement.getUserData(JOB); + if (job != null) { + return; + } + if (!info.isUnresolvedReferenceQuickFixesComputed()) { + job = ForkJoinPool.commonPool().submit(ThreadContext.captureThreadContext(() -> + ((ApplicationImpl)ApplicationManager.getApplication()).executeByImpatientReader( + () -> ProgressIndicatorUtils.runInReadActionWithWriteActionPriority( () -> registerReferenceFixes(info, editor, file, reference, visibleRange), new DaemonProgressIndicator()))), null); - refElement.putUserData(JOB, job); + refElement.putUserData(JOB, job); + } + } } - private void registerReferenceFixes(@NotNull HighlightInfo info, @NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiReference reference, + private void registerReferenceFixes(@NotNull HighlightInfo info, + @NotNull Editor editor, + @NotNull PsiFile file, + @NotNull PsiReference reference, @NotNull ProperTextRange visibleRange) { ApplicationManager.getApplication().assertIsNonDispatchThread(); ApplicationManager.getApplication().assertReadAccessAllowed(); @@ -155,25 +153,30 @@ public final class UnresolvedReferenceQuickFixUpdaterImpl implements UnresolvedR // this will be restarted anyway on smart mode switch return; } - AtomicBoolean changed = new AtomicBoolean(); - try { - UnresolvedReferenceQuickFixProvider.registerReferenceFixes( - reference, new QuickFixActionRegistrarImpl(info) { - @Override - void doRegister(@NotNull IntentionAction action, - @Nls(capitalization = Nls.Capitalization.Sentence) @Nullable String displayName, - @Nullable TextRange fixRange, - @Nullable HighlightDisplayKey key) { - super.doRegister(action, displayName, fixRange, key); - changed.set(true); - } - }); - info.setUnresolvedReferenceQuickFixesComputed(); + List quickfixes = new ArrayList<>(); + UnresolvedReferenceQuickFixProvider.registerReferenceFixes( + reference, new QuickFixActionRegistrarImpl(info) { + @Override + void doRegister(@NotNull IntentionAction action, + @Nls(capitalization = Nls.Capitalization.Sentence) @Nullable String displayName, + @Nullable TextRange fixRange, + @Nullable HighlightDisplayKey key) { + quickfixes.add(new HighlightInfo.IntentionActionDescriptor(action, null, displayName, null, key, info.getProblemGroup(), + info.getSeverity(),fixRange)); + } + }); + synchronized (info) { + try { + if (!info.isUnresolvedReferenceQuickFixesComputed()) { + info.registerFixes(quickfixes); + info.setUnresolvedReferenceQuickFixesComputed(); + } + } + finally { + referenceElement.putUserData(JOB, null); + } } - finally { - referenceElement.putUserData(JOB, null); - } - if (!changed.get() || ApplicationManager.getApplication().isHeadlessEnvironment()) { + if (quickfixes.isEmpty() || ApplicationManager.getApplication().isHeadlessEnvironment()) { return; } TextEditorHighlightingPass showAutoImportPass = new ShowAutoImportPass(file, editor, visibleRange); @@ -191,12 +194,12 @@ public final class UnresolvedReferenceQuickFixUpdaterImpl implements UnresolvedR } @TestOnly - void waitForBackgroundJobIfStartedInTests(@NotNull HighlightInfo info) throws InterruptedException, ExecutionException, TimeoutException { + void waitForBackgroundJobIfStartedInTests(@NotNull HighlightInfo info, int timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { PsiReference reference = info.unresolvedReference; if (reference == null) return; Future job = reference.getElement().getUserData(JOB); if (job != null) { - job.get(60, TimeUnit.SECONDS); + job.get(timeout, unit); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/toggle/DeclarativeHintsTogglingIntentionMenuContributor.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/toggle/DeclarativeHintsTogglingIntentionMenuContributor.kt index 1bd7f10d4ee8..6e0e4d705c46 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/toggle/DeclarativeHintsTogglingIntentionMenuContributor.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/toggle/DeclarativeHintsTogglingIntentionMenuContributor.kt @@ -23,7 +23,7 @@ class DeclarativeHintsTogglingIntentionMenuContributor : IntentionMenuContributo val context = Context.gather(hostFile.project, hostEditor, hostFile) ?: return for (providerInfo in context.providersToToggle) { val action = DeclarativeHintsTogglingIntention(providerInfo.providerId, providerInfo.providerName, providerInfo.providerEnabled) - val descriptor = HighlightInfo.IntentionActionDescriptor(action, mutableListOf(), null, null, null, null, HighlightSeverity.INFORMATION) + val descriptor = HighlightInfo.IntentionActionDescriptor(action, listOf(), null, null, null, null, HighlightSeverity.INFORMATION, null) intentions.intentionsToShow.add(descriptor) } val settings = DeclarativeInlayHintsSettings.getInstance() @@ -46,7 +46,7 @@ class DeclarativeHintsTogglingIntentionMenuContributor : IntentionMenuContributo DeclarativeHintsTogglingOptionIntention.Mode.EnableProviderAndOption } val action = DeclarativeHintsTogglingOptionIntention(optionId, providerId, providersWithOption.providerName, optionInfo.name, mode) - val descriptor = HighlightInfo.IntentionActionDescriptor(action, mutableListOf(), null, null, null, null, HighlightSeverity.INFORMATION) + val descriptor = HighlightInfo.IntentionActionDescriptor(action, listOf(), null, null, null, null, HighlightSeverity.INFORMATION, null) intentions.intentionsToShow.add(descriptor) } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/CachedIntentions.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/CachedIntentions.java index 54081b33117b..09ef372ba812 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/CachedIntentions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/CachedIntentions.java @@ -187,7 +187,7 @@ public final class CachedIntentions implements IntentionContainer { intentionAction.updateFromPresentation(presentation); if (!filter.test(intentionAction)) continue; HighlightInfo.IntentionActionDescriptor descriptor = new HighlightInfo.IntentionActionDescriptor( - intentionAction, Collections.emptyList(), intentionAction.getText(), intentionAction.getIcon(0), null, null, null); + intentionAction, Collections.emptyList(), intentionAction.getText(), intentionAction.getIcon(0), null, null, null, null); descriptors.add(descriptor); hasSeparatorAbove = false; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java index 7b332c503fbb..e0aa0aceac01 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java @@ -163,15 +163,15 @@ public class IntentionListStep implements ListPopupStep