Move refactoring available on cut/paste declarations from an object to top-level
This commit is contained in:
+12
-3
@@ -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<MoveDeclaratio
|
||||
if (startOffsets.size != 1) return emptyList()
|
||||
|
||||
val declarations = rangeToDeclarations(file, startOffsets[0], endOffsets[0])
|
||||
if (declarations.isEmpty() || declarations.any { it.parent !is KtFile }) return emptyList()
|
||||
|
||||
if (declarations.isEmpty()) return emptyList()
|
||||
|
||||
val parent = declarations.map { it.parent }.distinct().singleOrNull() ?: return emptyList()
|
||||
val sourceObjectFqName = when (parent) {
|
||||
is KtFile -> 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<MoveDeclarationsTransferableData> {
|
||||
|
||||
+4
-1
@@ -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 {
|
||||
|
||||
+22
-14
@@ -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<KtNamedDeclaration>,
|
||||
val pastedDeclarations: List<KtNamedDeclaration>,
|
||||
private val stubTexts: List<String>
|
||||
) {
|
||||
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<KtObjectDeclaration> { 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))
|
||||
|
||||
+1
-1
@@ -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<String>,
|
||||
val declarationNames: Set<String>
|
||||
) : TextBlockTransferableData {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// IS_AVAILABLE: false
|
||||
|
||||
fun foo() {
|
||||
val v = object : Runnable {
|
||||
override fun run() {
|
||||
}
|
||||
|
||||
<selection>
|
||||
fun bar() {
|
||||
}
|
||||
</selection>
|
||||
}
|
||||
}
|
||||
|
||||
<caret>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package source
|
||||
|
||||
class X {
|
||||
companion object {
|
||||
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
fun f() {
|
||||
bar++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun foo() {
|
||||
X.other()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package source
|
||||
|
||||
class X {
|
||||
companion object {
|
||||
<selection>
|
||||
fun foo() {
|
||||
other()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
</selection>
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
fun f() {
|
||||
bar++
|
||||
}
|
||||
}
|
||||
|
||||
<caret>
|
||||
@@ -0,0 +1,21 @@
|
||||
package source
|
||||
|
||||
fun sourcePackFun(){}
|
||||
|
||||
object SourceObject {
|
||||
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun foo() {
|
||||
SourceObject.other()
|
||||
sourcePackFun()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package source
|
||||
|
||||
fun sourcePackFun(){}
|
||||
|
||||
object SourceObject {
|
||||
<selection>
|
||||
fun foo() {
|
||||
other()
|
||||
sourcePackFun()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
</selection>
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
<caret>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package source
|
||||
|
||||
import target.foo
|
||||
|
||||
fun f() {
|
||||
foo()
|
||||
SourceObject.other()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package source
|
||||
|
||||
fun f() {
|
||||
SourceObject.foo()
|
||||
SourceObject.other()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package source
|
||||
|
||||
import target.foo
|
||||
|
||||
fun sourcePackFun(){}
|
||||
|
||||
object SourceObject {
|
||||
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package source
|
||||
|
||||
import target.targetPackFun
|
||||
|
||||
fun sourcePackFun(){}
|
||||
|
||||
object SourceObject {
|
||||
<selection>
|
||||
fun foo() {
|
||||
other()
|
||||
sourcePackFun()
|
||||
targetPackFun()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
</selection>
|
||||
|
||||
fun other() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package target
|
||||
|
||||
import source.SourceObject
|
||||
import source.sourcePackFun
|
||||
|
||||
fun targetPackFun(){}
|
||||
|
||||
|
||||
fun foo() {
|
||||
SourceObject.other()
|
||||
sourcePackFun()
|
||||
targetPackFun()
|
||||
bar++
|
||||
}
|
||||
|
||||
var bar = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
package target
|
||||
|
||||
fun targetPackFun(){}
|
||||
|
||||
<caret>
|
||||
@@ -0,0 +1,3 @@
|
||||
package to
|
||||
|
||||
<caret>
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user