Ordinary completion prefers item matching expected name
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-3
@@ -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)
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
+6
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user