Rename: Implement popup chooser for overriding members
#KT-13963 Fixed
This commit is contained in:
@@ -246,6 +246,7 @@ Pull Up: Support properties declared in the primary constructor
|
||||
Pull Up: Support members declared in the companion object of the original class
|
||||
Pull Up: Show member dependencies in the refactoring dialog
|
||||
- [`KT-9485`](https://youtrack.jetbrains.com/issue/KT-9485) Push Down: Support moving members from Java to Kotlin class
|
||||
- [`KT-13963`](https://youtrack.jetbrains.com/issue/KT-13963) Rename: Implement popup chooser for overriding members
|
||||
|
||||
#### Android Lint
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isMemberInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = element is KtNamedDeclaration
|
||||
|
||||
override fun getChangeSignatureHandler() = KotlinChangeSignatureHandler()
|
||||
|
||||
override fun getPullUpHandler() = KotlinPullUpHandler()
|
||||
|
||||
@@ -38,10 +38,7 @@ import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.ui.popup.JBPopup
|
||||
import com.intellij.openapi.ui.popup.JBPopupAdapter
|
||||
import com.intellij.openapi.ui.popup.LightweightWindowEvent
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder
|
||||
import com.intellij.openapi.ui.popup.*
|
||||
import com.intellij.openapi.util.Pass
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
@@ -49,6 +46,7 @@ import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.light.LightElement
|
||||
import com.intellij.psi.presentation.java.SymbolPresentationUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
|
||||
@@ -58,10 +56,12 @@ import com.intellij.refactoring.ui.ConflictsDialog
|
||||
import com.intellij.refactoring.util.ConflictsUtil
|
||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.usageView.UsageViewTypeLocation
|
||||
import com.intellij.util.VisibilityUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||
@@ -241,7 +241,7 @@ fun reportDeclarationConflict(
|
||||
conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize()))
|
||||
}
|
||||
|
||||
fun <T, E: PsiElement> getPsiElementPopup(
|
||||
fun <T, E : PsiElement> getPsiElementPopup(
|
||||
editor: Editor,
|
||||
elements: List<T>,
|
||||
renderer: PsiElementListCellRenderer<E>,
|
||||
@@ -270,7 +270,7 @@ fun <T, E: PsiElement> getPsiElementPopup(
|
||||
processor(elements[index])
|
||||
}
|
||||
}
|
||||
addListener(object: JBPopupAdapter() {
|
||||
addListener(object : JBPopupAdapter() {
|
||||
override fun onClosed(event: LightweightWindowEvent?) {
|
||||
highlighter?.dropHighlight()
|
||||
}
|
||||
@@ -656,7 +656,7 @@ fun (() -> Any).runRefactoringWithPostprocessing(
|
||||
) {
|
||||
val connection = project.messageBus.connect()
|
||||
connection.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC,
|
||||
object: RefactoringEventListener {
|
||||
object : RefactoringEventListener {
|
||||
override fun undoRefactoring(refactoringId: String) {
|
||||
|
||||
}
|
||||
@@ -702,7 +702,7 @@ fun <T : Any> Project.runSynchronouslyWithProgress(progressTitle: String, canBeC
|
||||
|
||||
fun invokeOnceOnCommandFinish(action: () -> Unit) {
|
||||
val commandProcessor = CommandProcessor.getInstance()
|
||||
val listener = object: CommandAdapter() {
|
||||
val listener = object : CommandAdapter() {
|
||||
override fun beforeCommandFinished(event: CommandEvent?) {
|
||||
action()
|
||||
commandProcessor.removeCommandListener(this)
|
||||
@@ -752,7 +752,7 @@ fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
|
||||
return originalList
|
||||
}
|
||||
|
||||
fun <T> Pass(body: (T) -> Unit) = object: Pass<T>() {
|
||||
fun <T> Pass(body: (T) -> Unit) = object : Pass<T>() {
|
||||
override fun pass(t: T) = body(t)
|
||||
}
|
||||
|
||||
@@ -887,6 +887,61 @@ fun checkSuperMethods(
|
||||
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor)
|
||||
}
|
||||
|
||||
fun checkSuperMethodsWithPopup(
|
||||
declaration: KtNamedDeclaration,
|
||||
deepestSuperMethods: List<PsiMethod>,
|
||||
actionString: String,
|
||||
editor: Editor,
|
||||
action: (List<PsiElement>) -> Unit
|
||||
) {
|
||||
if (deepestSuperMethods.isEmpty()) return action(listOf(declaration))
|
||||
|
||||
val superMethod = deepestSuperMethods.first()
|
||||
|
||||
val superClass = superMethod.containingClass ?: return action(listOf(declaration))
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return action(deepestSuperMethods)
|
||||
|
||||
val kind = when (declaration) {
|
||||
is KtNamedFunction -> "function"
|
||||
is KtProperty, is KtParameter -> "property"
|
||||
else -> return
|
||||
}
|
||||
|
||||
val unwrappedSupers = deepestSuperMethods.mapNotNull { it.namedUnwrappedElement }
|
||||
val hasJavaMethods = unwrappedSupers.any { it is PsiMethod }
|
||||
val hasKtMembers = unwrappedSupers.any { it is KtNamedDeclaration }
|
||||
val superKind = when {
|
||||
hasJavaMethods && hasKtMembers -> "member"
|
||||
hasJavaMethods -> "method"
|
||||
else -> kind
|
||||
}
|
||||
|
||||
val renameBase = actionString + " base $superKind" + (if (deepestSuperMethods.size > 1) "s" else "")
|
||||
val renameCurrent = actionString + " only current $kind"
|
||||
val title = buildString {
|
||||
append(declaration.name)
|
||||
append(if (superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) " implements " else " overrides ")
|
||||
append(ElementDescriptionUtil.getElementDescription(superMethod, UsageViewTypeLocation.INSTANCE))
|
||||
append(" of ")
|
||||
append(SymbolPresentationUtil.getSymbolPresentableText(superClass))
|
||||
}
|
||||
val list = JBList(renameBase, renameCurrent)
|
||||
JBPopupFactory.getInstance()
|
||||
.createListPopupBuilder(list)
|
||||
.setTitle(title)
|
||||
.setMovable(false)
|
||||
.setResizable(false)
|
||||
.setRequestFocus(true)
|
||||
.setItemChoosenCallback {
|
||||
val value = list.selectedValue as? String ?: return@setItemChoosenCallback
|
||||
val chosenElements = if (value == renameBase) deepestSuperMethods + declaration else listOf(declaration)
|
||||
action(chosenElements)
|
||||
}
|
||||
.createPopup()
|
||||
.showInBestPositionFor(editor)
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
|
||||
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
|
||||
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
|
||||
|
||||
+32
-1
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.refactoring.rename
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Pass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
@@ -37,7 +38,9 @@ import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.refactoring.Pass
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethods
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethodsWithPopup
|
||||
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
@@ -125,6 +128,34 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
|
||||
return substitutedJavaElement
|
||||
}
|
||||
|
||||
override fun substituteElementToRename(element: PsiElement, editor: Editor, renameCallback: Pass<PsiElement>) {
|
||||
fun preprocessAndPass(substitutedJavaElement: PsiElement) {
|
||||
val elementToProcess = if (substitutedJavaElement is KtLightMethod && element is KtDeclaration) {
|
||||
substitutedJavaElement.kotlinOrigin as? KtNamedFunction
|
||||
}
|
||||
else {
|
||||
substitutedJavaElement
|
||||
}
|
||||
renameCallback.pass(elementToProcess)
|
||||
}
|
||||
|
||||
val wrappedMethod = wrapPsiMethod(element) ?: return
|
||||
|
||||
val deepestSuperMethods = wrappedMethod.findDeepestSuperMethods()
|
||||
when {
|
||||
deepestSuperMethods.isEmpty() -> return
|
||||
wrappedMethod.isConstructor || element !is KtNamedFunction -> {
|
||||
javaMethodProcessorInstance.substituteElementToRename(wrappedMethod, editor, Pass(::preprocessAndPass))
|
||||
}
|
||||
else -> {
|
||||
val declaration = element.unwrapped as? KtNamedDeclaration ?: return
|
||||
checkSuperMethodsWithPopup(declaration, deepestSuperMethods.toList(), "Rename", editor) {
|
||||
preprocessAndPass(if (it.size > 1) FunctionWithSupersWrapper(element, it) else wrappedMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRenameDialog(project: Project, element: PsiElement, nameSuggestionContext: PsiElement?, editor: Editor?): RenameDialog {
|
||||
val elementForDialog = (element as? FunctionWithSupersWrapper)?.originalDeclaration ?: element
|
||||
return object : RenameDialog(project, elementForDialog, nameSuggestionContext, editor) {
|
||||
@@ -176,7 +207,7 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
|
||||
}
|
||||
|
||||
private fun wrapPsiMethod(element: PsiElement?): PsiMethod? = when (element) {
|
||||
is KtLightMethod -> element
|
||||
is PsiMethod -> element
|
||||
is KtNamedFunction, is KtSecondaryConstructor -> runReadAction {
|
||||
LightClassUtil.getLightClassMethod(element as KtFunction)
|
||||
}
|
||||
|
||||
+35
-3
@@ -20,6 +20,7 @@ import com.intellij.navigation.NavigationItem
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.Pass
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch
|
||||
@@ -43,6 +44,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
|
||||
import org.jetbrains.kotlin.idea.core.isEnumCompanionPropertyWithEntryConflict
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethodsWithPopup
|
||||
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
@@ -62,6 +64,7 @@ import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||
import org.jetbrains.kotlin.utils.DFS
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
|
||||
class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
|
||||
override fun canProcessElement(element: PsiElement): Boolean {
|
||||
@@ -81,9 +84,6 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
|
||||
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_FIELD = enabled
|
||||
}
|
||||
|
||||
/* Can't properly update getters and setters in Java */
|
||||
override fun isInplaceRenameSupported() = false
|
||||
|
||||
private fun getJvmNames(element: PsiElement): Pair<String?, String?> {
|
||||
val descriptor = (element.unwrapped as? KtDeclaration)?.resolveToDescriptor() as? PropertyDescriptor ?: return null to null
|
||||
val getterName = descriptor.getter?.let { DescriptorUtils.getJvmName(it) }
|
||||
@@ -253,6 +253,38 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
|
||||
return declarationToRename
|
||||
}
|
||||
|
||||
override fun substituteElementToRename(element: PsiElement, editor: Editor, renameCallback: Pass<PsiElement>) {
|
||||
val namedUnwrappedElement = element.namedUnwrappedElement ?: return
|
||||
|
||||
val callableDeclaration = namedUnwrappedElement as? KtCallableDeclaration
|
||||
?: throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
|
||||
|
||||
fun preprocessAndPass(substitutedJavaElement: PsiElement) {
|
||||
val (getterJvmName, setterJvmName) = getJvmNames(namedUnwrappedElement)
|
||||
val elementToProcess = if (element is KtLightMethod) {
|
||||
val name = element.name
|
||||
if (element.name != getterJvmName && element.name != setterJvmName) {
|
||||
substitutedJavaElement
|
||||
}
|
||||
else {
|
||||
substitutedJavaElement.toLightMethods().firstOrNull { it.name == name }
|
||||
}
|
||||
}
|
||||
else substitutedJavaElement
|
||||
renameCallback.pass(elementToProcess)
|
||||
}
|
||||
|
||||
val deepestSuperDeclaration = findDeepestOverriddenDeclaration(callableDeclaration)
|
||||
if (deepestSuperDeclaration == null || deepestSuperDeclaration == callableDeclaration) {
|
||||
return preprocessAndPass(callableDeclaration)
|
||||
}
|
||||
|
||||
val superPsiMethods = deepestSuperDeclaration.getRepresentativeLightMethod().singletonOrEmptyList()
|
||||
checkSuperMethodsWithPopup(callableDeclaration, superPsiMethods, "Rename", editor) {
|
||||
preprocessAndPass(if (it.size > 1) deepestSuperDeclaration else callableDeclaration)
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyMethodWrapper(val propertyMethod: PsiMethod) : PsiNamedElement by propertyMethod, NavigationItem by propertyMethod {
|
||||
override fun getName() = propertyMethod.name
|
||||
override fun setName(name: String) = this
|
||||
|
||||
Reference in New Issue
Block a user