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: 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
@@ -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)
}
@@ -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