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:
+7
-1
@@ -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 ->
|
||||
|
||||
+11
-2
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
+8
-2
@@ -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
|
||||
-2
@@ -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") {
|
||||
|
||||
+6
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user