From 0769c5453f328b20a7378f7ac1d151b894ae592c Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 28 Apr 2016 21:52:12 +0300 Subject: [PATCH] KT-12103 Smart completion for nested SAM-adapter produces short unresolved name #KT-12103 Fixed --- .../kotlin/idea/completion/CompletionUtils.kt | 36 ++++++------------- .../idea/completion/InsertHandlerProvider.kt | 18 ++++------ .../idea/completion/LookupElementFactory.kt | 7 ++-- .../handlers/KotlinCallableInsertHandler.kt | 25 +++++++------ .../handlers/KotlinFunctionInsertHandler.kt | 13 ++++--- .../handlers/KotlinPropertyInsertHandler.kt | 3 +- .../smart/TypeInstantiationItems.kt | 6 ++-- .../multifile/smart/NestedSamAdapter-1.kt | 6 ++++ .../multifile/smart/NestedSamAdapter.java | 5 +++ .../multifile/smart/NestedSamAdapter.kt.after | 6 ++++ .../SmartCompletionMultifileHandlerTest.kt | 26 +++++++++++--- 11 files changed, 88 insertions(+), 63 deletions(-) create mode 100644 idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter-1.kt create mode 100644 idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.java create mode 100644 idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.kt.after 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 7db88c67024..ef4b8faa20c 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 @@ -16,7 +16,10 @@ package org.jetbrains.kotlin.idea.completion -import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.completion.CompletionProgressIndicator +import com.intellij.codeInsight.completion.CompletionService +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.completion.PrefixMatcher import com.intellij.codeInsight.lookup.* import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.Key @@ -359,14 +362,14 @@ fun LookupElement.decorateAsStaticMember( memberDescriptor: DeclarationDescriptor, classNameAsLookupString: Boolean ): LookupElement? { - var container = memberDescriptor.containingDeclaration as? ClassDescriptor ?: return null - var classDescriptor = if (container.isCompanionObject) + val container = memberDescriptor.containingDeclaration as? ClassDescriptor ?: return null + val classDescriptor = if (container.isCompanionObject) container.containingDeclaration as? ClassDescriptor ?: return null else container + val containerFqName = container.importableFqName ?: return null val qualifierPresentation = classDescriptor.name.asString() - val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor) return object: LookupElementDecorator(this) { override fun getAllLookupStrings(): Set { @@ -395,31 +398,14 @@ fun LookupElement.decorateAsStaticMember( val psiDocumentManager = PsiDocumentManager.getInstance(context.project) val file = context.file as KtFile - val classImportableFqName = container.importableFqName - val useImport = classImportableFqName != null && file.importDirectives.any { - !it.isAllUnder && it.importPath?.fqnPart()?.parent() == classImportableFqName - } + val addMemberImport = file.importDirectives.any { !it.isAllUnder && it.importPath?.fqnPart()?.parent() == containerFqName } - var insertQualifier = true - if (useImport) { + if (addMemberImport) { psiDocumentManager.commitAllDocuments() - if (ImportInsertHelper.getInstance(context.project).importDescriptor(file, memberDescriptor) != ImportDescriptorResult.FAIL) { - insertQualifier = false - } + ImportInsertHelper.getInstance(context.project).importDescriptor(file, memberDescriptor) + psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document) } - if (insertQualifier) { - val prefix = qualifierText + "." - - val offset = context.startOffset - context.document.insertString(offset, prefix) - context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length) - - shortenReferences(context, offset, offset + prefix.length) - - } - - psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document) super.handleInsert(context) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt index e83350a5ab3..29399b16aac 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/InsertHandlerProvider.kt @@ -31,16 +31,12 @@ import org.jetbrains.kotlin.types.KotlinType import java.util.* class InsertHandlerProvider( - private val callType: CallType<*>?, + private val callType: CallType<*>, expectedInfosCalculator: () -> Collection ) { private val expectedInfos by lazy(LazyThreadSafetyMode.NONE) { expectedInfosCalculator() } fun insertHandler(descriptor: DeclarationDescriptor): InsertHandler { - if (callType == null) { - error("Cannot create InsertHandler when no CallType known") - } - return when (descriptor) { is FunctionDescriptor -> { when (callType) { @@ -48,7 +44,7 @@ class InsertHandlerProvider( val needTypeArguments = needTypeArguments(descriptor) val parameters = descriptor.valueParameters when (parameters.size) { - 0 -> KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = false) + 0 -> KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = false) 1 -> { if (callType != CallType.SUPER_MEMBERS) { // for super call we don't suggest to generate "super.foo { ... }" (seems to be non-typical use) @@ -57,28 +53,28 @@ class InsertHandlerProvider( if (getValueParametersCountFromFunctionType(parameterType) <= 1) { // otherwise additional item with lambda template is to be added return KotlinFunctionInsertHandler.Normal( - needTypeArguments, inputValueArguments = false, + callType, needTypeArguments, inputValueArguments = false, lambdaInfo = GenerateLambdaInfo(parameterType, false) ) } } } - KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = true) + KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = true) } - else -> KotlinFunctionInsertHandler.Normal(needTypeArguments, inputValueArguments = true) + else -> KotlinFunctionInsertHandler.Normal(callType, needTypeArguments, inputValueArguments = true) } } CallType.INFIX -> KotlinFunctionInsertHandler.Infix - else -> KotlinFunctionInsertHandler.OnlyName + else -> KotlinFunctionInsertHandler.OnlyName(callType) } } - is PropertyDescriptor -> KotlinPropertyInsertHandler + is PropertyDescriptor -> KotlinPropertyInsertHandler(callType) is ClassifierDescriptor -> KotlinClassifierInsertHandler diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt index a22336f9ac8..7ae604f71e0 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt @@ -63,7 +63,7 @@ data /* we need copy() */ class LookupElementFactory( val basicFactory: BasicLookupElementFactory, private val receiverTypes: Collection?, - private val callType: CallType<*>?, + private val callType: CallType<*>, private val inDescriptor: DeclarationDescriptor, private val contextVariablesProvider: ContextVariablesProvider, private val standardLookupElementsPostProcessor: (LookupElement) -> LookupElement = { it } @@ -188,7 +188,7 @@ class LookupElementFactory( } override fun handleInsert(context: InsertionContext) { - KotlinFunctionInsertHandler.Normal(inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this) + KotlinFunctionInsertHandler.Normal(callType, inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this) } } @@ -235,7 +235,8 @@ class LookupElementFactory( } override fun handleInsert(context: InsertionContext) { - KotlinFunctionInsertHandler.Normal(inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText).handleInsert(context, this) + KotlinFunctionInsertHandler.Normal(callType, inputTypeArguments = needTypeArguments, inputValueArguments = false, argumentText = argumentText) + .handleInsert(context, this) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinCallableInsertHandler.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinCallableInsertHandler.kt index e1be572bf59..e322166de89 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinCallableInsertHandler.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinCallableInsertHandler.kt @@ -20,14 +20,17 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.psi.PsiDocumentManager import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.idea.completion.isAfterDot +import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.util.CallType import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.DescriptorUtils -abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler() { +abstract class KotlinCallableInsertHandler(val callType: CallType<*>) : BaseDeclarationInsertHandler() { override fun handleInsert(context: InsertionContext, item: LookupElement) { super.handleInsert(context, item) @@ -40,19 +43,21 @@ abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler() { val file = context.file val o = item.`object` if (file is KtFile && o is DeclarationLookupObject) { - val descriptor = o.descriptor as? CallableDescriptor - if (descriptor != null) { - // for completion after dot, import insertion may be required only for extensions - if (context.isAfterDot() && descriptor.extensionReceiverParameter == null) { - return - } - + val descriptor = o.descriptor as? CallableDescriptor ?: return + if (descriptor.extensionReceiverParameter != null || callType == CallType.CALLABLE_REFERENCE) { if (DescriptorUtils.isTopLevelDeclaration(descriptor)) { runWriteAction { - ImportInsertHelper.getInstance(context.getProject()).importDescriptor(file, descriptor) + ImportInsertHelper.getInstance(context.project).importDescriptor(file, descriptor) } } } + else if (callType == CallType.DEFAULT) { + val fqName = descriptor.importableFqName ?: return + context.document.replaceString(context.startOffset, context.tailOffset, fqName.render()) + + PsiDocumentManager.getInstance(context.project).commitAllDocuments() + ShortenReferences.DEFAULT.process(file, context.startOffset, context.tailOffset) + } } } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinFunctionInsertHandler.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinFunctionInsertHandler.kt index c1575809274..7d917c7acc5 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinFunctionInsertHandler.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinFunctionInsertHandler.kt @@ -27,6 +27,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.codeStyle.CodeStyleSettingsManager import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings +import org.jetbrains.kotlin.idea.util.CallType import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtTypeArgumentList import org.jetbrains.kotlin.psi.psiUtil.endOffset @@ -34,15 +35,16 @@ import org.jetbrains.kotlin.types.KotlinType class GenerateLambdaInfo(val lambdaType: KotlinType, val explicitParameters: Boolean) -sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() { +sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) { class Normal( + callType: CallType<*>, val inputTypeArguments: Boolean, val inputValueArguments: Boolean, val argumentText: String = "", val lambdaInfo: GenerateLambdaInfo? = null, val argumentsOnly: Boolean = false - ) : KotlinFunctionInsertHandler() { + ) : KotlinFunctionInsertHandler(callType) { init { if (lambdaInfo != null) { assert(argumentText == "") @@ -51,12 +53,13 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() { //TODO: add 'data' or special annotation when supported fun copy( + callType: CallType<*> = this.callType, inputTypeArguments: Boolean = this.inputTypeArguments, inputValueArguments: Boolean = this.inputValueArguments, argumentText: String = this.argumentText, lambdaInfo: GenerateLambdaInfo? = this.lambdaInfo, argumentsOnly: Boolean = this.argumentsOnly - ) = Normal(inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly) + ) = Normal(callType, inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly) override fun handleInsert(context: InsertionContext, item: LookupElement) { val psiDocumentManager = PsiDocumentManager.getInstance(context.project) @@ -193,7 +196,7 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() { = CodeStyleSettingsManager.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD } - object Infix : KotlinFunctionInsertHandler() { + object Infix : KotlinFunctionInsertHandler(CallType.INFIX) { override fun handleInsert(context: InsertionContext, item: LookupElement) { super.handleInsert(context, item) @@ -207,7 +210,7 @@ sealed class KotlinFunctionInsertHandler : KotlinCallableInsertHandler() { } } - object OnlyName : KotlinFunctionInsertHandler() + class OnlyName(callType: CallType<*>) : KotlinFunctionInsertHandler(callType) override fun handleInsert(context: InsertionContext, item: LookupElement) { super.handleInsert(context, item) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinPropertyInsertHandler.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinPropertyInsertHandler.kt index e5d2a40cf8e..9c14e7b5a56 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinPropertyInsertHandler.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinPropertyInsertHandler.kt @@ -20,8 +20,9 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.LookupElement import com.intellij.psi.PsiDocumentManager +import org.jetbrains.kotlin.idea.util.CallType -object KotlinPropertyInsertHandler : KotlinCallableInsertHandler() { +class KotlinPropertyInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) { override fun handleInsert(context: InsertionContext, item: LookupElement) { super.handleInsert(context, item) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypeInstantiationItems.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypeInstantiationItems.kt index 129b1995d50..8783fdaab5c 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypeInstantiationItems.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypeInstantiationItems.kt @@ -153,7 +153,7 @@ class TypeInstantiationItems( var allLookupStrings = setOf(lookupString) var itemText = lookupString var signatureText: String? = null - var typeText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier) + val typeText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier) val insertHandler: InsertHandler if (isAbstract) { @@ -218,12 +218,12 @@ class TypeInstantiationItems( } val baseInsertHandler = when (visibleConstructors.size) { - 0 -> KotlinFunctionInsertHandler.Normal(inputTypeArguments = false, inputValueArguments = false, argumentsOnly = true) + 0 -> KotlinFunctionInsertHandler.Normal(CallType.DEFAULT, inputTypeArguments = false, inputValueArguments = false, argumentsOnly = true) 1 -> (lookupElementFactory.insertHandlerProvider.insertHandler(visibleConstructors.single()) as KotlinFunctionInsertHandler.Normal) .copy(argumentsOnly = true) - else -> KotlinFunctionInsertHandler.Normal(inputTypeArguments = false, inputValueArguments = true, argumentsOnly = true) + else -> KotlinFunctionInsertHandler.Normal(CallType.DEFAULT, inputTypeArguments = false, inputValueArguments = true, argumentsOnly = true) } insertHandler = object : InsertHandler { diff --git a/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter-1.kt b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter-1.kt new file mode 100644 index 00000000000..0e6da5e2652 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter-1.kt @@ -0,0 +1,6 @@ +fun foo(x: Outer.Nested) { +} + +fun bar() { + foo() +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.java b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.java new file mode 100644 index 00000000000..e0ae2899fa1 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.java @@ -0,0 +1,5 @@ +public class Outer { + public interface Nested { + void foo(); + } +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.kt.after b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.kt.after new file mode 100644 index 00000000000..8e907f77550 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/smart/NestedSamAdapter.kt.after @@ -0,0 +1,6 @@ +fun foo(x: Outer.Nested) { +} + +fun bar() { + foo(Outer.Nested { }) +} \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionMultifileHandlerTest.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionMultifileHandlerTest.kt index 22c60c50a55..48e08e309d6 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionMultifileHandlerTest.kt +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionMultifileHandlerTest.kt @@ -17,11 +17,11 @@ package org.jetbrains.kotlin.idea.completion.test.handlers import com.intellij.codeInsight.completion.CompletionType -import junit.framework.TestCase +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementPresentation import org.jetbrains.kotlin.idea.completion.test.COMPLETION_TEST_DATA_BASE_PATH import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase import java.io.File -import kotlin.test.assertTrue class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() { fun testImportExtensionFunction() { doTest() } @@ -30,12 +30,14 @@ class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() { fun testAnonymousObjectGenericJava() { doTest() } + fun testNestedSamAdapter() { doTest(lookupString = "Nested") } + override fun setUp() { setType(CompletionType.SMART) super.setUp() } - fun doTest() { + private fun doTest(lookupString: String? = null, itemText: String? = null) { val fileName = getTestName(false) val fileNames = listOf(fileName + "-1.kt", fileName + "-2.kt", fileName + ".java") @@ -44,8 +46,22 @@ class SmartCompletionMultifileHandlerTest : KotlinCompletionTestCase() { complete(1) if (myItems != null) { - assertTrue(myItems.size == 1, "Multiple items in completion") - selectItem(myItems[0]) + fun isMatching(lookupElement: LookupElement): Boolean { + if (lookupString != null && lookupElement.lookupString != lookupString) return false + + val presentation = LookupElementPresentation() + lookupElement.renderElement(presentation) + if (itemText != null && presentation.itemText != itemText) return false + + return true + } + + val items = myItems.filter(::isMatching) + when (items.size) { + 0 -> fail("No matching items found") + 1 -> selectItem(myItems[0]) + else -> fail("Multiple matching items found") + } } checkResultByFile(fileName + ".kt.after")