Ordinary completion prefers item matching expected name

This commit is contained in:
Valentin Kipyatkov
2015-08-05 17:51:15 +03:00
parent 07311234db
commit 9cba912fe5
6 changed files with 60 additions and 30 deletions
@@ -21,20 +21,15 @@ import com.intellij.codeInsight.lookup.LookupElementWeigher
import com.intellij.codeInsight.lookup.WeighingContext import com.intellij.codeInsight.lookup.WeighingContext
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY import org.jetbrains.kotlin.idea.completion.smart.*
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionItemPriority
import org.jetbrains.kotlin.idea.completion.smart.fuzzyTypesForSmartCompletion
import org.jetbrains.kotlin.idea.core.SmartCastCalculator import org.jetbrains.kotlin.idea.core.SmartCastCalculator
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.util.FuzzyType import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import java.util.HashSet import java.util.HashSet
object PriorityWeigher : LookupElementWeigher("kotlin.priority") { object PriorityWeigher : LookupElementWeigher("kotlin.priority") {
@@ -194,30 +189,47 @@ class ExpectedInfoMatchWeigher(
private val expectedInfos: Collection<ExpectedInfo>, private val expectedInfos: Collection<ExpectedInfo>,
private val smartCastCalculator: SmartCastCalculator private val smartCastCalculator: SmartCastCalculator
) : LookupElementWeigher("kotlin.expectedInfoMatch") { ) : LookupElementWeigher("kotlin.expectedInfoMatch") {
private enum class Weight {
matches, private val NO_MATCH = 0L
matchesIfNotNull,
doesNotMatch private fun fullMatch(nameSimilarity: Int): Long {
return -((2L shl 32) + nameSimilarity)
} }
override fun weigh(element: LookupElement): Weight { private fun ifNotNullMatch(nameSimilarity: Int): Long {
return -((1L shl 32) + nameSimilarity)
}
override fun weigh(element: LookupElement): Long {
// TODO: keywords with type // TODO: keywords with type
val o = element.`object` val o = element.`object`
val fuzzyTypes = when (o) { val (fuzzyTypes, name) = when (o) {
is DeclarationLookupObject -> { is DeclarationLookupObject -> {
val descriptor = o.descriptor ?: return Weight.doesNotMatch val descriptor = o.descriptor ?: return NO_MATCH
descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator) descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator) to descriptor.name
} }
is ThisItemLookupObject -> smartCastCalculator.types(o.receiverParameter).map { FuzzyType(it, emptyList()) } is ThisItemLookupObject -> smartCastCalculator.types(o.receiverParameter).map { FuzzyType(it, emptyList()) } to null
else -> return Weight.doesNotMatch else -> return NO_MATCH
} }
return when { if (fuzzyTypes.isEmpty()) return NO_MATCH
fuzzyTypes.any { type -> expectedInfos.any { it.matchingSubstitutor(type) != null } } -> Weight.matches
fuzzyTypes.any { type -> type.nullability() == TypeNullability.NULLABLE && expectedInfos.any { it.matchingSubstitutor(type.makeNotNullable()) != null } } -> Weight.matchesIfNotNull val classified: Collection<Pair<ExpectedInfo, ExpectedInfoClassification>> = expectedInfos.map { it to fuzzyTypes.classifyExpectedInfo(it) }
else -> Weight.doesNotMatch if (classified.all { it.second == ExpectedInfoClassification.noMatch }) return NO_MATCH
val nameSimilarity = if (name != null) {
val matchingInfos = classified.filter { it.second != ExpectedInfoClassification.noMatch }.map { it.first }
calcNameSimilarity(name.asString(), matchingInfos)
} }
else {
0
}
return if (classified.any { it.second.isMatch() })
fullMatch(nameSimilarity)
else
ifNotNullMatch(nameSimilarity)
} }
} }
@@ -54,7 +54,7 @@ object KeywordValues {
if (!skipTrueFalse) { if (!skipTrueFalse) {
val booleanInfoClassifier = { info: ExpectedInfo -> val booleanInfoClassifier = { info: ExpectedInfo ->
if (info.fuzzyType?.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches if (info.fuzzyType?.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.noMatch
} }
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) } collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) }
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) } collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) }
@@ -62,9 +62,9 @@ object KeywordValues {
val classifier = { info: ExpectedInfo -> val classifier = { info: ExpectedInfo ->
if (info.fuzzyType != null && info.fuzzyType!!.type.isMarkedNullable()) if (info.fuzzyType != null && info.fuzzyType!!.type.isMarkedNullable())
ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) ExpectedInfoClassification.match(TypeSubstitutor.EMPTY)
else else
ExpectedInfoClassification.notMatches ExpectedInfoClassification.noMatch
} }
collection.addLookupElements(null, expectedInfos, classifier) { collection.addLookupElements(null, expectedInfos, classifier) {
LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL) LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL)
@@ -75,7 +75,7 @@ class StaticMembers(
classifier = { expectedInfo -> returnType.classifyExpectedInfo(expectedInfo) } classifier = { expectedInfo -> returnType.classifyExpectedInfo(expectedInfo) }
} }
else if (DescriptorUtils.isEnumEntry(descriptor) && !enumEntriesToSkip.contains(descriptor)) { else if (DescriptorUtils.isEnumEntry(descriptor) && !enumEntriesToSkip.contains(descriptor)) {
classifier = { ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */ classifier = { ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */
} }
else { else {
return return
@@ -124,10 +124,12 @@ private constructor(
val substitutor: TypeSubstitutor?, val substitutor: TypeSubstitutor?,
val makeNotNullable: Boolean val makeNotNullable: Boolean
) { ) {
fun isMatch() = substitutor != null && !makeNotNullable
companion object { companion object {
val notMatches = ExpectedInfoClassification(null, false) val noMatch = ExpectedInfoClassification(null, false)
fun matches(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, false) fun match(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, false)
fun matchesIfNotNullable(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, true) fun ifNotNullMatch(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, true)
} }
} }
@@ -135,17 +137,17 @@ fun Collection<FuzzyType>.classifyExpectedInfo(expectedInfo: ExpectedInfo): Expe
val sequence = asSequence() val sequence = asSequence()
val substitutor = sequence.map { expectedInfo.matchingSubstitutor(it) }.firstOrNull() val substitutor = sequence.map { expectedInfo.matchingSubstitutor(it) }.firstOrNull()
if (substitutor != null) { if (substitutor != null) {
return ExpectedInfoClassification.matches(substitutor) return ExpectedInfoClassification.match(substitutor)
} }
if (sequence.any { it.nullability() == TypeNullability.NULLABLE }) { if (sequence.any { it.nullability() == TypeNullability.NULLABLE }) {
val substitutor2 = sequence.map { expectedInfo.matchingSubstitutor(it.makeNotNullable()) }.firstOrNull() val substitutor2 = sequence.map { expectedInfo.matchingSubstitutor(it.makeNotNullable()) }.firstOrNull()
if (substitutor2 != null) { if (substitutor2 != null) {
return ExpectedInfoClassification.matchesIfNotNullable(substitutor2) return ExpectedInfoClassification.ifNotNullMatch(substitutor2)
} }
} }
return ExpectedInfoClassification.notMatches return ExpectedInfoClassification.noMatch
} }
fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo) = listOf(this).classifyExpectedInfo(expectedInfo) fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo) = listOf(this).classifyExpectedInfo(expectedInfo)
@@ -0,0 +1,10 @@
fun foo(xxx1: String?, xxx2: String, xxx3: Any, xp: String?) {
bar(x<caret>)
}
fun bar(pXxx: String){}
// ORDER: xxx2
// ORDER: xxx1
// ORDER: xp
// ORDER: xxx3
@@ -157,6 +157,12 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion
doTest(fileName); doTest(fileName);
} }
@TestMetadata("NameSimilarity.kt")
public void testNameSimilarity() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo/NameSimilarity.kt");
doTest(fileName);
}
@TestMetadata("PreferMatchingThis.kt") @TestMetadata("PreferMatchingThis.kt")
public void testPreferMatchingThis() throws Exception { public void testPreferMatchingThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo/PreferMatchingThis.kt"); String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo/PreferMatchingThis.kt");