From 320c3f2442a4f2bdf4b7184e9f25fec972ec691e Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 25 Sep 2023 17:21:33 +0200 Subject: [PATCH] [mod-commands] IDEA-333301 Support suggested refactoring in ModCommands GitOrigin-RevId: dbd1401630a68e3e6ca932c5671f428f81e8324d --- .../JavaSuggestedRefactoringAvailability.kt | 14 +++++ .../JavaSuggestedRefactoringExecution.kt | 18 +++---- .../ChangeSignatureProcessor.java | 2 +- .../JavaChangeSignatureUsageProcessor.java | 7 ++- .../impl/JavaRefactoringFactoryImpl.java | 2 +- .../changeSignature/JavaChangeInfo.java | 7 +++ .../changeSignature/JavaChangeInfoImpl.java | 15 +++++- .../com/intellij/modcommand/ModCommand.java | 4 +- .../intellij/modcommand/ModPsiUpdater.java | 12 +++++ .../modcommand/ModUpdateReferences.java | 30 +++++++++++ .../lang/impl/modcommand/PsiUpdateImpl.java | 18 ++++++- .../resources/messages/LangBundle.properties | 1 + .../SuggestedRefactoringAvailability.kt | 5 ++ .../SuggestedRefactoringStateChanges.kt | 4 +- .../modcommand/ModCommandExecutorImpl.java | 53 +++++++++++++++++++ .../suggested/PerformSuggestedRefactoring.kt | 13 +++++ 16 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 platform/analysis-api/src/com/intellij/modcommand/ModUpdateReferences.java diff --git a/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringAvailability.kt b/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringAvailability.kt index 9ab344485ae2..5bc4be698ab9 100644 --- a/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringAvailability.kt +++ b/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringAvailability.kt @@ -48,6 +48,20 @@ class JavaSuggestedRefactoringAvailability(refactoringSupport: SuggestedRefactor } } + override fun isAvailable(state: SuggestedRefactoringState): Boolean { + val method = state.declaration as? PsiMethod + val declarationCopy = state.restoredDeclarationCopy() + if (method != null && method.canHaveOverrides(state.oldSignature)) { + val restoredMethod = declarationCopy as PsiMethod + if (OverridingMethodsSearch.search(restoredMethod, false).findFirst() != null) return true + } + if (declarationCopy != null) { + if (ReferencesSearch.search(declarationCopy, LocalSearchScope(declarationCopy.containingFile)).findFirst() != null) return true + if (ReferencesSearch.search(declarationCopy, declarationCopy.useScope).findFirst() != null) return true + } + return false + } + private fun callStateToDeclarationState(state: SuggestedRefactoringState): SuggestedRefactoringState? { val anchor = state.anchor as? PsiCallExpression ?: return null val resolveResult = anchor.resolveMethodGenerics() diff --git a/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringExecution.kt b/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringExecution.kt index d4b994e15cde..bb3458859dee 100644 --- a/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringExecution.kt +++ b/java/java-impl-refactorings/src/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringExecution.kt @@ -5,10 +5,7 @@ import com.intellij.openapi.application.runUndoTransparentWriteAction import com.intellij.openapi.command.executeCommand import com.intellij.psi.* import com.intellij.psi.codeStyle.JavaCodeStyleManager -import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor -import com.intellij.refactoring.changeSignature.JavaThrownExceptionInfo -import com.intellij.refactoring.changeSignature.ParameterInfoImpl -import com.intellij.refactoring.changeSignature.ThrownExceptionInfo +import com.intellij.refactoring.changeSignature.* import com.intellij.refactoring.suggested.SuggestedChangeSignatureData import com.intellij.refactoring.suggested.SuggestedRefactoringExecution import com.intellij.refactoring.suggested.SuggestedRefactoringSupport @@ -103,17 +100,18 @@ class JavaSuggestedRefactoringExecution(refactoringSupport: SuggestedRefactoring val exceptionInfos = prepareExceptionInfos(newExceptionTypes, oldExceptionTypes) - val processor = ChangeSignatureProcessor( - project, + val changeInfo = JavaChangeInfoImpl.generateChangeInfo( declaration, false, + false, data.newSignature.visibility?.takeIf { it != data.oldSignature.visibility }, data.newSignature.name, - returnType, + returnType?.let(CanonicalTypes::createTypeWrapper), newParameters.toTypedArray(), - exceptionInfos.toTypedArray() - ) - processor.run() + exceptionInfos.toTypedArray(), + null, + null) + ChangeSignatureProcessor(project, changeInfo).run() } private fun typesEqualWithAnnotations(type1: PsiType?, type2: PsiType?) = type1?.getCanonicalText(true) == type2?.getCanonicalText(true) diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/ChangeSignatureProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/ChangeSignatureProcessor.java index 79de44a85f12..da2e1aaae4f7 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/ChangeSignatureProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/ChangeSignatureProcessor.java @@ -72,7 +72,7 @@ public class ChangeSignatureProcessor extends ChangeSignatureProcessorBase { ThrownExceptionInfo[] thrownExceptions, Set propagateParametersMethods, Set propagateExceptionsMethods) { - this(project, JavaChangeInfoImpl.generateChangeInfo(method, generateDelegate, newVisibility, newName, newType, parameterInfo, thrownExceptions, + this(project, JavaChangeInfoImpl.generateChangeInfo(method, generateDelegate, true, newVisibility, newName, newType, parameterInfo, thrownExceptions, propagateParametersMethods, propagateExceptionsMethods)); } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java index 5b9f610313aa..14071e7eed7d 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java @@ -1031,7 +1031,12 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr final List newParameters = ContainerUtil.map(newElements, element -> (PsiParameter)element.variable()); final List newParameterNames = ContainerUtil.map(newElements, VariableWrapper::getName); final boolean[] toRemove = myChangeInfo.toRemoveParm(); - resolveVariableVsFieldsConflicts(newParameters, newParameterNames, myParent, toRemove, myMethodBody, ParameterList.INSTANCE); + if (myChangeInfo.isFixFieldConflicts()) { + resolveVariableVsFieldsConflicts(newParameters, newParameterNames, myParent, toRemove, myMethodBody, ParameterList.INSTANCE); + } else { + ChangeSignatureUtil.synchronizeList(myParent, newParameters, ParameterList.INSTANCE, toRemove); + JavaCodeStyleManager.getInstance(myParent.getProject()).shortenClassReferences(myParent); + } } } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/openapi/impl/JavaRefactoringFactoryImpl.java b/java/java-impl-refactorings/src/com/intellij/refactoring/openapi/impl/JavaRefactoringFactoryImpl.java index d287fb4a1f82..c174651d80f2 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/openapi/impl/JavaRefactoringFactoryImpl.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/openapi/impl/JavaRefactoringFactoryImpl.java @@ -151,7 +151,7 @@ public class JavaRefactoringFactoryImpl extends JavaRefactoringFactory { Set propagateParametersMethods, Set propagateExceptionsMethods, Consumer> callback) { - JavaChangeInfo changeInfo = JavaChangeInfoImpl.generateChangeInfo(method, generateDelegate, newVisibility, + JavaChangeInfo changeInfo = JavaChangeInfoImpl.generateChangeInfo(method, generateDelegate, true, newVisibility, newName, newReturnType != null ? CanonicalTypes.createTypeWrapper(newReturnType) : null, (ParameterInfoImpl[])parameterInfo, thrownExceptions, propagateParametersMethods, propagateExceptionsMethods); diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfo.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfo.java index 4368d68679e1..5e255b669e9e 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfo.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfo.java @@ -30,6 +30,13 @@ public interface JavaChangeInfo extends ChangeInfo { return type != null ? type.getCanonicalText() : null; } + /** + * @return true if it's desired to fix conflicts between new parameters and field names (adding {@code this.} qualifier to field accesses) + */ + default boolean isFixFieldConflicts() { + return true; + } + @Override JavaParameterInfo @NotNull [] getNewParameters(); diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfoImpl.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfoImpl.java index afd91d4388d6..253b9a74927f 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfoImpl.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeInfoImpl.java @@ -58,6 +58,7 @@ public class JavaChangeInfoImpl extends UserDataHolderBase implements JavaChange private final PsiExpression[] defaultValues; private final boolean isGenerateDelegate; + private boolean isFixFieldConflicts = true; final Set propagateParametersMethods; final Set propagateExceptionsMethods; @@ -65,8 +66,8 @@ public class JavaChangeInfoImpl extends UserDataHolderBase implements JavaChange public static JavaChangeInfo generateChangeInfo(PsiMethod method, boolean generateDelegate, - @Nullable // null means unchanged - @PsiModifier.ModifierConstant String newVisibility, + boolean fixFieldConflicts, + @Nullable("Null means unchanged") @PsiModifier.ModifierConstant String newVisibility, String newName, CanonicalTypes.Type newType, ParameterInfoImpl @NotNull [] parameterInfo, @@ -91,6 +92,7 @@ public class JavaChangeInfoImpl extends UserDataHolderBase implements JavaChange new JavaChangeInfoImpl(newVisibility, method, newName, newType, parameterInfo, thrownExceptions, generateDelegate, propagateParametersMethods, propagateExceptionsMethods); javaChangeInfo.setCheckUnusedParameter(); + javaChangeInfo.setFixFieldConflicts(fixFieldConflicts); return javaChangeInfo; } @@ -236,6 +238,15 @@ public class JavaChangeInfoImpl extends UserDataHolderBase implements JavaChange myCheckUnusedParameter = true; } + @Override + public boolean isFixFieldConflicts() { + return isFixFieldConflicts; + } + + public void setFixFieldConflicts(boolean fixFieldConflicts) { + isFixFieldConflicts = fixFieldConflicts; + } + protected void fillOldParams(PsiMethod method) { PsiParameter[] parameters = method.getParameterList().getParameters(); oldParameterNames = new String[parameters.length]; diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModCommand.java b/platform/analysis-api/src/com/intellij/modcommand/ModCommand.java index e139abec1ad2..a8fc047d7353 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/ModCommand.java +++ b/platform/analysis-api/src/com/intellij/modcommand/ModCommand.java @@ -31,8 +31,8 @@ import java.util.function.Function; */ public sealed interface ModCommand permits ModChooseAction, ModChooseMember, ModCompositeCommand, ModCopyToClipboard, ModCreateFile, ModDeleteFile, ModDisplayMessage, - ModHighlight, ModNavigate, ModNothing, ModRenameSymbol, ModShowConflicts, ModStartTemplate, ModUpdateFileText, - ModUpdateInspectionOptions { + ModHighlight, ModNavigate, ModNothing, ModRenameSymbol, ModShowConflicts, ModStartTemplate, ModUpdateReferences, + ModUpdateFileText, ModUpdateInspectionOptions { /** * @return true if the command does nothing diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java b/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java index 2b3fb7b895ac..cec549803551 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java +++ b/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java @@ -80,6 +80,18 @@ public interface ModPsiUpdater extends ModPsiNavigator { */ void rename(@NotNull PsiNameIdentifierOwner element, @NotNull List<@NotNull String> suggestedNames); + /** + * Tracks subsequent changes in a given declaration (e.g., method) and produce a command to + * update the references to the declaration (maybe displaying UI). + * The current file may be changed if the declaration is located in the different file. + *

+ * This method must be called before you actually update the declaration (e.g., change method parameters). + *

+ * + * @param declaration declaration to track + */ + void trackDeclaration(@NotNull PsiElement declaration); + /** * @return a builder that allows you to create a template. */ diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModUpdateReferences.java b/platform/analysis-api/src/com/intellij/modcommand/ModUpdateReferences.java new file mode 100644 index 000000000000..e51a78c9afa6 --- /dev/null +++ b/platform/analysis-api/src/com/intellij/modcommand/ModUpdateReferences.java @@ -0,0 +1,30 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.modcommand; + +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +/** + * A command to update the references to the declaration if possible. A common use-case is to + * invoke "change signature" refactoring automatically. May do nothing, if particular language, + * or particular kind of declaration change is not supported. The command should follow actual + * text update in the file via {@link ModUpdateFileText}. + * + * @implNote In IntelliJ IDEA interactive executor, it invokes the "suggested refactoring" mechanism. + * + * @param file virtual file where the declaration resides + * @param oldText the complete content of the virtual file before the declaration was updated + * @param oldRange the complete text range of the declaration within the oldText + * @param newRange the complete current text range of the declaration + */ +public record ModUpdateReferences(@NotNull VirtualFile file, @NotNull String oldText, @NotNull TextRange oldRange, + @NotNull TextRange newRange) implements ModCommand { + /** + * @param newRange updated new range + * @return the equivalent command but with updated newRange. + */ + public @NotNull ModUpdateReferences withNewRange(@NotNull TextRange newRange) { + return newRange.equals(newRange()) ? this : new ModUpdateReferences(file, oldText, oldRange, newRange); + } +} diff --git a/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java b/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java index 4e72e558d784..ad6154f21082 100644 --- a/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java +++ b/platform/analysis-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java @@ -219,6 +219,7 @@ final class PsiUpdateImpl { private final List myHighlightInfos = new ArrayList<>(); private final List myTemplateFields = new ArrayList<>(); private @Nullable ModRenameSymbol myRenameSymbol; + private final List myTrackedDeclarations = new ArrayList<>(); private boolean myPositionUpdated = false; private @NlsContexts.Tooltip String myErrorMessage; private @NlsContexts.Tooltip String myInfoMessage; @@ -426,6 +427,16 @@ final class PsiUpdateImpl { myRenameSymbol = new ModRenameSymbol(myNavigationFile, range, suggestedNames); } + @Override + public void trackDeclaration(@NotNull PsiElement declaration) { + TextRange range = getRange(declaration); + if (range == null) { + throw new IllegalArgumentException("Element disappeared after postponed operations: " + declaration); + } + String oldText = myTracker.myCopyFile.getText(); + myTrackedDeclarations.add(new ModUpdateReferences(myNavigationFile, oldText, range, range)); + } + @Override public void cancel(@NotNull @NlsContexts.Tooltip String errorMessage) { if (myErrorMessage != null) { @@ -481,6 +492,7 @@ final class PsiUpdateImpl { mySelection = updateRange(event, mySelection); myHighlightInfos.replaceAll(info -> info.withRange(updateRange(event, info.range()))); myTemplateFields.replaceAll(info -> info.withRange(updateRange(event, info.range()))); + myTrackedDeclarations.replaceAll(range -> range.withNewRange(updateRange(event, range.newRange()))); if (myRenameSymbol != null) { myRenameSymbol = myRenameSymbol.withRange(updateRange(event, myRenameSymbol.symbolRange())); } @@ -513,11 +525,13 @@ final class PsiUpdateImpl { if (myErrorMessage != null) { return error(myErrorMessage); } - return myChangedFiles.values().stream().map(FileTracker::getUpdateCommand).reduce(nop(), ModCommand::andThen) + return myChangedFiles.values().stream() + .map(fileTracker -> fileTracker.getUpdateCommand()).reduce(nop(), ModCommand::andThen) .andThen(myChangedDirectories.values().stream() .flatMap(info -> info.createFileCommands(myTracker.myProject)) .reduce(nop(), ModCommand::andThen)) .andThen(getNavigateCommand()).andThen(getHighlightCommand()).andThen(getTemplateCommand()) + .andThen(myTrackedDeclarations.stream().map(c -> c).reduce(nop(), ModCommand::andThen)) .andThen(myRenameSymbol == null ? nop() : myRenameSymbol) .andThen(myInfoMessage == null ? nop() : ModCommand.info(myInfoMessage)); } @@ -554,7 +568,7 @@ final class PsiUpdateImpl { private final TextRange myRange; private final @NotNull PsiElement myElement; - public DummyContext(TextRange range, @NotNull PsiElement element) { + private DummyContext(TextRange range, @NotNull PsiElement element) { myRange = range; myElement = element; } diff --git a/platform/lang-api/resources/messages/LangBundle.properties b/platform/lang-api/resources/messages/LangBundle.properties index 1dfcae53016a..ed2a4dffc049 100644 --- a/platform/lang-api/resources/messages/LangBundle.properties +++ b/platform/lang-api/resources/messages/LangBundle.properties @@ -593,3 +593,4 @@ executor.one.of.actions={0} of {1}: {2} command.title.finishing.template=Finishing Template notification.group.batch.quick.fix=Batch quick fix command.check.availability.for=Check Availability for {0} +dialog.title.searching.for.usages=Searching for Usages diff --git a/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringAvailability.kt b/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringAvailability.kt index 5ea8fed8c823..8fb29cbe92fa 100644 --- a/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringAvailability.kt +++ b/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringAvailability.kt @@ -43,6 +43,11 @@ abstract class SuggestedRefactoringAvailability(protected val refactoringSupport */ open fun amendStateInBackground(state: SuggestedRefactoringState): Iterator = iterator { } + /** + * Slow way to determine whether the action is available. Intended to be executed in background thread. + */ + open fun isAvailable(state: SuggestedRefactoringState): Boolean = true + /** * Determines refactoring availability for a given state and returns instance of [SuggestedRefactoringData], * providing information for presentation and execution of the refactoring. diff --git a/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringStateChanges.kt b/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringStateChanges.kt index e89e1912ccdf..3287640fbcfb 100644 --- a/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringStateChanges.kt +++ b/platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringStateChanges.kt @@ -46,7 +46,7 @@ abstract class SuggestedRefactoringStateChanges(protected val refactoringSupport val signatureRange = refactoringSupport.signatureRange(anchor) ?: return null val psiDocumentManager = PsiDocumentManager.getInstance(anchor.project) val file = anchor.containingFile - val document = psiDocumentManager.getDocument(file)!! + val document = file.viewProvider.document!! require(psiDocumentManager.isCommitted(document)) return SuggestedRefactoringState( anchor, @@ -163,7 +163,7 @@ abstract class SuggestedRefactoringStateChanges(protected val refactoringSupport } fun SuggestedRefactoringStateChanges.parameterMarkers(declaration: PsiElement, signature: Signature): List { - val document = PsiDocumentManager.getInstance(declaration.project).getDocument(declaration.containingFile)!! + val document = declaration.containingFile.viewProvider.document!! val markerRanges = parameterMarkerRanges(declaration) require(markerRanges.size == signature.parameters.size) return markerRanges.zip(signature.parameters) diff --git a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandExecutorImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandExecutorImpl.java index ff837d27f802..df9d30680560 100644 --- a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandExecutorImpl.java +++ b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandExecutorImpl.java @@ -24,6 +24,7 @@ import com.intellij.lang.LangBundle; import com.intellij.modcommand.*; import com.intellij.modcommand.ModChooseMember.SelectionMode; import com.intellij.modcommand.ModUpdateFileText.Fragment; +import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; @@ -52,6 +53,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.rename.RenamePsiElementProcessor; import com.intellij.refactoring.rename.inplace.MemberInplaceRenamer; +import com.intellij.refactoring.suggested.*; import com.intellij.refactoring.ui.ConflictsDialog; import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.concurrency.annotations.RequiresEdt; @@ -162,6 +164,9 @@ public class ModCommandExecutorImpl implements ModCommandExecutor { if (command instanceof ModUpdateFileText upd) { return executeUpdate(project, upd); } + if (command instanceof ModUpdateReferences decl) { + return !executeTrackDeclaration(context, decl, editor); + } if (command instanceof ModCompositeCommand cmp) { return executeComposite(context, cmp, editor); } @@ -207,6 +212,54 @@ public class ModCommandExecutorImpl implements ModCommandExecutor { throw new IllegalArgumentException("Unknown command: " + command); } + @Nullable + private static PsiElement findElementAtRange(PsiFile psiFile, TextRange declarationRange) { + PsiElement element = psiFile.findElementAt(declarationRange.getStartOffset()); + while (element != null && !element.getTextRange().contains(declarationRange)) { + element = element.getParent(); + } + if (element == null || !element.getTextRange().equals(declarationRange)) return null; + return element; + } + + private static boolean executeTrackDeclaration(@NotNull ActionContext context, @NotNull ModUpdateReferences decl, @Nullable Editor editor) { + // TODO: properly support multiple tracked declarations + VirtualFile file = decl.file(); + Project project = context.project(); + Callable computeNewState = () -> { + PsiFile psiFile = PsiManagerEx.getInstanceEx(project).findFile(file); + if (psiFile == null) return null; + SuggestedRefactoringSupport support = SuggestedRefactoringSupport.Companion.forLanguage(psiFile.getLanguage()); + if (support == null) return null; + PsiElement newElement = findElementAtRange(psiFile, decl.newRange()); + if (newElement == null || !support.isAnchor(newElement)) return null; + SuggestedRefactoringStateChanges stateChanges = support.getStateChanges(); + PsiFile fileCopy = (PsiFile)psiFile.copy(); + Document documentCopy = fileCopy.getViewProvider().getDocument(); + documentCopy.replaceString(0, documentCopy.getTextLength(), decl.oldText()); + PsiDocumentManager.getInstance(project).commitDocument(documentCopy); + PsiElement element = findElementAtRange(fileCopy, decl.oldRange()); + if (element == null) return null; + if (!support.isAnchor(element) || stateChanges.findDeclaration(element) != element) return null; + SuggestedRefactoringState state = stateChanges.createInitialState(element); + if (state == null) return null; + SuggestedRefactoringAvailability availability = support.getAvailability(); + SuggestedRefactoringState newState = stateChanges.updateState(state, newElement); + if (newState.getErrorLevel() != SuggestedRefactoringState.ErrorLevel.NO_ERRORS) return null; + if (availability.detectAvailableRefactoring(newState) != null && availability.isAvailable(newState)) return newState; + return null; + }; + SuggestedRefactoringState finalState = ProgressManager.getInstance().runProcessWithProgressSynchronously( + () -> ReadAction.nonBlocking(computeNewState).executeSynchronously(), + LangBundle.message("dialog.title.searching.for.usages"), true, project); + if (finalState == null) return false; + Editor finalEditor = getEditor(project, editor, decl.file()); + if (finalEditor == null) return false; + PerformSuggestedRefactoringKt.performSuggestedRefactoring( + finalState, finalEditor, project, ActionPlaces.INTENTION_MENU, true, null, null); + return true; + } + private static boolean executeUpdateInspectionOptions(@NotNull ActionContext context, @NotNull ModUpdateInspectionOptions options) { VirtualFile vFile = context.file().getVirtualFile(); Project project = context.project(); diff --git a/platform/lang-impl/src/com/intellij/refactoring/suggested/PerformSuggestedRefactoring.kt b/platform/lang-impl/src/com/intellij/refactoring/suggested/PerformSuggestedRefactoring.kt index 854d6622fd38..75687c16a3a5 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/suggested/PerformSuggestedRefactoring.kt +++ b/platform/lang-impl/src/com/intellij/refactoring/suggested/PerformSuggestedRefactoring.kt @@ -62,6 +62,19 @@ internal fun performSuggestedRefactoring( ?.let { it.refactoringSupport.availability.refineSignaturesWithResolve(it) } ?: return + performSuggestedRefactoring(state, originalEditor, project, actionPlace, showReviewBalloon, popupAnchorComponent, popupAnchorPoint) +} + +/** + * Launch suggested refactoring based on the specified state + */ +fun performSuggestedRefactoring(state: SuggestedRefactoringState, + originalEditor: Editor, + project: Project, + actionPlace: String, + showReviewBalloon: Boolean, + popupAnchorComponent: JComponent?, + popupAnchorPoint: Point?) { if (state.errorLevel != ErrorLevel.NO_ERRORS || state.oldSignature == state.newSignature) return val refactoringSupport = state.refactoringSupport