Introduce Type Alias

#KT-12902 Fixed
This commit is contained in:
Alexey Sedunov
2016-07-12 15:49:08 +03:00
parent ce0e5b4b46
commit 130e4fb745
63 changed files with 1482 additions and 90 deletions
+6
View File
@@ -157,6 +157,12 @@
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
</action>
<action id="IntroduceTypeAlias" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasAction"
text="Type _Alias...">
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift A"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunctionToScope"/>
</action>
<!-- Kotlin Console REPL-->
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
text="Kotlin REPL"
@@ -6,6 +6,7 @@ introduce.property=Introduce Property
introduce.parameter=Introduce Parameter
cannot.refactor.no.container=Cannot refactor in this place
cannot.refactor.no.expression=Cannot perform refactoring without an expression
cannot.refactor.no.type=Cannot perform refactoring without a type
cannot.refactor.syntax.errors=Cannot refactor due to erroneous code
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
cannot.refactor.package.expression=Cannot introduce package reference
@@ -24,11 +24,11 @@ import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
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.codeInsight.KotlinFileReferencesResolver
import org.jetbrains.kotlin.idea.core.compareDescriptors
import org.jetbrains.kotlin.idea.refactoring.getContextForContainingDeclarationBody
import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis
@@ -114,7 +114,7 @@ data class ExtractionData(
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
val bindingContext: BindingContext? by lazy { commonParent.analyze() }
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
@@ -0,0 +1,106 @@
/*
* 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.introduce.introduceTypeAlias
import com.intellij.lang.refactoring.RefactoringSupportProvider
import com.intellij.openapi.actionSystem.DataContext
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.PsiFile
import com.intellij.refactoring.RefactoringActionHandler
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeElement
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
@JvmField
val REFACTORING_NAME = "Introduce Type Alias"
fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit) {
selectElementsWithTargetSibling(
REFACTORING_NAME,
editor,
file,
"Select target code block",
CodeInsightUtils.ElementKind.TYPE_ELEMENT,
{ elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) },
continuation
)
}
private fun runRefactoring(descriptor: IntroduceTypeAliasDescriptor, project: Project, editor: Editor) {
val typeAlias = project.executeWriteCommand<KtTypeAlias>(REFACTORING_NAME) { descriptor.generateTypeAlias() }
val duplicateReplacers = findDuplicates(typeAlias)
if (duplicateReplacers.isNotEmpty()) {
processDuplicates(duplicateReplacers, project, editor)
}
}
fun doInvoke(
project: Project,
editor: Editor,
elements: List<PsiElement>,
targetSibling: PsiElement,
descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null
) {
val typeElement = elements.singleOrNull() as? KtTypeElement
?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME)
val introduceData = IntroduceTypeAliasData(typeElement, targetSibling)
val analysisResult = introduceData.analyze()
when (analysisResult) {
is IntroduceTypeAliasAnalysisResult.Error -> {
return showErrorHint(project, editor, analysisResult.message, REFACTORING_NAME)
}
is IntroduceTypeAliasAnalysisResult.Success -> {
val originalDescriptor = analysisResult.descriptor
if (ApplicationManager.getApplication().isUnitTestMode) {
val (descriptor, conflicts) = descriptorSubstitutor!!(originalDescriptor).validate()
project.checkConflictsInteractively(conflicts) { runRefactoring(descriptor, project, editor) }
}
else {
KotlinIntroduceTypeAliasDialog(project, originalDescriptor) { runRefactoring(it.currentDescriptor, project, editor) }.show()
}
}
}
}
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
if (file !is KtFile) return
selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling) }
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
throw AssertionError("$REFACTORING_NAME can only be invoked from editor")
}
}
class IntroduceTypeAliasAction : AbstractIntroduceAction() {
override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler
}
@@ -0,0 +1,80 @@
/*
* 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.introduce.introduceTypeAlias
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class TypeReferenceInfo(val reference: KtTypeReference, val type: KotlinType)
internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDataProperty(Key.create("RESOLVE_INFO"))
class IntroduceTypeAliasData(
val originalType: KtTypeElement,
val targetSibling: PsiElement
) : Disposable {
val resolutionFacade = originalType.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL)
init {
markReferences()
}
private fun markReferences() {
val visitor = object : KtTreeVisitorVoid() {
override fun visitTypeReference(typeReference: KtTypeReference) {
val typeElement = typeReference.typeElement ?: return
val kotlinType = bindingContext[BindingContext.ABBREVIATED_TYPE, typeReference] ?:
bindingContext[BindingContext.TYPE, typeReference] ?:
return
typeReference.resolveInfo = TypeReferenceInfo(typeReference, kotlinType)
typeElement.typeArgumentsAsTypes.forEach { it.accept(this) }
}
}
(originalType.parent as? KtTypeReference ?: originalType).accept(visitor)
}
override fun dispose() {
if (!originalType.isValid) return
originalType.forEachDescendantOfType<KtTypeReference> { it.resolveInfo = null }
}
}
data class TypeParameter(val name: String, val typeReferenceInfos: Collection<TypeReferenceInfo>)
data class IntroduceTypeAliasDescriptor(
val originalData: IntroduceTypeAliasData,
val name: String,
val visibility: KtModifierKeywordToken?,
val typeParameters: List<TypeParameter>
)
data class IntroduceTypeAliasDescriptorWithConflicts(
val descriptor: IntroduceTypeAliasDescriptor,
val conflicts: MultiMap<PsiElement, String>
)
@@ -0,0 +1,235 @@
/*
* 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.introduce.introduceTypeAlias
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.util.containers.LinkedMultiMap
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.compareDescriptors
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtTokens.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.utils.SmartList
sealed class IntroduceTypeAliasAnalysisResult {
class Error(val message: String) : IntroduceTypeAliasAnalysisResult()
class Success(val descriptor: IntroduceTypeAliasDescriptor) : IntroduceTypeAliasAnalysisResult()
}
private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade)
fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
val psiFactory = KtPsiFactory(originalType)
val contextExpression = originalType.getStrictParentOfType<KtExpression>()!!
val targetScope = getTargetScope()
val dummyVar = psiFactory.createProperty("val a: Int").apply {
typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType))
}
val newReferences = dummyVar.typeReference!!.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null }
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
val project = originalType.project
val unifier = KotlinPsiUnifier.DEFAULT
val groupedBrokenReferences = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
for (newReference in newReferences) {
val resolveInfo = newReference.resolveInfo!!
val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor
val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue
val equivalenceRepresentative = groupedBrokenReferences
.keySet()
.firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched }
if (equivalenceRepresentative != null) {
groupedBrokenReferences.putValue(equivalenceRepresentative, resolveInfo)
}
else {
groupedBrokenReferences.putValue(resolveInfo, resolveInfo)
}
val brokenReferenceInfoIterator = groupedBrokenReferences.values().iterator()
while (brokenReferenceInfoIterator.hasNext()) {
val brokenReferenceInfo = brokenReferenceInfoIterator.next()
if (resolveInfo.reference.isAncestor(brokenReferenceInfo.reference, true)) {
brokenReferenceInfoIterator.remove()
}
}
}
val typeParameterNameValidator = CollectingNameValidator()
val brokenReferences = groupedBrokenReferences.keySet().filter { groupedBrokenReferences[it].isNotEmpty() }
val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator)
val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedBrokenReferences[it.second]) }
if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalType } }) {
return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined")
}
return IntroduceTypeAliasAnalysisResult.Success(IntroduceTypeAliasDescriptor(this, "", null, typeParameters))
}
fun IntroduceTypeAliasData.getApplicableVisibilities(): List<KtModifierKeywordToken>{
val parent = targetSibling.parent
return when (parent) {
is KtClassBody -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD)
is KtFile -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD)
else -> emptyList()
}
}
fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>()
val originalType = originalData.originalType
if (name.isEmpty()) {
conflicts.putValue(originalType, "No name provided for type alias")
}
else if (!KotlinNameSuggester.isIdentifier(name)) {
conflicts.putValue(originalType, "Type alias name must be a valid identifier: $name")
}
else if (originalData.getTargetScope().findClassifier(Name.identifier(name), NoLookupLocation.FROM_IDE) != null) {
conflicts.putValue(originalType, "Type $name already exists in the target scope")
}
if (typeParameters.distinctBy { it.name }.size != typeParameters.size) {
conflicts.putValue(originalType, "Type parameter names must be distinct")
}
if (visibility != null && visibility !in originalData.getApplicableVisibilities()) {
conflicts.putValue(originalType, "'$visibility' is not allowed in the target context")
}
return IntroduceTypeAliasDescriptorWithConflicts(this, conflicts)
}
fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
val aliasName = typeAlias.name ?: return emptyMap()
val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor
val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) }
val unifier = KotlinPsiUnifier(unifierParameters)
val psiFactory = KtPsiFactory(typeAlias)
fun replaceOccurrence(occurrence: KtTypeElement, arguments: List<KtTypeElement>) {
val typeText = if (arguments.isNotEmpty()) "$aliasName<${arguments.joinToString { it.text }}>" else aliasName
occurrence.replace(psiFactory.createType(typeText).typeElement!!)
}
val aliasRange = typeAlias.textRange
return typeAlias
.getTypeReference()
?.typeElement
.toRange()
.match(typeAlias.parent, unifier)
.asSequence()
.filter { !(it.range.getTextRange().intersects(aliasRange)) }
.mapNotNull { match ->
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNull null
val arguments = unifierParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement }
if (arguments.size != unifierParameters.size) return@mapNotNull null
match.range to { replaceOccurrence(occurrence, arguments) }
}
.toMap()
}
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
val originalType = originalData.originalType
val targetSibling = originalData.targetSibling
val psiFactory = KtPsiFactory(originalType)
for (typeParameter in typeParameters)
for (it in typeParameter.typeReferenceInfos) {
it.reference.typeParameterInfo = typeParameter
}
val typeAlias = psiFactory.createTypeAlias(name, typeParameters.map { it.name }, originalType)
if (visibility != null && visibility != KtTokens.DEFAULT_VISIBILITY_KEYWORD) {
typeAlias.addModifier(visibility)
}
for (typeParameter in typeParameters)
for (it in typeParameter.typeReferenceInfos) {
it.reference.typeParameterInfo = null
}
fun replaceUsage() {
val aliasInstanceText = if (typeParameters.isNotEmpty()) {
"$name<${typeParameters.joinToString { it.typeReferenceInfos.first().reference.text }}>"
}
else {
name
}
originalType.replace(psiFactory.createType(aliasInstanceText).typeElement!!)
}
fun introduceTypeParameters() {
typeAlias.getTypeReference()!!.forEachDescendantOfType<KtTypeReference> {
val typeParameter = it.typeParameterInfo ?: return@forEachDescendantOfType
val typeParameterReference = psiFactory.createType(typeParameter.name)
it.replace(typeParameterReference)
}
}
fun insertDeclaration(): KtTypeAlias {
val targetParent = originalData.targetSibling.parent
val anchorCandidates = SmartList<PsiElement>()
anchorCandidates.add(targetSibling)
if (targetSibling is KtEnumEntry) {
anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
}
val anchor = anchorCandidates.minBy { it.startOffset }!!.parentsWithSelf.first { it.parent == targetParent }
val targetContainer = anchor.parent!!
return (targetContainer.addBefore(typeAlias, anchor) as KtTypeAlias).apply {
targetContainer.addBefore(psiFactory.createWhiteSpace("\n\n"), anchor)
}
}
return if (previewOnly) {
introduceTypeParameters()
typeAlias
}
else {
replaceUsage()
introduceTypeParameters()
insertDeclaration()
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2015 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.introduce.introduceTypeAlias.ui
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter
import org.jetbrains.kotlin.idea.refactoring.introduce.ui.AbstractParameterTablePanel
import java.util.*
open class IntroduceTypeAliasParameterTablePanel : AbstractParameterTablePanel<TypeParameter, IntroduceTypeAliasParameterTablePanel.TypeParameterInfo>() {
class TypeParameterInfo(
originalParameter: TypeParameter
) : AbstractParameterTablePanel.AbstractParameterInfo<TypeParameter>(originalParameter) {
init {
name = originalParameter.name
}
override fun toParameter() = originalParameter.copy(name)
}
fun init(parameters: List<TypeParameter>) {
parameterInfos = parameters.mapTo(ArrayList()) { TypeParameterInfo(it) }
super.init()
}
val selectedTypeParameterInfos: List<TypeParameterInfo>
get() = parameterInfos.filter { it.isEnabled }
val selectedTypeParameters: List<TypeParameter>
get() = selectedTypeParameterInfos.map { it.toParameter() }
}
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog">
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="522" height="396"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="34b30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="fa2d1" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="2aa47" class="com.intellij.ui.TitledSeparator" binding="inputParametersPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints border-constraint="Center"/>
<properties>
<text value=""/>
</properties>
<border type="none" title="Parameters"/>
<children/>
</grid>
</children>
</grid>
</children>
</grid>
<grid id="b45d1" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="200"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="4266d" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none" title="Signature Preview"/>
<children>
<component id="7722b" class="org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent" binding="signaturePreviewField" custom-create="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<minimum-size width="500" height="100"/>
<preferred-size width="500" height="100"/>
</grid>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="5fe96" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<visible value="true"/>
</properties>
<border type="empty"/>
<children>
<component id="d1d09" class="javax.swing.JLabel" binding="aliasNameLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Name:"/>
</properties>
</component>
<component id="52aa7" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="114" height="16"/>
</grid>
</constraints>
<properties>
<labelFor value="71ee7"/>
<text value="&amp;Visibility:"/>
<visible value="true"/>
</properties>
</component>
<grid id="d0f58" binding="aliasNamePanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="71ee7" class="javax.swing.JComboBox" binding="visibilityBox">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="114" height="26"/>
</grid>
</constraints>
<properties>
<visible value="true"/>
</properties>
</component>
</children>
</grid>
<vspacer id="cd10f">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
</grid>
</form>
@@ -0,0 +1,256 @@
/*
* 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.introduce.introduceTypeAlias.ui;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.ui.NameSuggestionsField;
import com.intellij.ui.TitledSeparator;
import com.intellij.util.containers.MultiMap;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinFileType;
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasDescriptor;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasImplKt;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter;
import org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent;
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.*;
import java.util.List;
public class KotlinIntroduceTypeAliasDialog extends DialogWrapper {
private JPanel contentPane;
private TitledSeparator inputParametersPanel;
private JComboBox visibilityBox;
private KotlinSignatureComponent signaturePreviewField;
private JPanel aliasNamePanel;
private NameSuggestionsField aliasNameField;
private JLabel aliasNameLabel;
private IntroduceTypeAliasParameterTablePanel parameterTablePanel;
private final Project project;
private final IntroduceTypeAliasDescriptor originalDescriptor;
private IntroduceTypeAliasDescriptor currentDescriptor;
private final Function1<KotlinIntroduceTypeAliasDialog, Unit> onAccept;
public KotlinIntroduceTypeAliasDialog(
@NotNull Project project,
@NotNull IntroduceTypeAliasDescriptor originalDescriptor,
@NotNull Function1<KotlinIntroduceTypeAliasDialog, Unit> onAccept) {
super(project, true);
this.project = project;
this.originalDescriptor = originalDescriptor;
this.currentDescriptor = originalDescriptor;
this.onAccept = onAccept;
setModal(true);
setTitle(KotlinIntroduceTypeAliasHandler.REFACTORING_NAME);
init();
update();
}
private void createUIComponents() {
this.signaturePreviewField = new KotlinSignatureComponent("", project);
}
private boolean isVisibilitySectionAvailable() {
return !getApplicableVisibilities().isEmpty();
}
@NotNull
private List<KtModifierKeywordToken> getApplicableVisibilities() {
return IntroduceTypeAliasImplKt.getApplicableVisibilities(originalDescriptor.getOriginalData());
}
private String getAliasName() {
return aliasNameField.getEnteredName();
}
@Nullable
private KtModifierKeywordToken getVisibility() {
if (!isVisibilitySectionAvailable()) return null;
return (KtModifierKeywordToken) visibilityBox.getSelectedItem();
}
private boolean checkNames() {
if (!KotlinNameSuggester.INSTANCE.isIdentifier(getAliasName())) return false;
if (parameterTablePanel != null) {
for (IntroduceTypeAliasParameterTablePanel.TypeParameterInfo parameterInfo : parameterTablePanel.getSelectedTypeParameterInfos()) {
if (!KotlinNameSuggester.INSTANCE.isIdentifier(parameterInfo.getName())) return false;
}
}
return true;
}
private void update() {
this.currentDescriptor = createDescriptor();
setOKActionEnabled(checkNames());
signaturePreviewField.setText(IntroduceTypeAliasImplKt.generateTypeAlias(currentDescriptor, true).getText());
}
@Override
protected void init() {
super.init();
//noinspection unchecked
visibilityBox.setModel(new DefaultComboBoxModel(getApplicableVisibilities().toArray()));
//noinspection unchecked
visibilityBox.setRenderer(
new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus
) {
String tokenValue = ((KtModifierKeywordToken) value).getValue();
return super.getListCellRendererComponent(list, tokenValue, index, isSelected, cellHasFocus);
}
}
);
aliasNameField = new NameSuggestionsField(ArrayUtil.EMPTY_STRING_ARRAY, project, KotlinFileType.INSTANCE);
aliasNameField.addDataChangedListener(
new NameSuggestionsField.DataChanged() {
@Override
public void dataChanged() {
update();
}
}
);
aliasNamePanel.add(aliasNameField, BorderLayout.CENTER);
aliasNameLabel.setLabelFor(aliasNameField);
boolean enableVisibility = isVisibilitySectionAvailable();
visibilityBox.setEnabled(enableVisibility);
if (enableVisibility) {
KtModifierKeywordToken defaultVisibility = originalDescriptor.getVisibility();
if (defaultVisibility == null) {
defaultVisibility = KtTokens.PUBLIC_KEYWORD;
}
visibilityBox.setSelectedItem(defaultVisibility);
}
visibilityBox.addItemListener(
new ItemListener() {
@Override
public void itemStateChanged(@NotNull ItemEvent e) {
update();
}
}
);
if (!originalDescriptor.getTypeParameters().isEmpty()) {
parameterTablePanel = new IntroduceTypeAliasParameterTablePanel() {
@Override
protected void updateSignature() {
KotlinIntroduceTypeAliasDialog.this.update();
}
@Override
protected void onEnterAction() {
doOKAction();
}
@Override
protected void onCancelAction() {
doCancelAction();
}
};
parameterTablePanel.init(originalDescriptor.getTypeParameters());
inputParametersPanel.setText("Type &Parameters");
inputParametersPanel.setLabelFor(parameterTablePanel.getTable());
inputParametersPanel.add(parameterTablePanel);
}
else {
inputParametersPanel.setVisible(false);
}
}
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void doOKAction() {
MultiMap<PsiElement, String> conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts();
KotlinRefactoringUtilKt.checkConflictsInteractively(
project,
conflicts,
new Function0<Unit>() {
@Override
public Unit invoke() {
close(OK_EXIT_CODE);
return Unit.INSTANCE;
}
},
new Function0<Unit>() {
@Override
public Unit invoke() {
KotlinIntroduceTypeAliasDialog.super.doOKAction();
return onAccept.invoke(KotlinIntroduceTypeAliasDialog.this);
}
}
);
}
@Override
public JComponent getPreferredFocusedComponent() {
return aliasNameField;
}
@Override
protected JComponent createCenterPanel() {
return contentPane;
}
@NotNull
@Override
protected JComponent createContentPane() {
return contentPane;
}
@NotNull
private IntroduceTypeAliasDescriptor createDescriptor() {
return originalDescriptor.copy(
originalDescriptor.getOriginalData(),
getAliasName(),
getVisibility(),
parameterTablePanel != null ? parameterTablePanel.getSelectedTypeParameters() : Collections.<TypeParameter>emptyList()
);
}
public IntroduceTypeAliasDescriptor getCurrentDescriptor() {
return currentDescriptor;
}
}
@@ -111,7 +111,11 @@ fun selectElementsWithTargetParent(
val elements = CodeInsightUtils.findElements(file, startOffset, endOffset, elementKind)
if (elements.isEmpty()) {
showErrorHintByKey("cannot.refactor.no.expression")
val messageKey = when (elementKind) {
CodeInsightUtils.ElementKind.EXPRESSION -> "cannot.refactor.no.expression"
CodeInsightUtils.ElementKind.TYPE_ELEMENT -> "cannot.refactor.no.type"
}
showErrorHintByKey(messageKey)
return
}
@@ -212,4 +216,4 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean {
if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true
return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') }
}
}