Additional item inserting lambda provided by completion when all parameters before the last are optional

This commit is contained in:
Valentin Kipyatkov
2015-08-27 22:38:03 +03:00
parent dfe4f8e34f
commit 2de5229098
15 changed files with 169 additions and 64 deletions
@@ -191,7 +191,7 @@ class BasicLookupElementFactory(
}
if (descriptor is CallableDescriptor) {
appendContainerAndReceiverInformation(descriptor) { tail, grayed -> element = element.appendTailText(tail, grayed) }
appendContainerAndReceiverInformation(descriptor) { element = element.appendTailText(it, true) }
}
if (descriptor is PropertyDescriptor) {
@@ -215,13 +215,13 @@ class BasicLookupElementFactory(
return element.withIconFromLookupObject()
}
public fun appendContainerAndReceiverInformation(descriptor: CallableDescriptor, appendTailText: (String, Boolean) -> Unit) {
public fun appendContainerAndReceiverInformation(descriptor: CallableDescriptor, appendTailText: (String) -> Unit) {
val extensionReceiver = descriptor.original.extensionReceiverParameter
when {
descriptor is SyntheticJavaPropertyDescriptor -> {
var from = descriptor.getMethod.getName().asString() + "()"
descriptor.setMethod?.let { from += "/" + it.getName().asString() + "()" }
appendTailText(" (from $from)", true)
appendTailText(" (from $from)")
}
// no need to show them as extensions
@@ -230,7 +230,7 @@ class BasicLookupElementFactory(
extensionReceiver != null -> {
val receiverPresentation = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(extensionReceiver.type)
appendTailText(" for $receiverPresentation", true)
appendTailText(" for $receiverPresentation")
val container = descriptor.getContainingDeclaration()
val containerPresentation = if (container is ClassDescriptor)
@@ -240,7 +240,7 @@ class BasicLookupElementFactory(
else
null
if (containerPresentation != null) {
appendTailText(" in $containerPresentation", true)
appendTailText(" in $containerPresentation")
}
}
@@ -249,7 +249,7 @@ class BasicLookupElementFactory(
if (container is PackageFragmentDescriptor) {
// we show container only for global functions and properties
//TODO: it would be probably better to show it also for static declarations which are not from the current class (imported)
appendTailText(" (${container.fqName})", true)
appendTailText(" (${container.fqName})")
}
}
}
@@ -28,15 +28,17 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.completion.handlers.GenerateLambdaInfo
import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.buildLambdaPresentation
import org.jetbrains.kotlin.idea.completion.handlers.lambdaPresentation
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
@@ -92,44 +94,55 @@ class LookupElementFactory(
}
private fun MutableCollection<LookupElement>.addSpecialFunctionCallElements(descriptor: FunctionDescriptor, useReceiverTypes: Boolean) {
val singleParameter = descriptor.valueParameters.singleOrNull() ?: return
val parameterType = singleParameter.type
// check that all parameters except for the last one are optional
val lastParameter = descriptor.valueParameters.lastOrNull() ?: return
if (!descriptor.valueParameters.all { it == lastParameter || it.hasDefaultValue() }) return
val parameterType = lastParameter.type
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) {
val isSingleParameter = descriptor.valueParameters.size() == 1
val functionParameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size()
if (functionParameterCount > 1) {
add(createFunctionCallElementWithExplicitLambdaParameters(descriptor, parameterType, useReceiverTypes))
// we don't need special item inserting lambda for single functional parameter that does not need multiple arguments because the default item will be special in this case
if (!isSingleParameter || functionParameterCount > 1) {
add(createFunctionCallElementWithLambda(descriptor, parameterType, functionParameterCount > 1, useReceiverTypes))
}
//TODO: also ::function? at least for local functions
//TODO: order for them
val fuzzyParameterType = FuzzyType(parameterType, descriptor.typeParameters)
for (variable in functionTypeContextVariables) {
val substitutor = variable.fuzzyReturnType()?.checkIsSubtypeOf(fuzzyParameterType)
if (substitutor != null) {
val substitutedDescriptor = descriptor.substitute(substitutor) ?: continue
add(createFunctionCallElementWithArgument(substitutedDescriptor, variable.name.asString(), useReceiverTypes))
if (isSingleParameter) {
//TODO: also ::function? at least for local functions
//TODO: order for them
val fuzzyParameterType = FuzzyType(parameterType, descriptor.typeParameters)
for (variable in functionTypeContextVariables) {
val substitutor = variable.fuzzyReturnType()?.checkIsSubtypeOf(fuzzyParameterType)
if (substitutor != null) {
val substitutedDescriptor = descriptor.substitute(substitutor) ?: continue
add(createFunctionCallElementWithArgument(substitutedDescriptor, variable.name.asString(), useReceiverTypes))
}
}
}
}
}
private fun createFunctionCallElementWithExplicitLambdaParameters(descriptor: FunctionDescriptor, parameterType: JetType, useReceiverTypes: Boolean): LookupElement {
private fun createFunctionCallElementWithLambda(descriptor: FunctionDescriptor, parameterType: JetType, explicitLambdaParameters: Boolean, useReceiverTypes: Boolean): LookupElement {
var lookupElement = createLookupElement(descriptor, useReceiverTypes)
val needTypeArguments = (insertHandlerProvider.insertHandler(descriptor) as KotlinFunctionInsertHandler).inputTypeArguments
val lambdaInfo = GenerateLambdaInfo(parameterType, true)
val inputTypeArguments = (insertHandlerProvider.insertHandler(descriptor) as KotlinFunctionInsertHandler).inputTypeArguments
val lambdaInfo = GenerateLambdaInfo(parameterType, explicitLambdaParameters)
val lambdaPresentation = lambdaPresentation(if (explicitLambdaParameters) parameterType else null)
// render only the last parameter because all other should be optional and will be omitted
val parameterPresentation = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderValueParameters(listOf(descriptor.valueParameters.last()), descriptor.hasSynthesizedParameterNames())
lookupElement = object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
val tails = presentation.getTailFragments()
presentation.clearTail()
presentation.appendTailText(" " + buildLambdaPresentation(parameterType) + " ", false)
tails.forEach { presentation.appendTailText(it.text, true) }
presentation.appendTailText(" $lambdaPresentation ", false)
presentation.appendTailText(parameterPresentation, true)
basicFactory.appendContainerAndReceiverInformation(descriptor) { presentation.appendTailText(it, true) }
}
override fun handleInsert(context: InsertionContext) {
KotlinFunctionInsertHandler(needTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this)
KotlinFunctionInsertHandler(inputTypeArguments, inputValueArguments = false, lambdaInfo = lambdaInfo).handleInsert(context, this)
}
}
@@ -168,7 +181,7 @@ class LookupElementFactory(
presentation.clearTail()
presentation.appendTailText("($argumentText)", false)
basicFactory.appendContainerAndReceiverInformation(descriptor) { tail, grayed -> presentation.appendTailText(tail, grayed) }
basicFactory.appendContainerAndReceiverInformation(descriptor) { presentation.appendTailText(it, true) }
}
override fun handleInsert(context: InsertionContext) {
@@ -64,7 +64,8 @@ fun insertLambdaTemplate(context: InsertionContext, placeholderRange: TextRange,
}
}
fun buildLambdaPresentation(lambdaType: JetType): String {
fun lambdaPresentation(lambdaType: JetType?): String {
if (lambdaType == null) return "{...}"
val parameterTypes = functionParameterTypes(lambdaType)
val parametersPresentation = parameterTypes.map { IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(it) }.joinToString(", ")
return "{ $parametersPresentation -> ... }"
@@ -22,10 +22,10 @@ import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.fuzzyType
import org.jetbrains.kotlin.idea.completion.handlers.buildLambdaPresentation
import org.jetbrains.kotlin.idea.completion.handlers.insertLambdaTemplate
import org.jetbrains.kotlin.idea.completion.handlers.lambdaPresentation
import org.jetbrains.kotlin.idea.completion.suppressAutoInsertion
import java.util.ArrayList
import java.util.*
object LambdaItems {
public fun collect(functionExpectedInfos: Collection<ExpectedInfo>): Collection<LookupElement> {
@@ -47,7 +47,7 @@ object LambdaItems {
val singleSignatureLength = singleType?.let { KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(it).size() }
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
if (offerNoParametersLambda) {
val lookupElement = LookupElementBuilder.create("{...}")
val lookupElement = LookupElementBuilder.create(lambdaPresentation(null))
.withInsertHandler(ArtificialElementInsertHandler("{ ", " }", false))
.suppressAutoInsertion()
.assignSmartCompletionPriority(SmartCompletionItemPriority.LAMBDA_NO_PARAMS)
@@ -57,7 +57,7 @@ object LambdaItems {
if (singleSignatureLength != 0) {
for (functionType in distinctTypes) {
val lookupString = buildLambdaPresentation(functionType)
val lookupString = lambdaPresentation(functionType)
val lookupElement = LookupElementBuilder.create(lookupString)
.withInsertHandler({ context, lookupElement ->
val offset = context.getStartOffset()
@@ -0,0 +1,25 @@
fun xfoo1(option1: String = "", option2: Int = 1, p: () -> Unit){}
fun xfoo2(option1: String = "", option2: Int = 1, p: (Int) -> Boolean){}
fun xfoo3(option1: String = "", option2: Int = 1, p: (String, Char) -> Unit){}
fun xfooBad1(option: String = "", notOption: Int, p: (String, Char) -> Unit){}
fun xfooBad2(option1: String = "", option2: Int = 1, p: (String, Char) -> Unit, option3: Int = 0){}
fun test(param: () -> Unit) {
xfoo<caret>
}
// EXIST: { itemText: "xfoo1", tailText: "(option1: String = ..., option2: Int = ..., p: () -> Unit) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfoo1", tailText: " {...} (p: () -> Unit) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfoo2", tailText: "(option1: String = ..., option2: Int = ..., p: (Int) -> Boolean) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfoo2", tailText: " {...} (p: (Int) -> Boolean) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfoo3", tailText: "(option1: String = ..., option2: Int = ..., p: (String, Char) -> Unit) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfoo3", tailText: " { String, Char -> ... } (p: (String, Char) -> Unit) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfooBad1", tailText: "(option: String = ..., notOption: Int, p: (String, Char) -> Unit) (<root>)", typeText:"Unit" }
// EXIST: { itemText: "xfooBad2", tailText: "(option1: String = ..., option2: Int = ..., p: (String, Char) -> Unit, option3: Int = ...) (<root>)", typeText:"Unit" }
// NOTHING_ELSE
@@ -0,0 +1,9 @@
fun xfoo(option1: String = "", option2: Int = 1, p: () -> Unit){}
fun test(param: () -> Unit) {
xfoo<caret>
}
// ELEMENT: xfoo
// TAIL_TEXT: " {...} (p: () -> Unit) (<root>)"
@@ -0,0 +1,9 @@
fun xfoo(option1: String = "", option2: Int = 1, p: () -> Unit){}
fun test(param: () -> Unit) {
xfoo { <caret> }
}
// ELEMENT: xfoo
// TAIL_TEXT: " {...} (p: () -> Unit) (<root>)"
@@ -0,0 +1,9 @@
fun xfoo(option1: String = "", option2: Int = 1, p: (String, Int) -> Unit){}
fun test(param: () -> Unit) {
xfoo<caret>
}
// ELEMENT: xfoo
// TAIL_TEXT: " { String, Int -> ... } (p: (String, Int) -> Unit) (<root>)"
@@ -0,0 +1,9 @@
fun xfoo(option1: String = "", option2: Int = 1, p: (String, Int) -> Unit){}
fun test(param: () -> Unit) {
xfoo { s, i -> <caret> }
}
// ELEMENT: xfoo
// TAIL_TEXT: " { String, Int -> ... } (p: (String, Int) -> Unit) (<root>)"
@@ -1364,6 +1364,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/HigherOrderFunction2.kt");
doTest(fileName);
}
@TestMetadata("OptionalParameters.kt")
public void testOptionalParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/OptionalParameters.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/lambdaSignature")
@@ -1364,6 +1364,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/HigherOrderFunction2.kt");
doTest(fileName);
}
@TestMetadata("OptionalParameters.kt")
public void testOptionalParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/highOrderFunctions/OptionalParameters.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/lambdaSignature")
@@ -274,6 +274,18 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("OptionalParameters1.kt")
public void testOptionalParameters1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/highOrderFunctions/OptionalParameters1.kt");
doTest(fileName);
}
@TestMetadata("OptionalParameters2.kt")
public void testOptionalParameters2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/highOrderFunctions/OptionalParameters2.kt");
doTest(fileName);
}
@TestMetadata("ParameterTypeIsDerivedFromFunction.kt")
public void testParameterTypeIsDerivedFromFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/highOrderFunctions/ParameterTypeIsDerivedFromFunction.kt");