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