Create Class From Usage: Allow user to choose a target directory when creating class in the specific package

This commit is contained in:
Alexey Sedunov
2014-11-11 15:54:15 +03:00
parent a9f9049223
commit fd4fa9a042
4 changed files with 81 additions and 52 deletions
@@ -59,6 +59,6 @@ public class JetSourceFilterScope extends DelegatingGlobalSearchScope {
return false; return false;
} }
return ProjectRootsUtil.isInSources(project, file, includeLibraries, /* withLibraryClassesRoots */ true, index); return ProjectRootsUtil.isInSources(project, file, /* includeTestSources */ false, includeLibraries, /* withLibraryClassesRoots */ true, index);
} }
} }
@@ -24,13 +24,14 @@ import org.jetbrains.jet.plugin.stubindex.JetSourceFilterScope
import org.jetbrains.jet.plugin.configuration.JetModuleTypeManager import org.jetbrains.jet.plugin.configuration.JetModuleTypeManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
public object ProjectRootsUtil { public object ProjectRootsUtil {
platformStatic platformStatic
public fun isInSources(project: Project, file: VirtualFile, public fun isInSources(project: Project, file: VirtualFile,
includeLibrarySources: Boolean, withLibraryClassesRoots: Boolean, includeTestSources: Boolean, includeLibrarySources: Boolean, withLibraryClassesRoots: Boolean,
fileIndex: ProjectFileIndex = ProjectFileIndex.SERVICE.getInstance(project)): Boolean { fileIndex: ProjectFileIndex = ProjectFileIndex.SERVICE.getInstance(project)): Boolean {
if (fileIndex.isInSourceContent(file)) { if (fileIndex.isInSourceContent(file) || (includeTestSources && fileIndex.isInTestSourceContent(file))) {
return !JetModuleTypeManager.getInstance()!!.isKtFileInGradleProjectInWrongFolder(file, project) return !JetModuleTypeManager.getInstance()!!.isKtFileInGradleProjectInWrongFolder(file, project)
} }
@@ -40,16 +41,20 @@ public object ProjectRootsUtil {
} }
platformStatic platformStatic
public fun isInSource(element: PsiElement, includeLibrarySources: Boolean, withLibraryClassesRoots: Boolean = false): Boolean { public fun isInSource(
element: PsiElement,
includeLibrarySources: Boolean,
includeTestSources: Boolean = false,
withLibraryClassesRoots: Boolean = false
): Boolean {
return runReadAction { (): Boolean -> return runReadAction { (): Boolean ->
val containingFile = element.getContainingFile() val virtualFile = when(element) {
if (containingFile == null) return@runReadAction false is PsiDirectory -> element.getVirtualFile()
else -> element.getContainingFile()?.getVirtualFile()
val virtualFile = containingFile.getVirtualFile() } ?: return@runReadAction false
if (virtualFile == null) return@runReadAction false
val project = element.getProject() val project = element.getProject()
return@runReadAction isInSources(project, virtualFile, includeLibrarySources, withLibraryClassesRoots) return@runReadAction isInSources(project, virtualFile, includeTestSources, includeLibrarySources, withLibraryClassesRoots)
} }
} }
@@ -30,13 +30,17 @@ import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.TypeInf
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ParameterInfo import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ParameterInfo
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ConstructorInfo import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ConstructorInfo
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration
import org.jetbrains.jet.lang.psi.JetExpression
import java.util.Collections import java.util.Collections
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.createBuilder import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.createBuilder
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallablePlacement import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallablePlacement
import org.jetbrains.jet.plugin.refactoring.getOrCreateKotlinFile import org.jetbrains.jet.plugin.refactoring.getOrCreateKotlinFile
import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassKind.* import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassKind.*
import com.intellij.psi.PsiPackage
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.ide.util.DirectoryChooserUtil
import java.util.HashMap
import com.intellij.openapi.application.ApplicationManager
enum class ClassKind(val keyword: String, val description: String) { enum class ClassKind(val keyword: String, val description: String) {
PLAIN_CLASS: ClassKind("class", "class") PLAIN_CLASS: ClassKind("class", "class")
@@ -66,26 +70,44 @@ public class CreateClassFromUsageFix(
JetBundle.message("create.0.from.usage", "${classInfo.kind.description} '${classInfo.name}'") JetBundle.message("create.0.from.usage", "${classInfo.kind.description} '${classInfo.name}'")
override fun invoke(project: Project, editor: Editor, file: JetFile) { override fun invoke(project: Project, editor: Editor, file: JetFile) {
fun createFileByPackage(psiPackage: PsiPackage): JetFile? {
val directories = psiPackage.getDirectories().filter { it.canRefactor() }
assert (directories.isNotEmpty(), "Package '${psiPackage.getQualifiedName() ?: ""}' must be refactorable")
val currentModule = ModuleUtilCore.findModuleForPsiElement(file)
val preferredDirectory =
directories.firstOrNull { ModuleUtilCore.findModuleForPsiElement(it) == currentModule }
?: directories.firstOrNull()
val targetDirectory = if (directories.size > 1 && !ApplicationManager.getApplication().isUnitTestMode()) {
DirectoryChooserUtil.chooseDirectory(directories.copyToArray(), preferredDirectory, project, HashMap<PsiDirectory, String>())
}
else {
preferredDirectory
} ?: return null
val fileName = "${classInfo.name}.${JetFileType.INSTANCE.getDefaultExtension()}"
val targetFile = getOrCreateKotlinFile(fileName, targetDirectory)
if (targetFile == null) {
val filePath = "${targetDirectory.getVirtualFile().getPath()}/$fileName"
CodeInsightUtils.showErrorHint(
targetDirectory.getProject(),
editor,
"File $filePath already exists but does not correspond to Kotlin file",
"Create file",
null
)
}
return targetFile
}
with (classInfo) { with (classInfo) {
val targetParent = when (targetParent) { val targetParent =
is JetElement -> targetParent when (targetParent) {
is PsiDirectory -> { is JetElement -> targetParent
val fileName = "$name.${JetFileType.INSTANCE.getDefaultExtension()}" is PsiPackage -> createFileByPackage(targetParent)
val targetFile = getOrCreateKotlinFile(fileName, targetParent) else -> throw AssertionError("Unexpected element: " + targetParent.getText())
if (targetFile == null) { } as? JetElement ?: return
val filePath = "${targetParent.getVirtualFile().getPath()}/$fileName"
CodeInsightUtils.showErrorHint(
targetParent.getProject(),
editor,
"File $filePath already exists but does not correspond to Kotlin file",
"Create file",
null
)
}
targetFile
}
else -> throw AssertionError("Unexpected element: " + targetParent.getText())
} as? JetElement ?: return
val constructorInfo = ConstructorInfo(classInfo, expectedTypeInfo) val constructorInfo = ConstructorInfo(classInfo, expectedTypeInfo)
val builder = CallableBuilderConfiguration( val builder = CallableBuilderConfiguration(
@@ -8,7 +8,6 @@ import org.jetbrains.jet.lang.descriptors.PackageViewDescriptor
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiDirectory
import org.jetbrains.jet.lang.psi.JetElement import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.JetExpression import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.guessTypes import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.guessTypes
@@ -26,6 +25,9 @@ import org.jetbrains.jet.plugin.util.ProjectRootsUtil
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.noSubstitutions import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.noSubstitutions
import org.jetbrains.jet.lang.resolve.DescriptorUtils import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
import com.intellij.psi.PsiPackage
import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.canRefactor
import com.intellij.psi.PsiDirectory
private fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUpperCase(first()) private fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUpperCase(first())
@@ -34,31 +36,20 @@ private fun getTargetParentByQualifier(
isQualified: Boolean, isQualified: Boolean,
qualifierDescriptor: DeclarationDescriptor?): PsiElement? { qualifierDescriptor: DeclarationDescriptor?): PsiElement? {
val project = file.getProject() val project = file.getProject()
val targetParent = when { val targetParent = when {
!isQualified -> file !isQualified ->
file
qualifierDescriptor is ClassDescriptor -> { qualifierDescriptor is ClassDescriptor ->
DescriptorToDeclarationUtil.getDeclaration(project, qualifierDescriptor) DescriptorToDeclarationUtil.getDeclaration(project, qualifierDescriptor)
} qualifierDescriptor is PackageViewDescriptor ->
if (qualifierDescriptor.getFqName() != file.getPackageFqName()) {
qualifierDescriptor is PackageViewDescriptor -> { JavaPsiFacade.getInstance(project).findPackage(qualifierDescriptor.getFqName().asString())
val currentModule = ModuleUtilCore.findModuleForPsiElement(file)
val targetFqName = qualifierDescriptor.getFqName()
if (targetFqName != file.getPackageFqName()) {
JavaPsiFacade.getInstance(project)
.findPackage(targetFqName.asString())
?.getDirectories()
?.firstOrNull { ModuleUtilCore.findModuleForPsiElement(it) == currentModule }
} }
else file else file : PsiElement
} else ->
null
else -> null
} ?: return null } ?: return null
return if (targetParent.isWritable() return if (targetParent.canRefactor()) return targetParent else null
&& ProjectRootsUtil.isInProjectOrLibSource(targetParent)
&& (targetParent is PsiDirectory || targetParent is JetElement)) return targetParent else null
} }
private fun getTargetParentByCall(call: Call, file: JetFile): PsiElement? { private fun getTargetParentByCall(call: Call, file: JetFile): PsiElement? {
@@ -97,4 +88,15 @@ private fun JetExpression.getInheritableTypeInfo(
else -> canHaveSubtypes else -> canHaveSubtypes
} }
} }
}
private fun PsiElement.canRefactor(): Boolean {
return when (this) {
is PsiPackage ->
getDirectories().any { it.canRefactor() }
is JetElement, is PsiDirectory ->
isWritable() && ProjectRootsUtil.isInSource(element = this, includeLibrarySources = false, includeTestSources = true)
else ->
false
}
} }