diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt index 0dd0fb0c3ad..ff835a985ce 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt @@ -203,7 +203,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } } - fun build() { + fun build(onFinish: () -> Unit = {}) { try { assert(config.currentEditor != null) { "Can't run build() without editor" } if (finished) throw IllegalStateException("Current builder has already finished") @@ -211,6 +211,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } finally { finished = true + onFinish() } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt index 12594b6e749..f71af33ab29 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt @@ -18,32 +18,44 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass import com.intellij.codeInsight.intention.HighPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.codeInsight.intention.impl.CreateClassDialog import com.intellij.ide.util.DirectoryChooserUtil import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiComment -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiPackage +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.psi.* import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.core.getPackage +import org.jetbrains.kotlin.idea.core.quoteIfNeeded import org.jetbrains.kotlin.idea.quickfix.IntentionActionPriority import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassKind.* +import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper import org.jetbrains.kotlin.idea.refactoring.canRefactor import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.idea.util.projectStructure.module +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.psi.psiUtil.containingClass +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector +import org.jetbrains.kotlin.utils.SmartList import java.util.* +import com.intellij.codeInsight.daemon.impl.quickfix.ClassKind as IdeaClassKind enum class ClassKind(val keyword: String, val description: String) { PLAIN_CLASS("class", "class"), @@ -55,6 +67,8 @@ enum class ClassKind(val keyword: String, val description: String) { DEFAULT("", "") // Used as a placeholder and must be replaced with one of the kinds above } +fun ClassKind.toIdeaClassKind() = IdeaClassKind { this@toIdeaClassKind.description.capitalize() } + val ClassKind.actionPriority: IntentionActionPriority get() = if (this == ANNOTATION_CLASS) IntentionActionPriority.LOW else IntentionActionPriority.NORMAL @@ -102,10 +116,17 @@ open class CreateClassFromUsageFix protected constructor ( override fun invoke(project: Project, editor: Editor?, file: KtFile) { if (editor == null) return - val applicableParents = if (classInfo.kind == ClassKind.INTERFACE) - classInfo.applicableParents.filter { it != element?.containingClass() } - else - classInfo.applicableParents + val applicableParents = SmartList().also { parents -> + if (classInfo.kind == ClassKind.INTERFACE) + classInfo.applicableParents.filterTo(parents) { it != element?.containingClass() } + else + parents += classInfo.applicableParents + + if (classInfo.kind != ClassKind.ENUM_ENTRY) { + parents += SeparateFileWrapper(PsiManager.getInstance(project)) + } + } + if (ApplicationManager.getApplication().isUnitTestMode) { val targetParent = applicableParents.firstOrNull { @@ -154,7 +175,45 @@ open class CreateClassFromUsageFix protected constructor ( return targetFile } - private fun doInvoke(selectedParent: PsiElement, editor: Editor, file: KtFile) { + private fun doInvoke(selectedParent: PsiElement, editor: Editor, file: KtFile, startCommand: Boolean = true) { + val className = classInfo.name + + if (selectedParent is SeparateFileWrapper) { + if (ApplicationManager.getApplication().isUnitTestMode) { + return doInvoke(file, editor, file) + } + + val ideaClassKind = classInfo.kind.toIdeaClassKind() + val defaultPackageFqName = file.packageFqName + val dialog = object : CreateClassDialog( + file.project, + "Create ${ideaClassKind.description.capitalize()}", + className, + defaultPackageFqName.asString(), + ideaClassKind, + false, + file.module + ) { + override fun reportBaseInSourceSelectionInTest() = true + } + dialog.show() + if (dialog.exitCode != DialogWrapper.OK_EXIT_CODE) return + + val targetDirectory = dialog.targetDirectory ?: return + val fileName = "$className.${KotlinFileType.EXTENSION}" + val packageFqName = targetDirectory.getPackage()?.qualifiedName?.let { FqName(it).quoteIfNeeded() } + + file.project.executeWriteCommand(text) { + val targetFile = getOrCreateKotlinFile(fileName, targetDirectory, (packageFqName ?: defaultPackageFqName).asString()) + if (targetFile != null) { + doInvoke(targetFile, editor, file, false) + } + } + return + } + + val element = element ?: return + runWriteAction { with(classInfo) { val targetParent = @@ -166,14 +225,33 @@ open class CreateClassFromUsageFix protected constructor ( val constructorInfo = ClassWithPrimaryConstructorInfo(classInfo, expectedTypeInfo) val builder = CallableBuilderConfiguration( Collections.singletonList(constructorInfo), - element as KtElement, + element, file, editor, false, kind == PLAIN_CLASS || kind == INTERFACE ).createBuilder() builder.placement = CallablePlacement.NoReceiver(targetParent) - file.project.executeCommand(text) { builder.build() } + + fun buildClass() { + builder.build { + if (targetParent !is KtFile || targetParent == file) return@build + val targetPackageFqName = targetParent.packageFqName + if (targetPackageFqName == file.packageFqName) return@build + val reference = (element.getQualifiedElementSelector() as? KtSimpleNameExpression)?.mainReference ?: return@build + reference.bindToFqName( + targetPackageFqName.child(Name.identifier(className)), + KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING + ) + } + } + + if (startCommand) { + file.project.executeCommand(text, command = ::buildClass) + } + else { + buildClass() + } } } }