From f984dbc5b64e9b15551b566aff717a601d488961 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 10 Apr 2014 18:17:53 +0400 Subject: [PATCH] Smart completion to insert comma when in argument list --- .../openapi/editor/event/annotations.xml | 9 ++ .../plugin/completion/CompletionSession.java | 46 ++++--- .../jet/plugin/completion/SmartCompletion.kt | 124 +++++++++++------- .../completion/WithCommaInsertHandler.kt | 50 +++++++ .../completion/handlers/smart/Comma1.kt | 7 + .../completion/handlers/smart/Comma1.kt.after | 7 + .../completion/handlers/smart/Comma2.kt | 7 + .../completion/handlers/smart/Comma2.kt.after | 7 + .../completion/handlers/smart/Comma3.kt | 8 ++ .../completion/handlers/smart/Comma3.kt.after | 8 ++ .../completion/handlers/smart/Comma4.kt | 8 ++ .../completion/handlers/smart/Comma4.kt.after | 8 ++ .../completion/handlers/smart/Comma5.kt | 8 ++ .../completion/handlers/smart/Comma5.kt.after | 8 ++ .../completion/handlers/smart/Comma6.kt | 9 ++ .../completion/handlers/smart/Comma6.kt.after | 9 ++ .../completion/handlers/smart/Comma7.kt | 9 ++ .../completion/handlers/smart/Comma7.kt.after | 9 ++ .../completion/handlers/smart/Comma8.kt | 9 ++ .../completion/handlers/smart/Comma8.kt.after | 9 ++ .../completion/handlers/smart/Comma9.kt | 12 ++ .../completion/handlers/smart/Comma9.kt.after | 12 ++ .../handlers/smart/TabReplaceComma1.kt | 8 ++ .../handlers/smart/TabReplaceComma1.kt.after | 8 ++ .../handlers/smart/TabReplaceComma2.kt | 8 ++ .../handlers/smart/TabReplaceComma2.kt.after | 8 ++ .../SmartCompletionHandlerTestGenerated.java | 55 ++++++++ 27 files changed, 401 insertions(+), 69 deletions(-) create mode 100644 annotations/com/intellij/openapi/editor/event/annotations.xml create mode 100644 idea/src/org/jetbrains/jet/plugin/completion/WithCommaInsertHandler.kt create mode 100644 idea/testData/completion/handlers/smart/Comma1.kt create mode 100644 idea/testData/completion/handlers/smart/Comma1.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma2.kt create mode 100644 idea/testData/completion/handlers/smart/Comma2.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma3.kt create mode 100644 idea/testData/completion/handlers/smart/Comma3.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma4.kt create mode 100644 idea/testData/completion/handlers/smart/Comma4.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma5.kt create mode 100644 idea/testData/completion/handlers/smart/Comma5.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma6.kt create mode 100644 idea/testData/completion/handlers/smart/Comma6.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma7.kt create mode 100644 idea/testData/completion/handlers/smart/Comma7.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma8.kt create mode 100644 idea/testData/completion/handlers/smart/Comma8.kt.after create mode 100644 idea/testData/completion/handlers/smart/Comma9.kt create mode 100644 idea/testData/completion/handlers/smart/Comma9.kt.after create mode 100644 idea/testData/completion/handlers/smart/TabReplaceComma1.kt create mode 100644 idea/testData/completion/handlers/smart/TabReplaceComma1.kt.after create mode 100644 idea/testData/completion/handlers/smart/TabReplaceComma2.kt create mode 100644 idea/testData/completion/handlers/smart/TabReplaceComma2.kt.after diff --git a/annotations/com/intellij/openapi/editor/event/annotations.xml b/annotations/com/intellij/openapi/editor/event/annotations.xml new file mode 100644 index 00000000000..a60f0e99d37 --- /dev/null +++ b/annotations/com/intellij/openapi/editor/event/annotations.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java index 8d32ed66235..6298b3badc1 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java @@ -16,8 +16,6 @@ package org.jetbrains.jet.plugin.completion; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; import com.intellij.codeInsight.completion.CompletionParameters; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.CompletionType; @@ -29,6 +27,7 @@ import com.intellij.openapi.util.Conditions; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; +import kotlin.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; @@ -52,6 +51,13 @@ class CompletionSession { private final JetCompletionResultSet jetResult; private final JetSimpleNameReference jetReference; + private final Condition descriptorFilter = new Condition() { + @Override + public boolean value(DeclarationDescriptor descriptor) { + return isVisibleDescriptor(descriptor); + } + }; + public CompletionSession( @NotNull CompletionParameters parameters, @NotNull CompletionResultSet result, @@ -71,12 +77,7 @@ class CompletionSession { this.jetResult = new JetCompletionResultSet( WeigherPackage.addJetSorting(result, parameters), resolveSession, - expressionBindingContext, new Condition() { - @Override - public boolean value(DeclarationDescriptor descriptor) { - return isVisibleDescriptor(descriptor); - } - }); + expressionBindingContext, descriptorFilter); } public void completeForReference() { @@ -134,10 +135,10 @@ class CompletionSession { final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession()); if (data != null) { - addReferenceVariants(new Condition() { + addReferenceVariants(new Function1() { @Override - public boolean value(DeclarationDescriptor descriptor) { - return data.accepts(descriptor); + public LookupElement invoke(DeclarationDescriptor descriptor) { + return data.toElement(descriptor); } }); for (LookupElement element : data.getAdditionalElements()) { @@ -241,17 +242,26 @@ class CompletionSession { } private void addReferenceVariants(@NotNull final Condition filterCondition) { + addReferenceVariants(new Function1() { + @Override + public LookupElement invoke(DeclarationDescriptor descriptor) { + return filterCondition.value(descriptor) ? DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor) : null; + } + }); + } + + private void addReferenceVariants(@NotNull Function1 filter) { Collection descriptors = TipsManager.getReferenceVariants( jetReference.getExpression(), getExpressionBindingContext()); - Collection filterDescriptors = Collections2.filter(descriptors, new Predicate() { - @Override - public boolean apply(@Nullable DeclarationDescriptor descriptor) { - return descriptor != null && filterCondition.value(descriptor); + for (DeclarationDescriptor descriptor : descriptors) { + if (descriptor != null && descriptorFilter.value(descriptor)) { + LookupElement element = filter.invoke(descriptor); + if (element != null) { + jetResult.addElement(element); + } } - }); - - jetResult.addAllElements(filterDescriptors); + } } private boolean isVisibleDescriptor(DeclarationDescriptor descriptor) { diff --git a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt index 3cdcd03babf..ffbfb7d3998 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt @@ -15,27 +15,14 @@ import com.google.common.collect.SetMultimap import java.util.* import org.jetbrains.jet.lang.resolve.calls.autocasts.* import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.jet.lang.resolve.scopes.JetScope -import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration -import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration import org.jetbrains.jet.lang.resolve.name.Name -import org.jetbrains.jet.lang.resolve.java.descriptor.JavaPropertyDescriptor import org.jetbrains.jet.lang.resolve.java.descriptor.JavaClassDescriptor -import org.jetbrains.jet.lang.resolve.java.resolver.DescriptorResolverUtils -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiModifier import com.intellij.psi.util.PsiTreeUtil -import com.intellij.openapi.editor.EditorModificationUtil import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler import com.intellij.psi.PsiDocumentManager -import com.intellij.openapi.application.ApplicationManager -import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache import org.jetbrains.jet.plugin.codeInsight.ShortenReferences -import org.jetbrains.jet.utils.* import org.jetbrains.jet.di.InjectorForMacros import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext -import org.jetbrains.jet.lang.resolve.calls.context.SimpleResolutionContext import org.jetbrains.jet.lang.resolve.calls.context.ContextDependency import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode import org.jetbrains.jet.lang.resolve.calls.CompositeExtension @@ -47,10 +34,16 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue import com.intellij.lang.ASTNode trait SmartCompletionData{ - fun accepts(descriptor: DeclarationDescriptor): Boolean + fun toElement(descriptor: DeclarationDescriptor): LookupElement? val additionalElements: Iterable } +enum class Tail { + COMMA +} + +data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?) + fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies): SmartCompletionData? { val parent = expression.getParent() val expressionWithType: JetExpression; @@ -67,7 +60,7 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession val bindingContext = resolveSession.resolveToElement(expressionWithType) val allExpectedTypes = calcExpectedTypes(expressionWithType, bindingContext, resolveSession.getModuleDescriptor()) ?: return null - val expectedTypes = allExpectedTypes.filter { !it.isError() } + val expectedTypes = allExpectedTypes.filter { !it.`type`.isError() } if (expectedTypes.isEmpty()) return null val itemsToSkip = calcItemsToSkip(expressionWithType, resolveSession) @@ -86,7 +79,7 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession thisItems(expressionWithType, expectedTypes, bindingContext).toCollection(additionalElements) } - val dataFlowInfo = bindingContext.get(BindingContext.EXPRESSION_DATA_FLOW_INFO, expressionWithType) + val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expressionWithType] val (variableToTypes: Map>, notNullVariables: Set) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext) fun typesOf(descriptor: DeclarationDescriptor): Iterable { @@ -116,28 +109,30 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession } return object: SmartCompletionData { - override fun accepts(descriptor: DeclarationDescriptor) - = !itemsToSkip.contains(descriptor) - && typesOf(descriptor).any { - descriptorType -> expectedTypes.any { expectedType -> isSubtypeOf(descriptorType, expectedType) } - } + override fun toElement(descriptor: DeclarationDescriptor): LookupElement? { + if (itemsToSkip.contains(descriptor)) return null + val matchedExpectedTypes = expectedTypes.filter { expectedType -> typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } } + if (matchedExpectedTypes.isEmpty()) return null + val tail = mergeTails(matchedExpectedTypes.map { it.tail }) + return decorateLookupElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor), tail) + } override val additionalElements = additionalElements } } -private fun calcExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection? { +private fun calcExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection? { val expectedTypes = calcArgumentExpectedTypes(expressionWithType, bindingContext, moduleDescriptor) if (expectedTypes != null) { return expectedTypes } else{ - val expectedType = bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType) ?: return null - return listOf(expectedType) + val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null + return listOf(ExpectedTypeInfo(expectedType, null)) } } -private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection? { +private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection? { val argument = expressionWithType.getParent() as? JetValueArgument ?: return null if (argument.isNamed()) return null //TODO - support named arguments (also do not forget to check for presence of named arguments before) val argumentList = argument.getParent() as JetValueArgumentList @@ -150,7 +145,7 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding val callOperationNode: ASTNode? if (parent is JetQualifiedExpression && callExpression == parent.getSelectorExpression()) { val receiverExpression = parent.getReceiverExpression() - val expressionType = bindingContext.get(BindingContext.EXPRESSION_TYPE, receiverExpression) ?: return null + val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression] ?: return null receiver = ExpressionReceiver(receiverExpression, expressionType) callOperationNode = parent.getOperationTokenNode() } @@ -160,14 +155,14 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding } val call = CallMaker.makeCall(receiver, callOperationNode, callExpression) - val resolutionScope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, calleeExpression) ?: return null //TODO: discuss it + val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it val callResolutionContext = BasicCallResolutionContext.create( DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"), resolutionScope, call, - bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression) ?: TypeUtils.NO_EXPECTED_TYPE, - bindingContext.get(BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression) ?: DataFlowInfo.EMPTY, + bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE, + bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression] ?: DataFlowInfo.EMPTY, ContextDependency.INDEPENDENT, CheckValueArgumentsMode.ENABLED, CompositeExtension(listOf()), @@ -175,12 +170,13 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!! val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) - val expectedTypes = HashSet() + val expectedTypes = HashSet() for (candidate: ResolvedCall in results.getAllCandidates()!!) { val parameters = candidate.getResultingDescriptor().getValueParameters() if (parameters.size <= argumentIndex) continue val parameterDescriptor = parameters[argumentIndex] - expectedTypes.add(parameterDescriptor.getType()) + val tail = if (argumentIndex == parameters.size - 1) null else Tail.COMMA + expectedTypes.add(ExpectedTypeInfo(parameterDescriptor.getType(), tail)) } return expectedTypes } @@ -191,7 +187,7 @@ private fun calcItemsToSkip(expression: JetExpression, resolveSession: ResolveSe is JetProperty -> { //TODO: this can be filtered out by ordinary completion if (expression == parent.getInitializer()) { - return resolveSession.resolveToElement(parent).get(BindingContext.DECLARATION_TO_DESCRIPTOR, parent).toList() + return resolveSession.resolveToElement(parent)[BindingContext.DECLARATION_TO_DESCRIPTOR, parent].toList() } } @@ -199,7 +195,7 @@ private fun calcItemsToSkip(expression: JetExpression, resolveSession: ResolveSe if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) { val left = parent.getLeft() if (left is JetReferenceExpression) { - return resolveSession.resolveToElement(left).get(BindingContext.REFERENCE_TARGET, left).toList() + return resolveSession.resolveToElement(left)[BindingContext.REFERENCE_TARGET, left].toList() } } } @@ -207,8 +203,8 @@ private fun calcItemsToSkip(expression: JetExpression, resolveSession: ResolveSe return listOf() } -private fun typeInstantiationItems(expectedType: JetType, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable { - val typeConstructor: TypeConstructor = expectedType.getConstructor() +private fun typeInstantiationItems(expectedType: ExpectedTypeInfo, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable { + val typeConstructor: TypeConstructor = expectedType.`type`.getConstructor() val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() if (!(classifier is ClassDescriptor)) return listOf() @@ -218,7 +214,7 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Resolv var lookupString = lookupElement.getLookupString() - val typeArgs = expectedType.getArguments() + val typeArgs = expectedType.`type`.getArguments() var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs) val insertHandler: InsertHandler @@ -289,16 +285,18 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Resolv } } + val lookupElementWithTail = decorateLookupElement(lookupElementDecorated, expectedType.tail) + if (suppressAutoInsertion) { - return listOf(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementDecorated)) + return listOf(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementWithTail)) } else{ - return listOf(lookupElementDecorated) + return listOf(lookupElementWithTail) } } -private fun thisItems(context: JetExpression, expectedTypes: Collection, bindingContext: BindingContext): Iterable { - val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, context) +private fun thisItems(context: JetExpression, expectedTypes: Collection, bindingContext: BindingContext): Iterable { + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] if (scope == null) return listOf() val receivers: List = scope.getImplicitReceiversHierarchy() @@ -306,12 +304,15 @@ private fun thisItems(context: JetExpression, expectedTypes: Collection for (i in 0..receivers.size - 1) { val receiver = receivers[i] val thisType = receiver.getType() - if (expectedTypes.any { isSubtypeOf(thisType, it) }) { + val matchedExpectedTypes = expectedTypes.filter { isSubtypeOf(thisType, it.`type`) } + if (matchedExpectedTypes.notEmpty) { //TODO: use this code when KT-4258 fixed //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue) val qualifier = if (i == 0) null else thisQualifierName(receiver, bindingContext) ?: continue val expressionText = if (qualifier == null) "this" else "this@" + qualifier - result.add(LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType))) + val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType)) + val tailType = mergeTails(matchedExpectedTypes.map { it.tail }) + result.add(decorateLookupElement(lookupElement, tailType)) } } return result @@ -344,7 +345,7 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres if (dataFlowInfo != null) { val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor? if (receiver != null) { - val receiverType = bindingContext.get(BindingContext.EXPRESSION_TYPE, receiver) + val receiverType = bindingContext[BindingContext.EXPRESSION_TYPE, receiver] if (receiverType != null) { val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId() dataFlowValueToVariable = {(value) -> @@ -371,7 +372,7 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres val nullabilityInfo: Map = dataFlowInfo.getCompleteNullabilityInfo() val notNullVariables = nullabilityInfo - .filter_tmp { it.getValue() == Nullability.NOT_NULL } + .filter { it.getValue() == Nullability.NOT_NULL } .map { dataFlowValueToVariable(it.getKey()) } .filterNotNullTo(HashSet()) @@ -382,17 +383,17 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres } // adds java static members, enum members and members from class object -private fun staticMembers(context: JetExpression, expectedType: JetType, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable { - val classDescriptor = TypeUtils.getClassDescriptor(expectedType) +private fun staticMembers(context: JetExpression, expectedType: ExpectedTypeInfo, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable { + val classDescriptor = TypeUtils.getClassDescriptor(expectedType.`type`) if (classDescriptor == null) return listOf() if (classDescriptor.getName().isSpecial()) return listOf() - val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, context) + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] if (scope == null) return listOf() val descriptors = ArrayList() val isSuitableCallable: (DeclarationDescriptor) -> Boolean = { - it is CallableDescriptor && it.getReturnType()?.let { isSubtypeOf(it, expectedType) } ?: false + it is CallableDescriptor && it.getReturnType()?.let { isSubtypeOf(it, expectedType.`type`) } ?: false } if (classDescriptor is JavaClassDescriptor) { @@ -447,7 +448,7 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve } } - return object: LookupElementDecorator(lookupElement){ + val lookupElementDecorated = object: LookupElementDecorator(lookupElement) { override fun getLookupString() = lookupString override fun renderElement(presentation: LookupElementPresentation) { @@ -459,7 +460,7 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve if (descriptor is FunctionDescriptor) { presentation.appendTailText(tailText, true) } - else{ + else { presentation.setTailText(tailText, true) } @@ -472,11 +473,32 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve insertHandler.handleInsert(context, lookupElement) } } + + return decorateLookupElement(lookupElementDecorated, expectedType.tail) } return descriptors .filter { !(it is DeclarationDescriptorWithVisibility) || Visibilities.isVisible(it, scope.getContainingDeclaration()) } - .map({toLookupElement(it)}) //TODO: use ::toLookupElement but it causes compiler to crash + .map(::toLookupElement) +} + +private fun mergeTails(tails: Collection): Tail? { + if (tails.size == 1) return tails.single() + return if (HashSet(tails).size == 1) tails.first() else null +} + +private fun decorateLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { + return when (tail) { + null -> lookupElement + + Tail.COMMA -> object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithCommaInsertHandler.handleInsert(context, lookupElement) + } + } + + else -> throw RuntimeException("Unknown tail type") + } } private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{ diff --git a/idea/src/org/jetbrains/jet/plugin/completion/WithCommaInsertHandler.kt b/idea/src/org/jetbrains/jet/plugin/completion/WithCommaInsertHandler.kt new file mode 100644 index 00000000000..767094671c7 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/WithCommaInsertHandler.kt @@ -0,0 +1,50 @@ +package org.jetbrains.jet.plugin.completion + +import com.intellij.codeInsight.lookup.* +import com.intellij.codeInsight.completion.* +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.util.TextRange + +object WithCommaInsertHandler : InsertHandler { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val document = context.getDocument() + val caretModel = context.getEditor().getCaretModel() + + var maxChangeOffset = caretModel.getOffset() + val documentListener = object: DocumentListener { + override fun documentChanged(event: DocumentEvent) { + val oldEndOffset = event.getOffset() + event.getOldLength() + if (oldEndOffset < maxChangeOffset) { + maxChangeOffset += event.getNewLength() - event.getOldLength() + } + else { + maxChangeOffset = event.getOffset() + event.getNewLength() + } + } + + override fun beforeDocumentChange(event: DocumentEvent) { + } + } + + document.addDocumentListener(documentListener) + item.handleInsert(context) + document.removeDocumentListener(documentListener) + + val moveCaret = caretModel.getOffset() == maxChangeOffset + + if (maxChangeOffset < document.getTextLength() && document.getText(TextRange(maxChangeOffset, maxChangeOffset + 1)) == ",") { + document.deleteString(maxChangeOffset, maxChangeOffset + 1) + + if (maxChangeOffset < document.getTextLength() && document.getText(TextRange(maxChangeOffset, maxChangeOffset + 1)) == " ") { + document.deleteString(maxChangeOffset, maxChangeOffset + 1) + } + } + + val textToInsert = ", " //TODO: code style option + document.insertString(maxChangeOffset, textToInsert) + if (moveCaret) { + caretModel.moveToOffset(maxChangeOffset + textToInsert.length) + } + } +} \ No newline at end of file diff --git a/idea/testData/completion/handlers/smart/Comma1.kt b/idea/testData/completion/handlers/smart/Comma1.kt new file mode 100644 index 00000000000..eed859fab46 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma1.kt @@ -0,0 +1,7 @@ +fun foo(s: String){ } + +fun bar(sss: String) { + foo() +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma1.kt.after b/idea/testData/completion/handlers/smart/Comma1.kt.after new file mode 100644 index 00000000000..0fbbf45ca01 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma1.kt.after @@ -0,0 +1,7 @@ +fun foo(s: String){ } + +fun bar(sss: String) { + foo(sss) +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma2.kt b/idea/testData/completion/handlers/smart/Comma2.kt new file mode 100644 index 00000000000..92af737c80c --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma2.kt @@ -0,0 +1,7 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo() +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma2.kt.after b/idea/testData/completion/handlers/smart/Comma2.kt.after new file mode 100644 index 00000000000..b7d3f1195f7 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma2.kt.after @@ -0,0 +1,7 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss, ) +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma3.kt b/idea/testData/completion/handlers/smart/Comma3.kt new file mode 100644 index 00000000000..63aed0b75f3 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma3.kt @@ -0,0 +1,8 @@ +fun foo(s: String){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo() +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma3.kt.after b/idea/testData/completion/handlers/smart/Comma3.kt.after new file mode 100644 index 00000000000..819db9a033f --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma3.kt.after @@ -0,0 +1,8 @@ +fun foo(s: String){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss) +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma4.kt b/idea/testData/completion/handlers/smart/Comma4.kt new file mode 100644 index 00000000000..6eb3da91360 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma4.kt @@ -0,0 +1,8 @@ +fun foo(o: Any){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo() +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma4.kt.after b/idea/testData/completion/handlers/smart/Comma4.kt.after new file mode 100644 index 00000000000..1963ba477aa --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma4.kt.after @@ -0,0 +1,8 @@ +fun foo(o: Any){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss) +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma5.kt b/idea/testData/completion/handlers/smart/Comma5.kt new file mode 100644 index 00000000000..82d6149f297 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma5.kt @@ -0,0 +1,8 @@ +fun foo(o: Any, c: Char){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo() +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma5.kt.after b/idea/testData/completion/handlers/smart/Comma5.kt.after new file mode 100644 index 00000000000..6aee486f6de --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma5.kt.after @@ -0,0 +1,8 @@ +fun foo(o: Any, c: Char){ } +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss, ) +} + +//ELEMENT: sss diff --git a/idea/testData/completion/handlers/smart/Comma6.kt b/idea/testData/completion/handlers/smart/Comma6.kt new file mode 100644 index 00000000000..4acee9b73f1 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma6.kt @@ -0,0 +1,9 @@ +fun foo(s: String, i: Int){ } + +fun bar() { + foo() +} + +fun getString(p: Int): String = "" + +//ELEMENT: getString diff --git a/idea/testData/completion/handlers/smart/Comma6.kt.after b/idea/testData/completion/handlers/smart/Comma6.kt.after new file mode 100644 index 00000000000..983ab204ae8 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma6.kt.after @@ -0,0 +1,9 @@ +fun foo(s: String, i: Int){ } + +fun bar() { + foo(getString(), ) +} + +fun getString(p: Int): String = "" + +//ELEMENT: getString diff --git a/idea/testData/completion/handlers/smart/Comma7.kt b/idea/testData/completion/handlers/smart/Comma7.kt new file mode 100644 index 00000000000..76f709e1c7b --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma7.kt @@ -0,0 +1,9 @@ +fun foo(c: C, i: Int){ } + +class C { + fun bar() { + foo() + } +} + +//ELEMENT: this diff --git a/idea/testData/completion/handlers/smart/Comma7.kt.after b/idea/testData/completion/handlers/smart/Comma7.kt.after new file mode 100644 index 00000000000..c827e9e7cb4 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma7.kt.after @@ -0,0 +1,9 @@ +fun foo(c: C, i: Int){ } + +class C { + fun bar() { + foo(this, ) + } +} + +//ELEMENT: this diff --git a/idea/testData/completion/handlers/smart/Comma8.kt b/idea/testData/completion/handlers/smart/Comma8.kt new file mode 100644 index 00000000000..66b472a1c2b --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma8.kt @@ -0,0 +1,9 @@ +trait Foo + +fun foo(f: Foo, i: Int){} + +fun bar() { + foo() +} + +//ELEMENT: object diff --git a/idea/testData/completion/handlers/smart/Comma8.kt.after b/idea/testData/completion/handlers/smart/Comma8.kt.after new file mode 100644 index 00000000000..e4b1f6a014e --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma8.kt.after @@ -0,0 +1,9 @@ +trait Foo + +fun foo(f: Foo, i: Int){} + +fun bar() { + foo(object: Foo {}, ) +} + +//ELEMENT: object diff --git a/idea/testData/completion/handlers/smart/Comma9.kt b/idea/testData/completion/handlers/smart/Comma9.kt new file mode 100644 index 00000000000..6392cdca7ca --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma9.kt @@ -0,0 +1,12 @@ +enum class Foo { + X + Y +} + +fun foo(f: Foo, i: Int){} + +fun bar(){ + foo() +} + +// ELEMENT: Foo.X diff --git a/idea/testData/completion/handlers/smart/Comma9.kt.after b/idea/testData/completion/handlers/smart/Comma9.kt.after new file mode 100644 index 00000000000..3ffc41649c8 --- /dev/null +++ b/idea/testData/completion/handlers/smart/Comma9.kt.after @@ -0,0 +1,12 @@ +enum class Foo { + X + Y +} + +fun foo(f: Foo, i: Int){} + +fun bar(){ + foo(Foo.X, ) +} + +// ELEMENT: Foo.X diff --git a/idea/testData/completion/handlers/smart/TabReplaceComma1.kt b/idea/testData/completion/handlers/smart/TabReplaceComma1.kt new file mode 100644 index 00000000000..d6587ab4008 --- /dev/null +++ b/idea/testData/completion/handlers/smart/TabReplaceComma1.kt @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(a, b) +} + +//ELEMENT: sss +//CHAR: \t diff --git a/idea/testData/completion/handlers/smart/TabReplaceComma1.kt.after b/idea/testData/completion/handlers/smart/TabReplaceComma1.kt.after new file mode 100644 index 00000000000..5c4cb9da176 --- /dev/null +++ b/idea/testData/completion/handlers/smart/TabReplaceComma1.kt.after @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss, b) +} + +//ELEMENT: sss +//CHAR: \t diff --git a/idea/testData/completion/handlers/smart/TabReplaceComma2.kt b/idea/testData/completion/handlers/smart/TabReplaceComma2.kt new file mode 100644 index 00000000000..4452b12e07b --- /dev/null +++ b/idea/testData/completion/handlers/smart/TabReplaceComma2.kt @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(a,b) +} + +//ELEMENT: sss +//CHAR: \t diff --git a/idea/testData/completion/handlers/smart/TabReplaceComma2.kt.after b/idea/testData/completion/handlers/smart/TabReplaceComma2.kt.after new file mode 100644 index 00000000000..5c4cb9da176 --- /dev/null +++ b/idea/testData/completion/handlers/smart/TabReplaceComma2.kt.after @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){ } + +fun bar(sss: String) { + foo(sss, b) +} + +//ELEMENT: sss +//CHAR: \t diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java index 2ac96b350cb..43a61bd5774 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java @@ -66,6 +66,51 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest("idea/testData/completion/handlers/smart/ClassObjectMethod2.kt"); } + @TestMetadata("Comma1.kt") + public void testComma1() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma1.kt"); + } + + @TestMetadata("Comma2.kt") + public void testComma2() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma2.kt"); + } + + @TestMetadata("Comma3.kt") + public void testComma3() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma3.kt"); + } + + @TestMetadata("Comma4.kt") + public void testComma4() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma4.kt"); + } + + @TestMetadata("Comma5.kt") + public void testComma5() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma5.kt"); + } + + @TestMetadata("Comma6.kt") + public void testComma6() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma6.kt"); + } + + @TestMetadata("Comma7.kt") + public void testComma7() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma7.kt"); + } + + @TestMetadata("Comma8.kt") + public void testComma8() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma8.kt"); + } + + @TestMetadata("Comma9.kt") + public void testComma9() throws Exception { + doTest("idea/testData/completion/handlers/smart/Comma9.kt"); + } + @TestMetadata("Constructor.kt") public void testConstructor() throws Exception { doTest("idea/testData/completion/handlers/smart/Constructor.kt"); @@ -131,6 +176,16 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest("idea/testData/completion/handlers/smart/JavaStaticMethodInsertsImport.kt"); } + @TestMetadata("TabReplaceComma1.kt") + public void testTabReplaceComma1() throws Exception { + doTest("idea/testData/completion/handlers/smart/TabReplaceComma1.kt"); + } + + @TestMetadata("TabReplaceComma2.kt") + public void testTabReplaceComma2() throws Exception { + doTest("idea/testData/completion/handlers/smart/TabReplaceComma2.kt"); + } + @TestMetadata("TabReplaceExpression.kt") public void testTabReplaceExpression() throws Exception { doTest("idea/testData/completion/handlers/smart/TabReplaceExpression.kt");