Smart completion: code improvements and refactorings after code review

This commit is contained in:
Valentin Kipyatkov
2014-04-15 12:22:58 +04:00
parent 6b1ecc3fb0
commit 06b1f1ec2f
3 changed files with 139 additions and 150 deletions
@@ -16,6 +16,8 @@
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;
@@ -43,7 +45,6 @@ import org.jetbrains.jet.plugin.project.ResolveSessionForBodies;
import org.jetbrains.jet.plugin.references.JetSimpleNameReference;
import java.util.Collection;
import java.util.Collections;
class CompletionSession {
@Nullable
@@ -52,13 +53,6 @@ class CompletionSession {
private final JetCompletionResultSet jetResult;
private final JetSimpleNameReference jetReference;
private final Condition<DeclarationDescriptor> descriptorFilter = new Condition<DeclarationDescriptor>() {
@Override
public boolean value(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
};
public CompletionSession(
@NotNull CompletionParameters parameters,
@NotNull CompletionResultSet result,
@@ -75,10 +69,17 @@ class CompletionSession {
inDescriptor = scope != null ? scope.getContainingDeclaration() : null;
Condition<DeclarationDescriptor> descriptorFilter = new Condition<DeclarationDescriptor>() {
@Override
public boolean value(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
};
this.jetResult = new JetCompletionResultSet(
WeigherPackage.addJetSorting(result, parameters),
resolveSession,
expressionBindingContext, descriptorFilter);
expressionBindingContext,
descriptorFilter);
}
public void completeForReference() {
@@ -134,20 +135,18 @@ class CompletionSession {
public void completeSmart() {
assert parameters.getCompletionType() == CompletionType.SMART;
final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession(), new Function1<DeclarationDescriptor, Boolean>() {
Collection<DeclarationDescriptor> descriptors = TipsManager.getReferenceVariants(
jetReference.getExpression(), getExpressionBindingContext());
Function1<DeclarationDescriptor, Boolean> visibilityFilter = new Function1<DeclarationDescriptor, Boolean>() {
@Override
public Boolean invoke(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
});
if (data != null) {
addReferenceVariants(new Function1<DeclarationDescriptor, Iterable<LookupElement>>(){
@Override
public Iterable<LookupElement> invoke(DeclarationDescriptor descriptor) {
return data.toElements(descriptor);
}
});
for (LookupElement element : data.getAdditionalElements()) {
};
Collection<LookupElement> elements = CompletionPackage.buildSmartCompletionData(
jetReference.getExpression(), getResolveSession(), descriptors, visibilityFilter);
if (elements != null) {
for (LookupElement element : elements) {
jetResult.addElement(element);
}
}
@@ -244,33 +243,21 @@ class CompletionSession {
}
private boolean shouldRunExtensionsCompletion() {
return !(parameters.getInvocationCount() <= 1 && jetResult.getResult().getPrefixMatcher().getPrefix().length() < 3);
return parameters.getInvocationCount() > 1 || jetResult.getResult().getPrefixMatcher().getPrefix().length() >= 3;
}
private void addReferenceVariants(@NotNull final Condition<DeclarationDescriptor> filterCondition) {
addReferenceVariants(new Function1<DeclarationDescriptor, Iterable<LookupElement>>(){
@Override
public Iterable<LookupElement> invoke(DeclarationDescriptor descriptor) {
return filterCondition.value(descriptor)
? Collections.singletonList(
DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor))
: Collections.<LookupElement>emptyList();
}
});
}
private void addReferenceVariants(@NotNull Function1<DeclarationDescriptor, Iterable<LookupElement>> filter) {
Collection<DeclarationDescriptor> descriptors = TipsManager.getReferenceVariants(
jetReference.getExpression(), getExpressionBindingContext());
for (DeclarationDescriptor descriptor : descriptors) {
if (descriptor != null && descriptorFilter.value(descriptor)) {
Iterable<LookupElement> elements = filter.invoke(descriptor);
for (LookupElement element : elements) {
jetResult.addElement(element);
}
Collection<DeclarationDescriptor> filterDescriptors = Collections2.filter(descriptors, new Predicate<DeclarationDescriptor>() {
@Override
public boolean apply(@Nullable DeclarationDescriptor descriptor) {
return descriptor != null && filterCondition.value(descriptor);
}
}
});
jetResult.addAllElements(filterDescriptors);
}
private boolean isVisibleDescriptor(DeclarationDescriptor descriptor) {
@@ -34,11 +34,6 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import com.intellij.lang.ASTNode
import org.jetbrains.jet.lang.resolve.scopes.JetScope
trait SmartCompletionData{
fun toElements(descriptor: DeclarationDescriptor): Iterable<LookupElement>
val additionalElements: Iterable<LookupElement>
}
enum class Tail {
COMMA
PARENTHESIS
@@ -48,7 +43,8 @@ data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?)
fun buildSmartCompletionData(expression: JetSimpleNameExpression,
resolveSession: ResolveSessionForBodies,
visibilityFilter: (DeclarationDescriptor) -> Boolean): SmartCompletionData? {
referenceVariants: Iterable<DeclarationDescriptor>,
visibilityFilter: (DeclarationDescriptor) -> Boolean): Collection<LookupElement>? {
val parent = expression.getParent()
val expressionWithType: JetExpression;
val receiver: JetExpression?
@@ -67,104 +63,72 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression,
val expectedTypes = allExpectedTypes.filter { !it.`type`.isError() }
if (expectedTypes.isEmpty()) return null
val result = ArrayList<LookupElement>()
val typesOf: (DeclarationDescriptor) -> Iterable<JetType> = dataFlowToDescriptorTypes(expressionWithType, receiver, bindingContext)
val itemsToSkip = calcItemsToSkip(expressionWithType, resolveSession)
val additionalElements = ArrayList<LookupElement>()
for (descriptor in referenceVariants) {
if (itemsToSkip.contains(descriptor)) continue
run {
val matchedExpectedTypes = expectedTypes.filter { expectedType ->
typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) }
}
if (matchedExpectedTypes.isNotEmpty()) {
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
result.add(addTailToLookupElement(lookupElement, matchedExpectedTypes))
}
}
val functionExpectedTypes = expectedTypes.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it.`type`) }
if (functionExpectedTypes.isNotEmpty()) {
fun functionReferenceLookupElement(descriptor: FunctionDescriptor): LookupElement? {
val functionType = functionType(descriptor)
if (functionType == null) return null
val matchedExpectedTypes = functionExpectedTypes.filter { isSubtypeOf(functionType, it.`type`) }
if (matchedExpectedTypes.isEmpty()) return null
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName())
val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = text
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText(text)
presentation.setTypeText("")
}
override fun handleInsert(context: InsertionContext) {
}
}
return addTailToLookupElement(lookupElementDecorated, matchedExpectedTypes)
}
if (descriptor is SimpleFunctionDescriptor) {
functionReferenceLookupElement(descriptor)?.let { result.add(it) }
}
else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) {
val constructors = descriptor.getConstructors().filter(visibilityFilter)
if (constructors.size == 1) { //TODO: this code is to be changed if overloads to start work after ::
functionReferenceLookupElement(constructors.single())?.let { result.add(it) }
}
}
}
}
if (receiver == null) {
additionalElements.addTypeInstantiationItems(expectedTypes, resolveSession, bindingContext)
result.addTypeInstantiationItems(expectedTypes, resolveSession, bindingContext)
additionalElements.addStaticMembers(expressionWithType, expectedTypes, resolveSession, bindingContext)
result.addStaticMembers(expressionWithType, expectedTypes, resolveSession, bindingContext)
additionalElements.addThisItems(expressionWithType, expectedTypes, bindingContext)
result.addThisItems(expressionWithType, expectedTypes, bindingContext)
}
val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expressionWithType]
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext)
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
if (descriptor is CallableDescriptor) {
var returnType = descriptor.getReturnType()
if (returnType != null && KotlinBuiltIns.getInstance().isNothing(returnType!!)) { //TODO: maybe we should include them on the second press?
return listOf()
}
if (descriptor is VariableDescriptor) {
if (notNullVariables.contains(descriptor) && returnType != null) {
returnType = TypeUtils.makeNotNullable(returnType!!)
}
val autoCastTypes = variableToTypes[descriptor]
if (autoCastTypes != null && !autoCastTypes.isEmpty()) {
return autoCastTypes + returnType.toList()
}
}
return returnType.toList()
}
else if (descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY) {
return listOf(descriptor.getDefaultType())
}
else {
return listOf()
}
}
return object: SmartCompletionData {
override fun toElements(descriptor: DeclarationDescriptor): Iterable<LookupElement> {
if (itemsToSkip.contains(descriptor)) return listOf()
val result = ArrayList<LookupElement>()
run {
val matchedExpectedTypes = expectedTypes.filter { expectedType -> typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } }
if (matchedExpectedTypes.isNotEmpty()) {
val tail = mergeTails(matchedExpectedTypes.map { it.tail })
result.add(addTailToLookupElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor), tail))
}
}
val functionExpectedTypes = expectedTypes.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it.`type`) }
if (functionExpectedTypes.isNotEmpty()) {
fun functionReferenceLookupElement(descriptor: FunctionDescriptor): LookupElement? {
val functionType = functionType(descriptor)
if (functionType == null) return null
val matchedExpectedTypes = functionExpectedTypes.filter { isSubtypeOf(functionType, it.`type`) }
if (matchedExpectedTypes.isEmpty()) return null
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName())
val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = text
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText(text)
presentation.setTypeText("")
}
override fun handleInsert(context: InsertionContext) {
}
}
val tail = mergeTails(matchedExpectedTypes.map { it.tail })
return addTailToLookupElement(lookupElementDecorated, tail)
}
if (descriptor is SimpleFunctionDescriptor) {
functionReferenceLookupElement(descriptor)?.let { result.add(it) }
}
else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) {
val constructors = descriptor.getConstructors().filter(visibilityFilter)
if (constructors.size == 1) { //TODO: this code is to be changed if overloads to start work after ::
functionReferenceLookupElement(constructors.single())?.let { result.add(it) }
}
}
}
return result
}
override val additionalElements = additionalElements
}
return result
}
private fun calcExpectedTypes(expressionWithType: JetExpression, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): Collection<ExpectedTypeInfo>? {
@@ -364,8 +328,7 @@ private fun MutableCollection<LookupElement>.addThisItems(context: JetExpression
val qualifier = if (i == 0) null else thisQualifierName(receiver, bindingContext) ?: continue
val expressionText = if (qualifier == null) "this" else "this@" + qualifier
val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType))
val tailType = mergeTails(matchedExpectedTypes.map { it.tail })
add(addTailToLookupElement(lookupElement, tailType))
add(addTailToLookupElement(lookupElement, matchedExpectedTypes))
}
}
}
@@ -388,6 +351,40 @@ private fun thisQualifierName(receiver: ReceiverParameterDescriptor, bindingCont
?.getReferencedName()
}
private fun dataFlowToDescriptorTypes(expression: JetExpression, receiver: JetExpression?, bindingContext: BindingContext): (DeclarationDescriptor) -> Iterable<JetType> {
val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression]
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>)
= processDataFlowInfo(dataFlowInfo, receiver, bindingContext)
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
if (descriptor is CallableDescriptor) {
var returnType = descriptor.getReturnType()
if (returnType != null && KotlinBuiltIns.getInstance().isNothing(returnType!!)) { //TODO: maybe we should include them on the second press?
return listOf()
}
if (descriptor is VariableDescriptor) {
if (notNullVariables.contains(descriptor) && returnType != null) {
returnType = TypeUtils.makeNotNullable(returnType!!)
}
val autoCastTypes = variableToTypes[descriptor]
if (autoCastTypes != null && !autoCastTypes.isEmpty()) {
return autoCastTypes + returnType.toList()
}
}
return returnType.toList()
}
else if (descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY) {
return listOf(descriptor.getDefaultType())
}
else {
return listOf()
}
}
return ::typesOf
}
private data class ProcessDataFlowInfoResult(
val variableToTypes: Map<VariableDescriptor, Collection<JetType>> = Collections.emptyMap(),
val notNullVariables: Set<VariableDescriptor> = Collections.emptySet()
@@ -550,8 +547,7 @@ private fun MutableCollection<LookupElement>.addStaticMembers(classDescriptor: C
}
}
val tail = mergeTails(descriptorExpectedTypes.map { it.tail })
add(addTailToLookupElement(lookupElementDecorated, tail))
add(addTailToLookupElement(lookupElementDecorated, descriptorExpectedTypes))
}
}
@@ -578,6 +574,9 @@ private fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): L
}
}
private fun addTailToLookupElement(lookupElement: LookupElement, expectedTypes: Collection<ExpectedTypeInfo>): LookupElement
= addTailToLookupElement(lookupElement, mergeTails(expectedTypes.map { it.tail }))
private fun functionType(function: FunctionDescriptor): JetType? {
return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(),
null,
@@ -1,9 +1,8 @@
package org.jetbrains.jet.plugin.completion
package org.jetbrains.jet.plugin.completion.handlers
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.codeInsight.lookup.LookupElement
import com.intellij.openapi.editor.event.*
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
@@ -29,18 +28,22 @@ class WithTailInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : Inser
}
document.addDocumentListener(documentListener)
item.handleInsert(context)
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(document)
document.removeDocumentListener(documentListener)
try{
item.handleInsert(context)
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(document)
}
finally {
document.removeDocumentListener(documentListener)
}
val moveCaret = caretModel.getOffset() == maxChangeOffset
if (maxChangeOffset < document.getTextLength() && document.getText(TextRange(maxChangeOffset, maxChangeOffset + 1))[0] == tailChar) {
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getText(TextRange(offset, offset + 1))[0] == c
if (isCharAt(maxChangeOffset, tailChar)) {
document.deleteString(maxChangeOffset, maxChangeOffset + 1)
if (spaceAfter && maxChangeOffset < document.getTextLength() && document.getText(TextRange(maxChangeOffset, maxChangeOffset + 1)) == " ") {
if (spaceAfter && isCharAt(maxChangeOffset, ' ')) {
document.deleteString(maxChangeOffset, maxChangeOffset + 1)
}
}