Completion: assign highest priority to dsl members in dsl context

Use the same style as for dsl for lookup items that belong to dsl
This commit is contained in:
Pavel V. Talanov
2018-02-09 16:22:37 +01:00
parent 8f8cbfcfa5
commit 1796d40118
13 changed files with 191 additions and 17 deletions
@@ -70,7 +70,6 @@ import org.jetbrains.kotlin.util.kind
import org.jetbrains.kotlin.util.supertypesWithAny
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
class BasicCompletionSession(
configuration: CompletionSessionConfiguration,
@@ -224,6 +223,13 @@ class BasicCompletionSession(
}
}
withCollectRequiredContextVariableTypes { lookupFactory ->
DslMembersCompletion(
prefixMatcher, lookupFactory, receiverTypes,
collector, indicesHelper(true), callTypeAndReceiver
).completeDslFunctions()
}
val contextVariableTypesForSmartCompletion = withCollectRequiredContextVariableTypes(::completeWithSmartCompletion)
val contextVariableTypesForReferenceVariants = withCollectRequiredContextVariableTypes { lookupElementFactory ->
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.idea.completion.handlers.KotlinClassifierInsertHandl
import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject
import org.jetbrains.kotlin.idea.highlighter.dsl.DslHighlighterExtension
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.FqName
@@ -37,11 +39,10 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import javax.swing.Icon
import java.awt.Font
class BasicLookupElementFactory(
private val project: Project,
@@ -233,6 +234,14 @@ class BasicLookupElementFactory(
if (descriptor is CallableDescriptor) {
appendContainerAndReceiverInformation(descriptor) { element = element.appendTailText(it, true) }
val dslTextAttributes = DslHighlighterExtension.dslCustomTextStyle(descriptor)?.let {
EditorColorsManager.getInstance().globalScheme.getAttributes(it)
}
if (dslTextAttributes != null) {
element = element.withBoldness(dslTextAttributes.fontType == Font.BOLD)
dslTextAttributes.foregroundColor?.let { element = element.withItemTextForeground(it) }
}
}
if (descriptor is PropertyDescriptor) {
@@ -41,7 +41,6 @@ import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -52,7 +51,6 @@ import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import java.util.*
import kotlin.collections.LinkedHashMap
class CompletionSessionConfiguration(
val useBetterPrefixMatcherForNonImportedClasses: Boolean,
@@ -271,6 +269,8 @@ abstract class CompletionSession(
sorter = sorter.weighAfter("kotlin.proximity", ByNameAlphabeticalWeigher, PreferLessParametersWeigher)
sorter = sorter.weighBefore("prefix", PreferDslMembers)
return sorter
}
@@ -79,6 +79,7 @@ enum class ItemPriority {
}
val ITEM_PRIORITY_KEY = Key<ItemPriority>("ITEM_PRIORITY_KEY")
var LookupElement.isDslMember: Boolean? by UserDataProperty(Key.create("DSL_LOOKUP_ITEM"))
fun LookupElement.assignPriority(priority: ItemPriority): LookupElement {
putUserData(ITEM_PRIORITY_KEY, priority)
@@ -0,0 +1,73 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.ReceiverType
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.psiUtil.collectAnnotationEntriesFromStubOrPsi
import org.jetbrains.kotlin.resolve.calls.DslMarkerUtils
class DslMembersCompletion(
private val prefixMatcher: PrefixMatcher,
private val elementFactory: LookupElementFactory,
receiverTypes: Collection<ReceiverType>?,
private val collector: LookupElementsCollector,
private val indicesHelper: KotlinIndicesHelper,
private val callTypeAndReceiver: CallTypeAndReceiver<*, *>
) {
private val nearestReceiver = receiverTypes?.lastOrNull()
private val nearestReceiverMarkers = nearestReceiver?.takeIf { it.implicit }
?.let { DslMarkerUtils.extractDslMarkerFqNames(it.type) }.orEmpty()
private val factory = object : AbstractLookupElementFactory {
override fun createLookupElement(
descriptor: DeclarationDescriptor,
useReceiverTypes: Boolean,
qualifyNestedClasses: Boolean,
includeClassTypeArguments: Boolean,
parametersAndTypeGrayed: Boolean
): LookupElement? {
error("Should not be called")
}
override fun createStandardLookupElementsForDescriptor(
descriptor: DeclarationDescriptor,
useReceiverTypes: Boolean
): Collection<LookupElement> {
return elementFactory.createStandardLookupElementsForDescriptor(descriptor, useReceiverTypes).also {
it.forEach { element ->
element.isDslMember = true
}
}
}
}
fun completeDslFunctions() {
if (nearestReceiver == null || nearestReceiverMarkers.isEmpty()) return
val receiverMarkersShortNames = nearestReceiverMarkers.map { it.shortName() }.distinct()
indicesHelper.getCallableTopLevelExtensions(
nameFilter = { prefixMatcher.prefixMatches(it) },
declarationFilter = {
(it as KtModifierListOwner).modifierList?.collectAnnotationEntriesFromStubOrPsi()?.any { it.shortName in receiverMarkersShortNames }
?: false
},
callTypeAndReceiver = callTypeAndReceiver,
receiverTypes = listOf(nearestReceiver.type)
).forEach { descriptor: DeclarationDescriptor ->
collector.addDescriptorElements(descriptor, factory, notImported = true, withReceiverCast = false)
}
collector.flushToResultSet()
}
}
@@ -22,6 +22,7 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.util.TextRange
import com.intellij.patterns.ElementPattern
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.completion.handlers.WithExpressionPrefixInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
@@ -47,6 +48,7 @@ class LookupElementsCollector(
.withRelevanceSorter(sorter)
private val postProcessors = ArrayList<(LookupElement) -> LookupElement>()
private val processedCallables = mutableSetOf<CallableDescriptor>()
var isResultEmpty: Boolean = true
private set
@@ -67,7 +69,7 @@ class LookupElementsCollector(
}
fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>,
lookupElementFactory: LookupElementFactory,
lookupElementFactory: AbstractLookupElementFactory,
notImported: Boolean = false,
withReceiverCast: Boolean = false
) {
@@ -78,10 +80,12 @@ class LookupElementsCollector(
fun addDescriptorElements(
descriptor: DeclarationDescriptor,
lookupElementFactory: LookupElementFactory,
lookupElementFactory: AbstractLookupElementFactory,
notImported: Boolean = false,
withReceiverCast: Boolean = false
) {
if (descriptor is CallableDescriptor && descriptor in processedCallables) return
var lookupElements = lookupElementFactory.createStandardLookupElementsForDescriptor(descriptor, useReceiverTypes = true)
if (withReceiverCast) {
@@ -89,6 +93,8 @@ class LookupElementsCollector(
}
addElements(lookupElements, notImported)
if (descriptor is CallableDescriptor) processedCallables.add(descriptor)
}
fun addElement(element: LookupElement, notImported: Boolean = false) {
@@ -43,6 +43,13 @@ object PriorityWeigher : LookupElementWeigher("kotlin.priority") {
= element.getUserData(ITEM_PRIORITY_KEY) ?: ItemPriority.DEFAULT
}
object PreferDslMembers : LookupElementWeigher("kotlin.preferDsl") {
override fun weigh(element: LookupElement, context: WeighingContext): Boolean {
if (element.isDslMember == true) return false // high priority
return true // lower priority
}
}
class NotImportedWeigher(private val classifier: ImportableFqNameClassifier) : LookupElementWeigher("kotlin.notImported") {
private enum class Weight {
default,
@@ -0,0 +1,42 @@
package bar
@DslMarker
annotation class Dsl
@Dsl
class R
fun r(body: R.() -> Unit) {
}
fun foo1(i: Int) {
}
fun foo3() {
}
@Dsl
fun R.foo2() {}
@Dsl
fun R.foo4() {
}
@Dsl
fun R.fooloooooong() {
}
val R.fooval
get() = Unit
@Dsl
fun R.SomethingSomethingFooSomething() {
}
@@ -0,0 +1,20 @@
// RUNTIME
package test
import bar.r
import bar.foo3
fun main() {
val foo5 = 3
r {
foo<caret>
}
}
// ORDER: foo2
// ORDER: foo4
// ORDER: fooloooooong
// ORDER: foo3
// ORDER: foo5
// ORDER: fooval
// ORDER: foo1
@@ -20,7 +20,6 @@ import com.intellij.codeInsight.completion.CompletionType
import org.jetbrains.kotlin.idea.completion.test.RELATIVE_COMPLETION_TEST_DATA_BASE_PATH
import org.jetbrains.kotlin.idea.completion.test.configureWithExtraFile
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.junit.Assert
@@ -44,7 +43,6 @@ abstract class AbstractCompletionWeigherTest(val completionType: CompletionType,
}
abstract class AbstractBasicCompletionWeigherTest() : AbstractCompletionWeigherTest(CompletionType.BASIC, "weighers/basic") {
override fun getProjectDescriptor() = KotlinLightProjectDescriptor.INSTANCE
}
abstract class AbstractSmartCompletionWeigherTest() : AbstractCompletionWeigherTest(CompletionType.SMART, "weighers/smart") {
@@ -61,6 +61,12 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("DslCalls.kt")
public void testDslCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/DslCalls.kt");
doTest(fileName);
}
@TestMetadata("ExactMatchForKeyword.kt")
public void testExactMatchForKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/ExactMatchForKeyword.kt");
@@ -141,7 +141,8 @@ class KotlinIndicesHelper(
fun getCallableTopLevelExtensions(
callTypeAndReceiver: CallTypeAndReceiver<*, *>,
receiverTypes: Collection<KotlinType>,
nameFilter: (String) -> Boolean
nameFilter: (String) -> Boolean,
declarationFilter: (KtDeclaration) -> Boolean = { true }
): Collection<CallableDescriptor> {
if (receiverTypes.isEmpty()) return emptyList()
@@ -157,7 +158,7 @@ class KotlinIndicesHelper(
KotlinTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames
&& nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it))
}
.flatMap { index.get(it, project, scope).asSequence() }
.flatMap { index.get(it, project, scope).asSequence() }.filter(declarationFilter)
val suitableExtensions = findSuitableExtensions(declarations, receiverTypes, callTypeAndReceiver.callType)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.highlighter.dsl
import com.intellij.ide.highlighter.custom.CustomHighlighterColors.*
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.highlighter.HighlighterExtension
@@ -23,12 +24,7 @@ class DslHighlighterExtension : HighlighterExtension() {
}
override fun highlightCall(elementToHighlight: PsiElement, resolvedCall: ResolvedCall<*>): TextAttributesKey? {
val markerAnnotation = resolvedCall.resultingDescriptor.annotations.find { annotation ->
annotation.annotationClass?.isDslHighlightingMarker() ?: false
}?.annotationClass ?: return null
val styleId = styleIdByMarkerAnnotation(markerAnnotation) ?: return null
return styles[styleId - 1]
return dslCustomTextStyle(resolvedCall.resultingDescriptor)
}
companion object {
@@ -57,6 +53,15 @@ class DslHighlighterExtension : HighlighterExtension() {
val markerAnnotationFqName = markerAnnotation.fqNameSafe
return (markerAnnotationFqName.asString().hashCode() % numStyles).absoluteValue + 1
}
fun dslCustomTextStyle(callableDescriptor: CallableDescriptor): TextAttributesKey? {
val markerAnnotation = callableDescriptor.annotations.find { annotation ->
annotation.annotationClass?.isDslHighlightingMarker() ?: false
}?.annotationClass ?: return null
val styleId = styleIdByMarkerAnnotation(markerAnnotation) ?: return null
return styles[styleId - 1]
}
}
}