mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
optimization: do not iterate all range markers in the document, use the visible area only (part of IJPL-162151 Scrolling is slow)
GitOrigin-RevId: 4353aab31f4af5e3c50662669f94b823b01d3031
This commit is contained in:
committed by
intellij-monorepo-bot
parent
87f94339b9
commit
5a4227cc44
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<HighlightInfo> errors = highlightErrors();
|
||||
assertNotEmpty(errors);
|
||||
List<HighlightInfo> 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<HintAction> 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<HintAction> hints = ShowAutoImportPass.extractHints(error);
|
||||
String message = error + ": " + i + " hasHints: "+error.hasHint() + "; hints:" + hints + "; visibleRange:" + visibleRange + "; contains: " + visibleRange.contains(error);
|
||||
fail(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +668,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
|
||||
- <init>(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
|
||||
- <init>(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
|
||||
|
||||
@@ -187,14 +187,16 @@ final class AnnotatorRunner {
|
||||
false, 0, injectedInfo.getProblemGroup(), injectedInfo.toolId, injectedInfo.getGutterIconRenderer(), injectedInfo.getGroup(), injectedInfo.unresolvedReference);
|
||||
patched.setHint(injectedInfo.hasHint());
|
||||
|
||||
List<HighlightInfo.IntentionActionDescriptor> quickFixes = new ArrayList<>();
|
||||
injectedInfo.findRegisteredQuickFix((descriptor, quickfixTextRange) -> {
|
||||
List<TextRange> 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);
|
||||
}
|
||||
|
||||
@@ -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<Pair<IntentionActionDescriptor, TextRange>> quickFixActionRanges;
|
||||
/**
|
||||
* @deprecated use {@link #findRegisteredQuickFix(BiFunction)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@Unmodifiable
|
||||
public List<Pair<IntentionActionDescriptor, RangeMarker>> quickFixActionMarkers;
|
||||
|
||||
private @Unmodifiable List<IntentionActionDescriptor> 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,34 +174,19 @@ 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> T findRegisteredQuickFix(@NotNull BiFunction<? super @NotNull IntentionActionDescriptor, ? super @NotNull TextRange, ? extends @Nullable T> predicate) {
|
||||
Set<IntentionActionDescriptor> processed = new HashSet<>();
|
||||
List<Pair<IntentionActionDescriptor, RangeMarker>> markers;
|
||||
List<Pair<IntentionActionDescriptor, TextRange>> 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> T find(@Nullable List<? extends Pair<IntentionActionDescriptor, ? extends Segment>> markers,
|
||||
@NotNull Set<? super IntentionActionDescriptor> processed,
|
||||
@NotNull BiFunction<? super @NotNull IntentionActionDescriptor, ? super @NotNull TextRange, ? extends T> predicate) {
|
||||
if (markers != null) {
|
||||
for (Pair<IntentionActionDescriptor, ? extends Segment> 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;
|
||||
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, range);
|
||||
T result = predicate.apply(descriptor, fixRange);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@@ -500,10 +488,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 +500,9 @@ public class HighlightInfo implements Segment {
|
||||
if (forcedTextAttributesKey != null) {
|
||||
s += "; forcedTextAttributesKey: " + forcedTextAttributesKey;
|
||||
}
|
||||
if (unresolvedReference != null) {
|
||||
s += "; unresolvedReference: " + unresolvedReference;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -633,11 +622,12 @@ public class HighlightInfo implements Segment {
|
||||
|
||||
List<? extends Annotation.QuickFixInfo> 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<IntentionActionDescriptor> 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 +695,23 @@ public class HighlightInfo implements Segment {
|
||||
|
||||
public static class IntentionActionDescriptor {
|
||||
private final IntentionAction myAction;
|
||||
volatile List<? extends IntentionAction> myOptions;
|
||||
volatile List<? extends IntentionAction> 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<? extends IntentionAction> options,
|
||||
@Nullable @Nls String displayName,
|
||||
@@ -721,6 +719,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<? extends IntentionAction> 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 +737,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 +750,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 +902,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 +939,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<? extends IntentionAction> 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<? extends @NotNull IntentionActionDescriptor> 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<IntentionActionDescriptor> 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<RangeMarker> 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<RangeMarker> cache = getRangeMarkerCache();
|
||||
updateQuickFixFields(highlighter.getDocument(), cache, highlighterRange);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Long2ObjectMap<RangeMarker> reuseRangeMarkerCacheIfCreated(long targetRange) {
|
||||
@NotNull
|
||||
private Long2ObjectMap<RangeMarker> getRangeMarkerCache() {
|
||||
Long2ObjectMap<RangeMarker> cache = new Long2ObjectOpenHashMap<>();
|
||||
if (quickFixActionMarkers != null) {
|
||||
for (Pair<IntentionActionDescriptor, RangeMarker> 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 +1011,16 @@ public class HighlightInfo implements Segment {
|
||||
|
||||
public synchronized //TODO rework to lock-free
|
||||
void unregisterQuickFix(@NotNull Condition<? super IntentionAction> 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<IntentionActionDescriptor, TextRange> 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 +1056,11 @@ public class HighlightInfo implements Segment {
|
||||
synchronized void updateQuickFixFields(@NotNull Document document,
|
||||
@NotNull Long2ObjectMap<RangeMarker> 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<IntentionActionDescriptor, TextRange> 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<Pair<IntentionActionDescriptor, RangeMarker>> list = new ArrayList<>(quickFixActionRanges.size());
|
||||
for (Pair<IntentionActionDescriptor, TextRange> 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 +1097,19 @@ public class HighlightInfo implements Segment {
|
||||
boolean isInjectionRelated() {
|
||||
return HighlightInfoUpdaterImpl.isInjectionRelated(toolId);
|
||||
}
|
||||
|
||||
static @NotNull HighlightInfo createComposite(@NotNull List<? extends HighlightInfo> 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<Pair<IntentionActionDescriptor, RangeMarker>> markers = ContainerUtil.emptyList();
|
||||
List<Pair<IntentionActionDescriptor, TextRange>> ranges = ContainerUtil.emptyList();
|
||||
for (HighlightInfo info : infos) {
|
||||
if (info.quickFixActionMarkers != null) {
|
||||
if (markers == ContainerUtil.<Pair<IntentionActionDescriptor, RangeMarker>>emptyList()) markers = new ArrayList<>();
|
||||
markers.addAll(info.quickFixActionMarkers);
|
||||
}
|
||||
if (info.quickFixActionRanges != null) {
|
||||
if (ranges == ContainerUtil.<Pair<IntentionActionDescriptor, TextRange>>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<? extends HighlightInfo> infos) {
|
||||
StringBuilder description = new StringBuilder();
|
||||
|
||||
@@ -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<FixInfo> fixes = new ArrayList<>();
|
||||
private final List<HighlightInfo.IntentionActionDescriptor> 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<? extends IntentionAction> options,
|
||||
@Nls @Nullable String displayName,
|
||||
@Nullable TextRange fixRange,
|
||||
@Nullable HighlightDisplayKey key) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HighlightInfo.@NotNull Builder registerFix(@NotNull IntentionAction action,
|
||||
@Nullable List<? extends IntentionAction> 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<HighlightInfo.IntentionActionDescriptor> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -26,7 +26,7 @@ final class EditorNotificationIntentionMenuContributor implements IntentionMenuC
|
||||
List<IntentionActionWithOptions> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli
|
||||
}
|
||||
resultSink.accept(InjectedLanguageManagerImpl.INJECTION_BACKGROUND_TOOL_ID, injectedPsi, result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<HighlightInfo> createPatchedInfos(@NotNull HighlightInfo info,
|
||||
@NotNull PsiFile injectedPsi,
|
||||
@NotNull DocumentWindow documentWindow,
|
||||
@@ -276,10 +276,13 @@ final class InjectedGeneralHighlightingPass extends ProgressableTextEditorHighli
|
||||
|
||||
info.findRegisteredQuickFix((descriptor, quickfixTextRange) -> {
|
||||
List<TextRange> 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<HighlightInfo.IntentionActionDescriptor> fixes =
|
||||
ContainerUtil.map(editableQF, editableRange ->
|
||||
{
|
||||
TextRange patchedFixRange = documentWindow.injectedToHost(editableRange);
|
||||
return descriptor.withFixRange(patchedFixRange);
|
||||
});
|
||||
patched.registerFixes(fixes);
|
||||
return null;
|
||||
});
|
||||
patched.markFromInjection();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Object> 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<Object> 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,31 +96,20 @@ 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;
|
||||
});
|
||||
}
|
||||
DaemonCodeAnalyzerEx.processHighlights(document, project, HighlightSeverity.ERROR, startOffset, endOffset, info -> {
|
||||
if (!info.isUnresolvedReference()) {
|
||||
return true;
|
||||
}
|
||||
startUnresolvedRefsJob(info, editor, file, visibleRange);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void startUnresolvedRefsJob(@NotNull HighlightInfo info, @NotNull Editor editor, @NotNull PsiFile file,
|
||||
@@ -133,19 +122,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 +150,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<HighlightInfo.IntentionActionDescriptor> 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 +191,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -163,15 +163,15 @@ public class IntentionListStep implements ListPopupStep<IntentionActionWithTextC
|
||||
}
|
||||
for (IntentionAction optionIntention : action.getOptionIntentions()) {
|
||||
intentions.intentionsToShow.add(
|
||||
new HighlightInfo.IntentionActionDescriptor(optionIntention, null, null, getIcon(optionIntention), null, null, null));
|
||||
new HighlightInfo.IntentionActionDescriptor(optionIntention, null, null, getIcon(optionIntention), null, null, null, null));
|
||||
}
|
||||
for (IntentionAction optionFix : action.getOptionErrorFixes()) {
|
||||
intentions.errorFixesToShow.add(
|
||||
new HighlightInfo.IntentionActionDescriptor(optionFix, null, null, getIcon(optionFix), null, null, null));
|
||||
new HighlightInfo.IntentionActionDescriptor(optionFix, null, null, getIcon(optionFix), null, null, null, null));
|
||||
}
|
||||
for (IntentionAction optionFix : action.getOptionInspectionFixes()) {
|
||||
intentions.inspectionFixesToShow.add(
|
||||
new HighlightInfo.IntentionActionDescriptor(optionFix, null, null, getIcon(optionFix), null, null, null));
|
||||
new HighlightInfo.IntentionActionDescriptor(optionFix, null, null, getIcon(optionFix), null, null, null, null));
|
||||
}
|
||||
intentions.setTitle(title);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class SuggestedRefactoringIntentionContributor : IntentionMenuContributor {
|
||||
// we add it into 'errorFixesToShow' if it's not empty to always be at the top of the list
|
||||
// we don't add into it if it's empty to keep the color of the bulb
|
||||
val collectionToAdd = intentions.inspectionFixesToShow
|
||||
collectionToAdd.add(HighlightInfo.IntentionActionDescriptor(intention, null, null, icon, null, null, null))
|
||||
collectionToAdd.add(HighlightInfo.IntentionActionDescriptor(intention, null, null, icon, null, null, null, null))
|
||||
}
|
||||
|
||||
private fun suggestRefactoringIntention(hostFile: PsiFile, offset: Int): MyIntention? {
|
||||
|
||||
@@ -5,5 +5,5 @@ fun foo() {
|
||||
|
||||
fun bar(l: Float) {
|
||||
}
|
||||
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.NumberConversionFix
|
||||
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddConversionCallFix
|
||||
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.NumberConversionFix
|
||||
@@ -5,5 +5,5 @@ fun foo() {
|
||||
|
||||
fun bar(l: Float) {
|
||||
}
|
||||
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.NumberConversionFix
|
||||
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddConversionCallFix
|
||||
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.NumberConversionFix
|
||||
@@ -57,7 +57,7 @@ public class ResourceBundleEditorShowQuickFixesAction extends AnAction {
|
||||
AllIcons.Actions.IntentionBulb,
|
||||
sourceKey,
|
||||
null,
|
||||
null));
|
||||
null, null));
|
||||
isQuickFixListEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user