Fix move refactoring for several class files
Fixed #KT-35235 (also fixed #KT-35427)
This commit is contained in:
+3
-8
@@ -76,18 +76,13 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
override fun performRefactoring(usages: Array<UsageInfo>) {
|
||||
val needTemporaryRename = targetDirectory.findFile(targetFileName) != null
|
||||
val needTemporaryRename = targetDirectory.findFile(sourceFile.name) != null
|
||||
if (needTemporaryRename) {
|
||||
renameFileTemporarily()
|
||||
}
|
||||
|
||||
try {
|
||||
super.performRefactoring(usages)
|
||||
} finally {
|
||||
if (needTemporaryRename) {
|
||||
sourceFile.name = targetFileName
|
||||
}
|
||||
}
|
||||
super.performRefactoring(usages)
|
||||
sourceFile.name = targetFileName
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+90
-74
@@ -18,6 +18,7 @@ import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.backend.common.onlyIf
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.util.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
|
||||
@@ -51,8 +52,11 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
val moveCallback: MoveCallback?
|
||||
) : Model<BaseRefactoringProcessor> {
|
||||
|
||||
private inline fun <T, K> List<T>.mapToSingleOrNull(transform: (T) -> K?): K? =
|
||||
mapTo(mutableSetOf(), transform).singleOrNull()
|
||||
|
||||
private val sourceDirectory by lazy {
|
||||
sourceFiles.singleOrNull { it.parent !== null }?.parent ?: throw ConfigurationException("Can't determine sources directory")
|
||||
sourceFiles.mapToSingleOrNull { it.parent } ?: throw ConfigurationException("Can't determine sources directory")
|
||||
}
|
||||
|
||||
private val sourceFiles = elementsToMove.map { it.containingKtFile }.distinct()
|
||||
@@ -81,48 +85,54 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
|
||||
private fun getFilesExistingInTargetDir(
|
||||
targetFileName: String?,
|
||||
targetDirectory: PsiDirectory?
|
||||
): List<PsiFile> {
|
||||
targetDirectory ?: return emptyList()
|
||||
|
||||
val fileNames = targetFileName?.let { listOf(it) } ?: sourceFiles.map { it.name }
|
||||
|
||||
return fileNames
|
||||
.distinct()
|
||||
.mapNotNull { targetDirectory.findFile(it) }
|
||||
targetDirectory: PsiDirectory
|
||||
): Set<PsiFile> {
|
||||
return if (targetFileName != null) {
|
||||
targetDirectory.findFile(targetFileName)?.let { setOf(it) }.orEmpty()
|
||||
} else {
|
||||
sourceFiles.mapNotNullTo(mutableSetOf()) { targetDirectory.findFile(it.name) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectMoveTargetToPackage(): KotlinMoveTarget {
|
||||
|
||||
if (sourceFiles.size > 1) throw ConfigurationException("Can't move from multiply source files")
|
||||
|
||||
checkTargetFileName(fileNameInPackage)
|
||||
require(sourceFiles.isNotEmpty())
|
||||
|
||||
val (targetDir, moveDestination) = selectPackageBasedTargetDirAndDestination()
|
||||
|
||||
val targetDirectory = moveDestination.getTargetIfExists(sourceDirectory)
|
||||
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(fileNameInPackage, targetDirectory)
|
||||
val destination = sourceFiles
|
||||
.mapToSingleOrNull { moveDestination.getTargetIfExists(it) }
|
||||
?: throw ConfigurationException("Can't get target for all source elements")
|
||||
|
||||
val singleSourceFileMode = sourceFiles.size == 1
|
||||
|
||||
val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDirectory)
|
||||
|
||||
if (filesExistingInTargetDir.isNotEmpty()) {
|
||||
if (filesExistingInTargetDir.size > 1) {
|
||||
if (singleSourceFileMode) {
|
||||
val singeTargetFile = filesExistingInTargetDir.single() as? KtFile
|
||||
if (singeTargetFile != null) {
|
||||
return KotlinMoveTargetForExistingElement(singeTargetFile)
|
||||
}
|
||||
} else {
|
||||
val filePathsToReport = filesExistingInTargetDir.joinToString(
|
||||
separator = "\n",
|
||||
prefix = "Cannot perform refactoring since the following files already exist:\n\n"
|
||||
) { it.virtualFile.path }
|
||||
throw ConfigurationException(filePathsToReport)
|
||||
}
|
||||
|
||||
(filesExistingInTargetDir[0] as? KtFile)?.let {
|
||||
return KotlinMoveTargetForExistingElement(it)
|
||||
}
|
||||
}
|
||||
|
||||
// All source files must be in the same directory
|
||||
return KotlinMoveTargetForDeferredFile(
|
||||
FqName(targetPackage),
|
||||
moveDestination.getTargetIfExists(sourceFiles[0]),
|
||||
destination,
|
||||
targetDir
|
||||
) { getOrCreateKotlinFile(fileNameInPackage, moveDestination.getTargetDirectory(it)) }
|
||||
) {
|
||||
val deferredFileName = if (singleSourceFileMode) fileNameInPackage else it.name
|
||||
val deferredFileDirectory = moveDestination.getTargetDirectory(it)
|
||||
getOrCreateKotlinFile(deferredFileName, deferredFileDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectMoveTargetToFile(): KotlinMoveTarget {
|
||||
@@ -137,7 +147,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
checkTargetFileName(targetFile.name)
|
||||
|
||||
val jetFile = targetFile.toPsiFile(project) as? KtFile
|
||||
if (jetFile !== null) {
|
||||
if (jetFile != null) {
|
||||
if (sourceFiles.size == 1 && sourceFiles.contains(jetFile)) {
|
||||
throw ConfigurationException("Can't move to the original file")
|
||||
}
|
||||
@@ -173,13 +183,11 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
private fun selectMoveTarget() =
|
||||
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
|
||||
|
||||
private fun verifyBeforeRun(target: KotlinMoveTarget) {
|
||||
private fun verifyBeforeRun() {
|
||||
|
||||
if (elementsToMove.isEmpty()) throw ConfigurationException("At least one member must be selected")
|
||||
|
||||
for (element in elementsToMove) {
|
||||
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
|
||||
}
|
||||
if (sourceFiles.isEmpty()) throw ConfigurationException("None elements were selected")
|
||||
if (sourceFiles.size == 1 && fileNameInPackage.isBlank()) throw ConfigurationException("File name may not be empty")
|
||||
|
||||
if (isMoveToPackage) {
|
||||
if (targetPackage.isNotEmpty() && !PsiNameHelper.getInstance(project).isQualifiedName(targetPackage)) {
|
||||
@@ -187,68 +195,76 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
}
|
||||
} else {
|
||||
val targetFile = File(targetFilePath).toPsiFile(project)
|
||||
if (targetFile !== null && targetFile !is KtFile) {
|
||||
if (targetFile != null && targetFile !is KtFile) {
|
||||
throw ConfigurationException(KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file"))
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceFiles.size == 1 && fileNameInPackage.isEmpty()) {
|
||||
throw ConfigurationException("File name may not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
private val verifiedMoveTarget get() = selectMoveTarget().also { verifyBeforeRun(it) }
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult(throwOnConflicts: Boolean): BaseRefactoringProcessor {
|
||||
|
||||
val target = verifiedMoveTarget
|
||||
verifyBeforeRun()
|
||||
|
||||
if (isFullFileMove && isMoveToPackage) {
|
||||
val (_, moveDestination) = selectPackageBasedTargetDirAndDestination()
|
||||
tryMoveFile(throwOnConflicts)?.let { return it }
|
||||
}
|
||||
|
||||
val targetDir = moveDestination.getTargetIfExists(sourceDirectory)
|
||||
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage
|
||||
return moveDeclaration(throwOnConflicts)
|
||||
}
|
||||
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir)
|
||||
private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? {
|
||||
|
||||
if (filesExistingInTargetDir.isEmpty() ||
|
||||
(filesExistingInTargetDir.size == 1 && sourceFiles.contains(filesExistingInTargetDir[0]))
|
||||
) {
|
||||
val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) {
|
||||
runWriteAction<PsiDirectory> {
|
||||
moveDestination.getTargetDirectory(sourceDirectory)
|
||||
}
|
||||
}
|
||||
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage
|
||||
if (targetFileName != null) checkTargetFileName(targetFileName)
|
||||
|
||||
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective }
|
||||
val moveDestination = selectPackageBasedTargetDirAndDestination().destination
|
||||
val targetDir = moveDestination.getTargetIfExists(sourceDirectory)
|
||||
|
||||
return if (sourceFiles.size == 1 && targetFileName !== null)
|
||||
MoveToKotlinFileProcessor(
|
||||
project,
|
||||
sourceFiles[0],
|
||||
targetDirectory,
|
||||
targetFileName,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
else
|
||||
KotlinAwareMoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
sourceFiles,
|
||||
targetDirectory,
|
||||
isSearchReferences,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
}
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir)
|
||||
|
||||
val moveAsFile = filesExistingInTargetDir.isEmpty() ||
|
||||
filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true
|
||||
|
||||
if (!moveAsFile) return null
|
||||
|
||||
val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) {
|
||||
runWriteAction<PsiDirectory> { moveDestination.getTargetDirectory(sourceDirectory) }
|
||||
}
|
||||
|
||||
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective }
|
||||
|
||||
return if (targetFileName != null)
|
||||
MoveToKotlinFileProcessor(
|
||||
project,
|
||||
sourceFiles.first(),
|
||||
targetDirectory,
|
||||
targetFileName,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
else
|
||||
KotlinAwareMoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
sourceFiles,
|
||||
targetDirectory,
|
||||
isSearchReferences,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
}
|
||||
|
||||
private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor {
|
||||
val target = selectMoveTarget()
|
||||
for (element in elementsToMove) {
|
||||
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
|
||||
}
|
||||
|
||||
val options = MoveDeclarationsDescriptor(
|
||||
|
||||
Reference in New Issue
Block a user