diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsCopyPasteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsCopyPasteProcessor.kt index 0b103aa1e4f..715899d352d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsCopyPasteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsCopyPasteProcessor.kt @@ -29,8 +29,10 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiModificationTracker import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.psi.KtClassBody import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.psiUtil.elementsInRange import java.awt.datatransfer.Transferable @@ -58,13 +60,20 @@ class MoveDeclarationsCopyPasteProcessor : CopyPastePostProcessor null + is KtClassBody -> (parent.parent as? KtObjectDeclaration)?.fqName?.asString() ?: return emptyList() + else -> return emptyList() + } + if (declarations.any { it.name == null }) return emptyList() val declarationNames = declarations.map { it.name!! }.toSet() val stubTexts = declarations.map { MoveDeclarationsTransferableData.STUB_RENDERER.render(it.resolveToDescriptor()) } - return listOf(MoveDeclarationsTransferableData(file.virtualFile.url, stubTexts, declarationNames)) + return listOf(MoveDeclarationsTransferableData(file.virtualFile.url, sourceObjectFqName, stubTexts, declarationNames)) } override fun extractTransferableData(content: Transferable): List { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsIntentionAction.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsIntentionAction.kt index 8d7abf5474a..ad32bee8b41 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsIntentionAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsIntentionAction.kt @@ -34,9 +34,12 @@ class MoveDeclarationsIntentionAction( private val bounds: RangeMarker, private val modificationCount: Long ) : BaseRefactoringIntentionAction(), HintAction { + + private val isSingleDeclaration = processor.pastedDeclarations.size == 1 + override fun startInWriteAction() = false - override fun getText() = "Update usages to reflect package name change" + override fun getText() = "Update usages to reflect declaration${if (isSingleDeclaration) "s" else ""} move" override fun getFamilyName() = "Update usages on declarations cut/paste" override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsProcessor.kt index ffa6203e114..0a7309ac077 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsProcessor.kt @@ -29,16 +29,16 @@ import org.jetbrains.kotlin.idea.conversion.copy.range import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.getSourceRoot -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.startOffset class MoveDeclarationsProcessor( val project: Project, - private val sourcePsiFile: KtFile, + private val sourceContainer: KtDeclarationContainer, private val targetPsiFile: KtFile, - private val pastedDeclarations: List, + val pastedDeclarations: List, private val stubTexts: List ) { companion object { @@ -57,22 +57,29 @@ class MoveDeclarationsProcessor( val targetPsiFile = psiDocumentManager.getPsiFile(editor.document) as? KtFile ?: return null if (targetPsiFile.virtualFile.getSourceRoot(project) == null) return null val sourcePsiFile = PsiManager.getInstance(project).findFile(sourceFile) as? KtFile ?: return null - if (targetPsiFile == sourcePsiFile) return null + + val sourceObject = data.sourceObjectFqName?.let { fqName -> + sourcePsiFile.findDescendantOfType { it.fqName?.asString() == fqName } ?: return null + } + val sourceContainer: KtDeclarationContainer = sourceObject ?: sourcePsiFile + + if (targetPsiFile == sourceContainer) return null val declarations = MoveDeclarationsCopyPasteProcessor.rangeToDeclarations(targetPsiFile, range.startOffset, range.endOffset) if (declarations.isEmpty() || declarations.any { it.parent !is KtFile }) return null - if (sourcePsiFile.packageFqName == targetPsiFile.packageFqName) return null + if (sourceContainer == sourcePsiFile && sourcePsiFile.packageFqName == targetPsiFile.packageFqName) return null // check that declarations were cut (not copied) - val filteredDeclarations = sourcePsiFile.declarations.filter { it.name in data.declarationNames } + val filteredDeclarations = sourceContainer.declarations.filter { it.name in data.declarationNames } val stubs = data.stubTexts.toSet() if (filteredDeclarations.any { MoveDeclarationsTransferableData.STUB_RENDERER.render(it.resolveToDescriptor()) in stubs }) return null - return MoveDeclarationsProcessor(project, sourcePsiFile, targetPsiFile, declarations, data.stubTexts) + return MoveDeclarationsProcessor(project, sourceContainer, targetPsiFile, declarations, data.stubTexts) } } + private val sourcePsiFile = (sourceContainer as KtElement).containingKtFile private val psiDocumentManager = PsiDocumentManager.getInstance(project) private val sourceDocument = psiDocumentManager.getDocument(sourcePsiFile)!! @@ -111,12 +118,8 @@ class MoveDeclarationsProcessor( ) val declarationUsages = declarationProcessor.findUsages().toList() -// val changeInfo = ContainerChangeInfo(ContainerInfo.Package(sourcePackageName), ContainerInfo.Package(targetPackageName)) -// val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(changeInfo) project.executeWriteCommand(commandName, commandGroupId) { - - // postProcessMoveUsages(internalUsages) //TODO? project.runRefactoringAndKeepDelayedRequests { declarationProcessor.execute(declarationUsages) } psiDocumentManager.doPostponedOperationsAndUnblockDocument(sourceDocument) @@ -126,7 +129,12 @@ class MoveDeclarationsProcessor( } private fun insertStubDeclarations(): RangeMarker { - val insertionOffset = sourcePsiFile.declarations.firstOrNull()?.startOffset ?: sourcePsiFile.textLength + val insertionOffset = sourceContainer.declarations.firstOrNull()?.startOffset + ?: when (sourceContainer) { + is KtFile -> sourceContainer.textLength + is KtObjectDeclaration -> sourceContainer.getBody()?.rBrace?.startOffset ?: sourceContainer.endOffset + else -> error("Unknown sourceContainer: $sourceContainer") + } val textToInsert = "\n//start\n\n" + stubTexts.joinToString(separator = "\n") + "\n//end\n" sourceDocument.insertString(insertionOffset, textToInsert) return sourceDocument.createRangeMarker(TextRange(insertionOffset, insertionOffset + textToInsert.length)) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsTransferableData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsTransferableData.kt index bfb7ec50863..f342910025e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsTransferableData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/cutPaste/MoveDeclarationsTransferableData.kt @@ -20,9 +20,9 @@ import com.intellij.codeInsight.editorActions.TextBlockTransferableData import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import java.awt.datatransfer.DataFlavor -//TODO: how to make it work inside same project only? class MoveDeclarationsTransferableData( val sourceFileUrl: String, + val sourceObjectFqName: String?, val stubTexts: List, val declarationNames: Set ) : TextBlockTransferableData { diff --git a/idea/testData/copyPaste/moveDeclarations/FromAnonymousObject.kt b/idea/testData/copyPaste/moveDeclarations/FromAnonymousObject.kt new file mode 100644 index 00000000000..1a8b5cab6d9 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromAnonymousObject.kt @@ -0,0 +1,15 @@ +// IS_AVAILABLE: false + +fun foo() { + val v = object : Runnable { + override fun run() { + } + + + fun bar() { + } + + } +} + + diff --git a/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.expected.kt b/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.expected.kt new file mode 100644 index 00000000000..56bd1c8e9fd --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.expected.kt @@ -0,0 +1,24 @@ +package source + +class X { + companion object { + + + fun other() { + foo() + } + } + + fun f() { + bar++ + } +} + + +fun foo() { + X.other() + bar++ +} + +var bar = 1 + diff --git a/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.kt b/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.kt new file mode 100644 index 00000000000..c10f1091844 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.kt @@ -0,0 +1,24 @@ +package source + +class X { + companion object { + + fun foo() { + other() + bar++ + } + + var bar = 1 + + + fun other() { + foo() + } + } + + fun f() { + bar++ + } +} + + diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.expected.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.expected.kt new file mode 100644 index 00000000000..f0fff08657e --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.expected.kt @@ -0,0 +1,21 @@ +package source + +fun sourcePackFun(){} + +object SourceObject { + + + fun other() { + foo() + } +} + + +fun foo() { + SourceObject.other() + sourcePackFun() + bar++ +} + +var bar = 1 + diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.kt new file mode 100644 index 00000000000..18cb95bacf4 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.kt @@ -0,0 +1,21 @@ +package source + +fun sourcePackFun(){} + +object SourceObject { + + fun foo() { + other() + sourcePackFun() + bar++ + } + + var bar = 1 + + + fun other() { + foo() + } +} + + diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.expected.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.expected.kt new file mode 100644 index 00000000000..5c6b8507ef5 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.expected.kt @@ -0,0 +1,8 @@ +package source + +import target.foo + +fun f() { + foo() + SourceObject.other() +} \ No newline at end of file diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.kt new file mode 100644 index 00000000000..32376f2f9c9 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.dependency.kt @@ -0,0 +1,6 @@ +package source + +fun f() { + SourceObject.foo() + SourceObject.other() +} \ No newline at end of file diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.expected.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.expected.kt new file mode 100644 index 00000000000..4567c169f1b --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.expected.kt @@ -0,0 +1,13 @@ +package source + +import target.foo + +fun sourcePackFun(){} + +object SourceObject { + + + fun other() { + foo() + } +} diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.kt new file mode 100644 index 00000000000..2f01b210386 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.kt @@ -0,0 +1,22 @@ +package source + +import target.targetPackFun + +fun sourcePackFun(){} + +object SourceObject { + + fun foo() { + other() + sourcePackFun() + targetPackFun() + bar++ + } + + var bar = 1 + + + fun other() { + foo() + } +} diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.expected.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.expected.kt new file mode 100644 index 00000000000..fc14e256dc4 --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.expected.kt @@ -0,0 +1,16 @@ +package target + +import source.SourceObject +import source.sourcePackFun + +fun targetPackFun(){} + + +fun foo() { + SourceObject.other() + sourcePackFun() + targetPackFun() + bar++ +} + +var bar = 1 diff --git a/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.kt b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.kt new file mode 100644 index 00000000000..562684d97db --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.to.kt @@ -0,0 +1,5 @@ +package target + +fun targetPackFun(){} + + \ No newline at end of file diff --git a/idea/testData/copyPaste/moveDeclarations/OptimizeImportsAfterCut.to.kt b/idea/testData/copyPaste/moveDeclarations/OptimizeImportsAfterCut.to.kt new file mode 100644 index 00000000000..419295f320c --- /dev/null +++ b/idea/testData/copyPaste/moveDeclarations/OptimizeImportsAfterCut.to.kt @@ -0,0 +1,3 @@ +package to + + \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractMoveOnCutPasteTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractMoveOnCutPasteTest.kt index 7dc2fabfc9b..f6b364f67d8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractMoveOnCutPasteTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractMoveOnCutPasteTest.kt @@ -22,8 +22,8 @@ import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiDocumentManager import junit.framework.TestCase import org.jetbrains.kotlin.idea.AbstractCopyPasteTest +import org.jetbrains.kotlin.idea.core.moveCaret import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsEditorCookie -import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsIntentionAction import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsProcessor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.dumpTextWithErrors @@ -51,7 +51,9 @@ abstract class AbstractMoveOnCutPasteTest : AbstractCopyPasteTest() { val dependencyPsiFile = configureByDependencyIfExists(dependencyFileName) as KtFile? val sourcePsiFile = myFixture.configureByFile(sourceFileName) as KtFile val useCopy = InTextDirectivesUtils.isDirectiveDefined(testFileText, COPY_DIRECTIVE) + val caretMarker = myFixture.editor.document.createRangeMarker(myFixture.caretOffset, myFixture.caretOffset) myFixture.performEditorAction(if (useCopy) IdeActions.ACTION_COPY else IdeActions.ACTION_CUT) + myFixture.editor.moveCaret(caretMarker.startOffset) PsiDocumentManager.getInstance(project).commitAllDocuments() if (InTextDirectivesUtils.isDirectiveDefined(testFileText, OPTIMIZE_IMPORTS_AFTER_CUT_DIRECTIVE)) { @@ -61,7 +63,8 @@ abstract class AbstractMoveOnCutPasteTest : AbstractCopyPasteTest() { editor.putUserData(MoveDeclarationsEditorCookie.KEY, null) // because editor is reused val targetFileName = sourceFileName.replace(".kt", ".to.kt") - val targetPsiFile = configureTargetFile(targetFileName) + val targetFileExists = File(testDataPath + File.separator + targetFileName).exists() + val targetPsiFile = if (targetFileExists) configureTargetFile(targetFileName) else null performNotWriteEditorAction(IdeActions.ACTION_PASTE) val shouldBeAvailable = InTextDirectivesUtils.getPrefixedBoolean(testFileText, IS_AVAILABLE_DIRECTIVE) ?: true @@ -82,8 +85,10 @@ abstract class AbstractMoveOnCutPasteTest : AbstractCopyPasteTest() { KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, sourceFileName.replace(".kt", ".expected.kt")), sourcePsiFile.dumpTextWithErrors()) - KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, targetFileName.replace(".kt", ".expected.kt")), - targetPsiFile.dumpTextWithErrors()) + if (targetPsiFile != null) { + KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, targetFileName.replace(".kt", ".expected.kt")), + targetPsiFile.dumpTextWithErrors()) + } } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MoveOnCutPasteTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MoveOnCutPasteTestGenerated.java index 57d2009a2cb..fc249642cd2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MoveOnCutPasteTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MoveOnCutPasteTestGenerated.java @@ -48,6 +48,30 @@ public class MoveOnCutPasteTestGenerated extends AbstractMoveOnCutPasteTest { doTest(fileName); } + @TestMetadata("FromAnonymousObject.kt") + public void testFromAnonymousObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/FromAnonymousObject.kt"); + doTest(fileName); + } + + @TestMetadata("FromCompanionObjectToTopLevel.kt") + public void testFromCompanionObjectToTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/FromCompanionObjectToTopLevel.kt"); + doTest(fileName); + } + + @TestMetadata("FromObjectToSameFile.kt") + public void testFromObjectToSameFile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/FromObjectToSameFile.kt"); + doTest(fileName); + } + + @TestMetadata("FromObjectToTopLevel.kt") + public void testFromObjectToTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/FromObjectToTopLevel.kt"); + doTest(fileName); + } + @TestMetadata("OptimizeImportsAfterCut.kt") public void testOptimizeImportsAfterCut() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/OptimizeImportsAfterCut.kt");