[grazie] IJPL-201996 Reimplement RenameTo to make it more responsive and simplify its usage

Merge-request: IJ-MR-176404
Merged-by: Ilia Permiashkin <ilia.permiashkin@jetbrains.com>

GitOrigin-RevId: df6ccbceb44d4d2c2fabb62e92a97007af9d2878
This commit is contained in:
Ilia Permiashkin
2025-09-24 13:30:07 +00:00
committed by intellij-monorepo-bot
parent 732b17bc67
commit 0cbe60bb0c
5 changed files with 97 additions and 22 deletions
@@ -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 <TYPO descr="Typo: In word 'barek'">barek<caret></TYPO> = 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;
}
}
""")
@@ -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()));
}
}
@@ -23,6 +23,6 @@ class PropertyRenameToTest : BasePlatformTestCase() {
myFixture.configureByText("a.properties", "<TYPO descr=\"Typo: In word 'helloworld'\">hellow<caret>orld</TYPO>=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")
}
}
@@ -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
@@ -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<PsiElement> pointer;
private final SpellcheckerRateTracker tracker;
private final List<String> suggestions = new ArrayList<>();
private final SequencedSet<String> suggestions = new LinkedHashSet<>();
private SmartPsiElementPointer<PsiElement> 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<String> 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();
}
}