Show Review Added Imports on paste action
Fixed multi-caret selection in kotlin-to-kotlin copy-paste processor Relates to #KT-33939
This commit is contained in:
@@ -14,6 +14,7 @@ class BasicKotlinReferenceTransferableData(
|
||||
val sourceFileUrl: String,
|
||||
val packageName: String,
|
||||
val imports: List<String>,
|
||||
val sourceTextOffset: Int,
|
||||
val sourceText: String,
|
||||
val textRanges: List<TextRange>
|
||||
) : TextBlockTransferableData, Cloneable, Serializable {
|
||||
|
||||
+74
-44
@@ -37,10 +37,13 @@ import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
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.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||
import org.jetbrains.kotlin.idea.codeInsight.ReviewAddedImports.reviewAddedImports
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
|
||||
import org.jetbrains.kotlin.idea.core.util.end
|
||||
import org.jetbrains.kotlin.idea.core.util.range
|
||||
@@ -62,6 +65,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
|
||||
@@ -109,15 +113,14 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
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)
|
||||
}
|
||||
val ranges = startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }
|
||||
|
||||
return listOf(
|
||||
BasicKotlinReferenceTransferableData(
|
||||
sourceFileUrl = file.virtualFile.url,
|
||||
packageName = packageName,
|
||||
imports = imports,
|
||||
sourceTextOffset = offsetDelta,
|
||||
sourceText = text,
|
||||
textRanges = ranges
|
||||
)
|
||||
@@ -165,8 +168,8 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
for ((range, elements) in elementsByRange) {
|
||||
elements.forEachDescendant { ktElement ->
|
||||
result.addReferenceDataInsideElement(
|
||||
ktElement, file, range.start, ranges, bindingContext,
|
||||
fakePackageName = fakePackageName, sourcePackageName = sourcePackageName, targetPackageName = targetPackageName
|
||||
ktElement, file, ranges, bindingContext, fakePackageName = fakePackageName,
|
||||
sourcePackageName = sourcePackageName, targetPackageName = targetPackageName
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -190,7 +193,6 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
private fun MutableCollection<KotlinReferenceData>.addReferenceDataInsideElement(
|
||||
ktElement: KtElement,
|
||||
file: KtFile,
|
||||
startOffset: Int,
|
||||
textRanges: List<TextRange>,
|
||||
bindingContext: BindingContext,
|
||||
fakePackageName: String? = null,
|
||||
@@ -230,14 +232,14 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
|
||||
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
|
||||
val relativeStart = ktElement.range.start
|
||||
val relativeEnd = ktElement.range.end
|
||||
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)
|
||||
private data class PsiElementByTextRange(val originalTextRange: TextRange, val element: PsiElement)
|
||||
|
||||
override fun processTransferableData(
|
||||
project: Project,
|
||||
@@ -255,30 +257,42 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
|
||||
if (file !is KtFile) return
|
||||
|
||||
processReferenceData(project, file, bounds.startOffset, values.single())
|
||||
processReferenceData(project, editor, file, bounds.startOffset, values.single())
|
||||
}
|
||||
|
||||
private fun processReferenceData(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
file: KtFile,
|
||||
blockStart: Int,
|
||||
transferableData: BasicKotlinReferenceTransferableData
|
||||
) {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
var startOffsetDelta = blockStart
|
||||
|
||||
// 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)
|
||||
val textRange = TextRange(
|
||||
startOffsetDelta,
|
||||
startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset
|
||||
)
|
||||
startOffsetDelta = startOffsetDelta + originalTextRange.endOffset - originalTextRange.startOffset + 1
|
||||
file.elementsInRange(textRange)
|
||||
.filter { it is KtElement || it is KDocElement }
|
||||
.map { PsiElementByTextRange(it.textRange, it) }
|
||||
.map {
|
||||
val range = TextRange(
|
||||
originalTextRange.startOffset + it.textRange.startOffset - textRange.startOffset,
|
||||
originalTextRange.startOffset + it.textRange.endOffset - textRange.startOffset
|
||||
)
|
||||
PsiElementByTextRange(range, it)
|
||||
}
|
||||
}
|
||||
|
||||
processReferenceData(project, file) { indicator: ProgressIndicator ->
|
||||
processReferenceData(project, editor, file) { indicator: ProgressIndicator ->
|
||||
findReferenceDataToRestore(
|
||||
file,
|
||||
blockStart,
|
||||
indicator,
|
||||
elementsByRange,
|
||||
transferableData
|
||||
@@ -286,14 +300,15 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
}
|
||||
}
|
||||
|
||||
fun processReferenceData(project: Project, file: KtFile, blockStart: Int, referenceData: Array<KotlinReferenceData>) {
|
||||
processReferenceData(project, file) { indicator: ProgressIndicator ->
|
||||
fun processReferenceData(project: Project, editor: Editor, file: KtFile, blockStart: Int, referenceData: Array<KotlinReferenceData>) {
|
||||
processReferenceData(project, editor, file) { indicator: ProgressIndicator ->
|
||||
findReferencesToRestore(file, blockStart, referenceData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processReferenceData(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
file: KtFile,
|
||||
findReferenceProvider: (indicator: ProgressIndicator) -> List<ReferenceToRestoreData>
|
||||
) {
|
||||
@@ -309,13 +324,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
.finishOnUiThread(
|
||||
ModalityState.defaultModalityState(),
|
||||
Consumer<List<ReferenceToRestoreData>> { referencesPossibleToRestore ->
|
||||
val selectedReferencesToRestore =
|
||||
showRestoreReferencesDialog(project, referencesPossibleToRestore)
|
||||
if (selectedReferencesToRestore.isEmpty()) return@Consumer
|
||||
|
||||
project.executeWriteCommand("resolve pasted references") {
|
||||
restoreReferences(selectedReferencesToRestore, file)
|
||||
}
|
||||
applyResolvedImports(project, referencesPossibleToRestore, file, editor)
|
||||
}
|
||||
)
|
||||
.withDocumentsCommitted(project)
|
||||
@@ -326,9 +335,26 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
ProgressManager.getInstance().run(task)
|
||||
}
|
||||
|
||||
private fun applyResolvedImports(
|
||||
project: Project,
|
||||
referencesPossibleToRestore: List<ReferenceToRestoreData>,
|
||||
file: KtFile,
|
||||
editor: Editor
|
||||
) {
|
||||
val selectedReferencesToRestore =
|
||||
showRestoreReferencesDialog(project, referencesPossibleToRestore)
|
||||
if (selectedReferencesToRestore.isEmpty()) return
|
||||
|
||||
project.executeWriteCommand("resolve pasted references") {
|
||||
val imported = TreeSet<String>()
|
||||
restoreReferences(selectedReferencesToRestore, file, imported)
|
||||
|
||||
reviewAddedImports(project, editor, file, imported)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferenceDataToRestore(
|
||||
file: PsiFile,
|
||||
blockStart: Int,
|
||||
indicator: ProgressIndicator,
|
||||
elementsByRange: List<PsiElementByTextRange>,
|
||||
transferableData: BasicKotlinReferenceTransferableData
|
||||
@@ -339,7 +365,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
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
|
||||
val offsetDelta = elementByTextRange.originalTextRange.startOffset - element.textRange.startOffset
|
||||
element.forEachDescendant { ktElement ->
|
||||
indicator.checkCanceled()
|
||||
|
||||
@@ -347,12 +373,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
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
|
||||
)
|
||||
val range = TextRange(textRange.startOffset + offsetDelta, textRange.endOffset + offsetDelta)
|
||||
refElementsRanges.add(range to it)
|
||||
}
|
||||
}
|
||||
@@ -384,7 +405,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
ctxFile
|
||||
)
|
||||
|
||||
val offsetDelta = dummyOrigFileProlog.length
|
||||
val offsetDelta = dummyOrigFileProlog.length - transferableData.sourceTextOffset
|
||||
|
||||
val dummyOriginalFileTextRanges =
|
||||
// it is required as it is shifted by dummy prolog
|
||||
@@ -392,12 +413,14 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
|
||||
// Step 1. Find references in copied blocks of (recreated) source file
|
||||
val sourceFileBasedReferences =
|
||||
collectReferenceData(dummyOriginalFileTextRanges, dummyOriginalFile, file, fakePackageName, sourcePackageName)
|
||||
collectReferenceData(dummyOriginalFileTextRanges, dummyOriginalFile, file, fakePackageName, sourcePackageName).map {
|
||||
it.copy(startOffset = it.startOffset - offsetDelta, endOffset = it.endOffset - offsetDelta)
|
||||
}
|
||||
|
||||
indicator.checkCanceled()
|
||||
|
||||
// Step 2. Find references to restore in a target file
|
||||
return findReferencesToRestore(file, blockStart, sourceFileBasedReferences, referencesByRange)
|
||||
return findReferencesToRestore(file, sourceFileBasedReferences, referencesByRange)
|
||||
}
|
||||
|
||||
private fun buildDummySourceScope(
|
||||
@@ -487,17 +510,15 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
|
||||
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
|
||||
})
|
||||
findReferences(file, referenceData.map {
|
||||
val textRange = TextRange(it.startOffset, it.endOffset)
|
||||
val reference = referencesByPosition[textRange]
|
||||
it to if (reference?.element?.isValid == true) reference else null
|
||||
})
|
||||
|
||||
private fun findReferences(
|
||||
file: PsiFile,
|
||||
@@ -575,7 +596,11 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
return reference.resolveToDescriptors(bindingContext).toList()
|
||||
}
|
||||
|
||||
private fun restoreReferences(referencesToRestore: Collection<ReferenceToRestoreData>, file: KtFile) {
|
||||
private fun restoreReferences(
|
||||
referencesToRestore: Collection<ReferenceToRestoreData>,
|
||||
file: KtFile,
|
||||
imported: MutableSet<String>
|
||||
) {
|
||||
val importHelper = ImportInsertHelper.getInstance(file.project)
|
||||
val smartPointerManager = SmartPointerManager.getInstance(file.project)
|
||||
|
||||
@@ -606,9 +631,14 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
|
||||
|
||||
for (descriptor in descriptorsToImport) {
|
||||
importHelper.importDescriptor(file, descriptor)
|
||||
descriptor.getImportableDescriptor().importableFqName?.let { imported.add(it.asString()) }
|
||||
}
|
||||
|
||||
for ((pointer, fqName) in bindingRequests) {
|
||||
pointer.element?.mainReference?.bindToFqName(fqName, KtSimpleNameReference.ShorteningMode.DELAYED_SHORTENING)
|
||||
pointer.element?.mainReference?.let {
|
||||
it.bindToFqName(fqName, KtSimpleNameReference.ShorteningMode.DELAYED_SHORTENING)
|
||||
imported.add(fqName.asString())
|
||||
}
|
||||
}
|
||||
performDelayedRefactoringRequests(file.project)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.CodeInsightBundle
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.codeInsight.hint.HintManagerImpl
|
||||
import com.intellij.codeInsight.hint.HintUtil
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.LightweightHint
|
||||
import com.intellij.util.ArrayUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import java.util.*
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
import javax.swing.event.HyperlinkListener
|
||||
|
||||
object ReviewAddedImports {
|
||||
@get:TestOnly
|
||||
var importsToBeReviewed: Collection<String> = emptyList()
|
||||
|
||||
@get:TestOnly
|
||||
var importsToBeDeleted: Collection<String> = emptyList()
|
||||
|
||||
fun reviewAddedImports(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
file: KtFile,
|
||||
imported: TreeSet<String>
|
||||
) {
|
||||
if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE == CodeInsightSettings.YES &&
|
||||
!imported.isEmpty()
|
||||
) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
importsToBeReviewed = imported
|
||||
removeImports(project, file, importsToBeDeleted)
|
||||
return
|
||||
}
|
||||
val notificationText = CodeInsightBundle
|
||||
.message("copy.paste.reference.notification", imported.size)
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
Runnable {
|
||||
showHint(
|
||||
editor,
|
||||
notificationText,
|
||||
HyperlinkListener { e: HyperlinkEvent ->
|
||||
if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
reviewImports(project, file, imported)
|
||||
}
|
||||
}
|
||||
)
|
||||
}, ModalityState.NON_MODAL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showHint(
|
||||
editor: Editor,
|
||||
info: String,
|
||||
hyperlinkListener: HyperlinkListener
|
||||
) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return
|
||||
val hint = LightweightHint(HintUtil.createInformationLabel(info, hyperlinkListener, null, null))
|
||||
val flags = HintManager.HIDE_BY_ANY_KEY or HintManager.HIDE_BY_TEXT_CHANGE
|
||||
HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, HintManager.UNDER, flags, 0, false)
|
||||
}
|
||||
|
||||
private fun reviewImports(
|
||||
project: Project,
|
||||
file: KtFile,
|
||||
importedClasses: Set<String>
|
||||
) {
|
||||
val dialog = RestoreReferencesDialog(project, ArrayUtil.toObjectArray(importedClasses))
|
||||
dialog.title = CodeInsightBundle.message("dialog.import.on.paste.title3")
|
||||
if (dialog.showAndGet()) {
|
||||
removeImports(project, file, dialog.selectedElements.map { it as String }.toSortedSet())
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeImports(
|
||||
project: Project,
|
||||
file: KtFile,
|
||||
importsToRemove: Collection<String>
|
||||
) {
|
||||
if (importsToRemove.isEmpty()) return
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project, "revert applied imports", null, Runnable {
|
||||
val newImports = file.importDirectives.mapNotNull {
|
||||
val importedFqName = it.importedFqName ?: return@mapNotNull null
|
||||
if (importsToRemove.contains(importedFqName.asString())) return@mapNotNull null
|
||||
ImportPath(importedFqName, it.isAllUnder, it.aliasName?.let { alias -> Name.identifier(alias) })
|
||||
}
|
||||
KotlinImportOptimizer.replaceImports(file, newImports)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.CodeInsightBundle
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.codeInsight.hint.HintManagerImpl
|
||||
import com.intellij.codeInsight.hint.HintUtil
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.LightweightHint
|
||||
import com.intellij.util.ArrayUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import java.util.*
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
import javax.swing.event.HyperlinkListener
|
||||
|
||||
object ReviewAddedImports {
|
||||
@get:TestOnly
|
||||
var importsToBeReviewed: Collection<String> = emptyList()
|
||||
|
||||
@get:TestOnly
|
||||
var importsToBeDeleted: Collection<String> = emptyList()
|
||||
|
||||
fun reviewAddedImports(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
file: KtFile,
|
||||
imported: TreeSet<String>
|
||||
) {
|
||||
if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE == CodeInsightSettings.YES &&
|
||||
!imported.isEmpty()
|
||||
) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
importsToBeReviewed = imported
|
||||
removeImports(project, file, importsToBeDeleted)
|
||||
return
|
||||
}
|
||||
// there is actual no such functionality in 191
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeImports(
|
||||
project: Project,
|
||||
file: KtFile,
|
||||
importsToRemove: Collection<String>
|
||||
) {
|
||||
if (importsToRemove.isEmpty()) return
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project, "revert applied imports", null, Runnable {
|
||||
val newImports = file.importDirectives.mapNotNull {
|
||||
val importedFqName = it.importedFqName ?: return@mapNotNull null
|
||||
if (importsToRemove.contains(importedFqName.asString())) return@mapNotNull null
|
||||
ImportPath(importedFqName, it.isAllUnder, it.aliasName?.let { alias -> Name.identifier(alias) })
|
||||
}
|
||||
KotlinImportOptimizer.replaceImports(file, newImports)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.CodeInsightBundle
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.codeInsight.hint.HintManagerImpl
|
||||
import com.intellij.codeInsight.hint.HintUtil
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.LightweightHint
|
||||
import com.intellij.util.ArrayUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import java.util.*
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
import javax.swing.event.HyperlinkListener
|
||||
|
||||
object ReviewAddedImports {
|
||||
@get:TestOnly
|
||||
var importsToBeReviewed: Collection<String> = emptyList()
|
||||
|
||||
@get:TestOnly
|
||||
var importsToBeDeleted: Collection<String> = emptyList()
|
||||
|
||||
fun reviewAddedImports(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
file: KtFile,
|
||||
imported: TreeSet<String>
|
||||
) {
|
||||
if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE == CodeInsightSettings.YES &&
|
||||
!imported.isEmpty()
|
||||
) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
importsToBeReviewed = imported
|
||||
removeImports(project, file, importsToBeDeleted)
|
||||
return
|
||||
}
|
||||
// there is actual no such functionality in 192
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeImports(
|
||||
project: Project,
|
||||
file: KtFile,
|
||||
importsToRemove: Collection<String>
|
||||
) {
|
||||
if (importsToRemove.isEmpty()) return
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project, "revert applied imports", null, Runnable {
|
||||
val newImports = file.importDirectives.mapNotNull {
|
||||
val importedFqName = it.importedFqName ?: return@mapNotNull null
|
||||
if (importsToRemove.contains(importedFqName.asString())) return@mapNotNull null
|
||||
ImportPath(importedFqName, it.isAllUnder, it.aliasName?.let { alias -> Name.identifier(alias) })
|
||||
}
|
||||
KotlinImportOptimizer.replaceImports(file, newImports)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
rangeMarker.isGreedyToLeft = true
|
||||
rangeMarker.isGreedyToRight = true
|
||||
|
||||
KotlinCopyPasteReferenceProcessor().processReferenceData(project, targetFile, bounds.start, referenceData.toTypedArray())
|
||||
KotlinCopyPasteReferenceProcessor().processReferenceData(project, editor, targetFile, bounds.start, referenceData.toTypedArray())
|
||||
|
||||
runWriteAction {
|
||||
explicitImports.forEach { fqName ->
|
||||
@@ -224,7 +224,11 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
}
|
||||
|
||||
val dummyFile = KtPsiFactory(targetFile.project).createAnalyzableFile("dummy.kt", fileText, targetFile)
|
||||
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArrayOf(blockStart!!), intArrayOf(blockEnd!!))
|
||||
val startOffset = blockStart!!
|
||||
val endOffset = blockEnd!!
|
||||
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArrayOf(startOffset), intArrayOf(endOffset)).map {
|
||||
it.copy(startOffset = it.startOffset - startOffset, endOffset = it.endOffset - startOffset)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateDummyFunctionName(convertedCode: String): String {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// ERROR: Unresolved reference: B
|
||||
// ERROR: Unresolved reference: B
|
||||
// ERROR: Unresolved reference: B
|
||||
// ERROR: A 'return' expression required in a function with a block body ('{...}')
|
||||
package to
|
||||
|
||||
import a.A
|
||||
import a.T
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.atomic.AtomicLong as ALong
|
||||
|
||||
fun g(t: T): Int {
|
||||
g(A)
|
||||
B.f()
|
||||
A
|
||||
B
|
||||
A.Companion
|
||||
B.Companion
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
a.A
|
||||
a.B
|
||||
a.T
|
||||
@@ -0,0 +1 @@
|
||||
a.B
|
||||
@@ -0,0 +1,25 @@
|
||||
package a
|
||||
|
||||
interface T
|
||||
|
||||
class A {
|
||||
companion object: T {
|
||||
}
|
||||
}
|
||||
|
||||
class B {
|
||||
companion object {
|
||||
fun f(): Int
|
||||
}
|
||||
}
|
||||
|
||||
<selection>fun g(t: T): Int {
|
||||
g(A)
|
||||
B.f()
|
||||
A
|
||||
B
|
||||
A.Companion
|
||||
B.Companion
|
||||
}</selection>
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package to
|
||||
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.atomic.AtomicLong as ALong
|
||||
|
||||
<caret>
|
||||
+1
-1
@@ -14,7 +14,7 @@ package d
|
||||
import foo.Bar
|
||||
|
||||
/**
|
||||
* Some kdoc for class Foo
|
||||
* Some kdoc [Bar] for class Foo
|
||||
*/
|
||||
class Foo(val a: A) {
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ package d
|
||||
import foo.Bar
|
||||
|
||||
/**
|
||||
* Some kdoc for class Foo
|
||||
* Some kdoc [Bar] for class Foo
|
||||
*/
|
||||
class Foo(val a: A) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package foo
|
||||
|
||||
class Bar(val v: Int){}
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
|
||||
import foo.Bar
|
||||
|
||||
/**
|
||||
* Some kdoc [Bar] for class Foo
|
||||
*/
|
||||
|
||||
class Foo(val a: A) {
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
d.A
|
||||
@@ -0,0 +1,22 @@
|
||||
// WITH_LIBRARY: copyPaste/imports/KotlinLibrary
|
||||
<selection>
|
||||
<caret>/*
|
||||
* 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</selection>
|
||||
|
||||
/*
|
||||
* Some comment in front of imports
|
||||
*/
|
||||
<selection>
|
||||
<caret>import foo.Bar</selection>
|
||||
|
||||
<selection>
|
||||
<caret>/**
|
||||
* Some kdoc [Bar] for class Foo
|
||||
*/</selection>
|
||||
<selection>
|
||||
<caret>class Foo(val a: A) {
|
||||
}
|
||||
</selection>
|
||||
@@ -47,6 +47,14 @@ abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
|
||||
}
|
||||
|
||||
KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested = emptyList()
|
||||
ReviewAddedImports.importsToBeReviewed = emptyList()
|
||||
|
||||
val importsToBeDeletedFile = testDataFile(testFileName.replace(".kt", ".imports_to_delete"))
|
||||
ReviewAddedImports.importsToBeDeleted = if (importsToBeDeletedFile.exists()) {
|
||||
importsToBeDeletedFile.readLines()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
configureTargetFile(testFileName.replace(".kt", ".to.kt"))
|
||||
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
|
||||
@@ -59,6 +67,7 @@ abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
|
||||
|
||||
val namesToImportDump = KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested.joinToString("\n")
|
||||
KotlinTestUtils.assertEqualsToFile(testDataFile(testFileName.replace(".kt", ".expected.names")), namesToImportDump)
|
||||
assertEquals(namesToImportDump, ReviewAddedImports.importsToBeReviewed.joinToString("\n"))
|
||||
|
||||
val resultFile = myFixture.file as KtFile
|
||||
val resultText = if (InTextDirectivesUtils.isDirectiveDefined(testFileText, NO_ERRORS_DUMP_DIRECTIVE))
|
||||
|
||||
+20
@@ -55,6 +55,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
||||
runTest("idea/testData/copyPaste/imports/ClassObject.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassObjectAndDropImports.kt")
|
||||
public void testClassObjectAndDropImports() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/ClassObjectAndDropImports.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassObjectFunInsideClass.kt")
|
||||
public void testClassObjectFunInsideClass() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/ClassObjectFunInsideClass.kt");
|
||||
@@ -245,6 +250,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
||||
runTest("idea/testData/copyPaste/imports/Local.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("MultiCaretSelectionEntireFile.kt")
|
||||
public void testMultiCaretSelectionEntireFile() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/MultiCaretSelectionEntireFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("MultiDeclaration.kt")
|
||||
public void testMultiDeclaration() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/MultiDeclaration.kt");
|
||||
@@ -408,6 +418,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
||||
runTest("idea/testData/copyPaste/imports/ClassObject.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassObjectAndDropImports.kt")
|
||||
public void testClassObjectAndDropImports() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/ClassObjectAndDropImports.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassObjectFunInsideClass.kt")
|
||||
public void testClassObjectFunInsideClass() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/ClassObjectFunInsideClass.kt");
|
||||
@@ -598,6 +613,11 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
|
||||
runTest("idea/testData/copyPaste/imports/Local.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("MultiCaretSelectionEntireFile.kt")
|
||||
public void testMultiCaretSelectionEntireFile() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/MultiCaretSelectionEntireFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("MultiDeclaration.kt")
|
||||
public void testMultiDeclaration() throws Exception {
|
||||
runTest("idea/testData/copyPaste/imports/MultiDeclaration.kt");
|
||||
|
||||
Reference in New Issue
Block a user