Copy refactoring update kotlin packages on target files
Fix #KT-18191
This commit is contained in:
+71
-21
@@ -10,11 +10,8 @@ import com.intellij.openapi.application.ApplicationManager
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.Messages
|
import com.intellij.openapi.ui.Messages
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.*
|
||||||
import com.intellij.psi.PsiDirectory
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import com.intellij.psi.PsiFileSystemItem
|
|
||||||
import com.intellij.psi.search.LocalSearchScope
|
import com.intellij.psi.search.LocalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||||
@@ -52,7 +49,7 @@ import org.jetbrains.kotlin.utils.ifEmpty
|
|||||||
class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val COMMAND_NAME = "Copy Declarations"
|
private val commandName = RefactoringBundle.message("copy.handler.copy.files.directories")
|
||||||
|
|
||||||
private val isUnitTestMode get() = ApplicationManager.getApplication().isUnitTestMode
|
private val isUnitTestMode get() = ApplicationManager.getApplication().isUnitTestMode
|
||||||
|
|
||||||
@@ -133,7 +130,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
|
|
||||||
val answer = Messages.showOkCancelDialog(
|
val answer = Messages.showOkCancelDialog(
|
||||||
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
||||||
COMMAND_NAME,
|
commandName,
|
||||||
"Overwrite",
|
"Overwrite",
|
||||||
"Cancel",
|
"Cancel",
|
||||||
Messages.getQuestionIcon()
|
Messages.getQuestionIcon()
|
||||||
@@ -144,7 +141,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
|
|
||||||
val answer = Messages.showYesNoCancelDialog(
|
val answer = Messages.showYesNoCancelDialog(
|
||||||
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
||||||
COMMAND_NAME,
|
commandName,
|
||||||
"Append",
|
"Append",
|
||||||
"Overwrite",
|
"Overwrite",
|
||||||
"Cancel",
|
"Cancel",
|
||||||
@@ -198,7 +195,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
|
|
||||||
if (singleNamedSourceElement !== null) {
|
if (singleNamedSourceElement !== null) {
|
||||||
val dialog = CopyKotlinDeclarationDialog(singleNamedSourceElement, sourceData.initialTargetDirectory, sourceData.project)
|
val dialog = CopyKotlinDeclarationDialog(singleNamedSourceElement, sourceData.initialTargetDirectory, sourceData.project)
|
||||||
dialog.title = COMMAND_NAME
|
dialog.title = commandName
|
||||||
if (!dialog.showAndGet()) return null
|
if (!dialog.showAndGet()) return null
|
||||||
|
|
||||||
openInEditor = dialog.openInEditor
|
openInEditor = dialog.openInEditor
|
||||||
@@ -246,14 +243,62 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTargetData(sourceData: SourceData) =
|
private fun trackedCopyFiles(sourceFiles: Array<out PsiElement>, initialTargetDirectory: PsiDirectory?): Set<VirtualFile> {
|
||||||
if (isUnitTestMode) getTargetDataForUnitTest(sourceData)
|
val mapper = object : VirtualFileListener {
|
||||||
else getTargetDataForUX(sourceData)
|
val filesCopied = mutableSetOf<VirtualFile>()
|
||||||
|
|
||||||
|
override fun fileCopied(event: VirtualFileCopyEvent) {
|
||||||
|
filesCopied.add(event.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fileCreated(event: VirtualFileEvent) {
|
||||||
|
filesCopied.add(event.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(VirtualFileManager.getInstance()) {
|
||||||
|
try {
|
||||||
|
addVirtualFileListener(mapper)
|
||||||
|
copyFilesHandler.doCopy(sourceFiles, initialTargetDirectory)
|
||||||
|
} finally {
|
||||||
|
removeVirtualFileListener(mapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapper.filesCopied
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doCopyFiles(filesToCopy: Array<out PsiElement>, initialTargetDirectory: PsiDirectory?) {
|
||||||
|
|
||||||
|
if (filesToCopy.isEmpty()) return
|
||||||
|
|
||||||
|
val project = filesToCopy.first().project
|
||||||
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
|
||||||
|
project.executeCommand(commandName) {
|
||||||
|
val copiedFiles = trackedCopyFiles(filesToCopy, initialTargetDirectory)
|
||||||
|
|
||||||
|
copiedFiles.forEach { copiedFile ->
|
||||||
|
val targetKtFile = psiManager.findFile(copiedFile) as? KtFile
|
||||||
|
if (targetKtFile !== null) {
|
||||||
|
runWriteAction {
|
||||||
|
if (!targetKtFile.packageMatchesDirectoryOrImplicit()) {
|
||||||
|
targetKtFile.containingDirectory?.getFqNameWithImplicitPrefix()?.let { targetDirectoryFqName ->
|
||||||
|
targetKtFile.packageFqName = targetDirectoryFqName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
performDelayedRefactoringRequests(project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun doCopy(elements: Array<out PsiElement>, defaultTargetDirectory: PsiDirectory?) {
|
override fun doCopy(elements: Array<out PsiElement>, defaultTargetDirectory: PsiDirectory?) {
|
||||||
|
|
||||||
|
if (elements.isEmpty()) return
|
||||||
|
|
||||||
if (!canCopyDeclarations(elements)) {
|
if (!canCopyDeclarations(elements)) {
|
||||||
val sourceFiles = getSourceFiles(elements) ?: return
|
return doCopyFiles(elements, defaultTargetDirectory)
|
||||||
return copyFilesHandler.doCopy(sourceFiles, defaultTargetDirectory)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val elementsToCopy = elements.mapNotNull { it.getCopyableElement() }
|
val elementsToCopy = elements.mapNotNull { it.getCopyableElement() }
|
||||||
@@ -273,7 +318,9 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
originalFile = originalFile,
|
originalFile = originalFile,
|
||||||
initialTargetDirectory = initialTargetDirectory
|
initialTargetDirectory = initialTargetDirectory
|
||||||
)
|
)
|
||||||
val targetData = getTargetData(sourceData) ?: return
|
|
||||||
|
val targetData = if (isUnitTestMode) getTargetDataForUnitTest(sourceData) else getTargetDataForUX(sourceData)
|
||||||
|
targetData ?: return
|
||||||
|
|
||||||
val internalUsages = collectInternalUsages(sourceData, targetData)
|
val internalUsages = collectInternalUsages(sourceData, targetData)
|
||||||
markInternalUsages(internalUsages)
|
markInternalUsages(internalUsages)
|
||||||
@@ -282,7 +329,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
|
|
||||||
project.checkConflictsInteractively(conflicts) {
|
project.checkConflictsInteractively(conflicts) {
|
||||||
try {
|
try {
|
||||||
project.executeCommand(COMMAND_NAME) {
|
project.executeCommand(commandName) {
|
||||||
doRefactor(sourceData, targetData)
|
doRefactor(sourceData, targetData)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -297,6 +344,10 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
val restoredInternalUsages: List<UsageInfo>? = null
|
val restoredInternalUsages: List<UsageInfo>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getTargetFileName(sourceData: SourceData, targetData: TargetData) =
|
||||||
|
if (targetData.newName.contains(".")) targetData.newName
|
||||||
|
else targetData.newName + "." + sourceData.originalFile.virtualFile.extension
|
||||||
|
|
||||||
private fun doRefactor(sourceData: SourceData, targetData: TargetData) {
|
private fun doRefactor(sourceData: SourceData, targetData: TargetData) {
|
||||||
|
|
||||||
var refactoringResult: RefactoringResult? = null
|
var refactoringResult: RefactoringResult? = null
|
||||||
@@ -305,9 +356,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
targetData.targetDirWrapper.getOrCreateDirectory(sourceData.initialTargetDirectory)
|
targetData.targetDirWrapper.getOrCreateDirectory(sourceData.initialTargetDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetFileName =
|
val targetFileName = getTargetFileName(sourceData, targetData)
|
||||||
if (targetData.newName.contains(".")) targetData.newName
|
|
||||||
else targetData.newName + "." + sourceData.originalFile.virtualFile.extension
|
|
||||||
|
|
||||||
val isSingleDeclarationInFile =
|
val isSingleDeclarationInFile =
|
||||||
sourceData.singleElementToCopy is KtNamedDeclaration &&
|
sourceData.singleElementToCopy is KtNamedDeclaration &&
|
||||||
@@ -322,7 +371,8 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
refactoringResult = if (fileToCopy !== null) {
|
refactoringResult = if (fileToCopy !== null) {
|
||||||
doRefactoringOnFile(fileToCopy, sourceData, targetDirectory, targetFileName, isSingleDeclarationInFile)
|
doRefactoringOnFile(fileToCopy, sourceData, targetDirectory, targetFileName, isSingleDeclarationInFile)
|
||||||
} else {
|
} else {
|
||||||
val targetFile = getOrCreateTargetFile(sourceData.originalFile, targetDirectory, targetFileName) ?: return
|
val targetFile = getOrCreateTargetFile(sourceData.originalFile, targetDirectory, targetFileName)
|
||||||
|
?: throw IncorrectOperationException("Could not create target file.")
|
||||||
doRefactoringOnElement(sourceData, targetFile)
|
doRefactoringOnElement(sourceData, targetFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +386,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetData.openInEditor) {
|
if (targetData.openInEditor) {
|
||||||
EditorHelper.openFilesInEditor(arrayOf(refactoringResult.targetFile))
|
EditorHelper.openInEditor(refactoringResult.targetFile)
|
||||||
}
|
}
|
||||||
} catch (e: IncorrectOperationException) {
|
} catch (e: IncorrectOperationException) {
|
||||||
Messages.showMessageDialog(sourceData.project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon())
|
Messages.showMessageDialog(sourceData.project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon())
|
||||||
|
|||||||
idea/testData/refactoring/copy/copyMultipleFilesDifferentDirectoriesToNewDirectory/after/bar/test.kt
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package boo
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
idea/testData/refactoring/copy/copyMultipleFilesDifferentDirectoriesToNewDirectory/after/foo/test.kt
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package boo
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"filesToMove": ["foo/test.kt", "boo/test2.kt"],
|
||||||
|
"targetPackage": "bar",
|
||||||
|
"type": "COPY_FILES"
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val a: A = A()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val b: B = B()
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"filesToMove": ["foo/test.kt", "foo/test2.kt"],
|
||||||
|
"targetPackage": "bar",
|
||||||
|
"type": "COPY_FILES"
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import com.intellij.refactoring.PackageWrapper
|
|||||||
import com.intellij.refactoring.copy.CopyHandler
|
import com.intellij.refactoring.copy.CopyHandler
|
||||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
|
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
|
||||||
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
|
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
|
||||||
|
import org.jetbrains.kotlin.idea.core.util.toPsiFile
|
||||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||||
import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest
|
import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest
|
||||||
@@ -22,12 +23,20 @@ import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest
|
|||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
import org.jetbrains.kotlin.utils.ifEmpty
|
import org.jetbrains.kotlin.utils.ifEmpty
|
||||||
|
|
||||||
abstract class AbstractCopyTest : AbstractMultifileRefactoringTest() {
|
private enum class CopyAction : AbstractMultifileRefactoringTest.RefactoringAction {
|
||||||
companion object : RefactoringAction {
|
COPY_FILES {
|
||||||
fun runCopyRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
|
||||||
runRefactoringTest(path, config, rootDir, project, this)
|
val project = mainFile.project
|
||||||
}
|
val elementsToCopy = config.getAsJsonArray("filesToMove").map {
|
||||||
|
val virtualFile = rootDir.findFileByRelativePath(it.asString)!!
|
||||||
|
if (virtualFile.isDirectory) virtualFile.toPsiDirectory(project)!! else virtualFile.toPsiFile(project)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT.runRefactoring(rootDir, mainFile, elementsToCopy, config)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
DEFAULT {
|
||||||
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
|
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
|
||||||
val project = mainFile.project
|
val project = mainFile.project
|
||||||
|
|
||||||
@@ -37,16 +46,26 @@ abstract class AbstractCopyTest : AbstractMultifileRefactoringTest() {
|
|||||||
val targetDirectory = config.getNullableString("targetDirectory")?.let {
|
val targetDirectory = config.getNullableString("targetDirectory")?.let {
|
||||||
rootDir.findFileByRelativePath(it)?.toPsiDirectory(project)
|
rootDir.findFileByRelativePath(it)?.toPsiDirectory(project)
|
||||||
}
|
}
|
||||||
?: run {
|
?: run {
|
||||||
val packageWrapper = PackageWrapper(mainFile.manager, config.getString("targetPackage"))
|
val packageWrapper = PackageWrapper(mainFile.manager, config.getString("targetPackage"))
|
||||||
runWriteAction { MultipleRootsMoveDestination(packageWrapper).getTargetDirectory(mainFile) }
|
runWriteAction { MultipleRootsMoveDestination(packageWrapper).getTargetDirectory(mainFile) }
|
||||||
}
|
}
|
||||||
|
|
||||||
project.newName = config.getNullableString("newName")
|
project.newName = config.getNullableString("newName")
|
||||||
|
|
||||||
CopyHandler.doCopy(elementsToCopy, targetDirectory)
|
CopyHandler.doCopy(elementsToCopy, targetDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractCopyTest : AbstractMultifileRefactoringTest() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun runCopyRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
||||||
|
val action = config.getNullableString("type")?.let { CopyAction.valueOf(it) } ?: CopyAction.DEFAULT
|
||||||
|
runRefactoringTest(path, config, rootDir, project, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
override fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
||||||
runCopyRefactoring(path, config, rootDir, project)
|
runCopyRefactoring(path, config, rootDir, project)
|
||||||
|
|||||||
+10
@@ -143,6 +143,16 @@ public class CopyTestGenerated extends AbstractCopyTest {
|
|||||||
runTest("idea/testData/refactoring/copy/copyMultipleDeclarations/copyMultipleDeclarations.test");
|
runTest("idea/testData/refactoring/copy/copyMultipleDeclarations/copyMultipleDeclarations.test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("copyMultipleFilesDifferentDirectoriesToNewDirectory/copyMultipleFilesDifferentDirectoriesToNewDirectory.test")
|
||||||
|
public void testCopyMultipleFilesDifferentDirectoriesToNewDirectory_CopyMultipleFilesDifferentDirectoriesToNewDirectory() throws Exception {
|
||||||
|
runTest("idea/testData/refactoring/copy/copyMultipleFilesDifferentDirectoriesToNewDirectory/copyMultipleFilesDifferentDirectoriesToNewDirectory.test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("copyMultipleFilesToNewDirectory/copyMultipleFilesToNewDirectory.test")
|
||||||
|
public void testCopyMultipleFilesToNewDirectory_CopyMultipleFilesToNewDirectory() throws Exception {
|
||||||
|
runTest("idea/testData/refactoring/copy/copyMultipleFilesToNewDirectory/copyMultipleFilesToNewDirectory.test");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("copyNestedClass/copyNestedClass.test")
|
@TestMetadata("copyNestedClass/copyNestedClass.test")
|
||||||
public void testCopyNestedClass_CopyNestedClass() throws Exception {
|
public void testCopyNestedClass_CopyNestedClass() throws Exception {
|
||||||
runTest("idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test");
|
runTest("idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user