IJ-CR-168801 [command-completion] IDEA-375698 Command completion. Highlight intention's scope

- use special range

GitOrigin-RevId: d3fc41b6361f1d685c743d64a66637bf57bd38ea
This commit is contained in:
Mikhail Pyltsin
2025-07-21 08:50:13 +00:00
committed by intellij-monorepo-bot
parent 6e1e1dbf46
commit b794b331f8
10 changed files with 101 additions and 21 deletions
@@ -726,7 +726,9 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
myFixture.doHighlighting()
myFixture.type(".")
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("flip '=='", ignoreCase = true) })
val item = elements.first { element -> element.lookupString.contains("flip '=='", ignoreCase = true) }
assertEquals(TextRange(33, 37), (item.`as`(CommandCompletionLookupElement::class.java))?.highlighting?.range)
selectItem(item)
myFixture.checkResult("""
class A {
void foo() {
@@ -1,12 +1,14 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.fixes.braces;
import com.intellij.codeInsight.intention.CustomizableIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.modcommand.Presentation;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.IGQuickFixesTestCase;
@@ -49,11 +51,23 @@ public class SingleStatementInBlockFixTest extends IGQuickFixesTestCase {
IntentionAction action = myFixture.findSingleIntention(getMessage("if"));
ModCommandAction mcAction = action.asModCommandAction();
assertNotNull(mcAction);
PsiFile psiFile = myFixture.getFile();
Presentation presentation =
mcAction.getPresentation(ActionContext.from(myFixture.getEditor(), myFixture.getFile()));
mcAction.getPresentation(ActionContext.from(myFixture.getEditor(), psiFile));
assertNotNull(presentation);
assertEquals(List.of(new Presentation.HighlightRange(TextRange.from(44, 1), EditorColors.DELETED_TEXT_ATTRIBUTES),
new Presentation.HighlightRange(TextRange.from(87, 1), EditorColors.DELETED_TEXT_ATTRIBUTES)), presentation.rangesToHighlight());
assertEquals(List.of(new Presentation.HighlightRange(TextRange.from(44, 1), Presentation.HighlightingKind.DELETED_RANGE),
new Presentation.HighlightRange(TextRange.from(87, 1), Presentation.HighlightingKind.DELETED_RANGE)),
presentation.rangesToHighlight());
assertInstanceOf(action, CustomizableIntentionAction.class);
List<CustomizableIntentionAction.RangeToHighlight> ranges =
((CustomizableIntentionAction)action).getRangesToHighlight(myFixture.getEditor(), psiFile);
assertSize(2, ranges);
assertEquals(
List.of(TextRange.from(44, 1), TextRange.from(87, 1)),
List.of(ranges.get(0).getRangeInFile(), ranges.get(1).getRangeInFile()));
assertEquals(
List.of(EditorColors.DELETED_TEXT_ATTRIBUTES, EditorColors.DELETED_TEXT_ATTRIBUTES),
List.of(ranges.get(0).getHighlightKey(), ranges.get(1).getHighlightKey()));
}
@Override
@@ -2281,10 +2281,8 @@ f:com.intellij.modcommand.Presentation$FixAllOption
- name():java.lang.String
f:com.intellij.modcommand.Presentation$HighlightRange
- java.lang.Record
- <init>(com.intellij.openapi.util.TextRange,com.intellij.openapi.editor.colors.TextAttributesKey):V
- f:equals(java.lang.Object):Z
- f:hashCode():I
- highlightKey():com.intellij.openapi.editor.colors.TextAttributesKey
- range():com.intellij.openapi.util.TextRange
a:com.intellij.modcommand.PsiBasedModCommandAction
- com.intellij.modcommand.ModCommandAction
+10
View File
@@ -632,6 +632,16 @@ com.intellij.modcommand.ModTemplateBuilder
- a:field(com.intellij.psi.PsiElement,java.lang.String,java.lang.String,Z):com.intellij.modcommand.ModTemplateBuilder
- a:finishAt(I):com.intellij.modcommand.ModTemplateBuilder
- a:onTemplateFinished(java.util.function.Function):com.intellij.modcommand.ModTemplateBuilder
f:com.intellij.modcommand.Presentation$HighlightRange
- <init>(com.intellij.openapi.util.TextRange,com.intellij.modcommand.Presentation$HighlightingKind):V
- highlightingKind():com.intellij.modcommand.Presentation$HighlightingKind
e:com.intellij.modcommand.Presentation$HighlightingKind
- java.lang.Enum
- sf:AFFECTED_RANGE:com.intellij.modcommand.Presentation$HighlightingKind
- sf:APPLICABLE_TO_RANGE:com.intellij.modcommand.Presentation$HighlightingKind
- sf:DELETED_RANGE:com.intellij.modcommand.Presentation$HighlightingKind
- s:valueOf(java.lang.String):com.intellij.modcommand.Presentation$HighlightingKind
- s:values():com.intellij.modcommand.Presentation$HighlightingKind[]
com.intellij.openapi.actionSystem.ShortcutProvider
- a:getShortcut():com.intellij.openapi.actionSystem.ShortcutSet
com.intellij.openapi.application.WriteActionAware
@@ -115,6 +115,7 @@ public interface CustomizableIntentionAction extends IntentionAction {
return psi.getContainingFile();
}
@NotNull
public TextAttributesKey getHighlightKey() {
return highlightKey;
}
@@ -15,6 +15,8 @@ import com.intellij.openapi.command.undo.UndoUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diagnostic.ReportingClassSubstitutor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.PossiblyDumbAware;
@@ -282,7 +284,10 @@ public final class QuickFixWrapper implements IntentionAction, PriorityAction, C
List<RangeToHighlight> highlight = myFix.getRangesToHighlight(context.project(), myDescriptor);
if (!highlight.isEmpty()) {
Presentation.HighlightRange[] ranges = ContainerUtil.map2Array(highlight, Presentation.HighlightRange.class,
r -> new Presentation.HighlightRange(r.getRangeInFile(), r.getHighlightKey()));
r -> {
return new Presentation.HighlightRange(r.getRangeInFile(),
convertToHighlightingType(r.getHighlightKey()));
});
presentation = presentation.withHighlighting(ranges);
}
if (myFix instanceof Iconable iconable) {
@@ -294,6 +299,17 @@ public final class QuickFixWrapper implements IntentionAction, PriorityAction, C
return presentation;
}
private static @NotNull Presentation.HighlightingKind convertToHighlightingType(@NotNull TextAttributesKey key) {
if (key == EditorColors.SEARCH_RESULT_ATTRIBUTES) {
return Presentation.HighlightingKind.AFFECTED_RANGE;
}
else if (key == EditorColors.DELETED_TEXT_ATTRIBUTES) {
return Presentation.HighlightingKind.DELETED_RANGE;
}
//just fallback to the default highlighting type
return Presentation.HighlightingKind.AFFECTED_RANGE;
}
@Override
public @NotNull ModCommand perform(@NotNull ActionContext context) {
return myFix.perform(context.project(), myDescriptor);
@@ -4,8 +4,6 @@ package com.intellij.modcommand;
import com.intellij.analysis.AnalysisBundle;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.util.TextRange;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -82,7 +80,7 @@ public record Presentation(
public @NotNull Presentation withHighlighting(@NotNull TextRange @NotNull ... ranges) {
List<HighlightRange> highlightRanges =
ContainerUtil.map(ranges, r -> new HighlightRange(r, EditorColors.SEARCH_RESULT_ATTRIBUTES));
ContainerUtil.map(ranges, r -> new HighlightRange(r, HighlightingKind.AFFECTED_RANGE));
return new Presentation(name, priority, highlightRanges, icon, fixAllOption);
}
@@ -103,10 +101,20 @@ public record Presentation(
* Represents a tuple of a TextRange and TextAttributesKey used for highlighting a specific range of text.
*
* @param range The TextRange to be highlighted.
* @param highlightKey The TextAttributesKey to be used for highlighting the range.
* @param highlightingKind The kind of highlighting. It is used to determine TextAttributesKey or special ranges used for highlighting.
*/
public record HighlightRange(
@NotNull TextRange range,
@NotNull TextAttributesKey highlightKey
@NotNull HighlightingKind highlightingKind
) {}
public enum HighlightingKind {
AFFECTED_RANGE,
DELETED_RANGE,
/**
* special range to define scope of applicability
*/
APPLICABLE_TO_RANGE,
}
}
@@ -13,6 +13,8 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@@ -56,11 +58,26 @@ public abstract class PsiBasedModCommandAction<E extends PsiElement> implements
@Override
public final @Nullable Presentation getPresentation(@NotNull ActionContext context) {
E element = getElement(context);
return element == null ? null : getPresentation(context, element);
if (element == null) return null;
Presentation presentation = getPresentation(context, element);
return addApplicableRange(presentation, context, element);
}
@ApiStatus.Internal
public @Nullable E getElement(@NotNull ActionContext context) {
private @Nullable Presentation addApplicableRange(@Nullable Presentation presentation,
@NotNull ActionContext context,
@NotNull E element) {
if (presentation == null) return null;
//applicable range is used only inside the file where the action is called,
//otherwise it can cause ast loading
if (context.file() != element.getContainingFile()) {
return presentation;
}
List<Presentation.HighlightRange> ranges = new ArrayList<>(presentation.rangesToHighlight());
ranges.add(new Presentation.HighlightRange(element.getTextRange(), Presentation.HighlightingKind.APPLICABLE_TO_RANGE));
return presentation.withHighlighting(ranges.toArray(Presentation.HighlightRange[]::new));
}
private @Nullable E getElement(@NotNull ActionContext context) {
if (myPointer != null) {
E element = myPointer.getElement();
if (element == null) return null;
@@ -10,6 +10,7 @@ import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.modcommand.*;
import com.intellij.openapi.diagnostic.ReportingClassSubstitutor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.PossiblyDumbAware;
import com.intellij.openapi.project.Project;
@@ -115,9 +116,18 @@ public final class ModCommandActionWrapper implements IntentionAction, PriorityA
@Override
public @Unmodifiable @NotNull List<RangeToHighlight> getRangesToHighlight(@NotNull Editor editor, @NotNull PsiFile file) {
if (myPresentation == null) return List.of();
return ContainerUtil.map(myPresentation.rangesToHighlight(), range -> new RangeToHighlight(file, range.range(), range.highlightKey()));
return ContainerUtil.mapNotNull(myPresentation.rangesToHighlight(), range -> convertToRangeToHighlight(file, range));
}
private static @Nullable RangeToHighlight convertToRangeToHighlight(@NotNull PsiFile file, @Nullable Presentation.HighlightRange range) {
if (range == null) return null;
return switch (range.highlightingKind()) {
case AFFECTED_RANGE -> new RangeToHighlight(file, range.range(), EditorColors.SEARCH_RESULT_ATTRIBUTES);
case DELETED_RANGE -> new RangeToHighlight(file, range.range(), EditorColors.DELETED_TEXT_ATTRIBUTES);
case APPLICABLE_TO_RANGE -> null;
};
}
@Override
public boolean belongsToMyFamily(@NotNull IntentionActionWithFixAllOption action) {
ModCommandAction unwrapped = action.asModCommandAction();
@@ -30,6 +30,7 @@ import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.lang.annotation.HighlightSeverity.INFORMATION
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.Presentation
import com.intellij.modcommand.PsiBasedModCommandAction
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.readAction
@@ -469,10 +470,13 @@ internal class DirectIntentionCommandProvider : CommandProvider {
}
val modCommandAction = intentionAction.asModCommandAction() ?: return null
if (modCommandAction is PsiBasedModCommandAction<*>) {
modCommandAction.getElement(ActionContext.from(editor, psiFile))?.let {
return HighlightInfoLookup(it.textRange.intersection(TextRange(it.textRange.startOffset, currentOffset)),
EditorColors.SEARCH_RESULT_ATTRIBUTES, 0)
}
modCommandAction.getPresentation(ActionContext.from(editor, psiFile))
?.rangesToHighlight()
?.firstOrNull { highlightRange -> highlightRange.highlightingKind() == Presentation.HighlightingKind.APPLICABLE_TO_RANGE }
?.let {
return HighlightInfoLookup(it.range().intersection(TextRange(it.range().startOffset, currentOffset)),
EditorColors.SEARCH_RESULT_ATTRIBUTES, 0)
}
}
return null
}