Auto-import popup uses new algorithm for import insertion
This commit is contained in:
-1
@@ -402,7 +402,6 @@
|
||||
</scope>
|
||||
<option name="m_ignoreJavadoc" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryInterfaceModifier" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryLabelOnBreakStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessaryLabelOnContinueStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessaryQualifierForThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
|
||||
@@ -34,7 +34,16 @@ import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToDeclarationUtil
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
|
||||
|
||||
/**
|
||||
* Automatically adds import directive to the file for resolving reference.
|
||||
@@ -44,32 +53,72 @@ public class JetAddImportAction(
|
||||
private val project: Project,
|
||||
private val editor: Editor,
|
||||
private val element: JetSimpleNameExpression,
|
||||
imports: Iterable<FqName>
|
||||
candidates: Collection<DeclarationDescriptor>
|
||||
) : QuestionAction {
|
||||
private val possibleImports = imports.toList()
|
||||
|
||||
private val module = ModuleUtilCore.findModuleForPsiElement(element)
|
||||
|
||||
private enum class Priority {
|
||||
MODULE
|
||||
PROJECT
|
||||
OTHER
|
||||
}
|
||||
|
||||
private fun detectPriority(descriptor: DeclarationDescriptor): Priority {
|
||||
val declaration = DescriptorToDeclarationUtil.getDeclaration(element.getContainingJetFile(), descriptor)
|
||||
return when {
|
||||
declaration == null -> Priority.OTHER
|
||||
ModuleUtilCore.findModuleForPsiElement(declaration) == module -> Priority.MODULE
|
||||
ProjectRootsUtil.isInProjectSource(declaration) -> Priority.PROJECT
|
||||
else -> Priority.OTHER
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Variant(
|
||||
val fqName: FqName,
|
||||
val descriptors: Collection<DeclarationDescriptor>
|
||||
) {
|
||||
val priority = descriptors.map { detectPriority(it) }.min()!!
|
||||
|
||||
val descriptorToImport: DeclarationDescriptor
|
||||
get() {
|
||||
if (descriptors.size() == 1) return descriptors.single()
|
||||
return descriptors.sortBy {
|
||||
when (it) {
|
||||
is ClassDescriptor -> 0
|
||||
is PackageViewDescriptor -> 1
|
||||
else -> 2
|
||||
}
|
||||
}.first()
|
||||
}
|
||||
}
|
||||
|
||||
private val variants = candidates
|
||||
.groupBy { DescriptorUtils.getFqNameSafe(it) }
|
||||
.map { Variant(it.key, it.value) }
|
||||
.sortBy { it.priority }
|
||||
|
||||
override fun execute(): Boolean {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
if (!element.isValid()) return false
|
||||
|
||||
// TODO: Validate resolution variants. See AddImportAction.execute()
|
||||
|
||||
if (possibleImports.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
addImport(element, project, possibleImports[0])
|
||||
if (variants.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
addImport(element, project, variants.first())
|
||||
}
|
||||
else {
|
||||
chooseClassAndImport()
|
||||
chooseCandidateAndImport()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun getImportSelectionPopup(): BaseListPopupStep<FqName> {
|
||||
return object : BaseListPopupStep<FqName>(JetBundle.message("imports.chooser.title"), possibleImports) {
|
||||
protected fun getImportSelectionPopup(): BaseListPopupStep<Variant> {
|
||||
return object : BaseListPopupStep<Variant>(JetBundle.message("imports.chooser.title"), variants) {
|
||||
override fun isAutoSelectionEnabled() = false
|
||||
|
||||
override fun onChosen(selectedValue: FqName?, finalChoice: Boolean): PopupStep<String>? {
|
||||
override fun onChosen(selectedValue: Variant?, finalChoice: Boolean): PopupStep<String>? {
|
||||
if (selectedValue == null) return null
|
||||
|
||||
if (finalChoice) {
|
||||
@@ -77,7 +126,7 @@ public class JetAddImportAction(
|
||||
return null
|
||||
}
|
||||
|
||||
val toExclude = AddImportAction.getAllExcludableStrings(selectedValue.asString())
|
||||
val toExclude = AddImportAction.getAllExcludableStrings(selectedValue.fqName.asString())
|
||||
|
||||
return object : BaseListPopupStep<String>(null, toExclude) {
|
||||
override fun getTextFor(value: String): String {
|
||||
@@ -93,29 +142,38 @@ public class JetAddImportAction(
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasSubstep(selectedValue: FqName?) = true
|
||||
override fun hasSubstep(selectedValue: Variant?) = true
|
||||
|
||||
override fun getTextFor(value: FqName) = value.asString()
|
||||
override fun getTextFor(value: Variant) = value.fqName.asString()
|
||||
|
||||
// TODO: change icon
|
||||
override fun getIconFor(aValue: FqName) = PlatformIcons.CLASS_ICON
|
||||
override fun getIconFor(aValue: Variant) = PlatformIcons.CLASS_ICON
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseClassAndImport() {
|
||||
private fun chooseCandidateAndImport() {
|
||||
JBPopupFactory.getInstance().createListPopup(getImportSelectionPopup()).showInBestPositionFor(editor)
|
||||
}
|
||||
|
||||
class object {
|
||||
|
||||
protected fun addImport(element: PsiElement, project: Project, selectedImport: FqName) {
|
||||
protected fun addImport(element: PsiElement, project: Project, selectedVariant: Variant) {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
CommandProcessor.getInstance().executeCommand(project, object : Runnable {
|
||||
override fun run() {
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
val file = element.getContainingFile() as JetFile
|
||||
ImportInsertHelper.getInstance(project).writeImportToFile(ImportPath(selectedImport, false), file)
|
||||
val descriptor = selectedVariant.descriptorToImport
|
||||
// for class or package we use ShortenReferences because we not necessary insert an import but may want to insert partly qualified name
|
||||
if (descriptor is ClassDescriptor || descriptor is PackageViewDescriptor) {
|
||||
val fqName = descriptor.importableFqNameSafe
|
||||
val reference = element.getReference() as JetSimpleNameReference
|
||||
reference.bindToFqName(fqName, JetSimpleNameReference.ShorteningMode.FORCED_SHORTENING)
|
||||
}
|
||||
else {
|
||||
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, QuickFixBundle.message("add.import"), null)
|
||||
|
||||
@@ -43,11 +43,8 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightClass
|
||||
import org.jetbrains.kotlin.types.TypeProjection
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.JavaResolveExtension
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.PossiblyBareType
|
||||
@@ -61,7 +58,7 @@ import org.jetbrains.kotlin.types.TypeProjectionImpl
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.KotlinLightClassForDecompiledDeclaration
|
||||
import org.jetbrains.kotlin.idea.util.psiClassToDescriptor.psiClassToDescriptor
|
||||
|
||||
class TypeInstantiationItems(
|
||||
val resolutionFacade: ResolutionFacade,
|
||||
@@ -285,14 +282,9 @@ class TypeInstantiationItems(
|
||||
override fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) {
|
||||
val parameters = ClassInheritorsSearch.SearchParameters(psiClass, inheritorSearchScope, true, true, false, nameFilter)
|
||||
for (inheritor in ClassInheritorsSearch.search(parameters)) {
|
||||
val descriptor = if (inheritor is KotlinLightClass && inheritor !is KotlinLightClassForDecompiledDeclaration) {
|
||||
val origin = inheritor.getOrigin() ?: continue
|
||||
val declaration = toFromOriginalFileMapper.toSyntheticFile(origin) ?: continue
|
||||
resolutionFacade.resolveToDescriptor(declaration)
|
||||
}
|
||||
else {
|
||||
resolutionFacade.get(JavaResolveExtension)(inheritor).first.resolveClass(JavaClassImpl(inheritor))
|
||||
} as? ClassDescriptor ?: continue
|
||||
val descriptor = resolutionFacade.psiClassToDescriptor(
|
||||
inheritor,
|
||||
{ toFromOriginalFileMapper.toSyntheticFile(it) as JetClassOrObject? }) as? ClassDescriptor ?: continue
|
||||
if (!visibilityFilter(descriptor)) continue
|
||||
|
||||
val hasTypeArgs = descriptor.getTypeConstructor().getParameters().isNotEmpty()
|
||||
|
||||
@@ -33,8 +33,6 @@ import org.jetbrains.kotlin.psi.JetPsiUtil
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.idea.JetBundle
|
||||
import org.jetbrains.kotlin.idea.actions.JetAddImportAction
|
||||
import org.jetbrains.kotlin.idea.caches.JetShortNamesCache
|
||||
@@ -44,32 +42,27 @@ import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
|
||||
import org.jetbrains.kotlin.idea.util.JetPsiHeuristicsUtil
|
||||
import java.util.ArrayList
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToDeclarationUtil
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.idea.completion.isVisible
|
||||
import org.jetbrains.kotlin.utils.CachedValueProperty
|
||||
import org.jetbrains.kotlin.idea.util.psiClassToDescriptor.psiClassToDescriptor
|
||||
|
||||
/**
|
||||
* Check possibility and perform fix for unresolved references.
|
||||
*/
|
||||
public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction<JetSimpleNameExpression>(element), HighPriorityAction {
|
||||
private val module = ModuleUtilCore.findModuleForPsiElement(element)
|
||||
private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount()
|
||||
|
||||
volatile private var anySuggestionFound: Boolean? = null
|
||||
|
||||
private val suggestions: Collection<FqName> by CachedValueProperty(
|
||||
private val suggestions: Collection<DeclarationDescriptor> by CachedValueProperty(
|
||||
{
|
||||
val fqNames = computeSuggestions(element)
|
||||
anySuggestionFound = !fqNames.isEmpty()
|
||||
fqNames
|
||||
val descriptors = computeSuggestions(element)
|
||||
anySuggestionFound = !descriptors.isEmpty()
|
||||
descriptors
|
||||
},
|
||||
{ PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount() })
|
||||
|
||||
@@ -81,7 +74,7 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction<Jet
|
||||
if (suggestions.isEmpty()) return false
|
||||
|
||||
if (!ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
||||
val hintText = ShowAutoImportPass.getMessage(suggestions.size() > 1, suggestions.first().asString())
|
||||
val hintText = ShowAutoImportPass.getMessage(suggestions.size() > 1, DescriptorUtils.getFqNameSafe(suggestions.first()).asString())
|
||||
|
||||
val project = editor.getProject() ?: return false
|
||||
HintManager.getInstance().showQuestionHint(editor, hintText, element.getTextOffset(), element.getTextRange()!!.getEndOffset(), createAction(project, editor))
|
||||
@@ -109,7 +102,7 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction<Jet
|
||||
|
||||
private fun createAction(project: Project, editor: Editor) = JetAddImportAction(project, editor, element, suggestions)
|
||||
|
||||
private fun computeSuggestions(element: JetSimpleNameExpression): Collection<FqName> {
|
||||
private fun computeSuggestions(element: JetSimpleNameExpression): Collection<DeclarationDescriptor> {
|
||||
if (!element.isValid()) return listOf()
|
||||
|
||||
val file = element.getContainingFile() as? JetFile ?: return listOf()
|
||||
@@ -143,37 +136,32 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction<Jet
|
||||
return true
|
||||
}
|
||||
|
||||
val result = ArrayList<PrioritizedFqName>()
|
||||
val result = ArrayList<DeclarationDescriptor>()
|
||||
|
||||
val moduleDescriptor = resolutionFacade.findModuleDescriptor(element)
|
||||
val indicesHelper = KotlinIndicesHelper(file.getProject(), resolutionFacade, bindingContext, searchScope, moduleDescriptor, ::isVisible)
|
||||
|
||||
if (!element.isImportDirectiveExpression() && !JetPsiUtil.isSelectorInQualified(element)) {
|
||||
result.addAll(getClassNames(referenceName, file, searchScope))
|
||||
result.addAll(getClasses(referenceName, file, searchScope))
|
||||
result.addAll(getTopLevelCallables(referenceName, element, indicesHelper))
|
||||
}
|
||||
|
||||
result.addAll(getExtensions(referenceName, element, indicesHelper))
|
||||
|
||||
return result
|
||||
.sortBy { it.priority }
|
||||
.map { it.fqName }
|
||||
}
|
||||
|
||||
private fun getTopLevelCallables(name: String, context: JetExpression, indicesHelper: KotlinIndicesHelper): Collection<PrioritizedFqName>
|
||||
private fun getTopLevelCallables(name: String, context: JetExpression, indicesHelper: KotlinIndicesHelper): Collection<DeclarationDescriptor>
|
||||
= indicesHelper.getTopLevelCallablesByName(name, context)
|
||||
.map { PrioritizedFqName(it) }
|
||||
.toSet()
|
||||
|
||||
private fun getExtensions(name: String, expression: JetSimpleNameExpression, indicesHelper: KotlinIndicesHelper): Collection<PrioritizedFqName>
|
||||
private fun getExtensions(name: String, expression: JetSimpleNameExpression, indicesHelper: KotlinIndicesHelper): Collection<DeclarationDescriptor>
|
||||
= indicesHelper.getCallableExtensions({ it == name }, expression)
|
||||
.map { PrioritizedFqName(it) }
|
||||
.toSet()
|
||||
|
||||
private fun getClassNames(name: String, file: JetFile, searchScope: GlobalSearchScope): Collection<PrioritizedFqName>
|
||||
private fun getClasses(name: String, file: JetFile, searchScope: GlobalSearchScope): Collection<DeclarationDescriptor>
|
||||
= getShortNamesCache(file).getClassesByName(name, searchScope)
|
||||
.filter { JetPsiHeuristicsUtil.isAccessible(it, file) }
|
||||
.map { PrioritizedFqName(it.unwrapped, FqName(it.getQualifiedName()!!)) }
|
||||
.map { element.getResolutionFacade().psiClassToDescriptor(it) }
|
||||
.filterNotNull()
|
||||
.toSet()
|
||||
|
||||
private fun getShortNamesCache(jetFile: JetFile): PsiShortNamesCache {
|
||||
@@ -184,31 +172,6 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction<Jet
|
||||
PsiShortNamesCache.getInstance(jetFile.getProject())
|
||||
}
|
||||
|
||||
private enum class Priority {
|
||||
MODULE
|
||||
PROJECT
|
||||
OTHER
|
||||
}
|
||||
private data class PrioritizedFqName(val fqName: FqName, val priority: Priority)
|
||||
|
||||
private fun PrioritizedFqName(declaration: PsiElement?, fqName: FqName): PrioritizedFqName {
|
||||
val priority = when {
|
||||
declaration == null -> Priority.OTHER
|
||||
ModuleUtilCore.findModuleForPsiElement(declaration) == module -> Priority.MODULE
|
||||
ProjectRootsUtil.isInProjectSource(declaration) -> Priority.PROJECT
|
||||
else -> Priority.OTHER
|
||||
}
|
||||
|
||||
return PrioritizedFqName(fqName, priority)
|
||||
}
|
||||
|
||||
private fun PrioritizedFqName(descriptor: DeclarationDescriptor): PrioritizedFqName {
|
||||
return PrioritizedFqName(
|
||||
DescriptorToDeclarationUtil.getDeclaration(element.getContainingJetFile(), descriptor),
|
||||
DescriptorUtils.getFqNameSafe(descriptor)
|
||||
)
|
||||
}
|
||||
|
||||
class object {
|
||||
private val ERRORS = setOf(Errors.UNRESOLVED_REFERENCE, Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER)
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.util.psiClassToDescriptor
|
||||
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
|
||||
import com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightClass
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.KotlinLightClassForDecompiledDeclaration
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.JavaResolveExtension
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
|
||||
|
||||
public fun ResolutionFacade.psiClassToDescriptor(
|
||||
psiClass: PsiClass,
|
||||
declarationTranslator: (JetClassOrObject) -> JetClassOrObject? = { it }
|
||||
): ClassifierDescriptor? {
|
||||
return if (psiClass is KotlinLightClass && psiClass !is KotlinLightClassForDecompiledDeclaration) {
|
||||
val origin = psiClass.getOrigin ()?: return null
|
||||
val declaration = declarationTranslator(origin) ?: return null
|
||||
resolveToDescriptor(declaration)
|
||||
} else {
|
||||
get(JavaResolveExtension)(psiClass).first.resolveClass(JavaClassImpl(psiClass))
|
||||
} as? ClassifierDescriptor
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Import" "true"
|
||||
// ERROR: Unresolved reference: Nested
|
||||
|
||||
import test.Test
|
||||
|
||||
val a = <caret>Test.Nested
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Import" "true"
|
||||
// ERROR: Unresolved reference: Nested
|
||||
|
||||
val a = <caret>Nested
|
||||
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
class Test {
|
||||
class Nested
|
||||
}
|
||||
@@ -134,6 +134,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nestedClass.before.Main.kt")
|
||||
public void testNestedClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/nestedClass.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noImportForFunInQualifiedNotFirst.before.Main.kt")
|
||||
public void testNoImportForFunInQualifiedNotFirst() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/noImportForFunInQualifiedNotFirst.before.Main.kt");
|
||||
|
||||
Reference in New Issue
Block a user