diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringBundle.properties b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringBundle.properties index f0bfb8de2fc..366dfa9e668 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringBundle.properties +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringBundle.properties @@ -1,6 +1,5 @@ cannot.refactor.not.expression=Cannot find an expression to introduce cannot.refactor.not.expression.to.extract=Cannot find an expression to extract -expressions.title=Expressions introduce.variable=Introduce Variable introduce.property=Introduce Property introduce.parameter=Introduce Parameter @@ -25,7 +24,7 @@ cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}' error.types.in.generated.function=Cannot generate function with erroneous return type cannot.introduce.parameter.of.0.type=Cannot introduce parameter of type ''{0}'' -0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration={0} has detected {1} code {1,choice,1#fragment|2#fragments} in this file that can be replaced with a usage of extracted declaration. Would you like to review and replace {1,choice,1#it|2#them}? +0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3={0} has detected {1} code {1,choice,1#fragment|2#fragments} in {2} that can be replaced with {3}. Would you like to review and replace {1,choice,1#it|2#them}? error.wrong.caret.position.function.or.constructor.name=The caret should be positioned at the name of the function or constructor to be refactored. error.cant.refactor.vararg.functions=Can't refactor the function with variable arguments diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringUtil.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringUtil.java index c6f48e797c5..fc297ae5d5a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringUtil.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringUtil.java @@ -522,8 +522,21 @@ public class KotlinRefactoringUtil { } }); + String title = "Elements"; + if (elementKinds.size() == 1) { + switch (elementKinds.iterator().next()) { + case EXPRESSION: + title = "Expressions"; + break; + case TYPE_ELEMENT: + case TYPE_CONSTRUCTOR: + title = "Types"; + break; + } + } + JBPopupFactory.getInstance().createListPopupBuilder(list). - setTitle(KotlinRefactoringBundle.message("expressions.title")).setMovable(false).setResizable(false). + setTitle(title).setMovable(false).setResizable(false). setRequestFocus(true).setItemChoosenCallback(new Runnable() { @Override public void run() { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt index 90963ab0831..552cf4c2d97 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt @@ -66,7 +66,9 @@ fun KotlinPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? fun processDuplicates( duplicateReplacers: Map Unit>, project: Project, - editor: Editor + editor: Editor, + scopeDescription: String = "this file", + usageDescription: String = "a usage of extracted declaration" ) { val size = duplicateReplacers.size if (size == 0) return @@ -81,9 +83,11 @@ fun processDuplicates( Messages.showYesNoDialog( project, KotlinRefactoringBundle.message( - "0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration", + "0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3", ApplicationNamesInfo.getInstance().productName, - duplicateReplacers.size + duplicateReplacers.size, + scopeDescription, + usageDescription ), "Process Duplicates", Messages.getQuestionIcon() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeParameter/KotlinIntroduceTypeParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeParameter/KotlinIntroduceTypeParameterHandler.kt index c5108244de2..3bab72d025b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeParameter/KotlinIntroduceTypeParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeParameter/KotlinIntroduceTypeParameterHandler.kt @@ -24,12 +24,14 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.project.Project +import com.intellij.psi.ElementDescriptionUtil import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.refactoring.RefactoringActionHandler -import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler +import com.intellij.usageView.UsageViewTypeLocation import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.core.CollectingNameValidator import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.CreateTypeParameterByRefActionFactory import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.CreateTypeParameterFromUsageFix @@ -39,6 +41,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processD import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint +import org.jetbrains.kotlin.idea.refactoring.rename.VariableInplaceRenameHandlerWithFinishHook import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.getResolutionScope @@ -50,6 +53,10 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtTypeElement import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier @@ -77,10 +84,15 @@ object KotlinIntroduceTypeParameterHandler : RefactoringActionHandler { val typeElementToExtract = elements.singleOrNull() as? KtTypeElement ?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME) + val typeElementToExtractPointer = typeElementToExtract.createSmartPointer() + val scope = targetOwner.getResolutionScope() - val suggestedNames = KotlinNameSuggester.suggestNamesForTypeParameters(1) { - scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null - } + val suggestedNames = KotlinNameSuggester.suggestNamesForTypeParameters( + 1, + CollectingNameValidator(targetOwner.typeParameters.mapNotNull { it.name }) { + scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null + } + ) val defaultName = suggestedNames.single() val context = typeElementToExtract.analyze(BodyResolveMode.PARTIAL) @@ -91,40 +103,60 @@ object KotlinIntroduceTypeParameterHandler : RefactoringActionHandler { ?.copy(upperBoundType = originalType, declaration = targetOwner) ?: return showErrorHint(project, editor, "Refactoring is not applicable in the current context", REFACTORING_NAME) - val parameterRefElement = KtPsiFactory(project).createType(defaultName).typeElement!! - - val duplicateRanges = typeElementToExtract - .toRange() - .match(targetParent, KotlinPsiUnifier.DEFAULT) - .filterNot { - val textRange = it.range.getTextRange() - typeElementToExtract.textRange.intersects(textRange) || targetOwner.typeParameterList?.textRange?.intersects(textRange) ?: false - } - .mapNotNull { it.range.elements.toRange() } - project.executeCommand(REFACTORING_NAME) { - runWriteAction { - typeElementToExtract.replace(parameterRefElement) - - processDuplicates( - duplicateRanges.keysToMap { - { - it.elements.singleOrNull()?.replace(parameterRefElement) - Unit - } - }, - project, - editor - ) - } - val newTypeParameter = CreateTypeParameterFromUsageFix(typeElementToExtract, createTypeParameterData).doInvoke() ?: return@executeCommand + val newTypeParameterPointer = newTypeParameter.createSmartPointer() + + val postRename = postRename@ { + val restoredTypeParameter = newTypeParameterPointer.element ?: return@postRename + val restoredOwner = restoredTypeParameter.getStrictParentOfType() ?: return@postRename + val restoredOriginalTypeElement = typeElementToExtractPointer.element ?: return@postRename + + runWriteAction { + val parameterRefElement = KtPsiFactory(project).createType(restoredTypeParameter.name ?: "_").typeElement!! + + val duplicateRanges = restoredOriginalTypeElement + .toRange() + .match(restoredOwner, KotlinPsiUnifier.DEFAULT) + .filterNot { + val textRange = it.range.getTextRange() + restoredOriginalTypeElement.textRange.intersects(textRange) + || restoredOwner.typeParameterList?.textRange?.intersects(textRange) ?: false + } + .mapNotNull { it.range.elements.toRange() } + + restoredOriginalTypeElement.replace(parameterRefElement) + + processDuplicates( + duplicateRanges.keysToMap { + { + it.elements.singleOrNull()?.replace(parameterRefElement) + Unit + } + }, + project, + editor, + ElementDescriptionUtil.getElementDescription(restoredOwner, UsageViewTypeLocation.INSTANCE) + " '${restoredOwner.name}'", + "a reference to extracted type parameter" + ) + + restoredTypeParameter.extendsBound?.let { + editor.selectionModel.setSelection(it.startOffset, it.endOffset) + editor.caretModel.moveToOffset(it.startOffset) + } + } + } if (!ApplicationManager.getApplication().isUnitTestMode) { val dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PSI_ELEMENT.name, newTypeParameter, (editor as? EditorEx)?.dataContext) - VariableInplaceRenameHandler().doRename(newTypeParameter, editor, dataContext) + editor.selectionModel.removeSelection() + editor.caretModel.moveToOffset(newTypeParameter.startOffset) + VariableInplaceRenameHandlerWithFinishHook(postRename).doRename(newTypeParameter, editor, dataContext) + } + else { + postRename() } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/VariableInplaceRenameHandlerWithFinishHook.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/VariableInplaceRenameHandlerWithFinishHook.kt new file mode 100644 index 00000000000..f1f04d392a4 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/VariableInplaceRenameHandlerWithFinishHook.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2016 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.rename + +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement +import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer + +class VariableInplaceRenameHandlerWithFinishHook(private val onFinish: () -> Unit) : VariableInplaceRenameHandler() { + override fun createRenamer(elementToRename: PsiElement, editor: Editor?): VariableInplaceRenamer? { + return object : VariableInplaceRenamer(elementToRename as PsiNamedElement, editor) { + override fun performRefactoring(): Boolean { + try { + return super.performRefactoring() + } + finally { + onFinish() + } + } + } + } +} \ No newline at end of file