From ad791d0480b37c0a19e84f9434df7c178777fad1 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 16 Apr 2014 20:00:38 +0400 Subject: [PATCH] Smart completion: some code improvements --- .../codeInsight/lookup/annotations.xml | 3 + .../jet/plugin/completion/SmartCompletion.kt | 206 +++++++++--------- 2 files changed, 104 insertions(+), 105 deletions(-) diff --git a/annotations/com/intellij/codeInsight/lookup/annotations.xml b/annotations/com/intellij/codeInsight/lookup/annotations.xml index 9277180b901..b60cd8c9298 100644 --- a/annotations/com/intellij/codeInsight/lookup/annotations.xml +++ b/annotations/com/intellij/codeInsight/lookup/annotations.xml @@ -83,6 +83,9 @@ name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'> + + + diff --git a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt index 33c9dbf854c..d5f8613b554 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt @@ -41,15 +41,9 @@ class SmartCompletion(val expression: JetSimpleNameExpression, val resolveSession: ResolveSessionForBodies, val visibilityFilter: (DeclarationDescriptor) -> Boolean) { - private val bindingContext: BindingContext - private val moduleDescriptor: ModuleDescriptor - private val project: Project - - { - this.bindingContext = resolveSession.resolveToElement(expression) - this.moduleDescriptor = resolveSession.getModuleDescriptor() - this.project = expression.getProject() - } + private val bindingContext = resolveSession.resolveToElement(expression) + private val moduleDescriptor = resolveSession.getModuleDescriptor() + private val project = expression.getProject() private enum class Tail { COMMA @@ -77,7 +71,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, val result = ArrayList() - val typesOf: (DeclarationDescriptor) -> Iterable = dataFlowToDescriptorTypes(expressionWithType, receiver) + val typesOf: (DeclarationDescriptor) -> Iterable = typesWithAutoCasts(expressionWithType, receiver) val itemsToSkip = calcItemsToSkip(expressionWithType) @@ -87,7 +81,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, if (itemsToSkip.contains(descriptor)) continue val matchedExpectedTypes = expectedTypes.filter { expectedType -> - typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } + typesOf(descriptor).any { it.isSubtypeOf(expectedType.`type`) } } if (matchedExpectedTypes.isNotEmpty()) { val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) @@ -202,12 +196,12 @@ class SmartCompletion(val expression: JetSimpleNameExpression, val functionType = functionType(descriptor) if (functionType == null) return null - val matchedExpectedTypes = functionExpectedTypes.filter { isSubtypeOf(functionType, it.`type`) } + val matchedExpectedTypes = functionExpectedTypes.filter { functionType.isSubtypeOf(it.`type`) } if (matchedExpectedTypes.isEmpty()) return null - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) + var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName()) - val lookupElementDecorated = object: LookupElementDecorator(lookupElement) { + lookupElement = object: LookupElementDecorator(lookupElement) { override fun getLookupString() = text override fun renderElement(presentation: LookupElementPresentation) { @@ -220,7 +214,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } } - return addTailToLookupElement(lookupElementDecorated, matchedExpectedTypes) + return addTailToLookupElement(lookupElement, matchedExpectedTypes) } if (descriptor is SimpleFunctionDescriptor) { @@ -277,7 +271,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true) } - lookupElement = suppressAutoInsertion(lookupElement) + lookupElement = lookupElement.suppressAutoInsertion() } else { itemText += "()" @@ -297,26 +291,26 @@ class SmartCompletion(val expression: JetSimpleNameExpression, val endOffset = startOffset + text.length editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) - //TODO: autopopup parameter info and other functionality from JetFunctionInsertHandler + //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler shortenReferences(context, startOffset, endOffset) } } - val lookupElementDecorated = object: LookupElementDecorator(lookupElement) { + lookupElement = object: LookupElementDecorator(lookupElement) { override fun getLookupString() = lookupString override fun renderElement(presentation: LookupElementPresentation) { - lookupElement.renderElement(presentation) + getDelegate().renderElement(presentation) presentation.setItemText(itemText) } override fun handleInsert(context: InsertionContext) { - insertHandler.handleInsert(context, lookupElement) + insertHandler.handleInsert(context, getDelegate()) } } - add(addTailToLookupElement(lookupElementDecorated, tail)) + add(addTailToLookupElement(lookupElement, tail)) } private fun MutableCollection.addThisItems(context: JetExpression, expectedTypes: Collection) { @@ -327,7 +321,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, for (i in 0..receivers.size - 1) { val receiver = receivers[i] val thisType = receiver.getType() - val matchedExpectedTypes = expectedTypes.filter { isSubtypeOf(thisType, it.`type`) } + val matchedExpectedTypes = expectedTypes.filter { thisType.isSubtypeOf(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) @@ -357,24 +351,13 @@ class SmartCompletion(val expression: JetSimpleNameExpression, ?.getReferencedName() } - private fun MutableCollection.addLambdaItems(functionExpectedTypes: Collection) { val distinctTypes = functionExpectedTypes.map { it.`type` }.toSet() - fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean): LookupElement { - val lookupElement = LookupElementBuilder.create(lookupString).withInsertHandler {(context, lookupElement) -> - val offset = context.getEditor().getCaretModel().getOffset() - val startOffset = offset - lookupString.length - context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string - context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret) - context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length) - - if (shortenRefs) { - shortenReferences(context, startOffset, startOffset + textBeforeCaret.length + textAfterCaret.length) - } - } - return suppressAutoInsertion(lookupElement) - } + fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean) + = LookupElementBuilder.create(lookupString) + .withInsertHandler(ArtificialElementInsertHandler(textBeforeCaret, textAfterCaret, shortenRefs)) + .suppressAutoInsertion() val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size } @@ -416,7 +399,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } } - private fun dataFlowToDescriptorTypes(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable { + private fun typesWithAutoCasts(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable { val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression] val (variableToTypes: Map>, notNullVariables: Set) = processDataFlowInfo(dataFlowInfo, receiver) @@ -532,7 +515,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } fun isSuitableCallable(descriptor: DeclarationDescriptor) - = descriptor is CallableDescriptor && descriptor.getReturnType()?.let { isSubtypeOf(it, expectedType.`type`) } ?: false + = descriptor is CallableDescriptor && descriptor.getReturnType()?.let { it.isSubtypeOf(expectedType.`type`) } ?: false if (classDescriptor is JavaClassDescriptor) { val pseudoPackage = classDescriptor.getCorrespondingPackageFragment() @@ -553,7 +536,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } for ((descriptor, descriptorExpectedTypes) in memberDescriptors) { - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) + var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) val qualifierPresentation = classDescriptor.getName().asString() val lookupString = qualifierPresentation + "." + lookupElement.getLookupString() val qualifierText = DescriptorUtils.getFqName(classDescriptor).asString() //TODO: escape keywords @@ -566,27 +549,11 @@ class SmartCompletion(val expression: JetSimpleNameExpression, caretPosition = null } - val insertHandler = InsertHandler {(context, item) -> - val editor = context.getEditor() - val startOffset = context.getStartOffset() - var text = qualifierText + "." + descriptor.getName().asString() //TODO: escape - if (descriptor is FunctionDescriptor) { - text += "()" - //TODO: autopopup parameter info and other functionality from JetFunctionInsertHandler - } - - editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) - val endOffset = startOffset + text.length - editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) - - shortenReferences(context, startOffset, startOffset + qualifierText.length) - } - - val lookupElementDecorated = object: LookupElementDecorator(lookupElement) { + lookupElement = object: LookupElementDecorator(lookupElement) { override fun getLookupString() = lookupString override fun renderElement(presentation: LookupElementPresentation) { - lookupElement.renderElement(presentation) + getDelegate().renderElement(presentation) presentation.setItemText(qualifierPresentation + "." + presentation.getItemText()) @@ -604,62 +571,91 @@ class SmartCompletion(val expression: JetSimpleNameExpression, } override fun handleInsert(context: InsertionContext) { - insertHandler.handleInsert(context, lookupElement) + val editor = context.getEditor() + val startOffset = context.getStartOffset() + var text = qualifierText + "." + descriptor.getName().asString() //TODO: escape + if (descriptor is FunctionDescriptor) { + text += "()" + //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler + } + + editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) + val endOffset = startOffset + text.length + editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) + + shortenReferences(context, startOffset, startOffset + qualifierText.length) } } - add(addTailToLookupElement(lookupElementDecorated, descriptorExpectedTypes)) + add(addTailToLookupElement(lookupElement, descriptorExpectedTypes)) } } - private fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) { - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - val file = context.getFile() as JetFile - val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass()) - if (element != null) { - ShortenReferences.process(element) - } - } + class object { + private class ArtificialElementInsertHandler( + val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val offset = context.getEditor().getCaretModel().getOffset() + val startOffset = offset - item.getLookupString().length + context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string + context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret) + context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length) - 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 addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { - return when (tail) { - null -> lookupElement - - Tail.COMMA -> object: LookupElementDecorator(lookupElement) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement) - } - } - - Tail.PARENTHESIS -> object: LookupElementDecorator(lookupElement) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(')', false).handleInsert(context, lookupElement) + if (shortenRefs) { + shortenReferences(context, startOffset, startOffset + textBeforeCaret.length + textAfterCaret.length) } } } + + private fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) { + PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); + val file = context.getFile() as JetFile + val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass()) + if (element != null) { + ShortenReferences.process(element) + } + } + + 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 addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { + return when (tail) { + null -> lookupElement + + Tail.COMMA -> object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement) + } + } + + Tail.PARENTHESIS -> object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler(')', false).handleInsert(context, lookupElement) + } + } + } + } + + private fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection): LookupElement + = addTailToLookupElement(lookupElement, mergeTails(matchedExpectedTypes.map { it.tail })) + + private fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this) + + private fun functionType(function: FunctionDescriptor): JetType? { + return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), + null, + function.getValueParameters().map { it.getType() }, + function.getReturnType() ?: return null, + function.getReceiverParameter() != null) + } + + private fun JetType.isSubtypeOf(expectedType: JetType) = !isError() && JetTypeChecker.INSTANCE.isSubtypeOf(this, expectedType) + + private fun T?.toList(): List = if (this != null) listOf(this) else listOf() + + private fun String?.isNullOrEmpty() = this == null || this.isEmpty() } - - private fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection): LookupElement - = addTailToLookupElement(lookupElement, mergeTails(matchedExpectedTypes.map { it.tail })) - - private fun suppressAutoInsertion(lookupElement: LookupElement) = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElement) - - private fun functionType(function: FunctionDescriptor): JetType? { - return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), - null, - function.getValueParameters().map { it.getType() }, - function.getReturnType() ?: return null, - function.getReceiverParameter() != null) - } - - private fun isSubtypeOf(t: JetType, expectedType: JetType) = !t.isError() && JetTypeChecker.INSTANCE.isSubtypeOf(t, expectedType) - - private fun T?.toList(): List = if (this != null) listOf(this) else listOf() - - private fun String?.isNullOrEmpty() = this == null || this.isEmpty() } \ No newline at end of file