Copy: Improve file copying

- Do not copy individual declarations if entire file is selected
  (keeping original content with comments, formatting, etc.)
- Update package directive when original file packages matches
  its directory

 #KT-20092 Fixed
 #KT-18196 Fixed
This commit is contained in:
Alexey Sedunov
2017-09-19 15:21:31 +03:00
parent fbf6bd534c
commit 0beadddb7f
22 changed files with 159 additions and 13 deletions
@@ -76,8 +76,18 @@ open class KtFile(viewProvider: FileViewProvider, val isCompiled: Boolean) :
return if (ast != null) ast.psi as KtPackageDirective else null
}
val packageFqName: FqName
var packageFqName: FqName
get() = stub?.getPackageFqName() ?: packageFqNameByTree
set(value) {
val packageDirective = packageDirective
if (packageDirective != null) {
packageDirective.fqName = value
}
else {
val newPackageDirective = KtPsiFactory(this).createPackageDirectiveIfNeeded(value) ?: return
addAfter(newPackageDirective, null)
}
}
val packageFqNameByTree: FqName
get() = packageDirectiveByTree?.fqName ?: FqName.ROOT
@@ -39,6 +39,8 @@ import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.MultiMap
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.packageMatchesDirectory
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.refactoring.move.*
@@ -63,8 +65,11 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
@set:TestOnly
var Project.newName: String? by UserDataProperty(Key.create("NEW_NAME"))
private fun PsiElement.getElementsToCopy(): List<KtElement> {
val declarationOrFile = parentsWithSelf.firstOrNull { it is KtFile || (it is KtNamedDeclaration && it.parent is KtFile) }
private fun PsiElement.getCopyableElement() =
parentsWithSelf.firstOrNull { it is KtFile || (it is KtNamedDeclaration && it.parent is KtFile) } as? KtElement
private fun PsiElement.getDeclarationsToCopy(): List<KtElement> {
val declarationOrFile = getCopyableElement()
return when (declarationOrFile) {
is KtFile -> declarationOrFile.declarations.filterIsInstance<KtNamedDeclaration>().ifEmpty { listOf(declarationOrFile) }
is KtNamedDeclaration -> listOf(declarationOrFile)
@@ -90,7 +95,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
private fun canCopyDeclarations(elements: Array<out PsiElement>): Boolean {
val containingFile =
elements
.flatMap { it.getElementsToCopy().ifEmpty { return false } }
.flatMap { it.getDeclarationsToCopy().ifEmpty { return false } }
.distinctBy { it.containingFile }
.singleOrNull()
?.containingFile ?: return false
@@ -174,7 +179,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
return copyFilesHandler.doCopy(sourceFiles, defaultTargetDirectory)
}
val elementsToCopy = elements.flatMap { it.getElementsToCopy() }
val elementsToCopy = elements.mapNotNull { it.getCopyableElement() }
if (elementsToCopy.isEmpty()) return
val singleElementToCopy = elementsToCopy.singleOrNull()
@@ -248,6 +253,9 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
if (singleElementToCopy is KtFile) {
targetFile = runWriteAction {
val copiedFile = targetDirectory.copyFileFrom(targetFileName, singleElementToCopy) as KtFile
if (singleElementToCopy.packageMatchesDirectory()) {
targetDirectory.getPackage()?.qualifiedName?.let { copiedFile.packageFqName = FqName(it) }
}
performDelayedRefactoringRequests(project)
copiedFile
}
@@ -0,0 +1,8 @@
package bar
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,6 @@
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,6 @@
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,4 @@
{
"mainFile": "test.kt",
"targetPackage": "bar"
}
@@ -0,0 +1,8 @@
package bar
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,8 @@
package foo
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,8 @@
package foo
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,4 @@
{
"mainFile": "foo/test.kt",
"targetPackage": "bar"
}
@@ -0,0 +1,8 @@
package foo
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,6 @@
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,8 @@
package foo
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,4 @@
{
"mainFile": "foo/test.kt",
"targetPackage": ""
}
@@ -0,0 +1,8 @@
package baz
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,8 @@
package baz
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,8 @@
package baz
val a = 42
// comment
// some other comment
val s = ""
@@ -0,0 +1,4 @@
{
"mainFile": "foo/test.kt",
"targetPackage": "bar"
}
@@ -8,4 +8,4 @@ class A {
class B {
val a: A = A()
val b: B = B()
}
}
@@ -1,15 +1,14 @@
package refactor.copy2
import refactor.ParentJava
import refactor.copy.Company
import kotlin.properties.Delegates
enum class Possible {
NO, YES
}
data class Potable(val p1: String)
class Insider(val peace: String)
class Init {
fun referred() = 0
fun moved() = referred()
@@ -28,6 +27,7 @@ class Simple {
}
annotation class MemAnn
class Variety<C> {
// object
object ExtractedObject {}
@@ -42,8 +42,7 @@ class Variety<C> {
private fun privateFun() = 0
fun <T> genFunB(p: T): T = p
fun <T> genFunC(p: T): C where T : C = p
@MemAnn
fun annotatedFun() = 0
@MemAnn fun annotatedFun() = 0
final fun finalFun() = 0
// property
var publicProp = 0
@@ -56,8 +55,7 @@ class Variety<C> {
var <T> List<T>.genVarL: T where T : C
get() = last()
set(p) {}
@MemAnn
val annotatedVal = 0
@MemAnn val annotatedVal = 0
var byVar by Delegates.notNull<Int>()
lateinit var lateVal: String
final val finalVal = 0
@@ -146,9 +144,9 @@ class CtorParameterChild(val pvc: String, var prc: String) : CtorParameter(pvc,
class CtorParameterChild2: CtorParameter {
constructor() : super("", "", "")
}
class CtorParameterChild3(override val pv: String, override var pr: String) : CtorParameter(pv, pv, pr)
data class CtorData(val pv: String, var pr: String) {}
class Company {
companion object {
val companyVal = 0
@@ -72,6 +72,30 @@ public class CopyTestGenerated extends AbstractCopyTest {
doTest(fileName);
}
@TestMetadata("copyFIleFromDefaultPackage/copyFIleFromDefaultPackage.test")
public void testCopyFIleFromDefaultPackage_CopyFIleFromDefaultPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyFIleFromDefaultPackage/copyFIleFromDefaultPackage.test");
doTest(fileName);
}
@TestMetadata("copyFIleRetainContent/copyFIleRetainContent.test")
public void testCopyFIleRetainContent_CopyFIleRetainContent() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyFIleRetainContent/copyFIleRetainContent.test");
doTest(fileName);
}
@TestMetadata("copyFIleToDefaultPackage/copyFIleToDefaultPackage.test")
public void testCopyFIleToDefaultPackage_CopyFIleToDefaultPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyFIleToDefaultPackage/copyFIleToDefaultPackage.test");
doTest(fileName);
}
@TestMetadata("copyFIleWithPackageAndDirUnmatched/copyFIleWithPackageAndDirUnmatched.test")
public void testCopyFIleWithPackageAndDirUnmatched_CopyFIleWithPackageAndDirUnmatched() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyFIleWithPackageAndDirUnmatched/copyFIleWithPackageAndDirUnmatched.test");
doTest(fileName);
}
@TestMetadata("copyFunCallQualificationWithParentheses/copyFunCallQualificationWithParentheses.test")
public void testCopyFunCallQualificationWithParentheses_CopyFunCallQualificationWithParentheses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyFunCallQualificationWithParentheses/copyFunCallQualificationWithParentheses.test");