Create from Usage: Support target directory selection for "Create class"

#KT-22152 Fixed
This commit is contained in:
Alexey Sedunov
2018-02-08 18:43:09 +03:00
parent 695a2a4098
commit 84fed8071c
2 changed files with 91 additions and 12 deletions
@@ -203,7 +203,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
} }
} }
fun build() { fun build(onFinish: () -> Unit = {}) {
try { try {
assert(config.currentEditor != null) { "Can't run build() without editor" } assert(config.currentEditor != null) { "Can't run build() without editor" }
if (finished) throw IllegalStateException("Current builder has already finished") if (finished) throw IllegalStateException("Current builder has already finished")
@@ -211,6 +211,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
} }
finally { finally {
finished = true finished = true
onFinish()
} }
} }
@@ -18,32 +18,44 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass
import com.intellij.codeInsight.intention.HighPriorityAction import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.codeInsight.intention.impl.CreateClassDialog
import com.intellij.ide.util.DirectoryChooserUtil import com.intellij.ide.util.DirectoryChooserUtil
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass import com.intellij.openapi.ui.DialogWrapper
import com.intellij.psi.PsiComment import com.intellij.psi.*
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils 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.IntentionActionPriority
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassKind.* 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.canRefactor
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile 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.executeCommand
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction 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.KtClass
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile 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.allChildren
import org.jetbrains.kotlin.psi.psiUtil.containingClass import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.utils.SmartList
import java.util.* import java.util.*
import com.intellij.codeInsight.daemon.impl.quickfix.ClassKind as IdeaClassKind
enum class ClassKind(val keyword: String, val description: String) { enum class ClassKind(val keyword: String, val description: String) {
PLAIN_CLASS("class", "class"), 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 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 val ClassKind.actionPriority: IntentionActionPriority
get() = if (this == ANNOTATION_CLASS) IntentionActionPriority.LOW else IntentionActionPriority.NORMAL get() = if (this == ANNOTATION_CLASS) IntentionActionPriority.LOW else IntentionActionPriority.NORMAL
@@ -102,10 +116,17 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
if (editor == null) return if (editor == null) return
val applicableParents = if (classInfo.kind == ClassKind.INTERFACE) val applicableParents = SmartList<PsiElement>().also { parents ->
classInfo.applicableParents.filter { it != element?.containingClass() } if (classInfo.kind == ClassKind.INTERFACE)
else classInfo.applicableParents.filterTo(parents) { it != element?.containingClass() }
classInfo.applicableParents else
parents += classInfo.applicableParents
if (classInfo.kind != ClassKind.ENUM_ENTRY) {
parents += SeparateFileWrapper(PsiManager.getInstance(project))
}
}
if (ApplicationManager.getApplication().isUnitTestMode) { if (ApplicationManager.getApplication().isUnitTestMode) {
val targetParent = applicableParents.firstOrNull { val targetParent = applicableParents.firstOrNull {
@@ -154,7 +175,45 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
return targetFile 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 { runWriteAction {
with(classInfo) { with(classInfo) {
val targetParent = val targetParent =
@@ -166,14 +225,33 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
val constructorInfo = ClassWithPrimaryConstructorInfo(classInfo, expectedTypeInfo) val constructorInfo = ClassWithPrimaryConstructorInfo(classInfo, expectedTypeInfo)
val builder = CallableBuilderConfiguration( val builder = CallableBuilderConfiguration(
Collections.singletonList(constructorInfo), Collections.singletonList(constructorInfo),
element as KtElement, element,
file, file,
editor, editor,
false, false,
kind == PLAIN_CLASS || kind == INTERFACE kind == PLAIN_CLASS || kind == INTERFACE
).createBuilder() ).createBuilder()
builder.placement = CallablePlacement.NoReceiver(targetParent) 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()
}
} }
} }
} }