Create from Usage: Support target directory selection for "Create class"
#KT-22152 Fixed
This commit is contained in:
+2
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+89
-11
@@ -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<E : KtElement> 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<PsiElement>().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<E : KtElement> 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<E : KtElement> 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user