Improvements in KotlinCopyPasteReferenceProcessor

reduced scope of nonBlockingRead to avoid long rerun of it
used smart pointers to keep valid elements over changes
moved back to non-modal progress

Relates to #KT-37414
This commit is contained in:
Vladimir Dolzhenko
2020-03-11 14:55:05 +01:00
parent b1c4b5f51b
commit 87242b419a
9 changed files with 217 additions and 139 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.builtins.isExtensionFunctionType import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
import org.jetbrains.kotlin.resolve.calls.components.transformToResolvedLambda import org.jetbrains.kotlin.resolve.calls.components.transformToResolvedLambda
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.NewConstraintSystem import org.jetbrains.kotlin.resolve.calls.inference.NewConstraintSystem
@@ -302,6 +303,7 @@ class KotlinConstraintSystemCompleter(
analyze: (PostponedResolvedAtom) -> Unit analyze: (PostponedResolvedAtom) -> Unit
): Boolean { ): Boolean {
for (argument in getOrderedNotAnalyzedPostponedArguments(topLevelAtoms)) { for (argument in getOrderedNotAnalyzedPostponedArguments(topLevelAtoms)) {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
if (canWeAnalyzeIt(c, argument)) { if (canWeAnalyzeIt(c, argument)) {
analyze(argument) analyze(argument)
return true return true
@@ -43,4 +43,10 @@ fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -
return result as T return result as T
} }
fun <T> runWithCancellationCheck(block: () -> T): T = block() fun <T> runWithCancellationCheck(block: () -> T): T = block()
inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
@@ -46,4 +46,10 @@ fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -
return result as T return result as T
} }
fun <T> runWithCancellationCheck(block: () -> T): T = block() fun <T> runWithCancellationCheck(block: () -> T): T = block()
inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
@@ -44,4 +44,10 @@ fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -
return result as T return result as T
} }
fun <T> runWithCancellationCheck(block: () -> T): T = CancellationCheck.runWithCancellationCheck(block) fun <T> runWithCancellationCheck(block: () -> T): T = CancellationCheck.runWithCancellationCheck(block)
inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
@@ -46,4 +46,10 @@ fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -
return result as T return result as T
} }
fun <T> runWithCancellationCheck(block: () -> T): T = block() fun <T> runWithCancellationCheck(block: () -> T): T = block()
inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
@@ -19,7 +19,6 @@ 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.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
@@ -54,6 +53,9 @@ 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.ProgressIndicatorUtils import org.jetbrains.kotlin.idea.util.ProgressIndicatorUtils
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.invokeLater
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
import org.jetbrains.kotlin.idea.util.application.runReadAction
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.idea.util.getSourceRoot
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
@@ -77,7 +79,6 @@ 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
class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinReferenceTransferableData>() { class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinReferenceTransferableData>() {
@@ -132,15 +133,17 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
file: KtFile, file: KtFile,
targetFile: KtFile, targetFile: KtFile,
fakePackageName: String, fakePackageName: String,
sourcePackageName: String sourcePackageName: String,
indicator: ProgressIndicator
): List<KotlinReferenceData> = ): List<KotlinReferenceData> =
collectReferenceData( collectReferenceData(
file, file = file,
textRanges.map { it.startOffset }.toIntArray(), startOffsets = textRanges.map { it.startOffset }.toIntArray(),
textRanges.map { it.endOffset }.toIntArray(), endOffsets = textRanges.map { it.endOffset }.toIntArray(),
fakePackageName, fakePackageName = fakePackageName,
sourcePackageName, sourcePackageName = sourcePackageName,
targetFile.packageDirective?.fqName?.asString() ?: "" targetPackageName = runReadAction { targetFile.packageDirective?.fqName?.asString() ?: "" },
indicator = indicator
) )
fun collectReferenceData( fun collectReferenceData(
@@ -149,24 +152,30 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
endOffsets: IntArray, endOffsets: IntArray,
fakePackageName: String? = null, fakePackageName: String? = null,
sourcePackageName: String? = null, sourcePackageName: String? = null,
targetPackageName: String? = null targetPackageName: String? = null,
indicator: ProgressIndicator? = null
): List<KotlinReferenceData> { ): List<KotlinReferenceData> {
val ranges = toTextRanges(startOffsets, endOffsets) val ranges = toTextRanges(startOffsets, endOffsets)
val elementsByRange = ranges.associateBy({ it }, { textRange -> val elements = ranges.flatMap { textRange ->
file.elementsInRange(textRange).filter { it is KtElement || it is KDocElement } runReadAction {
}) indicator?.checkCanceled()
file.elementsInRange(textRange).filter { it is KtElement || it is KDocElement }
}
}
val allElementsToResolve = elementsByRange.values.flatten().flatMap { it.collectDescendantsOfType<KtElement>() } val allElementsToResolve = elements.flatMap { it.collectDescendantsOfType<KtElement>() }
// TODO: allowResolveInDispatchThread could be dropped as soon as // TODO: allowResolveInDispatchThread could be dropped as soon as
// ConvertJavaCopyPasteProcessor will perform it on non UI thread // ConvertJavaCopyPasteProcessor will perform it on non UI thread
val bindingContext = val bindingContext = runReadAction {
allowResolveInDispatchThread { allowResolveInDispatchThread {
file.getResolutionFacade().analyze(allElementsToResolve, BodyResolveMode.PARTIAL) file.getResolutionFacade().analyze(allElementsToResolve, BodyResolveMode.PARTIAL)
} }
}
val result = mutableListOf<KotlinReferenceData>() val result = mutableListOf<KotlinReferenceData>()
for ((range, elements) in elementsByRange) { for (ktElement in allElementsToResolve) {
elements.forEachDescendant { ktElement -> runReadAction {
indicator?.checkCanceled()
result.addReferenceDataInsideElement( result.addReferenceDataInsideElement(
ktElement, file, ranges, bindingContext, fakePackageName = fakePackageName, ktElement, file, ranges, bindingContext, fakePackageName = fakePackageName,
sourcePackageName = sourcePackageName, targetPackageName = targetPackageName sourcePackageName = sourcePackageName, targetPackageName = targetPackageName
@@ -199,7 +208,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
sourcePackageName: String? = null, sourcePackageName: String? = null,
targetPackageName: String? = null targetPackageName: String? = null
) { ) {
val reference = ktElement.mainReference ?: return val reference = runReadAction { ktElement.mainReference } ?: return
val descriptors = resolveReference(reference, bindingContext) val descriptors = resolveReference(reference, bindingContext)
//check whether this reference is unambiguous //check whether this reference is unambiguous
@@ -239,7 +248,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
} }
private data class ReferenceToRestoreData(val reference: KtReference, val refData: KotlinReferenceData) private data class ReferenceToRestoreData(val reference: KtReference, val refData: KotlinReferenceData)
private data class PsiElementByTextRange(val originalTextRange: TextRange, val element: PsiElement) private data class PsiElementByTextRange(val originalTextRange: TextRange, val element: SmartPsiElementPointer<KtElement>)
override fun processTransferableData( override fun processTransferableData(
project: Project, project: Project,
@@ -273,21 +282,25 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
// figure out candidate elements in UI thread in paste phase // 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 // as psi elements could be changed later on - relative offsets are used for mapping purposes
val elementsByRange = transferableData.textRanges.flatMap { originalTextRange -> val elementsByRange = runReadAction {
val textRange = TextRange( transferableData.textRanges.flatMap { originalTextRange ->
startOffsetDelta, val elementsList = mutableListOf<PsiElementByTextRange>()
startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset val textRange = TextRange(
) startOffsetDelta,
startOffsetDelta = startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset + 1 startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset
file.elementsInRange(textRange) )
.filter { it is KtElement || it is KDocElement } startOffsetDelta = startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset + 1
.map { file.elementsInRange(textRange)
val range = TextRange( .filter { it is KtElement || it is KDocElement }
originalTextRange.startOffset + it.textRange.startOffset - textRange.startOffset, .forEachDescendant {
originalTextRange.startOffset + it.textRange.endOffset - textRange.startOffset val range = TextRange(
) originalTextRange.startOffset + it.textRange.startOffset - textRange.startOffset,
PsiElementByTextRange(range, it) originalTextRange.startOffset + it.textRange.endOffset - textRange.startOffset
} )
elementsList.add(PsiElementByTextRange(range, it.createSmartPointer()))
}
elementsList
}
} }
processReferenceData(project, editor, file) { indicator: ProgressIndicator -> processReferenceData(project, editor, file) { indicator: ProgressIndicator ->
@@ -312,24 +325,15 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
file: KtFile, file: KtFile,
findReferenceProvider: (indicator: ProgressIndicator) -> List<ReferenceToRestoreData> findReferenceProvider: (indicator: ProgressIndicator) -> List<ReferenceToRestoreData>
) { ) {
val task = object : Task.Modal(project, "Resolving pasted references ...", true) { val task = object : Task.Backgroundable(project, "Resolving pasted references ...", true) {
override fun run(indicator: ProgressIndicator) { override fun run(indicator: ProgressIndicator) {
assert(!ApplicationManager.getApplication().isWriteAccessAllowed) { assert(!ApplicationManager.getApplication().isWriteAccessAllowed) {
"Resolving references on dispatch thread leads to live lock" "Resolving references on dispatch thread leads to live lock"
} }
ProgressIndicatorUtils.awaitWithCheckCanceled(
nonBlocking<List<ReferenceToRestoreData>> { val referencesPossibleToRestore = findReferenceProvider(indicator)
return@nonBlocking findReferenceProvider(indicator)
} applyResolvedImports(project, referencesPossibleToRestore, file, editor)
.finishOnUiThread(
ModalityState.defaultModalityState(),
Consumer<List<ReferenceToRestoreData>> { referencesPossibleToRestore ->
applyResolvedImports(project, referencesPossibleToRestore, file, editor)
}
)
.withDocumentsCommitted(project)
.submit(AppExecutorUtil.getAppExecutorService())
)
} }
} }
ProgressManager.getInstance().run(task) ProgressManager.getInstance().run(task)
@@ -341,15 +345,17 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
file: KtFile, file: KtFile,
editor: Editor editor: Editor
) { ) {
val selectedReferencesToRestore = invokeLater {
showRestoreReferencesDialog(project, referencesPossibleToRestore) val selectedReferencesToRestore =
if (selectedReferencesToRestore.isEmpty()) return showRestoreReferencesDialog(project, referencesPossibleToRestore)
if (selectedReferencesToRestore.isEmpty()) return@invokeLater
project.executeWriteCommand("resolve pasted references") { project.executeWriteCommand("resolve pasted references") {
val imported = TreeSet<String>() val imported = TreeSet<String>()
restoreReferences(selectedReferencesToRestore, file, imported) restoreReferences(selectedReferencesToRestore, file, imported)
reviewAddedImports(project, editor, file, imported) reviewAddedImports(project, editor, file, imported)
}
} }
} }
@@ -361,31 +367,33 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
): List<ReferenceToRestoreData> { ): List<ReferenceToRestoreData> {
if (file !is KtFile) return emptyList() if (file !is KtFile) return emptyList()
val referencesByRange = elementsByRange.flatMap { elementByTextRange -> val referencesByRange: Map<TextRange, KtReference> = elementsByRange.mapNotNull { elementByTextRange ->
val element = elementByTextRange.element runReadAction {
if (!element.isValid) return@flatMap emptyList<Pair<TextRange, KtReference>>() val element = elementByTextRange.element.element ?: return@runReadAction null
val refElementsRanges = mutableListOf<Pair<TextRange, KtReference>>()
val offsetDelta = elementByTextRange.originalTextRange.startOffset - element.textRange.startOffset
element.forEachDescendant { ktElement ->
indicator.checkCanceled()
ktElement.mainReference?.let { ref -> val mainReference = element.mainReference
mainReference?.let { ref ->
val textRange = ref.element.textRange val textRange = ref.element.textRange
findReference(file, textRange)?.let { val findReference = findReference(file, textRange)
findReference?.let {
// remap reference to the original (as it was on paste phase) text range // remap reference to the original (as it was on paste phase) text range
val range = TextRange(textRange.startOffset + offsetDelta, textRange.endOffset + offsetDelta) val itTextRange = it.element.textRange
refElementsRanges.add(range to it) val refTextRange = ref.element.textRange
val originalTextRange = elementByTextRange.originalTextRange
val offset = originalTextRange.start - refTextRange.start
val range = TextRange(itTextRange.start + offset, itTextRange.end + offset)
range to it
} }
} }
} }
refElementsRanges
}.toMap() }.toMap()
val sourcePackageName = transferableData.packageName val sourcePackageName = transferableData.packageName
val imports: List<String> = transferableData.imports val imports: List<String> = transferableData.imports
val project = runReadAction { file.project }
// Step 0. Recreate original source file (i.e. source file as it was on copy action) and resolve references from it // 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 val ctxFile = sourceFile(project, transferableData) ?: file
// put original source file to some fake package to avoid ambiguous resolution ( a real file VS a virtual 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 fakePackageName = "__kotlin.__some.__funky.__package"
@@ -398,12 +406,14 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
""".trimIndent() """.trimIndent()
val dummyOriginalFile = KtPsiFactory(file.project) val dummyOriginalFile = runReadAction {
.createAnalyzableFile( KtPsiFactory(project)
"dummy-original.kt", .createAnalyzableFile(
"$dummyOrigFileProlog${transferableData.sourceText}", "dummy-original.kt",
ctxFile "$dummyOrigFileProlog${transferableData.sourceText}",
) ctxFile
)
}
val offsetDelta = dummyOrigFileProlog.length - transferableData.sourceTextOffset val offsetDelta = dummyOrigFileProlog.length - transferableData.sourceTextOffset
@@ -413,14 +423,21 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
// Step 1. Find references in copied blocks of (recreated) source file // Step 1. Find references in copied blocks of (recreated) source file
val sourceFileBasedReferences = val sourceFileBasedReferences =
collectReferenceData(dummyOriginalFileTextRanges, dummyOriginalFile, file, fakePackageName, sourcePackageName).map { collectReferenceData(dummyOriginalFileTextRanges, dummyOriginalFile, file, fakePackageName, sourcePackageName, indicator).map {
it.copy(startOffset = it.startOffset - offsetDelta, endOffset = it.endOffset - offsetDelta) it.copy(startOffset = it.startOffset - offsetDelta, endOffset = it.endOffset - offsetDelta)
} }
indicator.checkCanceled() indicator.checkCanceled()
// Step 2. Find references to restore in a target file // Step 2. Find references to restore in a target file
return findReferencesToRestore(file, sourceFileBasedReferences, referencesByRange) return ProgressIndicatorUtils.awaitWithCheckCanceled(
nonBlocking<List<ReferenceToRestoreData>> {
return@nonBlocking findReferencesToRestore(file, indicator, sourceFileBasedReferences, referencesByRange)
}
.withDocumentsCommitted(project)
.cancelWith(indicator)
.submit(AppExecutorUtil.getAppExecutorService())
)
} }
private fun buildDummySourceScope( private fun buildDummySourceScope(
@@ -448,19 +465,23 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
fun joinLines(items: Collection<String>) = items.joinToString("\n") fun joinLines(items: Collection<String>) = items.joinToString("\n")
val dummyImportsFile = KtPsiFactory(file.project) val dummyImportsFile = runReadAction {
.createAnalyzableFile( KtPsiFactory(file.project)
"dummy-imports.kt", .createAnalyzableFile(
"package $fakePkgName\n" + "dummy-imports.kt",
"${joinLines(fakePkgImports)}\n" + "package $fakePkgName\n" +
transferableData.sourceText, "${joinLines(fakePkgImports)}\n" +
ctxFile transferableData.sourceText,
) ctxFile
)
}
val dummyFileImports = dummyImportsFile.collectDescendantsOfType<KtImportDirective>().mapNotNull { directive -> val dummyFileImports = runReadAction {
val importedReference = dummyImportsFile.collectDescendantsOfType<KtImportDirective>().mapNotNull { directive ->
directive.importedReference?.getQualifiedElementSelector()?.mainReference?.resolve() as? KtNamedDeclaration val importedReference =
importedReference?.let { directive.text } directive.importedReference?.getQualifiedElementSelector()?.mainReference?.resolve() as? KtNamedDeclaration
importedReference?.let { directive.text }
}
} }
val dummyFileImportsSet = dummyFileImports.toSet() val dummyFileImportsSet = dummyFileImports.toSet()
@@ -476,12 +497,13 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
""" """
} }
private fun sourceFile(project: Project, transferableData: BasicKotlinReferenceTransferableData): KtFile? { private fun sourceFile(project: Project, transferableData: BasicKotlinReferenceTransferableData): KtFile? =
val sourceFile = VirtualFileManager.getInstance().findFileByUrl(transferableData.sourceFileUrl) ?: return null runReadAction {
if (sourceFile.getSourceRoot(project) == null) return null val sourceFile = VirtualFileManager.getInstance().findFileByUrl(transferableData.sourceFileUrl) ?: return@runReadAction null
if (sourceFile.getSourceRoot(project) == null) return@runReadAction null
return PsiManager.getInstance(project).findFile(sourceFile) as? KtFile PsiManager.getInstance(project).findFile(sourceFile) as? KtFile
} }
private fun filterReferenceData( private fun filterReferenceData(
refData: KotlinReferenceData, refData: KotlinReferenceData,
@@ -510,15 +532,18 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
private fun findReferencesToRestore( private fun findReferencesToRestore(
file: PsiFile, file: PsiFile,
indicator: ProgressIndicator,
referenceData: List<KotlinReferenceData>, referenceData: List<KotlinReferenceData>,
referencesByPosition: Map<TextRange, KtReference> referencesByPosition: Map<TextRange, KtReference>
): List<ReferenceToRestoreData> = ): List<ReferenceToRestoreData> {
// use already found reference candidates - so file could be changed // use already found reference candidates - so file could be changed
findReferences(file, referenceData.map { return findReferences(file, referenceData.map {
indicator.checkCanceled()
val textRange = TextRange(it.startOffset, it.endOffset) val textRange = TextRange(it.startOffset, it.endOffset)
val reference = referencesByPosition[textRange] val reference = referencesByPosition[textRange]
it to if (reference?.element?.isValid == true) reference else null it to reference
}) })
}
private fun findReferences( private fun findReferences(
file: PsiFile, file: PsiFile,
@@ -526,15 +551,25 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
): List<ReferenceToRestoreData> { ): List<ReferenceToRestoreData> {
val ktFile = file as? KtFile ?: return listOf() val ktFile = file as? KtFile ?: return listOf()
val bindingContext = try { val resolutionFacade = runReadAction { ktFile.getResolutionFacade() }
ktFile.getResolutionFacade().analyze(references.mapNotNull { it.second?.element }, BodyResolveMode.PARTIAL) val referencesList = mutableListOf<Pair<KotlinReferenceData, KtReference?>>()
} catch (e: Throwable) { val bindingContext =
if (e is ControlFlowException) throw e try {
LOG.error("Failed to analyze references after copy paste", e) runReadAction {
return emptyList() referencesList.addAll(references.mapNotNull { if (it.second?.element?.isValid == true) it.first to it.second else null })
} resolutionFacade.analyze(
val fileResolutionScope = ktFile.getResolutionFacade().getFileResolutionScope(ktFile) referencesList.mapNotNull { it.second?.element },
return references.mapNotNull { pair -> BodyResolveMode.PARTIAL
)
}
} catch (e: Throwable) {
if (e is ControlFlowException) throw e
LOG.error("Failed to analyze references after copy paste", e)
return emptyList()
}
val fileResolutionScope = runReadAction { resolutionFacade.getFileResolutionScope(ktFile) }
return referencesList.mapNotNull { pair ->
val data = pair.first val data = pair.first
val reference = pair.second val reference = pair.second
reference?.let { createReferenceToRestoreData(it, data, ktFile, fileResolutionScope, bindingContext) } reference?.let { createReferenceToRestoreData(it, data, ktFile, fileResolutionScope, bindingContext) }
@@ -545,19 +580,19 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
findReference(file, TextRange(data.startOffset + blockStart, data.endOffset + blockStart)) findReference(file, TextRange(data.startOffset + blockStart, data.endOffset + blockStart))
private fun findReference(file: KtFile, desiredRange: TextRange): KtReference? { private fun findReference(file: KtFile, desiredRange: TextRange): KtReference? {
val element = file.findElementAt(desiredRange.startOffset) ?: return null val element = runReadAction { file.findElementAt(desiredRange.startOffset) } ?: return null
return findReference(element, desiredRange) return findReference(element, desiredRange)
} }
private fun findReference(element: PsiElement, desiredRange: TextRange): KtReference? { private fun findReference(element: PsiElement, desiredRange: TextRange): KtReference? = runReadAction {
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) {
current.mainReference?.let { return it } current.mainReference?.let { return@runReadAction it }
} }
if (range !in desiredRange) return null if (range !in desiredRange) return@runReadAction null
} }
return null return@runReadAction null
} }
private fun createReferenceToRestoreData( private fun createReferenceToRestoreData(
@@ -587,13 +622,15 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
private fun resolveReference(reference: KtReference, bindingContext: BindingContext): List<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) { return runReadAction {
val classifierDescriptor = if (element.isValid && element is KtNameReferenceExpression && reference is KtSimpleNameReference) {
bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element] val classifierDescriptor =
(classifierDescriptor ?: bindingContext[BindingContext.REFERENCE_TARGET, element])?.let { return listOf(it) } bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element]
} (classifierDescriptor ?: bindingContext[BindingContext.REFERENCE_TARGET, element])?.let { return@runReadAction listOf(it) }
}
return reference.resolveToDescriptors(bindingContext).toList() reference.resolveToDescriptors(bindingContext).toList()
}
} }
private fun restoreReferences( private fun restoreReferences(
@@ -644,9 +681,11 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
} }
private fun findImportableDescriptors(fqName: FqName, file: KtFile): Collection<DeclarationDescriptor> { private fun findImportableDescriptors(fqName: FqName, file: KtFile): Collection<DeclarationDescriptor> {
return file.resolveImportReference(fqName).filterNot { return runReadAction {
/*TODO: temporary hack until we don't have ability to insert qualified reference into root package*/ file.resolveImportReference(fqName).filterNot {
DescriptorUtils.getParentOfType(it, PackageFragmentDescriptor::class.java)?.fqName?.isRoot ?: false /*TODO: temporary hack until we don't have ability to insert qualified reference into root package*/
DescriptorUtils.getParentOfType(it, PackageFragmentDescriptor::class.java)?.fqName?.isRoot ?: false
}
} }
} }
@@ -667,7 +706,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
): Collection<ReferenceToRestoreData> { ): Collection<ReferenceToRestoreData> {
val fqNames = referencesToRestore.map { it.refData.fqName }.toSortedSet() val fqNames = referencesToRestore.map { it.refData.fqName }.toSortedSet()
if (ApplicationManager.getApplication().isUnitTestMode) { if (isUnitTestMode()) {
declarationsToImportSuggested = fqNames declarationsToImportSuggested = fqNames
} }
@@ -688,10 +727,10 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
return startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) } return startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }
} }
private fun PsiElement.isInCopiedArea(fileCopiedFrom: KtFile, textRanges: List<TextRange>): Boolean { private fun PsiElement.isInCopiedArea(fileCopiedFrom: KtFile, textRanges: List<TextRange>): Boolean =
if (containingFile != fileCopiedFrom) return false runReadAction {
return textRanges.any { this.range in it } if (containingFile != fileCopiedFrom) false else textRanges.any { this.range in it }
} }
companion object { companion object {
@get:TestOnly @get:TestOnly
@@ -29,14 +29,12 @@ object ProgressIndicatorUtils {
): T = com.intellij.openapi.actionSystem.ex.ActionUtil.underModalProgress(project, progressTitle, computable) ): T = com.intellij.openapi.actionSystem.ex.ActionUtil.underModalProgress(project, progressTitle, computable)
@JvmStatic @JvmStatic
fun <T> awaitWithCheckCanceled(future: Future<T>) { fun <T> awaitWithCheckCanceled(future: Future<T>): T {
val indicator = val indicator = ProgressManager.getInstance().progressIndicator
ProgressManager.getInstance().progressIndicator
while (true) { while (true) {
checkCancelledEvenWithPCEDisabled(indicator) checkCancelledEvenWithPCEDisabled(indicator)
try { try {
future[10, TimeUnit.MILLISECONDS] return future[10, TimeUnit.MILLISECONDS]
return
} catch (ignore: TimeoutException) { } catch (ignore: TimeoutException) {
} catch (e: Throwable) { } catch (e: Throwable) {
val cause = e.cause val cause = e.cause
@@ -36,12 +36,11 @@ object ProgressIndicatorUtils {
} }
@JvmStatic @JvmStatic
fun <T> awaitWithCheckCanceled(future: Future<T>) { fun <T> awaitWithCheckCanceled(future: Future<T>): T {
while (true) { while (true) {
ProgressManager.checkCanceled() ProgressManager.checkCanceled()
try { try {
future.get(50, TimeUnit.MILLISECONDS) return future.get(50, TimeUnit.MILLISECONDS)
return
} catch (e: TimeoutException) { } catch (e: TimeoutException) {
// ignore // ignore
} catch (e: Exception) { } catch (e: Exception) {
@@ -1,10 +1,11 @@
/* /*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * 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 package org.jetbrains.kotlin.idea.util
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
@@ -14,8 +15,12 @@ import com.intellij.openapi.util.ThrowableComputable
import org.jetbrains.annotations.Nls import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
object ProgressIndicatorUtils { object ProgressIndicatorUtils {
private val LOG = Logger.getInstance(ProgressIndicatorUtils::class.java)
fun <T> underModalProgress( fun <T> underModalProgress(
project: Project, project: Project,
@Nls progressTitle: String, @Nls progressTitle: String,
@@ -33,6 +38,17 @@ object ProgressIndicatorUtils {
} }
@JvmStatic @JvmStatic
fun <T> awaitWithCheckCanceled(future: Future<T>) = fun <T> awaitWithCheckCanceled(future: Future<T>): T {
com.intellij.openapi.progress.util.BackgroundTaskUtil.awaitWithCheckCanceled(future) while (true) {
ProgressManager.checkCanceled()
try {
return future.get(50, TimeUnit.MILLISECONDS)
} catch (e: TimeoutException) {
// ignore
} catch (e: Exception) {
LOG.warn(e)
throw ProcessCanceledException(e)
}
}
}
} }