From 639186ddae51bc61fe1567c63e9ccbb0d7ac9a04 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 24 Aug 2016 17:29:07 +0300 Subject: [PATCH] Convert Receiver to Parameter: Use template instead of the dialog #KT-9490 Fixed --- ChangeLog.md | 2 + .../kotlin/idea/core/psiModificationUtils.kt | 7 ++ .../ConvertReceiverToParameterIntention.kt | 88 ++++++++++++++++--- .../KotlinChangeSignatureData.kt | 18 +--- .../changeSignature/changeSignatureUtils.kt | 24 +++++ 5 files changed, 111 insertions(+), 28 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9e90f9d37c0..94bcf8845c9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -103,6 +103,8 @@ These artifacts include extensions for the types available in the latter JDKs, s #### Intention actions, inspections and quickfixes +- [`KT-9490`](https://youtrack.jetbrains.com/issue/KT-9490) Convert receiver to parameter: use template instead of the dialog + ##### New features - [`KT-11525`](https://youtrack.jetbrains.com/issue/KT-11525) Implement "Create type parameter" quickfix diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt index eab8fbd69a5..50cf812eaa4 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt @@ -285,4 +285,11 @@ fun KtTypeParameterListOwner.addTypeParameter(typeParameter: KtTypeParameter): K else -> null } ?: return null return (addAfter(list, leftAnchor) as KtTypeParameterList).parameters.first() +} + +fun KtNamedFunction.getOrCreateValueParameterList(): KtParameterList { + valueParameterList?.let { return it } + val parameterList = KtPsiFactory(this).createParameterList("()") + val anchor = nameIdentifier ?: funKeyword!! + return addAfter(parameterList, anchor) as KtParameterList } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReceiverToParameterIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReceiverToParameterIntention.kt index 0e0d51a130d..75739588628 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReceiverToParameterIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReceiverToParameterIntention.kt @@ -17,27 +17,43 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.codeInsight.template.Template +import com.intellij.codeInsight.template.TemplateBuilderImpl +import com.intellij.codeInsight.template.TemplateEditingAdapter +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureConfiguration -import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor -import org.jetbrains.kotlin.idea.refactoring.changeSignature.modify -import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.core.getOrCreateValueParameterList +import org.jetbrains.kotlin.idea.refactoring.changeSignature.* +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtTypeReference -import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference class ConvertReceiverToParameterIntention : SelfTargetingOffsetIndependentIntention(KtTypeReference::class.java, "Convert receiver to parameter"), LowPriorityAction { override fun isApplicableTo(element: KtTypeReference): Boolean { return (element.parent as? KtNamedFunction)?.receiverTypeReference == element } - private fun configureChangeSignature(): KotlinChangeSignatureConfiguration { + private fun configureChangeSignature(newName: String? = null): KotlinChangeSignatureConfiguration { return object : KotlinChangeSignatureConfiguration { override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor { - return originalDescriptor.modify { it.receiver = null } + return originalDescriptor.modify { + it.receiver = null + if (newName != null) { + it.parameters.last().name = newName + } + } } + + override fun performSilently(affectedFunctions: Collection) = true } } @@ -45,8 +61,58 @@ class ConvertReceiverToParameterIntention : SelfTargetingOffsetIndependentIntent override fun applyTo(element: KtTypeReference, editor: Editor?) { val function = element.parent as? KtNamedFunction ?: return - val context = function.analyze() - val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, function] as? FunctionDescriptor ?: return - runChangeSignature(element.project, descriptor, configureChangeSignature(), element, text) + val descriptor = function.resolveToDescriptor() as FunctionDescriptor + + val project = function.project + + if (editor != null && !ApplicationManager.getApplication().isUnitTestMode) { + val receiverNames = suggestReceiverNames(project, descriptor) + val defaultReceiverName = receiverNames.first() + val receiverTypeRef = function.receiverTypeReference!! + val psiFactory = KtPsiFactory(element) + val newParameter = psiFactory.createParameter("$defaultReceiverName: Dummy").apply { typeReference!!.replace(receiverTypeRef) } + + project.executeWriteCommand(text) { + function.setReceiverTypeReference(null) + val addedParameter = function.getOrCreateValueParameterList().addParameter(newParameter) + + with(PsiDocumentManager.getInstance(project)) { + commitDocument(editor.document) + doPostponedOperationsAndUnblockDocument(editor.document) + } + + editor.caretModel.moveToOffset(function.startOffset) + + val templateBuilder = TemplateBuilderImpl(function) + templateBuilder.replaceElement(addedParameter.nameIdentifier!!, ChooseStringExpression(receiverNames)) + TemplateManager.getInstance(project).startTemplate( + editor, + templateBuilder.buildInlineTemplate(), + object: TemplateEditingAdapter() { + private fun revertChanges() { + runWriteAction { + function.setReceiverTypeReference(addedParameter.typeReference) + function.valueParameterList!!.removeParameter(addedParameter) + } + } + + override fun templateFinished(template: Template?, brokenOff: Boolean) { + val newName = addedParameter.name + revertChanges() + if (!brokenOff) { + runChangeSignature(element.project, descriptor, configureChangeSignature(newName), function.receiverTypeReference!!, text) + } + } + + override fun templateCancelled(template: Template?) { + revertChanges() + } + } + ) + } + } + else { + runChangeSignature(element.project, descriptor, configureChangeSignature(), element, text) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureData.kt index 95222903574..d176a642d4d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureData.kt @@ -26,23 +26,15 @@ import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.asJava.toLightMethods import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde -import org.jetbrains.kotlin.idea.core.CollectingNameValidator -import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.idea.util.getResolutionScope -import org.jetbrains.kotlin.incremental.components.NoLookupLocation -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.scopes.utils.findVariable import java.util.* class KotlinChangeSignatureData( @@ -80,16 +72,8 @@ class KotlinChangeSignatureData( } private fun createReceiverInfoIfNeeded(): KotlinParameterInfo? { - val callable = baseDeclaration as? KtCallableDeclaration ?: return null - val bodyScope = (callable as? KtFunction)?.bodyExpression?.let { it.getResolutionScope(it.analyze(), it.getResolutionFacade()) } - val paramNames = baseDescriptor.valueParameters.map { it.name.asString() } - val validator = bodyScope?.let { bodyScope -> - CollectingNameValidator(paramNames) { - bodyScope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null - } - } ?: CollectingNameValidator(paramNames) val receiverType = baseDescriptor.extensionReceiverParameter?.type ?: return null - val receiverName = KotlinNameSuggester.suggestNamesByType(receiverType, validator, "receiver").first() + val receiverName = suggestReceiverNames(baseDeclaration.project, baseDescriptor).first() val receiverTypeText = (baseDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.text ?: IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType) return KotlinParameterInfo(callableDescriptor = baseDescriptor, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/changeSignatureUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/changeSignatureUtils.kt index 0956b9ac06e..b66174716ad 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/changeSignatureUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/changeSignatureUtils.kt @@ -16,19 +16,30 @@ package org.jetbrains.kotlin.idea.refactoring.changeSignature +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.refactoring.changeSignature.CallerUsageInfo import com.intellij.refactoring.changeSignature.OverriderUsageInfo import com.intellij.usageView.UsageInfo +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.CollectingNameValidator +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.DeferredJavaMethodKotlinCallerUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JavaMethodKotlinUsageWithDelegate import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.resolve.scopes.utils.findVariable import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.substitutions.getCallableSubstitutor import org.jetbrains.kotlin.types.typeUtil.asTypeProjection @@ -95,3 +106,16 @@ private object ForceTypeCopySubstitution : TypeSubstitution() { override fun isEmpty() = false } + +fun suggestReceiverNames(project: Project, descriptor: CallableDescriptor): List { + val callable = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? KtCallableDeclaration ?: return emptyList() + val bodyScope = (callable as? KtFunction)?.bodyExpression?.let { it.getResolutionScope(it.analyze(), it.getResolutionFacade()) } + val paramNames = descriptor.valueParameters.map { it.name.asString() } + val validator = bodyScope?.let { bodyScope -> + CollectingNameValidator(paramNames) { + bodyScope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null + } + } ?: CollectingNameValidator(paramNames) + val receiverType = descriptor.extensionReceiverParameter?.type ?: return emptyList() + return KotlinNameSuggester.suggestNamesByType(receiverType, validator, "receiver") +} \ No newline at end of file