Auto-import popup uses new algorithm for import insertion

This commit is contained in:
Valentin Kipyatkov
2015-01-29 18:23:35 +03:00
parent 0967ccb1cd
commit c915d2b0cd
9 changed files with 154 additions and 81 deletions
-1
View File
@@ -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");