From 0cbe60bb0c07b3faba3dc7a1ea300f2ac2785962 Mon Sep 17 00:00:00 2001 From: Ilia Permiashkin Date: Wed, 24 Sep 2025 13:30:07 +0000 Subject: [PATCH] [grazie] IJPL-201996 Reimplement `RenameTo` to make it more responsive and simplify its usage Merge-request: IJ-MR-176404 Merged-by: Ilia Permiashkin GitOrigin-RevId: df6ccbceb44d4d2c2fabb62e92a97007af9d2878 --- .../grazie/ide/language/JavaSupportTest.kt | 26 +++++- .../PlainTextSpellCheckerFixesTest.java | 8 +- .../properties/rename/PropertyRenameToTest.kt | 2 +- .../messages/SpellCheckerBundle.properties | 1 + .../spellchecker/quickfixes/RenameTo.java | 82 +++++++++++++++---- 5 files changed, 97 insertions(+), 22 deletions(-) diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt index 5bdfeee42034..ab17c674ba84 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt @@ -13,6 +13,8 @@ import com.intellij.spellchecker.settings.SpellCheckerSettings import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase import com.intellij.tools.ide.metrics.benchmark.Benchmark +import com.intellij.ui.ChooserInterceptor +import com.intellij.ui.UiInterceptors import java.util.function.Consumer @@ -123,7 +125,7 @@ class JavaSupportTest : GrazieTestBase() { ) } - fun `test meaningful suggestions in RenameTo action`() { + fun `test meaningful single suggestion in RenameTo action`() { myFixture.configureByText("a.java", """ class A { void foo() { @@ -132,12 +134,32 @@ class JavaSupportTest : GrazieTestBase() { } """) myFixture.checkHighlighting() + val intention = myFixture.findSingleIntention("Typo: Rename to 'targetDir'") + myFixture.launchAction(intention) + myFixture.checkResult(""" + class A { + void foo() { + int targetDir = 1; + } + } + """) + } + + fun `test multiple suggestions in RenameTo action`() { + myFixture.configureByText("a.java", """ + class A { + void foo() { + int barek = 1; + } + } + """) + myFixture.checkHighlighting() val intention = myFixture.findSingleIntention("Typo: Rename to…") myFixture.launchAction(intention) myFixture.checkResult(""" class A { void foo() { - int targetDir = 1; + int bark = 1; } } """) diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/PlainTextSpellCheckerFixesTest.java b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/PlainTextSpellCheckerFixesTest.java index 3b728c846f7d..cda448d878ea 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/PlainTextSpellCheckerFixesTest.java +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/PlainTextSpellCheckerFixesTest.java @@ -19,6 +19,8 @@ import com.intellij.spellchecker.quickfixes.ChangeTo; import com.intellij.spellchecker.quickfixes.RenameTo; import com.intellij.spellchecker.quickfixes.SaveTo; +import java.util.List; + public class PlainTextSpellCheckerFixesTest extends AbstractSpellCheckerFixesTest { @Override protected String getExtension() { @@ -54,7 +56,7 @@ public class PlainTextSpellCheckerFixesTest extends AbstractSpellCheckerFixesTes } public void testEmptyRenameTo() { - doNoQuickFixTest(RenameTo.getFixName()); + doNoQuickFixTest(RenameTo.getFixName(List.of())); } public void testEmptySaveTo() { @@ -66,10 +68,10 @@ public class PlainTextSpellCheckerFixesTest extends AbstractSpellCheckerFixesTes } public void testNoTypoRenameTo() { - doNoQuickFixTest(RenameTo.getFixName()); + doNoQuickFixTest(RenameTo.getFixName(List.of())); } public void testSimpleWordRenameTo() { - doNoQuickFixTest(RenameTo.getFixName()); + doNoQuickFixTest(RenameTo.getFixName(List.of())); } } diff --git a/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt b/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt index a75201ecebfc..e46f1ba1613c 100644 --- a/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt +++ b/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt @@ -23,6 +23,6 @@ class PropertyRenameToTest : BasePlatformTestCase() { myFixture.configureByText("a.properties", "helloworld=value") myFixture.enableInspections(GrazieSpellCheckingInspection()) myFixture.checkHighlighting() - myFixture.getAvailableIntention("Typo: Rename to…") ?: error("RenameTo intention is not available") + myFixture.getAvailableIntention("Typo: Rename to 'hello-world'") ?: error("RenameTo intention is not available") } } diff --git a/spellchecker/resources/messages/SpellCheckerBundle.properties b/spellchecker/resources/messages/SpellCheckerBundle.properties index ce5b32cc540e..9229f9e07f8d 100644 --- a/spellchecker/resources/messages/SpellCheckerBundle.properties +++ b/spellchecker/resources/messages/SpellCheckerBundle.properties @@ -1,6 +1,7 @@ change.to.title=Fix typo change.to.tooltip=Replace with ''{0}'' rename.to=Typo: Rename to\u2026 +rename.to.0=Typo: Rename to ''{0}'' proofread=Proofreading spelling=Spelling spellchecking.inspection.name=Spelling diff --git a/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java b/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java index 007a20a4e0a0..5106e6928df2 100644 --- a/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java +++ b/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java @@ -3,8 +3,10 @@ package com.intellij.spellchecker.quickfixes; import com.intellij.codeInsight.intention.EventTrackingIntentionAction; import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils; -import com.intellij.modcommand.ModPsiUpdater; -import com.intellij.modcommand.PsiUpdateModCommandQuickFix; +import com.intellij.codeInspection.IntentionAndQuickFixAction; +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformCoreDataKeys; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Iconable; @@ -12,7 +14,9 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.refactoring.rename.RenameUtil; +import com.intellij.refactoring.RefactoringActionHandler; +import com.intellij.refactoring.RefactoringActionHandlerFactory; +import com.intellij.refactoring.rename.*; import com.intellij.spellchecker.SpellCheckerManager; import com.intellij.spellchecker.statistics.SpellcheckerActionStatistics; import com.intellij.spellchecker.statistics.SpellcheckerRateTracker; @@ -23,16 +27,16 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; -public class RenameTo extends PsiUpdateModCommandQuickFix implements Iconable, EventTrackingIntentionAction { +public class RenameTo extends IntentionAndQuickFixAction implements Iconable, EventTrackingIntentionAction { private final String typo; private final TextRange range; private final SmartPsiElementPointer pointer; private final SpellcheckerRateTracker tracker; - private final List suggestions = new ArrayList<>(); + private final SequencedSet suggestions = new LinkedHashSet<>(); + private SmartPsiElementPointer namedPointer; public RenameTo(String typo, TextRange range, PsiElement psi, SpellcheckerRateTracker tracker) { this.typo = typo; @@ -42,16 +46,50 @@ public class RenameTo extends PsiUpdateModCommandQuickFix implements Iconable, E } @Override - public @NotNull String getFamilyName() { - return getFixName(); + public boolean isAvailable(@NotNull Project project, @Nullable Editor editor, PsiFile psiFile) { + PsiElement element = Objects.requireNonNull(pointer.getElement()); + var presentationName = getPresentationName(element); + if (presentationName == null) return false; + generateSuggestions(presentationName.getSecond(), element); + this.namedPointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(presentationName.getFirst()); + if (suggestions.isEmpty()) return false; + return true; } @Override - protected void applyFix(@NotNull Project project, @NotNull PsiElement psiElement, @NotNull ModPsiUpdater updater) { - var name = getPresentationName(psiElement); - if (name == null) return; - generateSuggestions(name.second, psiElement); - updater.rename(name.first, psiElement, suggestions); + public boolean startInWriteAction() { + return false; + } + + @Override + public @NotNull String getName() { + return getFixName(suggestions); + } + + @Override + public @NotNull String getFamilyName() { + return getFixName(suggestions); + } + + @Override + public void applyFix(@NotNull Project project, PsiFile psiFile, @Nullable Editor editor) { + PsiElement element = namedPointer.getElement() == null ? null : namedPointer.getElement(); + if (element == null) return; + + if (suggestions.size() == 1) { + runRenamer(element, suggestions.getFirst()); + } + else { + var context = DataManager.getInstance().getDataContext(editor.getContentComponent()); + DataContext contextWithSuggestions = dataId -> { + if (PsiElementRenameHandler.NAME_SUGGESTIONS.is(dataId)) return new ArrayList<>(suggestions); + if (PlatformCoreDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) return new PsiElement[]{element}; + return context.getData(dataId); + }; + RefactoringActionHandler handler = getRenameHandler(contextWithSuggestions); + handler.invoke(project, editor, psiFile, contextWithSuggestions); + } + if (!IntentionPreviewUtils.isIntentionPreviewActive()) { SpellcheckerActionStatistics.renameToPerformed(tracker, suggestions.size()); } @@ -64,8 +102,10 @@ public class RenameTo extends PsiUpdateModCommandQuickFix implements Iconable, E } } - public static @Nls String getFixName() { - return SpellCheckerBundle.message("rename.to"); + public static @Nls String getFixName(SequencedCollection suggestions) { + return suggestions.size() == 1 ? + SpellCheckerBundle.message("rename.to.0", suggestions.getFirst()) : + SpellCheckerBundle.message("rename.to"); } @Override @@ -83,6 +123,12 @@ public class RenameTo extends PsiUpdateModCommandQuickFix implements Iconable, E return new Pair<>(namedElement, name); } + private static RefactoringActionHandler getRenameHandler(DataContext dataContext) { + RenameHandler handler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext); + if (handler == null) return RefactoringActionHandlerFactory.getInstance().createRenameHandler(); + return handler; + } + private void generateSuggestions(String name, PsiElement element) { if (suggestions.isEmpty()) { TextRange range = this.range.shiftLeft(element.getText().indexOf(name)); @@ -93,4 +139,8 @@ public class RenameTo extends PsiUpdateModCommandQuickFix implements Iconable, E .forEach(suggestions::add); } } + + private void runRenamer(PsiElement element, String suggestion) { + new RenameProcessor(pointer.getProject(), element, suggestion, true, true).run(); + } } \ No newline at end of file