Reworked k2k copy-paste action.
Get rid of resolve from copy phase, paste phase performs resolve in bg #KT-33939 Fixed
This commit is contained in:
+2
@@ -9,6 +9,7 @@ import com.intellij.ide.highlighter.JavaFileType
|
|||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.application.runWriteAction
|
import com.intellij.openapi.application.runWriteAction
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.conversion.copy.AbstractJavaToKotlinCopyPasteConversionTest
|
import org.jetbrains.kotlin.idea.conversion.copy.AbstractJavaToKotlinCopyPasteConversionTest
|
||||||
import org.jetbrains.kotlin.idea.conversion.copy.ConvertJavaCopyPasteProcessor
|
import org.jetbrains.kotlin.idea.conversion.copy.ConvertJavaCopyPasteProcessor
|
||||||
@@ -118,6 +119,7 @@ abstract class AbstractPerformanceJavaToKotlinCopyPasteConversionTest(private va
|
|||||||
|
|
||||||
private fun perfTestCore() {
|
private fun perfTestCore() {
|
||||||
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
|
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
|
||||||
|
UIUtil.dispatchAllInvocationEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stats() = stats[j2kIndex()]
|
private fun stats() = stats[j2kIndex()]
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import java.awt.datatransfer.DataFlavor
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class BasicKotlinReferenceTransferableData(
|
||||||
|
val sourceFileUrl: String,
|
||||||
|
val packageName: String,
|
||||||
|
val imports: List<String>,
|
||||||
|
val sourceText: String,
|
||||||
|
val textRanges: List<TextRange>
|
||||||
|
) : TextBlockTransferableData, Cloneable, Serializable {
|
||||||
|
override fun getFlavor() = dataFlavor
|
||||||
|
override fun getOffsetCount() = 0
|
||||||
|
|
||||||
|
override fun getOffsets(offsets: IntArray?, index: Int) = index
|
||||||
|
override fun setOffsets(offsets: IntArray?, index: Int) = index
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val dataFlavor: DataFlavor? by lazy {
|
||||||
|
try {
|
||||||
|
val dataClass = KotlinReferenceData::class.java
|
||||||
|
DataFlavor(
|
||||||
|
DataFlavor.javaJVMLocalObjectMimeType + ";class=" + dataClass.name,
|
||||||
|
"BasicKotlinReferenceTransferableData",
|
||||||
|
dataClass.classLoader
|
||||||
|
)
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
null
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun clone(): BasicKotlinReferenceTransferableData {
|
||||||
|
try {
|
||||||
|
return super.clone() as BasicKotlinReferenceTransferableData
|
||||||
|
} catch (e: CloneNotSupportedException) {
|
||||||
|
throw RuntimeException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+406
-118
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
* Copyright 2010-2020 JetBrains s.r.o.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -19,21 +19,25 @@ package org.jetbrains.kotlin.idea.codeInsight
|
|||||||
import com.intellij.codeInsight.CodeInsightSettings
|
import com.intellij.codeInsight.CodeInsightSettings
|
||||||
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor
|
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.application.ReadAction.nonBlocking
|
||||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.RangeMarker
|
import com.intellij.openapi.editor.RangeMarker
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.progress.Task
|
||||||
import com.intellij.openapi.project.DumbService
|
import com.intellij.openapi.project.DumbService
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.util.concurrency.AppExecutorUtil
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.allowResolveInDispatchThread
|
import org.jetbrains.kotlin.idea.caches.resolve.allowResolveInDispatchThread
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||||
@@ -45,19 +49,19 @@ import org.jetbrains.kotlin.idea.imports.importableFqName
|
|||||||
import org.jetbrains.kotlin.idea.kdoc.KDocReference
|
import org.jetbrains.kotlin.idea.kdoc.KDocReference
|
||||||
import org.jetbrains.kotlin.idea.references.*
|
import org.jetbrains.kotlin.idea.references.*
|
||||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.ProgressIndicatorUtils
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
|
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
|
||||||
|
import org.jetbrains.kotlin.idea.util.getSourceRoot
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.kdoc.psi.api.KDocElement
|
import org.jetbrains.kotlin.kdoc.psi.api.KDocElement
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
|
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
|
||||||
@@ -69,21 +73,15 @@ import java.awt.datatransfer.Transferable
|
|||||||
import java.awt.datatransfer.UnsupportedFlavorException
|
import java.awt.datatransfer.UnsupportedFlavorException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
//NOTE: this class is based on CopyPasteReferenceProcessor and JavaCopyPasteReferenceProcessor
|
class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinReferenceTransferableData>() {
|
||||||
class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReferenceTransferableData>() {
|
|
||||||
private val LOG = Logger.getInstance(KotlinCopyPasteReferenceProcessor::class.java)
|
|
||||||
|
|
||||||
private val IGNORE_REFERENCES_INSIDE: Array<Class<out KtElement>> = arrayOf(
|
override fun extractTransferableData(content: Transferable): List<BasicKotlinReferenceTransferableData> {
|
||||||
KtImportList::class.java,
|
|
||||||
KtPackageDirective::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun extractTransferableData(content: Transferable): List<KotlinReferenceTransferableData> {
|
|
||||||
if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE != CodeInsightSettings.NO) {
|
if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE != CodeInsightSettings.NO) {
|
||||||
try {
|
try {
|
||||||
val flavor = KotlinReferenceData.dataFlavor ?: return listOf()
|
val flavor = KotlinReferenceData.dataFlavor ?: return listOf()
|
||||||
val data = content.getTransferData(flavor) as? KotlinReferenceTransferableData ?: return listOf()
|
val data = content.getTransferData(flavor) as? BasicKotlinReferenceTransferableData ?: return listOf()
|
||||||
// copy to prevent changing of original by convertLineSeparators
|
// copy to prevent changing of original by convertLineSeparators
|
||||||
return listOf(data.clone())
|
return listOf(data.clone())
|
||||||
} catch (ignored: UnsupportedFlavorException) {
|
} catch (ignored: UnsupportedFlavorException) {
|
||||||
@@ -99,30 +97,56 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
editor: Editor,
|
editor: Editor,
|
||||||
startOffsets: IntArray,
|
startOffsets: IntArray,
|
||||||
endOffsets: IntArray
|
endOffsets: IntArray
|
||||||
): List<KotlinReferenceTransferableData> {
|
): List<BasicKotlinReferenceTransferableData> {
|
||||||
if (file !is KtFile || DumbService.getInstance(file.getProject()).isDumb) return listOf()
|
if (file !is KtFile || DumbService.getInstance(file.getProject()).isDumb) return listOf()
|
||||||
|
|
||||||
val collectedData = try {
|
check(startOffsets.size == endOffsets.size) {
|
||||||
collectReferenceData(file, startOffsets, endOffsets)
|
"startOffsets ${startOffsets.contentToString()} has to have same size as endOffsets ${endOffsets.contentToString()}"
|
||||||
} catch (e: ProcessCanceledException) {
|
|
||||||
// supposedly analysis can only be canceled from another thread
|
|
||||||
// do not log ProcessCanceledException as it is rethrown by IdeaLogger and code won't be copied
|
|
||||||
LOG.debug("ProcessCanceledException while analyzing references in ${file.getName()}. References can't be processed.")
|
|
||||||
return listOf()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
LOG.error("Exception in processing references for copy paste in file ${file.getName()}}", e)
|
|
||||||
return listOf()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collectedData.isEmpty()) return listOf()
|
val packageName = file.packageDirective?.fqName?.asString() ?: ""
|
||||||
|
val imports = file.importDirectives.map { it.text }
|
||||||
|
val endOfImportsOffset = file.importDirectives.map { it.endOffset }.max() ?: file.packageDirective?.endOffset ?: 0
|
||||||
|
val offsetDelta = if (startOffsets.any { it <= endOfImportsOffset }) 0 else endOfImportsOffset
|
||||||
|
val text = file.text.substring(offsetDelta)
|
||||||
|
val ranges = startOffsets.indices.map {
|
||||||
|
TextRange(startOffsets[it] - offsetDelta, endOffsets[it] - offsetDelta)
|
||||||
|
}
|
||||||
|
|
||||||
return listOf(KotlinReferenceTransferableData(collectedData.toTypedArray()))
|
return listOf(
|
||||||
|
BasicKotlinReferenceTransferableData(
|
||||||
|
sourceFileUrl = file.virtualFile.url,
|
||||||
|
packageName = packageName,
|
||||||
|
imports = imports,
|
||||||
|
sourceText = text,
|
||||||
|
textRanges = ranges
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun collectReferenceData(
|
||||||
|
textRanges: List<TextRange>,
|
||||||
|
file: KtFile,
|
||||||
|
targetFile: KtFile,
|
||||||
|
fakePackageName: String,
|
||||||
|
sourcePackageName: String
|
||||||
|
): List<KotlinReferenceData> =
|
||||||
|
collectReferenceData(
|
||||||
|
file,
|
||||||
|
textRanges.map { it.startOffset }.toIntArray(),
|
||||||
|
textRanges.map { it.endOffset }.toIntArray(),
|
||||||
|
fakePackageName,
|
||||||
|
sourcePackageName,
|
||||||
|
targetFile.packageDirective?.fqName?.asString() ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
fun collectReferenceData(
|
fun collectReferenceData(
|
||||||
file: KtFile,
|
file: KtFile,
|
||||||
startOffsets: IntArray,
|
startOffsets: IntArray,
|
||||||
endOffsets: IntArray
|
endOffsets: IntArray,
|
||||||
|
fakePackageName: String? = null,
|
||||||
|
sourcePackageName: String? = null,
|
||||||
|
targetPackageName: String? = null
|
||||||
): List<KotlinReferenceData> {
|
): List<KotlinReferenceData> {
|
||||||
val ranges = toTextRanges(startOffsets, endOffsets)
|
val ranges = toTextRanges(startOffsets, endOffsets)
|
||||||
val elementsByRange = ranges.associateBy({ it }, { textRange ->
|
val elementsByRange = ranges.associateBy({ it }, { textRange ->
|
||||||
@@ -130,60 +154,90 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
})
|
})
|
||||||
|
|
||||||
val allElementsToResolve = elementsByRange.values.flatten().flatMap { it.collectDescendantsOfType<KtElement>() }
|
val allElementsToResolve = elementsByRange.values.flatten().flatMap { it.collectDescendantsOfType<KtElement>() }
|
||||||
|
// TODO: allowResolveInDispatchThread could be dropped as soon as
|
||||||
|
// ConvertJavaCopyPasteProcessor will perform it on non UI thread
|
||||||
val bindingContext =
|
val bindingContext =
|
||||||
allowResolveInDispatchThread {
|
allowResolveInDispatchThread {
|
||||||
file.getResolutionFacade().analyze(allElementsToResolve, BodyResolveMode.PARTIAL)
|
file.getResolutionFacade().analyze(allElementsToResolve, BodyResolveMode.PARTIAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = ArrayList<KotlinReferenceData>()
|
val result = mutableListOf<KotlinReferenceData>()
|
||||||
for ((range, elements) in elementsByRange) {
|
for ((range, elements) in elementsByRange) {
|
||||||
for (element in elements) {
|
elements.forEachDescendant { ktElement ->
|
||||||
result.addReferenceDataInsideElement(element, file, range.start, startOffsets, endOffsets, bindingContext)
|
result.addReferenceDataInsideElement(
|
||||||
|
ktElement, file, range.start, ranges, bindingContext,
|
||||||
|
fakePackageName = fakePackageName, sourcePackageName = sourcePackageName, targetPackageName = targetPackageName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableCollection<KotlinReferenceData>.addReferenceDataInsideElement(
|
private fun List<PsiElement>.forEachDescendant(consumer: (KtElement) -> Unit) {
|
||||||
element: PsiElement,
|
this.forEach { it.forEachDescendant(consumer) }
|
||||||
file: KtFile,
|
}
|
||||||
startOffset: Int,
|
|
||||||
startOffsets: IntArray,
|
|
||||||
endOffsets: IntArray,
|
|
||||||
bindingContext: BindingContext
|
|
||||||
) {
|
|
||||||
if (PsiTreeUtil.getNonStrictParentOfType(element, *IGNORE_REFERENCES_INSIDE) != null) return
|
|
||||||
|
|
||||||
element.forEachDescendantOfType<KtElement>(canGoInside = { it::class.java as Class<*> !in IGNORE_REFERENCES_INSIDE }) { ktElement ->
|
private fun PsiElement.forEachDescendant(consumer: (KtElement) -> Unit) {
|
||||||
val reference = ktElement.mainReference ?: return@forEachDescendantOfType
|
if (PsiTreeUtil.getNonStrictParentOfType(this, *IGNORE_REFERENCES_INSIDE) != null) return
|
||||||
|
|
||||||
val descriptors = resolveReference(reference, bindingContext)
|
this.forEachDescendantOfType<KtElement>(canGoInside = {
|
||||||
//check whether this reference is unambiguous
|
it::class.java as Class<*> !in IGNORE_REFERENCES_INSIDE
|
||||||
if (reference !is KtMultiReference<*> && descriptors.size > 1) return@forEachDescendantOfType
|
}) { ktElement ->
|
||||||
|
consumer(ktElement)
|
||||||
for (descriptor in descriptors) {
|
|
||||||
val effectiveReferencedDescriptors = DescriptorToSourceUtils.getEffectiveReferencedDescriptors(descriptor).asSequence()
|
|
||||||
val declaration = effectiveReferencedDescriptors
|
|
||||||
.map { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
|
|
||||||
.singleOrNull()
|
|
||||||
if (declaration != null && declaration.isInCopiedArea(file, startOffsets, endOffsets)) continue
|
|
||||||
|
|
||||||
if (!reference.canBeResolvedViaImport(descriptor, bindingContext)) continue
|
|
||||||
|
|
||||||
val fqName = descriptor.importableFqName ?: continue
|
|
||||||
val kind = KotlinReferenceData.Kind.fromDescriptor(descriptor) ?: continue
|
|
||||||
val isQualifiable = KotlinReferenceData.isQualifiable(ktElement, descriptor)
|
|
||||||
val relativeStart = ktElement.range.start - startOffset
|
|
||||||
val relativeEnd = ktElement.range.end - startOffset
|
|
||||||
add(KotlinReferenceData(relativeStart, relativeEnd, fqName.asString(), isQualifiable, kind))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ReferenceToRestoreData(
|
private fun MutableCollection<KotlinReferenceData>.addReferenceDataInsideElement(
|
||||||
val reference: KtReference,
|
ktElement: KtElement,
|
||||||
val refData: KotlinReferenceData
|
file: KtFile,
|
||||||
)
|
startOffset: Int,
|
||||||
|
textRanges: List<TextRange>,
|
||||||
|
bindingContext: BindingContext,
|
||||||
|
fakePackageName: String? = null,
|
||||||
|
sourcePackageName: String? = null,
|
||||||
|
targetPackageName: String? = null
|
||||||
|
) {
|
||||||
|
val reference = ktElement.mainReference ?: return
|
||||||
|
|
||||||
|
val descriptors = resolveReference(reference, bindingContext)
|
||||||
|
//check whether this reference is unambiguous
|
||||||
|
if (reference !is KtMultiReference<*> && descriptors.size > 1) return
|
||||||
|
|
||||||
|
for (descriptor in descriptors) {
|
||||||
|
val effectiveReferencedDescriptors =
|
||||||
|
DescriptorToSourceUtils.getEffectiveReferencedDescriptors(descriptor).asSequence()
|
||||||
|
val declaration = effectiveReferencedDescriptors
|
||||||
|
.map { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
if (declaration?.isInCopiedArea(file, textRanges) == true ||
|
||||||
|
!reference.canBeResolvedViaImport(descriptor, bindingContext)
|
||||||
|
) continue
|
||||||
|
|
||||||
|
val importableFqName = descriptor.importableFqName ?: continue
|
||||||
|
val importableName = importableFqName.asString()
|
||||||
|
val pkgName = descriptor.findPackageFqNameSafe()?.asString() ?: ""
|
||||||
|
val importableShortName = importableFqName.shortName().asString()
|
||||||
|
|
||||||
|
val fqName = if (fakePackageName == pkgName) {
|
||||||
|
// It is possible to resolve unnecessary references from a target package (as we resolve it from a fake package)
|
||||||
|
if (sourcePackageName == targetPackageName && importableName == "$fakePackageName.$importableShortName") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// roll back to original package name when we faced faked pkg name
|
||||||
|
sourcePackageName + importableName.substring(fakePackageName.length)
|
||||||
|
} else importableName
|
||||||
|
|
||||||
|
val kind = KotlinReferenceData.Kind.fromDescriptor(descriptor) ?: continue
|
||||||
|
val isQualifiable = KotlinReferenceData.isQualifiable(ktElement, descriptor)
|
||||||
|
val relativeStart = ktElement.range.start - startOffset
|
||||||
|
val relativeEnd = ktElement.range.end - startOffset
|
||||||
|
add(KotlinReferenceData(relativeStart, relativeEnd, fqName, isQualifiable, kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ReferenceToRestoreData(val reference: KtReference, val refData: KotlinReferenceData)
|
||||||
|
private data class PsiElementByTextRange(val textRange: TextRange, val element: PsiElement)
|
||||||
|
|
||||||
override fun processTransferableData(
|
override fun processTransferableData(
|
||||||
project: Project,
|
project: Project,
|
||||||
@@ -191,29 +245,237 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
bounds: RangeMarker,
|
bounds: RangeMarker,
|
||||||
caretOffset: Int,
|
caretOffset: Int,
|
||||||
indented: Ref<Boolean>,
|
indented: Ref<Boolean>,
|
||||||
values: List<KotlinReferenceTransferableData>
|
values: List<BasicKotlinReferenceTransferableData>
|
||||||
) {
|
) {
|
||||||
if (DumbService.getInstance(project).isDumb) return
|
if (DumbService.getInstance(project).isDumb ||
|
||||||
|
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE == CodeInsightSettings.NO
|
||||||
|
) return
|
||||||
|
|
||||||
val document = editor.document
|
val document = editor.document
|
||||||
val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
|
val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
|
||||||
if (file !is KtFile) return
|
if (file !is KtFile) return
|
||||||
|
|
||||||
val referenceData = values.single().data
|
processReferenceData(project, file, bounds.startOffset, values.single())
|
||||||
|
}
|
||||||
|
|
||||||
processReferenceData(project, file, bounds.startOffset, referenceData)
|
private fun processReferenceData(
|
||||||
|
project: Project,
|
||||||
|
file: KtFile,
|
||||||
|
blockStart: Int,
|
||||||
|
transferableData: BasicKotlinReferenceTransferableData
|
||||||
|
) {
|
||||||
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
|
||||||
|
// figure out candidate elements in UI thread in paste phase
|
||||||
|
// as psi elements could be changed later on - relative offsets are used for mapping purposes
|
||||||
|
val elementsByRange = transferableData.textRanges.flatMap { originalTextRange ->
|
||||||
|
val textRange = TextRange(blockStart, blockStart + originalTextRange.endOffset - originalTextRange.startOffset)
|
||||||
|
file.elementsInRange(textRange)
|
||||||
|
.filter { it is KtElement || it is KDocElement }
|
||||||
|
.map { PsiElementByTextRange(it.textRange, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
processReferenceData(project, file) { indicator: ProgressIndicator ->
|
||||||
|
findReferenceDataToRestore(
|
||||||
|
file,
|
||||||
|
blockStart,
|
||||||
|
indicator,
|
||||||
|
elementsByRange,
|
||||||
|
transferableData
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processReferenceData(project: Project, file: KtFile, blockStart: Int, referenceData: Array<KotlinReferenceData>) {
|
fun processReferenceData(project: Project, file: KtFile, blockStart: Int, referenceData: Array<KotlinReferenceData>) {
|
||||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
processReferenceData(project, file) { indicator: ProgressIndicator ->
|
||||||
|
findReferencesToRestore(file, blockStart, referenceData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val referencesPossibleToRestore = findReferencesToRestore(file, blockStart, referenceData)
|
private fun processReferenceData(
|
||||||
|
project: Project,
|
||||||
|
file: KtFile,
|
||||||
|
findReferenceProvider: (indicator: ProgressIndicator) -> List<ReferenceToRestoreData>
|
||||||
|
) {
|
||||||
|
val task: Task.Backgroundable = object : Task.Backgroundable(project, "Resolve pasted references", true) {
|
||||||
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
assert(!ApplicationManager.getApplication().isWriteAccessAllowed) {
|
||||||
|
"Resolving references on dispatch thread leads to live lock"
|
||||||
|
}
|
||||||
|
ProgressIndicatorUtils.awaitWithCheckCanceled(
|
||||||
|
nonBlocking<List<ReferenceToRestoreData>> {
|
||||||
|
return@nonBlocking findReferenceProvider(indicator)
|
||||||
|
}
|
||||||
|
.finishOnUiThread(
|
||||||
|
ModalityState.defaultModalityState(),
|
||||||
|
Consumer<List<ReferenceToRestoreData>> { referencesPossibleToRestore ->
|
||||||
|
val selectedReferencesToRestore =
|
||||||
|
showRestoreReferencesDialog(project, referencesPossibleToRestore)
|
||||||
|
if (selectedReferencesToRestore.isEmpty()) return@Consumer
|
||||||
|
|
||||||
val selectedReferencesToRestore = showRestoreReferencesDialog(project, referencesPossibleToRestore)
|
project.executeWriteCommand("resolve pasted references") {
|
||||||
if (selectedReferencesToRestore.isEmpty()) return
|
restoreReferences(selectedReferencesToRestore, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.withDocumentsCommitted(project)
|
||||||
|
.submit(AppExecutorUtil.getAppExecutorService())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProgressManager.getInstance().run(task)
|
||||||
|
}
|
||||||
|
|
||||||
runWriteAction {
|
private fun findReferenceDataToRestore(
|
||||||
restoreReferences(selectedReferencesToRestore, file)
|
file: PsiFile,
|
||||||
|
blockStart: Int,
|
||||||
|
indicator: ProgressIndicator,
|
||||||
|
elementsByRange: List<PsiElementByTextRange>,
|
||||||
|
transferableData: BasicKotlinReferenceTransferableData
|
||||||
|
): List<ReferenceToRestoreData> {
|
||||||
|
if (file !is KtFile) return emptyList()
|
||||||
|
|
||||||
|
val referencesByRange = elementsByRange.flatMap { elementByTextRange ->
|
||||||
|
val element = elementByTextRange.element
|
||||||
|
if (!element.isValid) return@flatMap emptyList<Pair<TextRange, KtReference>>()
|
||||||
|
val refElementsRanges = mutableListOf<Pair<TextRange, KtReference>>()
|
||||||
|
val offsetDelta = element.startOffset - elementByTextRange.textRange.startOffset
|
||||||
|
element.forEachDescendant { ktElement ->
|
||||||
|
indicator.checkCanceled()
|
||||||
|
|
||||||
|
ktElement.mainReference?.let { ref ->
|
||||||
|
val textRange = ref.element.textRange
|
||||||
|
findReference(file, textRange)?.let {
|
||||||
|
// remap reference to the original (as it was on paste phase) text range
|
||||||
|
val range = if (offsetDelta == 0) textRange
|
||||||
|
else
|
||||||
|
TextRange(
|
||||||
|
textRange.startOffset - offsetDelta,
|
||||||
|
textRange.endOffset - offsetDelta
|
||||||
|
)
|
||||||
|
refElementsRanges.add(range to it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refElementsRanges
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val sourcePackageName = transferableData.packageName
|
||||||
|
val imports: List<String> = transferableData.imports
|
||||||
|
|
||||||
|
// Step 0. Recreate original source file (i.e. source file as it was on copy action) and resolve references from it
|
||||||
|
val ctxFile = sourceFile(file.project, transferableData) ?: file
|
||||||
|
|
||||||
|
// put original source file to some fake package to avoid ambiguous resolution ( a real file VS a virtual file )
|
||||||
|
val fakePackageName = "__kotlin.__some.__funky.__package"
|
||||||
|
|
||||||
|
val dummyOrigFileProlog =
|
||||||
|
"""
|
||||||
|
package $fakePackageName
|
||||||
|
|
||||||
|
${buildDummySourceScope(sourcePackageName, imports, fakePackageName, file, transferableData, ctxFile)}
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val dummyOriginalFile = KtPsiFactory(file.project)
|
||||||
|
.createAnalyzableFile(
|
||||||
|
"dummy-original.kt",
|
||||||
|
"$dummyOrigFileProlog${transferableData.sourceText}",
|
||||||
|
ctxFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val offsetDelta = dummyOrigFileProlog.length
|
||||||
|
|
||||||
|
val dummyOriginalFileTextRanges =
|
||||||
|
// it is required as it is shifted by dummy prolog
|
||||||
|
transferableData.textRanges.map { TextRange(it.startOffset + offsetDelta, it.endOffset + offsetDelta) }
|
||||||
|
|
||||||
|
// Step 1. Find references in copied blocks of (recreated) source file
|
||||||
|
val sourceFileBasedReferences =
|
||||||
|
collectReferenceData(dummyOriginalFileTextRanges, dummyOriginalFile, file, fakePackageName, sourcePackageName)
|
||||||
|
|
||||||
|
indicator.checkCanceled()
|
||||||
|
|
||||||
|
// Step 2. Find references to restore in a target file
|
||||||
|
return findReferencesToRestore(file, blockStart, sourceFileBasedReferences, referencesByRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildDummySourceScope(
|
||||||
|
sourcePkgName: String,
|
||||||
|
imports: List<String>,
|
||||||
|
fakePkgName: String,
|
||||||
|
file: KtFile,
|
||||||
|
transferableData: BasicKotlinReferenceTransferableData,
|
||||||
|
ctxFile: KtFile
|
||||||
|
): String {
|
||||||
|
// it could be that copied block contains inner classes or enums
|
||||||
|
// to solve this problem a special file is build:
|
||||||
|
// it contains imports from source package replaced with a fake package prefix
|
||||||
|
//
|
||||||
|
// result scope has to contain
|
||||||
|
// - those fake package imports those are successfully resolved (i.e. present in copied block)
|
||||||
|
// - those source package imports those are not present in a fake package
|
||||||
|
// - all rest imports
|
||||||
|
|
||||||
|
val sourceImportPrefix = "import $sourcePkgName"
|
||||||
|
val fakeImportPrefix = "import $fakePkgName"
|
||||||
|
|
||||||
|
val affectedSourcePkgImports = imports.filter { it.startsWith(sourceImportPrefix) }
|
||||||
|
val fakePkgImports = affectedSourcePkgImports.map { fakeImportPrefix + it.substring(sourceImportPrefix.length) }
|
||||||
|
|
||||||
|
fun joinLines(items: Collection<String>) = items.joinToString("\n")
|
||||||
|
|
||||||
|
val dummyImportsFile = KtPsiFactory(file.project)
|
||||||
|
.createAnalyzableFile(
|
||||||
|
"dummy-imports.kt",
|
||||||
|
"package $fakePkgName\n" +
|
||||||
|
"${joinLines(fakePkgImports)}\n" +
|
||||||
|
transferableData.sourceText,
|
||||||
|
ctxFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val dummyFileImports = dummyImportsFile.collectDescendantsOfType<KtImportDirective>().mapNotNull { directive ->
|
||||||
|
val importedReference =
|
||||||
|
directive.importedReference?.getQualifiedElementSelector()?.mainReference?.resolve() as? KtNamedDeclaration
|
||||||
|
importedReference?.let { directive.text }
|
||||||
|
}
|
||||||
|
|
||||||
|
val dummyFileImportsSet = dummyFileImports.toSet()
|
||||||
|
val filteredImports = imports.filter {
|
||||||
|
!it.startsWith(sourceImportPrefix) ||
|
||||||
|
!dummyFileImportsSet.contains(fakeImportPrefix + it.substring(sourceImportPrefix.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
return """
|
||||||
|
${joinLines(dummyFileImports)}
|
||||||
|
import ${sourcePkgName}.*
|
||||||
|
${joinLines(filteredImports)}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sourceFile(project: Project, transferableData: BasicKotlinReferenceTransferableData): KtFile? {
|
||||||
|
val sourceFile = VirtualFileManager.getInstance().findFileByUrl(transferableData.sourceFileUrl) ?: return null
|
||||||
|
if (sourceFile.getSourceRoot(project) == null) return null
|
||||||
|
|
||||||
|
return PsiManager.getInstance(project).findFile(sourceFile) as? KtFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterReferenceData(
|
||||||
|
refData: KotlinReferenceData,
|
||||||
|
fileResolutionScope: LexicalScope
|
||||||
|
): Boolean {
|
||||||
|
if (refData.isQualifiable) return true
|
||||||
|
|
||||||
|
val originalFqName = FqName(refData.fqName)
|
||||||
|
val name = originalFqName.shortName()
|
||||||
|
return when (refData.kind) {
|
||||||
|
// filter if function is already imported
|
||||||
|
KotlinReferenceData.Kind.FUNCTION -> fileResolutionScope
|
||||||
|
.findFunction(name, NoLookupLocation.FROM_IDE) { it.importableFqName == originalFqName } == null
|
||||||
|
// filter if property is already imported
|
||||||
|
KotlinReferenceData.Kind.PROPERTY -> fileResolutionScope
|
||||||
|
.findVariable(name, NoLookupLocation.FROM_IDE) { it.importableFqName == originalFqName } == null
|
||||||
|
else -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,33 +483,52 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
file: PsiFile,
|
file: PsiFile,
|
||||||
blockStart: Int,
|
blockStart: Int,
|
||||||
referenceData: Array<out KotlinReferenceData>
|
referenceData: Array<out KotlinReferenceData>
|
||||||
): List<ReferenceToRestoreData> {
|
): List<ReferenceToRestoreData> = findReferences(file, referenceData.map { it to findReference(it, file as KtFile, blockStart) })
|
||||||
if (file !is KtFile) return listOf()
|
|
||||||
|
private fun findReferencesToRestore(
|
||||||
|
file: PsiFile,
|
||||||
|
blockStart: Int,
|
||||||
|
referenceData: List<KotlinReferenceData>,
|
||||||
|
referencesByPosition: Map<TextRange, KtReference>
|
||||||
|
): List<ReferenceToRestoreData> =
|
||||||
|
// use already found reference candidates - so file could be changed
|
||||||
|
findReferences(file, referenceData
|
||||||
|
.map {
|
||||||
|
val textRange = TextRange(it.startOffset + blockStart, it.endOffset + blockStart)
|
||||||
|
val reference = referencesByPosition[textRange]
|
||||||
|
it to if (reference?.element?.isValid == true) reference else null
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun findReferences(
|
||||||
|
file: PsiFile,
|
||||||
|
references: List<Pair<KotlinReferenceData, KtReference?>>
|
||||||
|
): List<ReferenceToRestoreData> {
|
||||||
|
val ktFile = file as? KtFile ?: return listOf()
|
||||||
|
|
||||||
val references = referenceData.map { it to findReference(it, file, blockStart) }
|
|
||||||
val bindingContext = try {
|
val bindingContext = try {
|
||||||
file.getResolutionFacade().analyze(references.mapNotNull { it.second?.element }, BodyResolveMode.PARTIAL)
|
ktFile.getResolutionFacade().analyze(references.mapNotNull { it.second?.element }, BodyResolveMode.PARTIAL)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e is ControlFlowException) throw e
|
if (e is ControlFlowException) throw e
|
||||||
LOG.error("Failed to analyze references after copy paste", e)
|
LOG.error("Failed to analyze references after copy paste", e)
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
val fileResolutionScope = file.getResolutionFacade().getFileResolutionScope(file)
|
val fileResolutionScope = ktFile.getResolutionFacade().getFileResolutionScope(ktFile)
|
||||||
return references.mapNotNull { pair ->
|
return references.mapNotNull { pair ->
|
||||||
val data = pair.first
|
val data = pair.first
|
||||||
val reference = pair.second
|
val reference = pair.second
|
||||||
if (reference != null)
|
reference?.let { createReferenceToRestoreData(it, data, ktFile, fileResolutionScope, bindingContext) }
|
||||||
createReferenceToRestoreData(reference, data, file, fileResolutionScope, bindingContext)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findReference(data: KotlinReferenceData, file: KtFile, blockStart: Int): KtReference? {
|
private fun findReference(data: KotlinReferenceData, file: KtFile, blockStart: Int): KtReference? =
|
||||||
val startOffset = data.startOffset + blockStart
|
findReference(file, TextRange(data.startOffset + blockStart, data.endOffset + blockStart))
|
||||||
val endOffset = data.endOffset + blockStart
|
|
||||||
val element = file.findElementAt(startOffset) ?: return null
|
private fun findReference(file: KtFile, desiredRange: TextRange): KtReference? {
|
||||||
val desiredRange = TextRange(startOffset, endOffset)
|
val element = file.findElementAt(desiredRange.startOffset) ?: return null
|
||||||
|
return findReference(element, desiredRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findReference(element: PsiElement, desiredRange: TextRange): KtReference? {
|
||||||
for (current in element.parentsWithSelf) {
|
for (current in element.parentsWithSelf) {
|
||||||
val range = current.range
|
val range = current.range
|
||||||
if (current is KtElement && range == desiredRange) {
|
if (current is KtElement && range == desiredRange) {
|
||||||
@@ -265,21 +546,9 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
fileResolutionScope: LexicalScope,
|
fileResolutionScope: LexicalScope,
|
||||||
bindingContext: BindingContext
|
bindingContext: BindingContext
|
||||||
): ReferenceToRestoreData? {
|
): ReferenceToRestoreData? {
|
||||||
|
if (!filterReferenceData(refData, fileResolutionScope)) return null
|
||||||
|
|
||||||
val originalFqName = FqName(refData.fqName)
|
val originalFqName = FqName(refData.fqName)
|
||||||
val name = originalFqName.shortName()
|
|
||||||
|
|
||||||
if (!refData.isQualifiable) {
|
|
||||||
if (refData.kind == KotlinReferenceData.Kind.FUNCTION) {
|
|
||||||
if (fileResolutionScope.findFunction(name, NoLookupLocation.FROM_IDE) { it.importableFqName == originalFqName } != null) {
|
|
||||||
return null // already imported
|
|
||||||
}
|
|
||||||
} else if (refData.kind == KotlinReferenceData.Kind.PROPERTY) {
|
|
||||||
if (fileResolutionScope.findVariable(name, NoLookupLocation.FROM_IDE) { it.importableFqName == originalFqName } != null) {
|
|
||||||
return null // already imported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val referencedDescriptors = resolveReference(reference, bindingContext)
|
val referencedDescriptors = resolveReference(reference, bindingContext)
|
||||||
val referencedFqNames = referencedDescriptors
|
val referencedFqNames = referencedDescriptors
|
||||||
.filterNot { ErrorUtils.isError(it) }
|
.filterNot { ErrorUtils.isError(it) }
|
||||||
@@ -295,14 +564,15 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
return ReferenceToRestoreData(reference, refData)
|
return ReferenceToRestoreData(reference, refData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveReference(reference: KtReference, bindingContext: BindingContext): Collection<DeclarationDescriptor> {
|
private fun resolveReference(reference: KtReference, bindingContext: BindingContext): List<DeclarationDescriptor> {
|
||||||
val element = reference.element
|
val element = reference.element
|
||||||
if (element is KtNameReferenceExpression && reference is KtSimpleNameReference) {
|
if (element is KtNameReferenceExpression && reference is KtSimpleNameReference) {
|
||||||
bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element]
|
val classifierDescriptor =
|
||||||
?.let { return listOf(it) }
|
bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element]
|
||||||
|
(classifierDescriptor ?: bindingContext[BindingContext.REFERENCE_TARGET, element])?.let { return listOf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return reference.resolveToDescriptors(bindingContext)
|
return reference.resolveToDescriptors(bindingContext).toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreReferences(referencesToRestore: Collection<ReferenceToRestoreData>, file: KtFile) {
|
private fun restoreReferences(referencesToRestore: Collection<ReferenceToRestoreData>, file: KtFile) {
|
||||||
@@ -318,11 +588,13 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
val descriptorsToImport = ArrayList<DeclarationDescriptor>()
|
val descriptorsToImport = ArrayList<DeclarationDescriptor>()
|
||||||
|
|
||||||
for ((reference, refData) in referencesToRestore) {
|
for ((reference, refData) in referencesToRestore) {
|
||||||
|
if (!reference.element.isValid) continue
|
||||||
val fqName = FqName(refData.fqName)
|
val fqName = FqName(refData.fqName)
|
||||||
|
|
||||||
if (refData.isQualifiable) {
|
if (refData.isQualifiable) {
|
||||||
if (reference is KtSimpleNameReference) {
|
if (reference is KtSimpleNameReference) {
|
||||||
val pointer = smartPointerManager.createSmartPsiElementPointer(reference.element, file)
|
val pointer =
|
||||||
|
smartPointerManager.createSmartPsiElementPointer(reference.element, file)
|
||||||
bindingRequests.add(BindingRequest(pointer, fqName))
|
bindingRequests.add(BindingRequest(pointer, fqName))
|
||||||
} else if (reference is KDocReference) {
|
} else if (reference is KDocReference) {
|
||||||
descriptorsToImport.addAll(findImportableDescriptors(fqName, file))
|
descriptorsToImport.addAll(findImportableDescriptors(fqName, file))
|
||||||
@@ -351,11 +623,19 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
private fun findCallableToImport(fqName: FqName, file: KtFile): CallableDescriptor? =
|
private fun findCallableToImport(fqName: FqName, file: KtFile): CallableDescriptor? =
|
||||||
findImportableDescriptors(fqName, file).firstIsInstanceOrNull()
|
findImportableDescriptors(fqName, file).firstIsInstanceOrNull()
|
||||||
|
|
||||||
|
private tailrec fun DeclarationDescriptor.findPackageFqNameSafe(): FqName? {
|
||||||
|
return when {
|
||||||
|
this is PackageFragmentDescriptor -> this.fqNameOrNull()
|
||||||
|
containingDeclaration == null -> this.fqNameOrNull()
|
||||||
|
else -> this.containingDeclaration!!.findPackageFqNameSafe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showRestoreReferencesDialog(
|
private fun showRestoreReferencesDialog(
|
||||||
project: Project,
|
project: Project,
|
||||||
referencesToRestore: List<ReferenceToRestoreData>
|
referencesToRestore: List<ReferenceToRestoreData>
|
||||||
): Collection<ReferenceToRestoreData> {
|
): Collection<ReferenceToRestoreData> {
|
||||||
val fqNames = referencesToRestore.asSequence().map { it.refData.fqName }.toSortedSet()
|
val fqNames = referencesToRestore.map { it.refData.fqName }.toSortedSet()
|
||||||
|
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
declarationsToImportSuggested = fqNames
|
declarationsToImportSuggested = fqNames
|
||||||
@@ -378,13 +658,21 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<KotlinReference
|
|||||||
return startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }
|
return startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PsiElement.isInCopiedArea(fileCopiedFrom: KtFile, startOffsets: IntArray, endOffsets: IntArray): Boolean {
|
private fun PsiElement.isInCopiedArea(fileCopiedFrom: KtFile, textRanges: List<TextRange>): Boolean {
|
||||||
if (containingFile != fileCopiedFrom) return false
|
if (containingFile != fileCopiedFrom) return false
|
||||||
return toTextRanges(startOffsets, endOffsets).any { this.range in it }
|
return textRanges.any { this.range in it }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@get:TestOnly
|
@get:TestOnly
|
||||||
var declarationsToImportSuggested: Collection<String> = emptyList()
|
var declarationsToImportSuggested: Collection<String> = emptyList()
|
||||||
|
|
||||||
|
private val LOG = Logger.getInstance(KotlinCopyPasteReferenceProcessor::class.java)
|
||||||
|
|
||||||
|
private val IGNORE_REFERENCES_INSIDE: Array<Class<out KtElement>> = arrayOf(
|
||||||
|
KtImportList::class.java,
|
||||||
|
KtPackageDirective::class.java
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-31
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.codeInsight
|
package org.jetbrains.kotlin.idea.codeInsight
|
||||||
|
|
||||||
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
|
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
|
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
|
||||||
@@ -30,36 +29,7 @@ import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
|
|||||||
import java.awt.datatransfer.DataFlavor
|
import java.awt.datatransfer.DataFlavor
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class KotlinReferenceTransferableData(
|
data class KotlinReferenceData(
|
||||||
val data: Array<KotlinReferenceData>
|
|
||||||
) : TextBlockTransferableData, Cloneable, Serializable {
|
|
||||||
|
|
||||||
override fun getFlavor() = KotlinReferenceData.dataFlavor
|
|
||||||
|
|
||||||
override fun getOffsetCount() = data.size * 2
|
|
||||||
|
|
||||||
override fun getOffsets(offsets: IntArray, index: Int): Int {
|
|
||||||
var i = index
|
|
||||||
for (d in data) {
|
|
||||||
offsets[i++] = d.startOffset
|
|
||||||
offsets[i++] = d.endOffset
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setOffsets(offsets: IntArray, index: Int): Int {
|
|
||||||
var i = index
|
|
||||||
for (d in data) {
|
|
||||||
d.startOffset = offsets[i++]
|
|
||||||
d.endOffset = offsets[i++]
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun clone() = KotlinReferenceTransferableData(Array(data.size) { data[it].clone() })
|
|
||||||
}
|
|
||||||
|
|
||||||
class KotlinReferenceData(
|
|
||||||
var startOffset: Int,
|
var startOffset: Int,
|
||||||
var endOffset: Int,
|
var endOffset: Int,
|
||||||
val fqName: String,
|
val fqName: String,
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.util
|
||||||
|
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
|
object ProgressIndicatorUtils {
|
||||||
|
@JvmStatic
|
||||||
|
fun <T> awaitWithCheckCanceled(future: Future<T>) =
|
||||||
|
com.intellij.openapi.progress.util.BackgroundTaskUtil.awaitWithCheckCanceled(future)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.util
|
||||||
|
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
|
object ProgressIndicatorUtils {
|
||||||
|
@JvmStatic
|
||||||
|
fun <T> awaitWithCheckCanceled(future: Future<T>) =
|
||||||
|
com.intellij.openapi.progress.util.ProgressIndicatorUtils.awaitWithCheckCanceled(future)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class Bar(val v: Int){}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package to
|
||||||
|
|
||||||
|
import d.A
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
package d
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some comment in front of imports
|
||||||
|
*/
|
||||||
|
import foo.Bar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some kdoc for class Foo
|
||||||
|
*/
|
||||||
|
class Foo(val a: A) {
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d.A
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// WITH_LIBRARY: copyPaste/imports/KotlinLibrary
|
||||||
|
<selection>
|
||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
package d
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some comment in front of imports
|
||||||
|
*/
|
||||||
|
import foo.Bar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some kdoc for class Foo
|
||||||
|
*/
|
||||||
|
class Foo(val a: A) {
|
||||||
|
}
|
||||||
|
</selection>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package to
|
||||||
|
<caret>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class Bar(val v: Int){}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package to
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import d.A
|
||||||
|
import foo.Bar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some kdoc for class Foo
|
||||||
|
*/
|
||||||
|
class Foo(val a: A) {
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d.A
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// WITH_LIBRARY: copyPaste/imports/KotlinLibrary
|
||||||
|
package d
|
||||||
|
<selection>
|
||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import foo.Bar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some kdoc for class Foo
|
||||||
|
*/
|
||||||
|
class Foo(val a: A) {
|
||||||
|
}
|
||||||
|
</selection>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package to
|
||||||
|
<caret>
|
||||||
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.codeInsight
|
|||||||
|
|
||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
|
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
|
||||||
import org.jetbrains.kotlin.idea.test.dumpTextWithErrors
|
import org.jetbrains.kotlin.idea.test.dumpTextWithErrors
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
@@ -15,6 +16,7 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
|||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
|
|
||||||
abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
|
abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
|
||||||
|
private val TODO_INVESTIGATE_DIRECTIVE = "// TODO: Investigation is required"
|
||||||
private val NO_ERRORS_DUMP_DIRECTIVE = "// NO_ERRORS_DUMP"
|
private val NO_ERRORS_DUMP_DIRECTIVE = "// NO_ERRORS_DUMP"
|
||||||
private val DELETE_DEPENDENCIES_BEFORE_PASTE_DIRECTIVE = "// DELETE_DEPENDENCIES_BEFORE_PASTE"
|
private val DELETE_DEPENDENCIES_BEFORE_PASTE_DIRECTIVE = "// DELETE_DEPENDENCIES_BEFORE_PASTE"
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
|
|||||||
|
|
||||||
configureTargetFile(testFileName.replace(".kt", ".to.kt"))
|
configureTargetFile(testFileName.replace(".kt", ".to.kt"))
|
||||||
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
|
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
|
||||||
|
UIUtil.dispatchAllInvocationEvents()
|
||||||
|
|
||||||
|
if (InTextDirectivesUtils.isDirectiveDefined(testFileText, TODO_INVESTIGATE_DIRECTIVE)) {
|
||||||
|
println("File $testFile has $TODO_INVESTIGATE_DIRECTIVE")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val namesToImportDump = KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested.joinToString("\n")
|
val namesToImportDump = KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested.joinToString("\n")
|
||||||
KotlinTestUtils.assertEqualsToFile(testDataFile(testFileName.replace(".kt", ".expected.names")), namesToImportDump)
|
KotlinTestUtils.assertEqualsToFile(testDataFile(testFileName.replace(".kt", ".expected.names")), namesToImportDump)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.actions.OptimizeImportsProcessor
|
|||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
|
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
|
||||||
import org.jetbrains.kotlin.idea.core.moveCaret
|
import org.jetbrains.kotlin.idea.core.moveCaret
|
||||||
@@ -48,6 +49,7 @@ abstract class AbstractMoveOnCutPasteTest : AbstractCopyPasteTest() {
|
|||||||
val targetFileExists = testDataFile(targetFileName).exists()
|
val targetFileExists = testDataFile(targetFileName).exists()
|
||||||
val targetPsiFile = if (targetFileExists) configureTargetFile(targetFileName) else null
|
val targetPsiFile = if (targetFileExists) configureTargetFile(targetFileName) else null
|
||||||
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
|
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
|
||||||
|
UIUtil.dispatchAllInvocationEvents()
|
||||||
|
|
||||||
val shouldBeAvailable = InTextDirectivesUtils.getPrefixedBoolean(testFileText, IS_AVAILABLE_DIRECTIVE) ?: true
|
val shouldBeAvailable = InTextDirectivesUtils.getPrefixedBoolean(testFileText, IS_AVAILABLE_DIRECTIVE) ?: true
|
||||||
val cookie = editor.getUserData(MoveDeclarationsEditorCookie.KEY)
|
val cookie = editor.getUserData(MoveDeclarationsEditorCookie.KEY)
|
||||||
|
|||||||
+20
@@ -130,6 +130,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
|||||||
runTest("idea/testData/copyPaste/imports/DependencyOnStdLib.kt");
|
runTest("idea/testData/copyPaste/imports/DependencyOnStdLib.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EntireFile.kt")
|
||||||
|
public void testEntireFile() throws Exception {
|
||||||
|
runTest("idea/testData/copyPaste/imports/EntireFile.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("EnumEntries.kt")
|
@TestMetadata("EnumEntries.kt")
|
||||||
public void testEnumEntries() throws Exception {
|
public void testEnumEntries() throws Exception {
|
||||||
runTest("idea/testData/copyPaste/imports/EnumEntries.kt");
|
runTest("idea/testData/copyPaste/imports/EnumEntries.kt");
|
||||||
@@ -195,6 +200,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
|||||||
runTest("idea/testData/copyPaste/imports/ImportDirective.kt");
|
runTest("idea/testData/copyPaste/imports/ImportDirective.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ImportDirectiveAndClassBody.kt")
|
||||||
|
public void testImportDirectiveAndClassBody() throws Exception {
|
||||||
|
runTest("idea/testData/copyPaste/imports/ImportDirectiveAndClassBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("ImportableEntityInExtensionLiteral.kt")
|
@TestMetadata("ImportableEntityInExtensionLiteral.kt")
|
||||||
public void testImportableEntityInExtensionLiteral() throws Exception {
|
public void testImportableEntityInExtensionLiteral() throws Exception {
|
||||||
runTest("idea/testData/copyPaste/imports/ImportableEntityInExtensionLiteral.kt");
|
runTest("idea/testData/copyPaste/imports/ImportableEntityInExtensionLiteral.kt");
|
||||||
@@ -473,6 +483,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
|||||||
runTest("idea/testData/copyPaste/imports/DependencyOnStdLib.kt");
|
runTest("idea/testData/copyPaste/imports/DependencyOnStdLib.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EntireFile.kt")
|
||||||
|
public void testEntireFile() throws Exception {
|
||||||
|
runTest("idea/testData/copyPaste/imports/EntireFile.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("EnumEntries.kt")
|
@TestMetadata("EnumEntries.kt")
|
||||||
public void testEnumEntries() throws Exception {
|
public void testEnumEntries() throws Exception {
|
||||||
runTest("idea/testData/copyPaste/imports/EnumEntries.kt");
|
runTest("idea/testData/copyPaste/imports/EnumEntries.kt");
|
||||||
@@ -538,6 +553,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
|||||||
runTest("idea/testData/copyPaste/imports/ImportDirective.kt");
|
runTest("idea/testData/copyPaste/imports/ImportDirective.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ImportDirectiveAndClassBody.kt")
|
||||||
|
public void testImportDirectiveAndClassBody() throws Exception {
|
||||||
|
runTest("idea/testData/copyPaste/imports/ImportDirectiveAndClassBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("ImportableEntityInExtensionLiteral.kt")
|
@TestMetadata("ImportableEntityInExtensionLiteral.kt")
|
||||||
public void testImportableEntityInExtensionLiteral() throws Exception {
|
public void testImportableEntityInExtensionLiteral() throws Exception {
|
||||||
runTest("idea/testData/copyPaste/imports/ImportableEntityInExtensionLiteral.kt");
|
runTest("idea/testData/copyPaste/imports/ImportableEntityInExtensionLiteral.kt");
|
||||||
|
|||||||
+2
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.conversion.copy
|
package org.jetbrains.kotlin.idea.conversion.copy
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
import org.jetbrains.kotlin.idea.editor.KotlinEditorOptions
|
import org.jetbrains.kotlin.idea.editor.KotlinEditorOptions
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||||
@@ -53,6 +54,7 @@ abstract class AbstractJavaToKotlinCopyPasteConversionTest : AbstractJ2kCopyPast
|
|||||||
ConvertJavaCopyPasteProcessor.conversionPerformed = false
|
ConvertJavaCopyPasteProcessor.conversionPerformed = false
|
||||||
|
|
||||||
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
|
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
|
||||||
|
UIUtil.dispatchAllInvocationEvents()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
noConversionExpected, !ConvertJavaCopyPasteProcessor.conversionPerformed,
|
noConversionExpected, !ConvertJavaCopyPasteProcessor.conversionPerformed,
|
||||||
|
|||||||
Reference in New Issue
Block a user