From b794b331f8a0c556e106ddd80cdf837b25b9e8f2 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Fri, 18 Jul 2025 16:02:01 +0200 Subject: [PATCH] IJ-CR-168801 [command-completion] IDEA-375698 Command completion. Highlight intention's scope - use special range GitOrigin-RevId: d3fc41b6361f1d685c743d64a66637bf57bd38ea --- .../commands/JavaCommandsCompletionTest.kt | 4 +++- .../braces/SingleStatementInBlockFixTest.java | 20 +++++++++++++--- platform/analysis-api/api-dump-unreviewed.txt | 2 -- platform/analysis-api/api-dump.txt | 10 ++++++++ .../CustomizableIntentionAction.java | 1 + .../codeInspection/ex/QuickFixWrapper.java | 18 ++++++++++++++- .../com/intellij/modcommand/Presentation.java | 18 +++++++++++---- .../modcommand/PsiBasedModCommandAction.java | 23 ++++++++++++++++--- .../modcommand/ModCommandActionWrapper.java | 14 +++++++++-- .../DirectIntentionCommandProvider.kt | 12 ++++++---- 10 files changed, 101 insertions(+), 21 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt index a5c689f37ba6..208cb8a7d5e2 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt @@ -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() { diff --git a/java/java-tests/testSrc/com/siyeh/ig/fixes/braces/SingleStatementInBlockFixTest.java b/java/java-tests/testSrc/com/siyeh/ig/fixes/braces/SingleStatementInBlockFixTest.java index ab951373e357..15d394df43aa 100644 --- a/java/java-tests/testSrc/com/siyeh/ig/fixes/braces/SingleStatementInBlockFixTest.java +++ b/java/java-tests/testSrc/com/siyeh/ig/fixes/braces/SingleStatementInBlockFixTest.java @@ -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 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 diff --git a/platform/analysis-api/api-dump-unreviewed.txt b/platform/analysis-api/api-dump-unreviewed.txt index 0f9cd7a78c13..d33cfa5048b8 100644 --- a/platform/analysis-api/api-dump-unreviewed.txt +++ b/platform/analysis-api/api-dump-unreviewed.txt @@ -2281,10 +2281,8 @@ f:com.intellij.modcommand.Presentation$FixAllOption - name():java.lang.String f:com.intellij.modcommand.Presentation$HighlightRange - java.lang.Record -- (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 diff --git a/platform/analysis-api/api-dump.txt b/platform/analysis-api/api-dump.txt index 504eb53bc9ac..2fed11a2de29 100644 --- a/platform/analysis-api/api-dump.txt +++ b/platform/analysis-api/api-dump.txt @@ -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 +- (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 diff --git a/platform/analysis-api/src/com/intellij/codeInsight/intention/CustomizableIntentionAction.java b/platform/analysis-api/src/com/intellij/codeInsight/intention/CustomizableIntentionAction.java index 7e1377675827..bf7a85ee51b1 100644 --- a/platform/analysis-api/src/com/intellij/codeInsight/intention/CustomizableIntentionAction.java +++ b/platform/analysis-api/src/com/intellij/codeInsight/intention/CustomizableIntentionAction.java @@ -115,6 +115,7 @@ public interface CustomizableIntentionAction extends IntentionAction { return psi.getContainingFile(); } + @NotNull public TextAttributesKey getHighlightKey() { return highlightKey; } diff --git a/platform/analysis-api/src/com/intellij/codeInspection/ex/QuickFixWrapper.java b/platform/analysis-api/src/com/intellij/codeInspection/ex/QuickFixWrapper.java index 66c2055ecbb4..1dd6c6b1fccb 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/ex/QuickFixWrapper.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/ex/QuickFixWrapper.java @@ -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 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); diff --git a/platform/analysis-api/src/com/intellij/modcommand/Presentation.java b/platform/analysis-api/src/com/intellij/modcommand/Presentation.java index 9d0485b292e8..fae54a0ed84e 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/Presentation.java +++ b/platform/analysis-api/src/com/intellij/modcommand/Presentation.java @@ -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 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, + } } diff --git a/platform/analysis-api/src/com/intellij/modcommand/PsiBasedModCommandAction.java b/platform/analysis-api/src/com/intellij/modcommand/PsiBasedModCommandAction.java index dee6e67f1e76..c6db8cb36bde 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/PsiBasedModCommandAction.java +++ b/platform/analysis-api/src/com/intellij/modcommand/PsiBasedModCommandAction.java @@ -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 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 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; diff --git a/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/ModCommandActionWrapper.java b/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/ModCommandActionWrapper.java index b33fde9bd6f1..f3c9fd711c1b 100644 --- a/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/ModCommandActionWrapper.java +++ b/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/ModCommandActionWrapper.java @@ -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 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(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt index 0e045a68191a..e03c825f6b9c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt @@ -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 }