Move Directory: Support Kotlin files

#KT-8350 Fixed
 #KT-8351 Fixed
This commit is contained in:
Alexey Sedunov
2015-08-14 14:25:36 +03:00
parent feacefe50a
commit 46fe7dc7a1
22 changed files with 298 additions and 37 deletions
+3
View File
@@ -286,6 +286,9 @@
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsHandler"
order="before kotlin.moveFilesOrDirectories"/>
<moveFileHandler implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinFileHandler" />
<refactoring.moveDirectoryWithClassesHelper
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.KotlinMoveDirectoryWithClassesHelper"
order="first" />
<refactoring.moveInnerClassUsagesHandler
implementationClass="org.jetbrains.kotlin.idea.refactoring.move.MoveJavaInnerClassKotlinUsagesHandler"
language="jet" />
@@ -22,6 +22,9 @@ import com.intellij.codeInsight.unwrap.UnwrapHandler
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandAdapter
import com.intellij.openapi.command.CommandEvent
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
@@ -111,6 +114,8 @@ public fun File.toPsiDirectory(project: Project): PsiDirectory? {
public fun VirtualFile.toPsiFile(project: Project): PsiFile? = PsiManager.getInstance(project).findFile(this)
public fun VirtualFile.toPsiDirectory(project: Project): PsiDirectory? = PsiManager.getInstance(project).findDirectory(this)
public fun PsiElement.getUsageContext(): PsiElement {
return when (this) {
is JetElement -> PsiTreeUtil.getParentOfType(this, javaClass<JetNamedDeclaration>(), javaClass<JetFile>())!!
@@ -645,4 +650,15 @@ public fun <T : Any> Project.runSynchronouslyWithProgress(progressTitle: String,
var result: T? = null
ProgressManager.getInstance().runProcessWithProgressSynchronously( { result = action() }, progressTitle, canBeCanceled, this)
return result
}
public fun invokeOnceOnCommandFinish(action: () -> Unit) {
val commandProcessor = CommandProcessor.getInstance()
val listener = object: CommandAdapter() {
override fun beforeCommandFinished(event: CommandEvent?) {
action()
commandProcessor.removeCommandListener(this)
}
}
commandProcessor.addCommandListener(listener)
}
@@ -58,7 +58,7 @@ public class KotlinChangePackageRefactoring(val file: JetFile) {
)
val declarationUsages = declarationProcessor.findUsages().toList()
val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(PackageNameInfo(currentFqName, newFqName))
val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(PackageNameInfo(currentFqName, newFqName.toUnsafe()))
project.executeWriteCommand("Change file's package to '${newFqName.asString()}'") {
packageDirective.setFqName(newFqName)
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesHelper
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import com.intellij.usageView.UsageInfo
import com.intellij.util.Function
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.refactoring.invokeOnceOnCommandFinish
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsProcessor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import java.util.ArrayList
import java.util.HashMap
public class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
private class FileUsagesWrapper(
val psiFile: PsiFile,
val usages: List<UsageInfo>,
val moveDeclarationsProcessor: MoveKotlinTopLevelDeclarationsProcessor?
) : UsageInfo(psiFile)
private class MoveContext(
val newParent: PsiDirectory,
val internalUsages: MoveKotlinFileHandler.InternalUsagesWrapper,
val moveDeclarationsProcessor: MoveKotlinTopLevelDeclarationsProcessor?
)
private val fileHandler = MoveKotlinFileHandler()
private var fileToMoveContext: MutableMap<PsiFile, MoveContext>? = null
private fun getOrCreateMoveContextMap(): MutableMap<PsiFile, MoveContext> {
return fileToMoveContext
?: HashMap<PsiFile, MoveContext>().apply {
fileToMoveContext = this
invokeOnceOnCommandFinish { fileToMoveContext = null }
}
}
override fun findUsages(
filesToMove: MutableCollection<PsiFile>,
directoriesToMove: Array<out PsiDirectory>,
result: MutableCollection<UsageInfo>,
searchInComments: Boolean,
searchInNonJavaFiles: Boolean,
project: Project) {
filesToMove
.filterIsInstance<JetFile>()
.mapTo(result) { FileUsagesWrapper(it, fileHandler.findUsages(it, null, searchInComments, searchInNonJavaFiles), null) }
}
override fun beforeMove(psiFile: PsiFile) {
}
// Actual move logic is implemented in postProcessUsages since usages are not available here
override fun move(
file: PsiFile,
moveDestination: PsiDirectory,
oldToNewElementsMapping: MutableMap<PsiElement, PsiElement>,
movedFiles: MutableList<PsiFile>,
listener: RefactoringElementListener?
): Boolean {
if (file !is JetFile) return false
val moveDeclarationsProcessor = fileHandler.initMoveProcessor(file, moveDestination)
val moveContextMap = getOrCreateMoveContextMap()
moveContextMap[file] = MoveContext(moveDestination,
fileHandler.findInternalUsages(file, moveDestination),
moveDeclarationsProcessor)
moveDestination.getPackage()?.let { newPackage -> file.packageDirective?.fqName = FqName(newPackage.qualifiedName) }
return true
}
override fun afterMove(newElement: PsiElement) {
}
override fun postProcessUsages(usages: Array<out UsageInfo>, newDirMapper: Function<PsiDirectory, PsiDirectory>) {
val fileToMoveContext = fileToMoveContext ?: return
try {
val usagesToProcessAfterMove = ArrayList<FileUsagesWrapper>()
usages
.filterIsInstance<FileUsagesWrapper>()
.forEach body@ {
val file = it.psiFile
val moveContext = fileToMoveContext[file] ?: return@body
MoveFilesOrDirectoriesUtil.doMoveFile(file, moveContext.newParent)
val movedFile = moveContext.newParent.findFile(file.name) ?: return@body
usagesToProcessAfterMove +=
FileUsagesWrapper(movedFile, it.usages + moveContext.internalUsages, moveContext.moveDeclarationsProcessor)
}
usagesToProcessAfterMove.forEach { fileHandler.retargetUsages(it.usages, it.moveDeclarationsProcessor!!) }
}
finally {
this.fileToMoveContext = null
}
}
}
@@ -26,22 +26,16 @@ import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.idea.codeInsight.shorten.runWithElementsToShortenIsEmptyIgnored
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.packageMatchesDirectory
import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo
import org.jetbrains.kotlin.idea.refactoring.move.getInternalReferencesToUpdateOnPackageNameChange
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.DeferredJetFileKotlinMoveTarget
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsOptions
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsProcessor
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.Mover
import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages
import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.idea.refactoring.move.*
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
public class MoveKotlinFileHandler : MoveFileHandler() {
private class InternalUsagesWrapper(file: JetFile, val usages: List<UsageInfo>) : UsageInfo(file)
internal class InternalUsagesWrapper(file: JetFile, val usages: List<UsageInfo>) : UsageInfo(file)
// This is special 'PsiElement' whose purpose is to wrap MoveKotlinTopLevelDeclarationsProcessor
// so that it can be kept in the transition map
@@ -52,33 +46,42 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
override fun toString() = ""
}
private fun JetFile.getPackageNameInfo(newParent: PsiDirectory, clearUserData: Boolean): PackageNameInfo? {
private fun JetFile.getPackageNameInfo(newParent: PsiDirectory?, clearUserData: Boolean): PackageNameInfo? {
val shouldUpdatePackageDirective = updatePackageDirective ?: packageMatchesDirectory()
updatePackageDirective = if (clearUserData) null else shouldUpdatePackageDirective
if (!shouldUpdatePackageDirective) return null
val newPackage = newParent.getPackage() ?: return null
val oldPackageName = getPackageFqName()
val newPackageName = FqName(newPackage.getQualifiedName())
if (oldPackageName == newPackageName) return null
val oldPackageName = packageFqName
val newPackage = newParent?.getPackage() ?: return PackageNameInfo(oldPackageName, UNKNOWN_PACKAGE_FQ_NAME)
val newPackageName = FqNameUnsafe(newPackage.qualifiedName)
if (oldPackageName.asString() == newPackageName.asString()) return null
return PackageNameInfo(oldPackageName, newPackageName)
}
private fun initMoveProcessor(psiFile: PsiFile, newParent: PsiDirectory): MoveKotlinTopLevelDeclarationsProcessor? {
fun initMoveProcessor(psiFile: PsiFile, newParent: PsiDirectory?): MoveKotlinTopLevelDeclarationsProcessor? {
if (psiFile !is JetFile) return null
val packageNameInfo = psiFile.getPackageNameInfo(newParent, false) ?: return null
val project = psiFile.getProject()
val project = psiFile.project
val newPackageName = packageNameInfo.newPackageName
val moveTarget = when (newPackageName) {
UNKNOWN_PACKAGE_FQ_NAME -> EmptyKotlinMoveTarget
else -> DeferredJetFileKotlinMoveTarget(project, newPackageName.toSafe()) {
MoveFilesOrDirectoriesUtil.doMoveFile(psiFile, newParent)
newParent?.findFile(psiFile.name) as? JetFile
}
}
val declarationMoveProcessor = MoveKotlinTopLevelDeclarationsProcessor(
project,
MoveKotlinTopLevelDeclarationsOptions(
elementsToMove = psiFile.getDeclarations().filterIsInstance<JetNamedDeclaration>(),
moveTarget = DeferredJetFileKotlinMoveTarget(project, packageNameInfo.newPackageName) {
MoveFilesOrDirectoriesUtil.doMoveFile(psiFile, newParent)
newParent.findFile(psiFile.getName()) as? JetFile
},
elementsToMove = psiFile.declarations.filterIsInstance<JetNamedDeclaration>(),
moveTarget = moveTarget,
updateInternalReferences = false
),
Mover.Idle
@@ -91,9 +94,15 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
return !JavaProjectRootsUtil.isOutsideJavaSourceRoot(element)
}
fun findInternalUsages(file: JetFile, newParent: PsiDirectory): InternalUsagesWrapper {
val packageNameInfo = file.getPackageNameInfo(newParent, false)
val usages = packageNameInfo?.let { file.getInternalReferencesToUpdateOnPackageNameChange(it) } ?: emptyList()
return InternalUsagesWrapper(file, usages)
}
override fun findUsages(
psiFile: PsiFile,
newParent: PsiDirectory,
newParent: PsiDirectory?,
searchInComments: Boolean,
searchInNonJavaFiles: Boolean
): List<UsageInfo> {
@@ -101,10 +110,7 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
val usages = ArrayList<UsageInfo>()
initMoveProcessor(psiFile, newParent)?.findUsages()?.let { usages += it }
val packageNameInfo = psiFile.getPackageNameInfo(newParent, false)
if (packageNameInfo != null) {
usages += InternalUsagesWrapper(psiFile, psiFile.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo))
}
newParent?.let { usages += findInternalUsages(psiFile, it) }
return usages
}
@@ -118,15 +124,20 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
if (file !is JetFile) return
val newDirectory = file.parent ?: return
val packageNameInfo = file.getPackageNameInfo(newDirectory, true) ?: return
file.packageDirective?.fqName = packageNameInfo.newPackageName
val newPackageName = packageNameInfo.newPackageName
assert(newPackageName.isSafe, newPackageName)
file.packageDirective?.fqName = newPackageName.toSafe()
}
override fun retargetUsages(usageInfos: List<UsageInfo>?, oldToNewMap: Map<PsiElement, PsiElement>) {
postProcessMoveUsages(usageInfos?.firstIsInstanceOrNull<InternalUsagesWrapper>()?.usages ?: emptyList())
val moveContext = oldToNewMap.keySet().firstIsInstanceOrNull<MoveContext>() ?: return
val processor = moveContext.declarationMoveProcessor
processor.project.runWithElementsToShortenIsEmptyIgnored {
usageInfos?.let { processor.execute(it) }
retargetUsages(usageInfos, moveContext.declarationMoveProcessor)
}
fun retargetUsages(usageInfos: List<UsageInfo>?, moveDeclarationsProcessor: MoveKotlinTopLevelDeclarationsProcessor) {
postProcessMoveUsages(usageInfos?.firstIsInstanceOrNull<InternalUsagesWrapper>()?.usages ?: emptyList())
moveDeclarationsProcessor.project.runWithElementsToShortenIsEmptyIgnored {
usageInfos?.let { moveDeclarationsProcessor.execute(it) }
}
}
}
@@ -23,7 +23,7 @@ import com.intellij.psi.PsiManager
import com.intellij.refactoring.PackageWrapper
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import java.util.*
import java.util.HashMap
public interface KotlinMoveTarget {
val packageWrapper: PackageWrapper?
@@ -34,6 +34,13 @@ public interface KotlinMoveTarget {
fun verify(file: PsiFile): String?
}
public object EmptyKotlinMoveTarget: KotlinMoveTarget {
override val packageWrapper: PackageWrapper? get() = null
override fun getOrCreateTargetPsi(originalPsi: PsiElement) = null
override fun getTargetPsiIfExists(originalPsi: PsiElement) = null
override fun verify(file: PsiFile) = null
}
public class JetFileKotlinMoveTarget(val targetFile: JetFile): KotlinMoveTarget {
override val packageWrapper: PackageWrapper? = targetFile.getPackageFqName().asString().let { packageName ->
PackageWrapper(PsiManager.getInstance(targetFile.getProject()), packageName)
@@ -184,6 +184,8 @@ public class MoveKotlinTopLevelDeclarationsProcessor(
}
fun collectConflictsInDeclarations() {
if (newPackageName == UNKNOWN_PACKAGE_FQ_NAME.asString()) return
val declarationToReferenceTargets = HashMap<JetNamedDeclaration, MutableSet<PsiElement>>()
for (declaration in elementsToMove) {
val referenceToContext = JetFileReferencesResolver.resolve(element = declaration, resolveQualifiers = false)
@@ -251,7 +253,7 @@ public class MoveKotlinTopLevelDeclarationsProcessor(
?: throw AssertionError("Couldn't create Kotlin file for: ${declaration.javaClass}: ${declaration.getText()}")
if (options.updateInternalReferences) {
val packageNameInfo = PackageNameInfo(file!!.getPackageFqName(), targetFile.getPackageFqName())
val packageNameInfo = PackageNameInfo(file!!.getPackageFqName(), targetFile.getPackageFqName().toUnsafe())
val (usagesToProcessLater, usagesToProcessNow) = declaration
.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo)
.partition { it is MoveRenameUsageInfoForExtension }
@@ -56,6 +56,7 @@ import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -65,7 +66,9 @@ import java.util.ArrayList
import java.util.Collections
import java.util.Comparator
public class PackageNameInfo(val oldPackageName: FqName, val newPackageName: FqName)
val UNKNOWN_PACKAGE_FQ_NAME = FqNameUnsafe("org.jetbrains.kotlin.idea.refactoring.move.<unknown-package>")
public class PackageNameInfo(val oldPackageName: FqName, val newPackageName: FqNameUnsafe)
public fun JetElement.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo: PackageNameInfo): List<UsageInfo> {
val file = getContainingFile() as? JetFile ?: return listOf()
@@ -113,7 +116,7 @@ public fun JetElement.getInternalReferencesToUpdateOnPackageNameChange(packageNa
return when {
isExtension,
packageName == packageNameInfo.oldPackageName,
packageName == packageNameInfo.newPackageName,
packageName?.asString() == packageNameInfo.newPackageName.asString(),
isImported(descriptor) -> {
createMoveUsageInfoIfPossible(refExpr.mainReference, declaration, false)
}
@@ -0,0 +1,9 @@
package p1
import p2.p.J
import p2.p.K
public class M {
val j = J()
val k = K()
}
@@ -0,0 +1,9 @@
package p2;
import p2.p.K;
import p1.M;
public class JJ {
K k = new K();
M m = new M();
}
@@ -0,0 +1,9 @@
package p2
import p1.M
import p2.p.K
public class L {
private val k = K()
private val m = M()
}
@@ -0,0 +1,5 @@
package p2.p;
public class J {
K k = new K();
}
@@ -0,0 +1,5 @@
package p2.p
public class K {
val j = J()
}
@@ -0,0 +1,9 @@
package p1
import p1.p.J
import p1.p.K
public class M {
val j = J()
val k = K()
}
@@ -0,0 +1,5 @@
package p1.p;
public class J {
K k = new K();
}
@@ -0,0 +1,5 @@
package p1.p
public class K {
val j = J()
}
@@ -0,0 +1,9 @@
package p2;
import p1.p.K;
import p1.M;
public class JJ {
K k = new K();
M m = new M();
}
@@ -0,0 +1,9 @@
package p2
import p1.M
import p1.p.K
public class L {
private val k = K()
private val m = M()
}
@@ -0,0 +1,6 @@
{
"mainFile": "p1/M.kt",
"type": "MOVE_DIRECTORY_WITH_CLASSES",
"sourceDir": "p1/p",
"targetDir": "p2"
}
@@ -30,6 +30,7 @@ import com.intellij.refactoring.PackageWrapper
import com.intellij.refactoring.move.MoveHandler
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassToInnerProcessor
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesProcessor
import com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesProcessor
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesProcessor
import com.intellij.refactoring.move.moveInner.MoveInnerProcessor
@@ -37,6 +38,7 @@ import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions
import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor
import com.intellij.util.ActionRunner
import org.jetbrains.kotlin.idea.core.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.core.refactoring.toPsiDirectory
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
import org.jetbrains.kotlin.idea.jsonUtils.getString
import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring
@@ -264,6 +266,15 @@ enum class MoveAction {
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementAtCaret: PsiElement?, config: JsonObject) {
KotlinChangePackageRefactoring(mainFile as JetFile).run(FqName(config.getString("newPackageName")))
}
},
MOVE_DIRECTORY_WITH_CLASSES {
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementAtCaret: PsiElement?, config: JsonObject) {
val project = mainFile.project
val sourceDir = rootDir.findFileByRelativePath(config.getString("sourceDir"))!!.toPsiDirectory(project)!!
val targetDir = rootDir.findFileByRelativePath(config.getString("targetDir"))!!.toPsiDirectory(project)!!
MoveDirectoryWithClassesProcessor(project, arrayOf(sourceDir), targetDir, true, true, true, {}).run()
}
};
abstract fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementAtCaret: PsiElement?, config: JsonObject)
@@ -227,6 +227,12 @@ public class JetMoveTestGenerated extends AbstractJetMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveDirectory/moveDirectory.test")
public void testKotlin_moveDirectory_MoveDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveDirectory/moveDirectory.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveFile/addExtensionImport/addExtensionImport.test")
public void testKotlin_moveFile_addExtensionImport_AddExtensionImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/addExtensionImport/addExtensionImport.test");