diff --git a/.idea/dictionaries/valentin.xml b/.idea/dictionaries/valentin.xml index 5cebee5ec8d..8615d4e262c 100644 --- a/.idea/dictionaries/valentin.xml +++ b/.idea/dictionaries/valentin.xml @@ -1,4 +1,3 @@ - @@ -10,6 +9,7 @@ inserter negatable pparent + prioritizer processings rbrace rbracket diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index 01a52fe198e..f85a2b34552 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -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) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt index 2f862a31a55..87e47102e8f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt @@ -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() - private val preciseImportPackages = HashSet() - private val allUnderImports = HashSet() - - 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) } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SymbolUsageProximityPrioritizer.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SymbolUsageProximityPrioritizer.kt new file mode 100644 index 00000000000..403c92887d6 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SymbolUsageProximityPrioritizer.kt @@ -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 { + 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() + private val preciseImportPackages = HashSet() + private val allUnderImports = HashSet() + + 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 + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt index 0e32a365942..a62b9e6b471 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt @@ -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 ) : 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 ) { - 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) } diff --git a/idea/testData/quickfix/autoImports/ambiguousClassName.after.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.after.kt similarity index 100% rename from idea/testData/quickfix/autoImports/ambiguousClassName.after.kt rename to idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.after.kt diff --git a/idea/testData/quickfix/autoImports/ambiguousClassName.before.Dependency.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.before.Dependency.kt similarity index 100% rename from idea/testData/quickfix/autoImports/ambiguousClassName.before.Dependency.kt rename to idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.before.Dependency.kt diff --git a/idea/testData/quickfix/autoImports/ambiguousClassName.before.Main.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.before.Main.kt similarity index 100% rename from idea/testData/quickfix/autoImports/ambiguousClassName.before.Main.kt rename to idea/testData/quickfix/autoImports/ambiguousNamePreferFromProject.before.Main.kt diff --git a/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.after.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.after.kt new file mode 100644 index 00000000000..d9a3b5b64b1 --- /dev/null +++ b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.after.kt @@ -0,0 +1,8 @@ +// "Import" "true" +// ERROR: Unresolved reference: XXX + +import dependency2.XXX +import dependency2.YYY + +fun foo(x: XXX) { +} diff --git a/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency1.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency1.kt new file mode 100644 index 00000000000..d6dc3bf459b --- /dev/null +++ b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency1.kt @@ -0,0 +1,3 @@ +package dependency1 + +class XXX \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency2.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency2.kt new file mode 100644 index 00000000000..0efed36d875 --- /dev/null +++ b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Dependency2.kt @@ -0,0 +1,5 @@ +package dependency2 + +class XXX + +class YYY \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Main.kt b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Main.kt new file mode 100644 index 00000000000..2071d6cf555 --- /dev/null +++ b/idea/testData/quickfix/autoImports/ambiguousNamePreferWithImportsFromPackage.before.Main.kt @@ -0,0 +1,7 @@ +// "Import" "true" +// ERROR: Unresolved reference: XXX + +import dependency2.YYY + +fun foo(x: XXX) { +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 638f8cd5a74..fecee50e22d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -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); }