Introduce dialog for function inlining #KT-6159 Fixed

See KotlinInlineFunctionProcessor and KotlinInlineFunctionDialog
This commit is contained in:
Mikhail Glukhikh
2017-03-23 19:58:27 +03:00
parent b6803af746
commit e79f006659
4 changed files with 197 additions and 6 deletions
@@ -69,12 +69,12 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
})
}
private fun UsageReplacementStrategy.replaceUsages(
fun UsageReplacementStrategy.replaceUsages(
usages: Collection<KtSimpleNameExpression>,
targetPsiElement: PsiElement,
project: Project,
commandName: String,
postAction: () -> Unit
postAction: () -> Unit = {}
) {
GuiUtils.invokeLaterIfNeeded({
project.executeWriteCommand(commandName) {
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.inline
import com.intellij.openapi.help.HelpManager
import com.intellij.openapi.project.Project
import com.intellij.refactoring.HelpID
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.inline.InlineOptionsDialog
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtNamedFunction
// NB: similar to IDEA InlineMethodDialog / KotlinInlineValDialog
class KotlinInlineFunctionDialog(
project: Project,
private val function: KtNamedFunction,
private val reference: KtSimpleNameReference?,
private val replacementStrategy: CallableUsageReplacementStrategy,
private val allowInlineThisOnly: Boolean
) : InlineOptionsDialog(project, true, function) {
private var occurrencesNumber = initOccurrencesNumber(function)
init {
myInvokedOnReference = reference != null
title = RefactoringBundle.message("inline.method.title")
init()
}
override fun allowInlineAll() = true
override fun getNameLabelText(): String {
val occurrencesString =
if (occurrencesNumber > -1) " - $occurrencesNumber occurrence${if (occurrencesNumber == 1) "" else "s"}"
else ""
val methodText = "${function.nameAsSafeName}" + function.valueParameters.joinToString(prefix = "(", postfix = ")") {
"${it.nameAsSafeName}: ${it.typeReference?.text}"
} + (function.getReturnTypeReference()?.let { ": " + it.text } ?: "")
return RefactoringBundle.message("inline.method.method.label", methodText, occurrencesString)
}
override fun getBorderTitle(): String = RefactoringBundle.message("inline.method.border.title")
override fun getInlineThisText(): String = RefactoringBundle.message("this.invocation.only.and.keep.the.method")
override fun getInlineAllText(): String = RefactoringBundle.message(
if (function.isWritable) "all.invocations.and.remove.the.method"
else "all.invocations.in.project"
)
override fun getKeepTheDeclarationText(): String =
if (function.isWritable) RefactoringBundle.message("all.invocations.keep.the.method")
else super.getKeepTheDeclarationText()
public override fun doAction() {
invokeRefactoring(
KotlinInlineFunctionProcessor(project, replacementStrategy, function,
reference, isInlineThisOnly, !isInlineThisOnly && !isKeepTheDeclaration)
)
val settings = JavaRefactoringSettings.getInstance()
if (myRbInlineThisOnly.isEnabled && myRbInlineAll.isEnabled) {
settings.INLINE_METHOD_THIS = isInlineThisOnly
}
}
override fun doHelpAction() =
HelpManager.getInstance().invokeHelp(if (function is KtConstructor<*>) HelpID.INLINE_CONSTRUCTOR else HelpID.INLINE_METHOD)
override fun canInlineThisOnly() = allowInlineThisOnly
override fun isInlineThis() = JavaRefactoringSettings.getInstance().INLINE_METHOD_THIS
}
@@ -16,11 +16,14 @@
package org.jetbrains.kotlin.idea.refactoring.inline
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.lang.Language
import com.intellij.lang.refactoring.InlineActionHandler
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
@@ -30,8 +33,8 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
import org.jetbrains.kotlin.idea.codeInliner.replaceUsagesInWholeProject
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -39,6 +42,7 @@ import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class KotlinInlineFunctionHandler: InlineActionHandler() {
override fun isEnabledForLanguage(language: Language) = language == KotlinLanguage.INSTANCE
@@ -92,8 +96,22 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
replacementBuilder.prepareCodeToInline(bodyCopy, emptyList(), ::analyzeBodyCopy)
}
val commandName = RefactoringBundle.message("inline.command", element.name)
CallableUsageReplacementStrategy(replacement)
.replaceUsagesInWholeProject(element, commandName, commandName, postAction = { element.delete() })
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) }
val nameReference = when (reference) {
is KtSimpleNameReference -> reference
is PsiMultiReference -> reference.references.firstIsInstanceOrNull<KtSimpleNameReference>()
else -> null
}
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
// TODO: allowInlineThisOnly
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy, allowInlineThisOnly = false)
if (!ApplicationManager.getApplication().isUnitTestMode) {
dialog.show()
}
else {
dialog.doAction()
}
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.inline
import com.intellij.lang.findUsages.DescriptiveNameUtil
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.RefactoringBundle
import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewBundle
import com.intellij.usageView.UsageViewDescriptor
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
import org.jetbrains.kotlin.idea.codeInliner.replaceUsages
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
class KotlinInlineFunctionProcessor(
project: Project,
private val replacementStrategy: CallableUsageReplacementStrategy,
private val function: KtNamedFunction,
private val reference: KtSimpleNameReference?,
private val inlineThisOnly: Boolean,
private val deleteAfter: Boolean
) : BaseRefactoringProcessor(project) {
private val commandName = RefactoringBundle.message("inline.method.command", DescriptiveNameUtil.getDescriptiveName(function))
override fun findUsages(): Array<UsageInfo> {
if (inlineThisOnly && reference != null) return arrayOf(UsageInfo(reference))
val usages = runReadAction {
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(myProject), myProject)
ReferencesSearch.search(function, searchScope).filterIsInstance<KtSimpleNameReference>()
}
return usages.map(::UsageInfo).toTypedArray()
}
override fun performRefactoring(usages: Array<out UsageInfo>) {
replacementStrategy.replaceUsages(
usages.mapNotNull { it.element as? KtSimpleNameExpression },
function,
myProject,
commandName,
{ if (deleteAfter) function.delete() }
)
}
override fun getCommandName(): String = commandName
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>): UsageViewDescriptor {
return object : UsageViewDescriptor {
override fun getCommentReferencesText(usagesCount: Int, filesCount: Int) =
RefactoringBundle.message("comments.elements.header", UsageViewBundle.getOccurencesString(usagesCount, filesCount))
override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) =
RefactoringBundle.message("invocations.to.be.inlined", UsageViewBundle.getReferencesString(usagesCount, filesCount))
override fun getElements() = arrayOf(function)
override fun getProcessedElementsHeader(): String =
RefactoringBundle.message("inline.method.elements.header")
}
}
}