From 1796d40118588c813092f59baf1be2ad387252b5 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Fri, 9 Feb 2018 16:22:37 +0100 Subject: [PATCH] Completion: assign highest priority to dsl members in dsl context Use the same style as for dsl for lookup items that belong to dsl --- .../idea/completion/BasicCompletionSession.kt | 8 +- .../completion/BasicLookupElementFactory.kt | 13 +++- .../idea/completion/CompletionSession.kt | 4 +- .../kotlin/idea/completion/CompletionUtils.kt | 1 + .../idea/completion/DslMembersCompletion.kt | 73 +++++++++++++++++++ .../completion/LookupElementsCollector.kt | 10 ++- .../kotlin/idea/completion/Weighers.kt | 7 ++ .../testData/weighers/basic/DslCalls.Data1.kt | 42 +++++++++++ .../testData/weighers/basic/DslCalls.kt | 20 +++++ .../weighers/AbstractCompletionWeigherTest.kt | 2 - .../BasicCompletionWeigherTestGenerated.java | 6 ++ .../kotlin/idea/core/KotlinIndicesHelper.kt | 5 +- .../dsl/DslHighlighterExtension.kt | 17 +++-- 13 files changed, 191 insertions(+), 17 deletions(-) create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DslMembersCompletion.kt create mode 100644 idea/idea-completion/testData/weighers/basic/DslCalls.Data1.kt create mode 100644 idea/idea-completion/testData/weighers/basic/DslCalls.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 40248ba71ae..bb79aabfa12 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -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 -> diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicLookupElementFactory.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicLookupElementFactory.kt index 15043dd1d1c..f959067f25e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicLookupElementFactory.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicLookupElementFactory.kt @@ -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) { 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 cd427e66ee4..5625725b691 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 @@ -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 } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index 4714733d010..0e9382a4d52 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -79,6 +79,7 @@ enum class ItemPriority { } val ITEM_PRIORITY_KEY = Key("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) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DslMembersCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DslMembersCompletion.kt new file mode 100644 index 00000000000..cc0d90aa804 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/DslMembersCompletion.kt @@ -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?, + 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 { + 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() + } + +} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt index 88fa35a63ee..37dd294c56f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt @@ -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() var isResultEmpty: Boolean = true private set @@ -67,7 +69,7 @@ class LookupElementsCollector( } fun addDescriptorElements(descriptors: Iterable, - 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) { 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 569f36088b8..f6ebbe5ed91 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 @@ -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, diff --git a/idea/idea-completion/testData/weighers/basic/DslCalls.Data1.kt b/idea/idea-completion/testData/weighers/basic/DslCalls.Data1.kt new file mode 100644 index 00000000000..32a91bd351c --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/DslCalls.Data1.kt @@ -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() { + +} + diff --git a/idea/idea-completion/testData/weighers/basic/DslCalls.kt b/idea/idea-completion/testData/weighers/basic/DslCalls.kt new file mode 100644 index 00000000000..1fac007f663 --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/DslCalls.kt @@ -0,0 +1,20 @@ +// RUNTIME +package test + +import bar.r +import bar.foo3 + +fun main() { + val foo5 = 3 + r { + foo + } +} + +// ORDER: foo2 +// ORDER: foo4 +// ORDER: fooloooooong +// ORDER: foo3 +// ORDER: foo5 +// ORDER: fooval +// ORDER: foo1 diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/AbstractCompletionWeigherTest.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/AbstractCompletionWeigherTest.kt index 8d7f5eaf003..e801c65ee10 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/AbstractCompletionWeigherTest.kt +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/AbstractCompletionWeigherTest.kt @@ -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") { diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java index 08fd125aebc..4b28ecf8489 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java @@ -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"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt index f0eb0812762..0149ae71a50 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt @@ -141,7 +141,8 @@ class KotlinIndicesHelper( fun getCallableTopLevelExtensions( callTypeAndReceiver: CallTypeAndReceiver<*, *>, receiverTypes: Collection, - nameFilter: (String) -> Boolean + nameFilter: (String) -> Boolean, + declarationFilter: (KtDeclaration) -> Boolean = { true } ): Collection { 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) diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/dsl/DslHighlighterExtension.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/dsl/DslHighlighterExtension.kt index ac68e74a694..85203e7fcbc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/dsl/DslHighlighterExtension.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/dsl/DslHighlighterExtension.kt @@ -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] + } } }