diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt index f2b4278a2a4..5095863ccd3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt @@ -69,12 +69,12 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject( }) } -private fun UsageReplacementStrategy.replaceUsages( +fun UsageReplacementStrategy.replaceUsages( usages: Collection, targetPsiElement: PsiElement, project: Project, commandName: String, - postAction: () -> Unit + postAction: () -> Unit = {} ) { GuiUtils.invokeLaterIfNeeded({ project.executeWriteCommand(commandName) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt new file mode 100644 index 00000000000..dd194e1614e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt @@ -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 +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt index ef089ca0775..c2ba916ad59 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt @@ -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() + 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() + } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionProcessor.kt new file mode 100644 index 00000000000..48d21255aa9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionProcessor.kt @@ -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 { + if (inlineThisOnly && reference != null) return arrayOf(UsageInfo(reference)) + val usages = runReadAction { + val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(myProject), myProject) + ReferencesSearch.search(function, searchScope).filterIsInstance() + } + return usages.map(::UsageInfo).toTypedArray() + } + + override fun performRefactoring(usages: Array) { + 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): 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") + } + } +} \ No newline at end of file