Completion and add import popup to use same sorter

#KT-8842 In progress
This commit is contained in:
Valentin Kipyatkov
2015-08-20 21:43:50 +03:00
parent adea67b219
commit 9837d383a9
13 changed files with 168 additions and 103 deletions
+1 -1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="ProjectDictionaryState">
<dictionary name="valentin">
<words>
@@ -10,6 +9,7 @@
<w>inserter</w>
<w>negatable</w>
<w>pparent</w>
<w>prioritizer</w>
<w>processings</w>
<w>rbrace</w>
<w>rbracket</w>
@@ -236,7 +236,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
sorter = sorter.weighBefore("stats", DeprecatedWeigher, PriorityWeigher, KindWeigher)
sorter = sorter.weighAfter("stats", DeclarationRemotenessWeigher(parameters.originalFile as JetFile))
sorter = sorter.weighAfter("stats", SymbolUsageProximityWeigher(parameters.position.containingFile as JetFile))
sorter = sorter.weighBefore("middleMatching", PreferMatchingItemWeigher)
@@ -24,17 +24,13 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.completion.smart.*
import org.jetbrains.kotlin.idea.core.SymbolUsageProximityPrioritizer
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
import org.jetbrains.kotlin.types.typeUtil.nullability
import java.util.HashSet
object PriorityWeigher : LookupElementWeigher("kotlin.priority") {
override fun weigh(element: LookupElement, context: WeighingContext)
@@ -118,74 +114,13 @@ object PreferMatchingItemWeigher : LookupElementWeigher("kotlin.preferMatching",
}
}
class DeclarationRemotenessWeigher(private val file: JetFile) : LookupElementWeigher("kotlin.declarationRemoteness") {
private val importCache = ImportCache()
class SymbolUsageProximityWeigher(private val file: JetFile) : LookupElementWeigher("kotlin.symbolUsageProximity") {
private val prioritizer = SymbolUsageProximityPrioritizer(file)
private enum class Weight {
thisFile,
kotlinDefaultImport,
preciseImport,
allUnderImport,
default,
hasImportFromSamePackage,
notImported,
notToBeUsedInKotlin
}
override fun weigh(element: LookupElement): Weight {
val o = element.getObject() as? DeclarationLookupObject ?: return Weight.default
val elementFile = o.psiElement?.getContainingFile()
if (elementFile is JetFile && elementFile.getOriginalFile() == file) {
return Weight.thisFile
}
val fqName = o.importableFqName
if (fqName != null) {
val importPath = ImportPath(fqName, false)
if (o is PackageLookupObject) {
return when {
importCache.isImportedWithPreciseImport(fqName) -> Weight.preciseImport
else -> Weight.default
}
}
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName).isNotEmpty() -> Weight.notToBeUsedInKotlin
ImportInsertHelper.getInstance(file.getProject()).isImportedWithDefault(importPath, file) -> Weight.kotlinDefaultImport
importCache.isImportedWithPreciseImport(fqName) -> Weight.preciseImport
importCache.isImportedWithAllUnderImport(fqName) -> Weight.allUnderImport
importCache.hasPreciseImportFromPackage(fqName.parent()) -> Weight.hasImportFromSamePackage
else -> Weight.notImported
}
}
return Weight.default
}
private inner class ImportCache {
private val preciseImports = HashSet<FqName>()
private val preciseImportPackages = HashSet<FqName>()
private val allUnderImports = HashSet<FqName>()
init {
for (import in file.getImportDirectives()) {
val importPath = import.getImportPath() ?: continue
val fqName = importPath.fqnPart()
if (importPath.isAllUnder()) {
allUnderImports.add(fqName)
}
else {
preciseImports.add(fqName)
preciseImportPackages.add(fqName.parent())
}
}
}
fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
override fun weigh(element: LookupElement): SymbolUsageProximityPrioritizer.Priority {
val o = element.`object` as? DeclarationLookupObject ?: return SymbolUsageProximityPrioritizer.Priority.DEFAULT
val fqName = o.importableFqName ?: return SymbolUsageProximityPrioritizer.Priority.DEFAULT
return prioritizer.priority(fqName, o.psiElement, o is PackageLookupObject)
}
}
@@ -0,0 +1,119 @@
/*
* 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.core
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.*
class SymbolUsageProximityPrioritizer(private val file: JetFile) {
private val importCache = ImportCache()
private val thisModule = ModuleUtilCore.findModuleForPsiElement(file)
private enum class PriorityBasedOnImports {
thisFile,
defaultImport,
preciseImport,
allUnderImport,
default,
hasImportFromSamePackage,
notImported,
notToBeUsedInKotlin
}
private enum class PriorityBasedOnModule {
thisModule,
project,
other
}
data class Priority(private val priority1: PriorityBasedOnImports, private val priority2: PriorityBasedOnModule) : Comparable<Priority> {
override fun compareTo(other: Priority): Int {
val c1 = priority1.compareTo(other.priority1)
if (c1 != 0) return c1
return priority2.compareTo(other.priority2)
}
companion object {
val DEFAULT = Priority(PriorityBasedOnImports.default, PriorityBasedOnModule.other)
}
}
fun priority(fqName: FqName, declaration: PsiElement?, isPackage: Boolean)
= Priority(priorityBasedOnImports(fqName, declaration, isPackage), priorityBasedOnModule(declaration))
private fun priorityBasedOnImports(fqName: FqName, declaration: PsiElement?, isPackage: Boolean): PriorityBasedOnImports {
if (declaration?.containingFile == file) return PriorityBasedOnImports.thisFile
val importPath = ImportPath(fqName, false)
if (isPackage) {
return when {
importCache.isImportedWithPreciseImport(fqName) -> PriorityBasedOnImports.preciseImport
else -> PriorityBasedOnImports.default
}
}
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName).isNotEmpty() -> PriorityBasedOnImports.notToBeUsedInKotlin
ImportInsertHelper.getInstance(file.getProject()).isImportedWithDefault(importPath, file) -> PriorityBasedOnImports.defaultImport
importCache.isImportedWithPreciseImport(fqName) -> PriorityBasedOnImports.preciseImport
importCache.isImportedWithAllUnderImport(fqName) -> PriorityBasedOnImports.allUnderImport
importCache.hasPreciseImportFromPackage(fqName.parent()) -> PriorityBasedOnImports.hasImportFromSamePackage
else -> PriorityBasedOnImports.notImported
}
}
private fun priorityBasedOnModule(declaration: PsiElement?): PriorityBasedOnModule {
return when {
declaration == null -> PriorityBasedOnModule.other
ModuleUtilCore.findModuleForPsiElement(declaration) == thisModule -> PriorityBasedOnModule.thisModule
ProjectRootsUtil.isInProjectSource(declaration) -> PriorityBasedOnModule.project
else -> PriorityBasedOnModule.other
}
}
private inner class ImportCache {
private val preciseImports = HashSet<FqName>()
private val preciseImportPackages = HashSet<FqName>()
private val allUnderImports = HashSet<FqName>()
init {
for (import in file.getImportDirectives()) {
val importPath = import.getImportPath() ?: continue
val fqName = importPath.fqnPart()
if (importPath.isAllUnder()) {
allUnderImports.add(fqName)
}
else {
preciseImports.add(fqName)
preciseImportPackages.add(fqName.parent())
}
}
}
fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
}
}
@@ -43,6 +43,7 @@ import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.JetDescriptorIconProvider
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.SymbolUsageProximityPrioritizer
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
@@ -57,41 +58,22 @@ public class KotlinAddImportAction(
candidates: Collection<DeclarationDescriptor>
) : QuestionAction {
private val module = ModuleUtilCore.findModuleForPsiElement(element)
private enum class Priority {
MODULE,
PROJECT,
OTHER
}
private fun detectPriority(descriptor: DeclarationDescriptor): Priority {
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
return when {
declaration == null -> Priority.OTHER
ModuleUtilCore.findModuleForPsiElement(declaration) == module -> Priority.MODULE
ProjectRootsUtil.isInProjectSource(declaration) -> Priority.PROJECT
else -> Priority.OTHER
}
}
private val prioritizer = SymbolUsageProximityPrioritizer(element.getContainingJetFile())
private inner class Variant(
val fqName: FqName,
val descriptors: Collection<DeclarationDescriptor>
) {
val priority = descriptors.map { detectPriority(it) }.min()!!
val priority = descriptors
.map { prioritizer.priority(fqName, DescriptorToSourceUtilsIde.getAnyDeclaration(project, it), false) }
.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()
return descriptors.singleOrNull()
?: descriptors.sortBy { if (it is ClassDescriptor) 0 else 1 }.first()
}
val declarationToImport = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptorToImport)
}
@@ -0,0 +1,8 @@
// "Import" "true"
// ERROR: Unresolved reference: XXX
import dependency2.XXX
import dependency2.YYY
fun foo(x: XXX<caret>) {
}
@@ -0,0 +1,3 @@
package dependency1
class XXX
@@ -0,0 +1,5 @@
package dependency2
class XXX
class YYY
@@ -0,0 +1,7 @@
// "Import" "true"
// ERROR: Unresolved reference: XXX
import dependency2.YYY
fun foo(x: XXX<caret>) {
}
@@ -53,9 +53,15 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/autoImports"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
}
@TestMetadata("ambiguousClassName.before.Main.kt")
public void testAmbiguousClassName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/ambiguousClassName.before.Main.kt");
@TestMetadata("ambiguousNamePreferFromProject.before.Main.kt")
public void testAmbiguousNamePreferFromProject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("ambiguousNamePreferWithImportsFromPackage.before.Main.kt")
public void testAmbiguousNamePreferWithImportsFromPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Main.kt");
doTestWithExtraFile(fileName);
}