diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt index ee11a1b5345..aadd5dcb745 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt @@ -5,14 +5,25 @@ package org.jetbrains.kotlin.idea.core -import com.intellij.psi.JavaDirectoryService -import com.intellij.psi.PsiDirectory -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiPackage +import com.intellij.ide.util.DirectoryChooserUtil +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.roots.ModulePackageIndex +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.Query +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes +import org.jetbrains.jps.model.java.JavaSourceRootProperties +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.config.KotlinSourceRootType import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService import org.jetbrains.kotlin.idea.util.sourceRoot import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile +import java.io.File fun PsiDirectory.getPackage(): PsiPackage? = JavaDirectoryService.getInstance()!!.getPackage(this) @@ -34,3 +45,98 @@ fun KtFile.packageMatchesDirectory(): Boolean = packageFqName == getFqNameByDire fun KtFile.packageMatchesDirectoryOrImplicit() = packageFqName == getFqNameByDirectory() || packageFqName == parent?.getFqNameWithImplicitPrefix() + +private fun getWritableModuleDirectory(vFiles: Query, module: Module, manager: PsiManager): PsiDirectory? { + for (vFile in vFiles) { + if (ModuleUtil.findModuleForFile(vFile, module.project) !== module) continue + val directory = manager.findDirectory(vFile) + if (directory != null && directory.isValid && directory.isWritable) { + return directory + } + } + return null +} + +private fun findLongestExistingPackage(module: Module, packageName: String): PsiPackage? { + val manager = PsiManager.getInstance(module.project) + + var nameToMatch = packageName + while (true) { + val vFiles = ModulePackageIndex.getInstance(module).getDirsByPackageName(nameToMatch, false) + val directory = getWritableModuleDirectory(vFiles, module, manager) + if (directory != null) return directory.getPackage() + + val lastDotIndex = nameToMatch.lastIndexOf('.') + if (lastDotIndex < 0) { + return null + } + nameToMatch = nameToMatch.substring(0, lastDotIndex) + } +} + +private val kotlinSourceRootTypes: Set> = + setOf(KotlinSourceRootType.Source, KotlinSourceRootType.TestSource) + JavaModuleSourceRootTypes.SOURCES + +private fun Module.getSourceRoots(): List = + ModuleRootManager.getInstance(this).getSourceRoots(kotlinSourceRootTypes) + +private fun getPackageDirectoriesInModule(rootPackage: PsiPackage, module: Module): Array = + rootPackage.getDirectories(GlobalSearchScope.moduleScope(module)) + +// This is Kotlin version of PackageUtil.findOrCreateDirectoryForPackage +fun findOrCreateDirectoryForPackage(module: Module, packageName: String): PsiDirectory? { + val project = module.project + var existingDirectoryByPackage: PsiDirectory? = null + var restOfName = packageName + if (!packageName.isEmpty()) { + val rootPackage = findLongestExistingPackage(module, packageName) + if (rootPackage != null) { + val beginIndex = rootPackage.qualifiedName.length + 1 + val subPackageName = if (beginIndex < packageName.length) packageName.substring(beginIndex) else "" + var postfixToShow = subPackageName.replace('.', File.separatorChar) + if (subPackageName.isNotEmpty()) { + postfixToShow = File.separatorChar + postfixToShow + } + val moduleDirectories = getPackageDirectoriesInModule(rootPackage, module) + existingDirectoryByPackage = + DirectoryChooserUtil.selectDirectory(project, moduleDirectories, null, postfixToShow) ?: return null + restOfName = subPackageName + } + } + + val existingDirectory = existingDirectoryByPackage ?: run { + val sourceRoots = module.getSourceRoots() + if (sourceRoots.isEmpty()) { + return null + } + val directoryList = mutableListOf() + for (sourceRoot in sourceRoots) { + val directory = PsiManager.getInstance(project).findDirectory(sourceRoot) ?: continue + directoryList += directory + } + val sourceDirectories = directoryList.toTypedArray() + DirectoryChooserUtil.selectDirectory( + project, sourceDirectories, null, + File.separatorChar + packageName.replace('.', File.separatorChar) + ) ?: return null + } + + fun getLeftPart(packageName: String): String { + val index = packageName.indexOf('.') + return if (index > -1) packageName.substring(0, index) else packageName + } + + fun cutLeftPart(packageName: String): String { + val index = packageName.indexOf('.') + return if (index > -1) packageName.substring(index + 1) else "" + } + + var psiDirectory = existingDirectory + while (restOfName.isNotEmpty()) { + val name = getLeftPart(restOfName) + val foundExistingDirectory = psiDirectory.findSubdirectory(name) + psiDirectory = foundExistingDirectory ?: WriteAction.compute { psiDirectory.createSubdirectory(name) } + restOfName = cutLeftPart(restOfName) + } + return psiDirectory +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index ecf6980d835..222a334e5a8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.quickfix.expectactual import com.intellij.codeInsight.intention.IntentionAction import com.intellij.ide.util.EditorHelper -import com.intellij.ide.util.PackageUtil import com.intellij.openapi.editor.Editor import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project @@ -112,8 +111,8 @@ sealed class CreateActualFix( val expectedDir = declaration.containingFile.containingDirectory val expectedPackage = JavaDirectoryService.getInstance().getPackage(expectedDir) - val actualDirectory = PackageUtil.findOrCreateDirectoryForPackage( - actualModule, expectedPackage?.qualifiedName ?: "", null, false + val actualDirectory = findOrCreateDirectoryForPackage( + actualModule, expectedPackage?.qualifiedName ?: "" ) ?: return null return runWriteAction { val fileName = "$name.kt"