fixup! Introduce Type Parameter

This commit is contained in:
Alexey Sedunov
2016-08-31 12:51:28 +03:00
parent 2f9a911624
commit da273419b9
5 changed files with 123 additions and 37 deletions
@@ -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
@@ -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() {
@@ -66,7 +66,9 @@ fun KotlinPsiRange.preview(project: Project, editor: Editor): RangeHighlighter?
fun processDuplicates(
duplicateReplacers: Map<KotlinPsiRange, () -> 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()
@@ -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<KtTypeParameterListOwner>() ?: 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()
}
}
}
@@ -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()
}
}
}
}
}