Smart completion to insert comma when in argument list

This commit is contained in:
Valentin Kipyatkov
2014-04-10 18:17:53 +04:00
parent 7b8f2fe396
commit f984dbc5b6
27 changed files with 401 additions and 69 deletions
@@ -0,0 +1,9 @@
<root>
<item
name='com.intellij.openapi.editor.event.DocumentListener void beforeDocumentChange(com.intellij.openapi.editor.event.DocumentEvent) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='com.intellij.openapi.editor.event.DocumentListener void documentChanged(com.intellij.openapi.editor.event.DocumentEvent) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -16,8 +16,6 @@
package org.jetbrains.jet.plugin.completion; 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.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType; 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.PsiElement;
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import kotlin.Function1;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.*;
@@ -52,6 +51,13 @@ class CompletionSession {
private final JetCompletionResultSet jetResult; private final JetCompletionResultSet jetResult;
private final JetSimpleNameReference jetReference; private final JetSimpleNameReference jetReference;
private final Condition<DeclarationDescriptor> descriptorFilter = new Condition<DeclarationDescriptor>() {
@Override
public boolean value(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
};
public CompletionSession( public CompletionSession(
@NotNull CompletionParameters parameters, @NotNull CompletionParameters parameters,
@NotNull CompletionResultSet result, @NotNull CompletionResultSet result,
@@ -71,12 +77,7 @@ class CompletionSession {
this.jetResult = new JetCompletionResultSet( this.jetResult = new JetCompletionResultSet(
WeigherPackage.addJetSorting(result, parameters), WeigherPackage.addJetSorting(result, parameters),
resolveSession, resolveSession,
expressionBindingContext, new Condition<DeclarationDescriptor>() { expressionBindingContext, descriptorFilter);
@Override
public boolean value(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
});
} }
public void completeForReference() { public void completeForReference() {
@@ -134,10 +135,10 @@ class CompletionSession {
final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession()); final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession());
if (data != null) { if (data != null) {
addReferenceVariants(new Condition<DeclarationDescriptor>() { addReferenceVariants(new Function1<DeclarationDescriptor, LookupElement>() {
@Override @Override
public boolean value(DeclarationDescriptor descriptor) { public LookupElement invoke(DeclarationDescriptor descriptor) {
return data.accepts(descriptor); return data.toElement(descriptor);
} }
}); });
for (LookupElement element : data.getAdditionalElements()) { for (LookupElement element : data.getAdditionalElements()) {
@@ -241,17 +242,26 @@ class CompletionSession {
} }
private void addReferenceVariants(@NotNull final Condition<DeclarationDescriptor> filterCondition) { private void addReferenceVariants(@NotNull final Condition<DeclarationDescriptor> filterCondition) {
addReferenceVariants(new Function1<DeclarationDescriptor, LookupElement>() {
@Override
public LookupElement invoke(DeclarationDescriptor descriptor) {
return filterCondition.value(descriptor) ? DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor) : null;
}
});
}
private void addReferenceVariants(@NotNull Function1<DeclarationDescriptor, LookupElement> filter) {
Collection<DeclarationDescriptor> descriptors = TipsManager.getReferenceVariants( Collection<DeclarationDescriptor> descriptors = TipsManager.getReferenceVariants(
jetReference.getExpression(), getExpressionBindingContext()); jetReference.getExpression(), getExpressionBindingContext());
Collection<DeclarationDescriptor> filterDescriptors = Collections2.filter(descriptors, new Predicate<DeclarationDescriptor>() { for (DeclarationDescriptor descriptor : descriptors) {
@Override if (descriptor != null && descriptorFilter.value(descriptor)) {
public boolean apply(@Nullable DeclarationDescriptor descriptor) { LookupElement element = filter.invoke(descriptor);
return descriptor != null && filterCondition.value(descriptor); if (element != null) {
jetResult.addElement(element);
}
} }
}); }
jetResult.addAllElements(filterDescriptors);
} }
private boolean isVisibleDescriptor(DeclarationDescriptor descriptor) { private boolean isVisibleDescriptor(DeclarationDescriptor descriptor) {
@@ -15,27 +15,14 @@ import com.google.common.collect.SetMultimap
import java.util.* import java.util.*
import org.jetbrains.jet.lang.resolve.calls.autocasts.* import org.jetbrains.jet.lang.resolve.calls.autocasts.*
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns 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.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.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.psi.util.PsiTreeUtil
import com.intellij.openapi.editor.EditorModificationUtil
import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler
import com.intellij.psi.PsiDocumentManager 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.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.utils.*
import org.jetbrains.jet.di.InjectorForMacros import org.jetbrains.jet.di.InjectorForMacros
import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext 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.ContextDependency
import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode
import org.jetbrains.jet.lang.resolve.calls.CompositeExtension 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 import com.intellij.lang.ASTNode
trait SmartCompletionData{ trait SmartCompletionData{
fun accepts(descriptor: DeclarationDescriptor): Boolean fun toElement(descriptor: DeclarationDescriptor): LookupElement?
val additionalElements: Iterable<LookupElement> val additionalElements: Iterable<LookupElement>
} }
enum class Tail {
COMMA
}
data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?)
fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies): SmartCompletionData? { fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies): SmartCompletionData? {
val parent = expression.getParent() val parent = expression.getParent()
val expressionWithType: JetExpression; val expressionWithType: JetExpression;
@@ -67,7 +60,7 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession
val bindingContext = resolveSession.resolveToElement(expressionWithType) val bindingContext = resolveSession.resolveToElement(expressionWithType)
val allExpectedTypes = calcExpectedTypes(expressionWithType, bindingContext, resolveSession.getModuleDescriptor()) ?: return null 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 if (expectedTypes.isEmpty()) return null
val itemsToSkip = calcItemsToSkip(expressionWithType, resolveSession) val itemsToSkip = calcItemsToSkip(expressionWithType, resolveSession)
@@ -86,7 +79,7 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession
thisItems(expressionWithType, expectedTypes, bindingContext).toCollection(additionalElements) 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<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext) val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext)
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> { fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
@@ -116,28 +109,30 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession
} }
return object: SmartCompletionData { return object: SmartCompletionData {
override fun accepts(descriptor: DeclarationDescriptor) override fun toElement(descriptor: DeclarationDescriptor): LookupElement? {
= !itemsToSkip.contains(descriptor) if (itemsToSkip.contains(descriptor)) return null
&& typesOf(descriptor).any { val matchedExpectedTypes = expectedTypes.filter { expectedType -> typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } }
descriptorType -> expectedTypes.any { expectedType -> isSubtypeOf(descriptorType, expectedType) } if (matchedExpectedTypes.isEmpty()) return null
} val tail = mergeTails(matchedExpectedTypes.map { it.tail })
return decorateLookupElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor), tail)
}
override val additionalElements = additionalElements override val additionalElements = additionalElements
} }
} }
private fun calcExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection<JetType>? { private fun calcExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection<ExpectedTypeInfo>? {
val expectedTypes = calcArgumentExpectedTypes(expressionWithType, bindingContext, moduleDescriptor) val expectedTypes = calcArgumentExpectedTypes(expressionWithType, bindingContext, moduleDescriptor)
if (expectedTypes != null) { if (expectedTypes != null) {
return expectedTypes return expectedTypes
} }
else{ else{
val expectedType = bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType) ?: return null val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null
return listOf(expectedType) return listOf(ExpectedTypeInfo(expectedType, null))
} }
} }
private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection<JetType>? { private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection<ExpectedTypeInfo>? {
val argument = expressionWithType.getParent() as? JetValueArgument ?: return null 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) 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 val argumentList = argument.getParent() as JetValueArgumentList
@@ -150,7 +145,7 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding
val callOperationNode: ASTNode? val callOperationNode: ASTNode?
if (parent is JetQualifiedExpression && callExpression == parent.getSelectorExpression()) { if (parent is JetQualifiedExpression && callExpression == parent.getSelectorExpression()) {
val receiverExpression = parent.getReceiverExpression() 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) receiver = ExpressionReceiver(receiverExpression, expressionType)
callOperationNode = parent.getOperationTokenNode() callOperationNode = parent.getOperationTokenNode()
} }
@@ -160,14 +155,14 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding
} }
val call = CallMaker.makeCall(receiver, callOperationNode, callExpression) 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( val callResolutionContext = BasicCallResolutionContext.create(
DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"), DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"),
resolutionScope, resolutionScope,
call, call,
bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression) ?: TypeUtils.NO_EXPECTED_TYPE, bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE,
bindingContext.get(BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression) ?: DataFlowInfo.EMPTY, bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression] ?: DataFlowInfo.EMPTY,
ContextDependency.INDEPENDENT, ContextDependency.INDEPENDENT,
CheckValueArgumentsMode.ENABLED, CheckValueArgumentsMode.ENABLED,
CompositeExtension(listOf()), CompositeExtension(listOf()),
@@ -175,12 +170,13 @@ private fun calcArgumentExpectedTypes(expressionWithType: JetExpression, binding
val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!! val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!!
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext) val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedTypes = HashSet<JetType>() val expectedTypes = HashSet<ExpectedTypeInfo>()
for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) { for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) {
val parameters = candidate.getResultingDescriptor().getValueParameters() val parameters = candidate.getResultingDescriptor().getValueParameters()
if (parameters.size <= argumentIndex) continue if (parameters.size <= argumentIndex) continue
val parameterDescriptor = parameters[argumentIndex] 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 return expectedTypes
} }
@@ -191,7 +187,7 @@ private fun calcItemsToSkip(expression: JetExpression, resolveSession: ResolveSe
is JetProperty -> { is JetProperty -> {
//TODO: this can be filtered out by ordinary completion //TODO: this can be filtered out by ordinary completion
if (expression == parent.getInitializer()) { 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) { if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) {
val left = parent.getLeft() val left = parent.getLeft()
if (left is JetReferenceExpression) { 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() return listOf()
} }
private fun typeInstantiationItems(expectedType: JetType, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable<LookupElement> { private fun typeInstantiationItems(expectedType: ExpectedTypeInfo, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable<LookupElement> {
val typeConstructor: TypeConstructor = expectedType.getConstructor() val typeConstructor: TypeConstructor = expectedType.`type`.getConstructor()
val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor()
if (!(classifier is ClassDescriptor)) return listOf() if (!(classifier is ClassDescriptor)) return listOf()
@@ -218,7 +214,7 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Resolv
var lookupString = lookupElement.getLookupString() var lookupString = lookupElement.getLookupString()
val typeArgs = expectedType.getArguments() val typeArgs = expectedType.`type`.getArguments()
var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs) var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs)
val insertHandler: InsertHandler<LookupElement> val insertHandler: InsertHandler<LookupElement>
@@ -289,16 +285,18 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Resolv
} }
} }
val lookupElementWithTail = decorateLookupElement(lookupElementDecorated, expectedType.tail)
if (suppressAutoInsertion) { if (suppressAutoInsertion) {
return listOf(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementDecorated)) return listOf(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementWithTail))
} }
else{ else{
return listOf(lookupElementDecorated) return listOf(lookupElementWithTail)
} }
} }
private fun thisItems(context: JetExpression, expectedTypes: Collection<JetType>, bindingContext: BindingContext): Iterable<LookupElement> { private fun thisItems(context: JetExpression, expectedTypes: Collection<ExpectedTypeInfo>, bindingContext: BindingContext): Iterable<LookupElement> {
val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, context) val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context]
if (scope == null) return listOf() if (scope == null) return listOf()
val receivers: List<ReceiverParameterDescriptor> = scope.getImplicitReceiversHierarchy() val receivers: List<ReceiverParameterDescriptor> = scope.getImplicitReceiversHierarchy()
@@ -306,12 +304,15 @@ private fun thisItems(context: JetExpression, expectedTypes: Collection<JetType>
for (i in 0..receivers.size - 1) { for (i in 0..receivers.size - 1) {
val receiver = receivers[i] val receiver = receivers[i]
val thisType = receiver.getType() 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 //TODO: use this code when KT-4258 fixed
//val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue) //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue)
val qualifier = if (i == 0) null else thisQualifierName(receiver, bindingContext) ?: continue val qualifier = if (i == 0) null else thisQualifierName(receiver, bindingContext) ?: continue
val expressionText = if (qualifier == null) "this" else "this@" + qualifier 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 return result
@@ -344,7 +345,7 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres
if (dataFlowInfo != null) { if (dataFlowInfo != null) {
val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor? val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor?
if (receiver != null) { if (receiver != null) {
val receiverType = bindingContext.get(BindingContext.EXPRESSION_TYPE, receiver) val receiverType = bindingContext[BindingContext.EXPRESSION_TYPE, receiver]
if (receiverType != null) { if (receiverType != null) {
val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId() val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId()
dataFlowValueToVariable = {(value) -> dataFlowValueToVariable = {(value) ->
@@ -371,7 +372,7 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres
val nullabilityInfo: Map<DataFlowValue, Nullability> = dataFlowInfo.getCompleteNullabilityInfo() val nullabilityInfo: Map<DataFlowValue, Nullability> = dataFlowInfo.getCompleteNullabilityInfo()
val notNullVariables = nullabilityInfo val notNullVariables = nullabilityInfo
.filter_tmp { it.getValue() == Nullability.NOT_NULL } .filter { it.getValue() == Nullability.NOT_NULL }
.map { dataFlowValueToVariable(it.getKey()) } .map { dataFlowValueToVariable(it.getKey()) }
.filterNotNullTo(HashSet<VariableDescriptor>()) .filterNotNullTo(HashSet<VariableDescriptor>())
@@ -382,17 +383,17 @@ private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpres
} }
// adds java static members, enum members and members from class object // adds java static members, enum members and members from class object
private fun staticMembers(context: JetExpression, expectedType: JetType, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable<LookupElement> { private fun staticMembers(context: JetExpression, expectedType: ExpectedTypeInfo, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext): Iterable<LookupElement> {
val classDescriptor = TypeUtils.getClassDescriptor(expectedType) val classDescriptor = TypeUtils.getClassDescriptor(expectedType.`type`)
if (classDescriptor == null) return listOf() if (classDescriptor == null) return listOf()
if (classDescriptor.getName().isSpecial()) 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() if (scope == null) return listOf()
val descriptors = ArrayList<DeclarationDescriptor>() val descriptors = ArrayList<DeclarationDescriptor>()
val isSuitableCallable: (DeclarationDescriptor) -> Boolean = { 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) { if (classDescriptor is JavaClassDescriptor) {
@@ -447,7 +448,7 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve
} }
} }
return object: LookupElementDecorator<LookupElement>(lookupElement){ val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = lookupString override fun getLookupString() = lookupString
override fun renderElement(presentation: LookupElementPresentation) { override fun renderElement(presentation: LookupElementPresentation) {
@@ -459,7 +460,7 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve
if (descriptor is FunctionDescriptor) { if (descriptor is FunctionDescriptor) {
presentation.appendTailText(tailText, true) presentation.appendTailText(tailText, true)
} }
else{ else {
presentation.setTailText(tailText, true) presentation.setTailText(tailText, true)
} }
@@ -472,11 +473,32 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve
insertHandler.handleInsert(context, lookupElement) insertHandler.handleInsert(context, lookupElement)
} }
} }
return decorateLookupElement(lookupElementDecorated, expectedType.tail)
} }
return descriptors return descriptors
.filter { !(it is DeclarationDescriptorWithVisibility) || Visibilities.isVisible(it, scope.getContainingDeclaration()) } .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?>): 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>(lookupElement) {
override fun handleInsert(context: InsertionContext) {
WithCommaInsertHandler.handleInsert(context, lookupElement)
}
}
else -> throw RuntimeException("Unknown tail type")
}
} }
private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{ private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{
@@ -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<LookupElement> {
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)
}
}
}
@@ -0,0 +1,7 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,7 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,7 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,7 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss, <caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(s: String){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(s: String){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(o: Any){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(o: Any){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(o: Any, c: Char){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>)
}
//ELEMENT: sss
@@ -0,0 +1,8 @@
fun foo(o: Any, c: Char){ }
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss, <caret>)
}
//ELEMENT: sss
@@ -0,0 +1,9 @@
fun foo(s: String, i: Int){ }
fun bar() {
foo(<caret>)
}
fun getString(p: Int): String = ""
//ELEMENT: getString
@@ -0,0 +1,9 @@
fun foo(s: String, i: Int){ }
fun bar() {
foo(getString(<caret>), )
}
fun getString(p: Int): String = ""
//ELEMENT: getString
@@ -0,0 +1,9 @@
fun foo(c: C, i: Int){ }
class C {
fun bar() {
foo(<caret>)
}
}
//ELEMENT: this
@@ -0,0 +1,9 @@
fun foo(c: C, i: Int){ }
class C {
fun bar() {
foo(this, <caret>)
}
}
//ELEMENT: this
@@ -0,0 +1,9 @@
trait Foo
fun foo(f: Foo, i: Int){}
fun bar() {
foo(<caret>)
}
//ELEMENT: object
@@ -0,0 +1,9 @@
trait Foo
fun foo(f: Foo, i: Int){}
fun bar() {
foo(object: Foo {<caret>}, )
}
//ELEMENT: object
@@ -0,0 +1,12 @@
enum class Foo {
X
Y
}
fun foo(f: Foo, i: Int){}
fun bar(){
foo(<caret>)
}
// ELEMENT: Foo.X
@@ -0,0 +1,12 @@
enum class Foo {
X
Y
}
fun foo(f: Foo, i: Int){}
fun bar(){
foo(Foo.X, <caret>)
}
// ELEMENT: Foo.X
@@ -0,0 +1,8 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>a, b)
}
//ELEMENT: sss
//CHAR: \t
@@ -0,0 +1,8 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss, <caret>b)
}
//ELEMENT: sss
//CHAR: \t
@@ -0,0 +1,8 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(<caret>a,b)
}
//ELEMENT: sss
//CHAR: \t
@@ -0,0 +1,8 @@
fun foo(s: String, i: Int){ }
fun bar(sss: String) {
foo(sss, <caret>b)
}
//ELEMENT: sss
//CHAR: \t
@@ -66,6 +66,51 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest("idea/testData/completion/handlers/smart/ClassObjectMethod2.kt"); 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") @TestMetadata("Constructor.kt")
public void testConstructor() throws Exception { public void testConstructor() throws Exception {
doTest("idea/testData/completion/handlers/smart/Constructor.kt"); 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"); 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") @TestMetadata("TabReplaceExpression.kt")
public void testTabReplaceExpression() throws Exception { public void testTabReplaceExpression() throws Exception {
doTest("idea/testData/completion/handlers/smart/TabReplaceExpression.kt"); doTest("idea/testData/completion/handlers/smart/TabReplaceExpression.kt");