Rename: Implement popup chooser for overriding members

#KT-13963 Fixed
This commit is contained in:
Alexey Sedunov
2016-10-04 14:43:59 +03:00
parent fe711da594
commit e4dc2ae367
5 changed files with 134 additions and 13 deletions
+1
View File
@@ -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: Support members declared in the companion object of the original class
Pull Up: Show member dependencies in the refactoring dialog 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-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 #### Android Lint
@@ -75,6 +75,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
return false return false
} }
override fun isMemberInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = element is KtNamedDeclaration
override fun getChangeSignatureHandler() = KotlinChangeSignatureHandler() override fun getChangeSignatureHandler() = KotlinChangeSignatureHandler()
override fun getPullUpHandler() = KotlinPullUpHandler() override fun getPullUpHandler() = KotlinPullUpHandler()
@@ -38,10 +38,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.*
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.util.Pass import com.intellij.openapi.util.Pass
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil 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.openapi.vfs.VirtualFile
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.light.LightElement import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.presentation.java.SymbolPresentationUtil
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil 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.ConflictsUtil
import com.intellij.refactoring.util.RefactoringUIUtil import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.ui.components.JBList import com.intellij.ui.components.JBList
import com.intellij.usageView.UsageViewTypeLocation
import com.intellij.util.VisibilityUtil import com.intellij.util.VisibilityUtil
import com.intellij.util.containers.MultiMap import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
@@ -241,7 +241,7 @@ fun reportDeclarationConflict(
conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize())) conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize()))
} }
fun <T, E: PsiElement> getPsiElementPopup( fun <T, E : PsiElement> getPsiElementPopup(
editor: Editor, editor: Editor,
elements: List<T>, elements: List<T>,
renderer: PsiElementListCellRenderer<E>, renderer: PsiElementListCellRenderer<E>,
@@ -270,7 +270,7 @@ fun <T, E: PsiElement> getPsiElementPopup(
processor(elements[index]) processor(elements[index])
} }
} }
addListener(object: JBPopupAdapter() { addListener(object : JBPopupAdapter() {
override fun onClosed(event: LightweightWindowEvent?) { override fun onClosed(event: LightweightWindowEvent?) {
highlighter?.dropHighlight() highlighter?.dropHighlight()
} }
@@ -656,7 +656,7 @@ fun (() -> Any).runRefactoringWithPostprocessing(
) { ) {
val connection = project.messageBus.connect() val connection = project.messageBus.connect()
connection.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC, connection.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC,
object: RefactoringEventListener { object : RefactoringEventListener {
override fun undoRefactoring(refactoringId: String) { override fun undoRefactoring(refactoringId: String) {
} }
@@ -702,7 +702,7 @@ fun <T : Any> Project.runSynchronouslyWithProgress(progressTitle: String, canBeC
fun invokeOnceOnCommandFinish(action: () -> Unit) { fun invokeOnceOnCommandFinish(action: () -> Unit) {
val commandProcessor = CommandProcessor.getInstance() val commandProcessor = CommandProcessor.getInstance()
val listener = object: CommandAdapter() { val listener = object : CommandAdapter() {
override fun beforeCommandFinished(event: CommandEvent?) { override fun beforeCommandFinished(event: CommandEvent?) {
action() action()
commandProcessor.removeCommandListener(this) commandProcessor.removeCommandListener(this)
@@ -752,7 +752,7 @@ fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
return originalList 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) override fun pass(t: T) = body(t)
} }
@@ -887,6 +887,61 @@ fun checkSuperMethods(
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor) 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 { fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pass
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiNamedElement 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.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor 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.checkSuperMethods
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethodsWithPopup
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -125,6 +128,34 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
return substitutedJavaElement 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 { override fun createRenameDialog(project: Project, element: PsiElement, nameSuggestionContext: PsiElement?, editor: Editor?): RenameDialog {
val elementForDialog = (element as? FunctionWithSupersWrapper)?.originalDeclaration ?: element val elementForDialog = (element as? FunctionWithSupersWrapper)?.originalDeclaration ?: element
return object : RenameDialog(project, elementForDialog, nameSuggestionContext, editor) { return object : RenameDialog(project, elementForDialog, nameSuggestionContext, editor) {
@@ -176,7 +207,7 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
} }
private fun wrapPsiMethod(element: PsiElement?): PsiMethod? = when (element) { private fun wrapPsiMethod(element: PsiElement?): PsiMethod? = when (element) {
is KtLightMethod -> element is PsiMethod -> element
is KtNamedFunction, is KtSecondaryConstructor -> runReadAction { is KtNamedFunction, is KtSecondaryConstructor -> runReadAction {
LightClassUtil.getLightClassMethod(element as KtFunction) LightClassUtil.getLightClassMethod(element as KtFunction)
} }
@@ -20,6 +20,7 @@ import com.intellij.navigation.NavigationItem
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.Pass
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.search.SearchScope import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.DirectClassInheritorsSearch 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.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.core.isEnumCompanionPropertyWithEntryConflict 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.refactoring.dropOverrideKeywordIfNecessary
import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference 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.util.findCallableMemberBySignature
import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.singletonOrEmptyList
class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement): Boolean { override fun canProcessElement(element: PsiElement): Boolean {
@@ -81,9 +84,6 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_FIELD = enabled 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?> { private fun getJvmNames(element: PsiElement): Pair<String?, String?> {
val descriptor = (element.unwrapped as? KtDeclaration)?.resolveToDescriptor() as? PropertyDescriptor ?: return null to null val descriptor = (element.unwrapped as? KtDeclaration)?.resolveToDescriptor() as? PropertyDescriptor ?: return null to null
val getterName = descriptor.getter?.let { DescriptorUtils.getJvmName(it) } val getterName = descriptor.getter?.let { DescriptorUtils.getJvmName(it) }
@@ -253,6 +253,38 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
return declarationToRename 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 { class PropertyMethodWrapper(val propertyMethod: PsiMethod) : PsiNamedElement by propertyMethod, NavigationItem by propertyMethod {
override fun getName() = propertyMethod.name override fun getName() = propertyMethod.name
override fun setName(name: String) = this override fun setName(name: String) = this