From 6c23d3a220a6b7806524506138a132989622d0fa Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 4 Jan 2018 15:51:01 +0100 Subject: [PATCH] Show inlay hints for implicit parameters and receivers of lambdas #KT-20533 Fixed --- .../KotlinInlayParameterHintsProvider.kt | 17 +++++- .../idea/parameterInfo/LambdaImpicitHints.kt | 61 +++++++++++++++++++ .../kotlin/idea/parameterInfo/TypeHints.kt | 19 ++---- .../parameterInfo/LambdaImplicitHintsTest.kt | 45 ++++++++++++++ 4 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/parameterInfo/LambdaImpicitHints.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/parameterInfo/LambdaImplicitHintsTest.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt index b13ea55fd8a..820c4efcbdc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt @@ -84,7 +84,8 @@ enum class HintType(desc: String, enabled: Boolean) { }, LAMBDA_RETURN_EXPRESSION("Show lambda return expression hints", true) { - override fun isApplicable(elem: PsiElement) = elem is KtExpression + override fun isApplicable(elem: PsiElement) = + elem is KtExpression && elem !is KtLambdaExpression && elem !is KtFunctionLiteral override fun provideHints(elem: PsiElement): List { if (elem is KtExpression) { @@ -92,8 +93,18 @@ enum class HintType(desc: String, enabled: Boolean) { } return emptyList() } - } - ; + }, + + LAMBDA_IMPLICIT_PARAMETER_RECEIVER("Show hints for implicit receivers and parameters of lambdas", true) { + override fun isApplicable(elem: PsiElement) = elem is KtLambdaExpression + + override fun provideHints(elem: PsiElement): List { + (elem as? KtLambdaExpression)?.let { + return provideLambdaImplicitHints(elem) + } + return emptyList() + } + }; companion object { diff --git a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/LambdaImpicitHints.kt b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/LambdaImpicitHints.kt new file mode 100644 index 00000000000..8e68329c6fc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/LambdaImpicitHints.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.parameterInfo + +import com.intellij.codeInsight.hints.InlayInfo +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiComment +import com.intellij.psi.TokenType +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.typeUtil.isUnit + +fun provideLambdaImplicitHints(lambda: KtLambdaExpression): List { + val lbrace = lambda.leftCurlyBrace + if (!lbrace.isFollowedByNewLine()) { + return emptyList() + } + val bindingContext = lambda.analyze(BodyResolveMode.PARTIAL) + val functionDescriptor = bindingContext[BindingContext.FUNCTION, lambda.functionLiteral] ?: return emptyList() + + val implicitReceiver = functionDescriptor.extensionReceiverParameter + if (implicitReceiver != null) { + val text = buildString { + append(TYPE_INFO_PREFIX) + append("this: ") + append(inlayHintsTypeRenderer.renderType(implicitReceiver.type)) + } + return listOf(InlayInfo(text, lbrace.textRange.endOffset)) + } + + val singleParameter = functionDescriptor.valueParameters.singleOrNull() + if (singleParameter != null && bindingContext[BindingContext.AUTO_CREATED_IT, singleParameter] == true) { + val type = singleParameter.type + if (type.isUnit()) return emptyList() + val text = buildString { + append(TYPE_INFO_PREFIX) + append("it: ") + append(inlayHintsTypeRenderer.renderType(type)) + } + return listOf(InlayInfo(text, lbrace.textRange.endOffset)) + } + return emptyList() +} + +private fun ASTNode.isFollowedByNewLine(): Boolean { + for (sibling in siblings()) { + if (sibling.elementType != TokenType.WHITE_SPACE && sibling.psi !is PsiComment) { + return false + } + if (sibling.elementType == TokenType.WHITE_SPACE && sibling.textContains('\n')) { + return true + } + } + return false +} diff --git a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/TypeHints.kt b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/TypeHints.kt index 080e692599d..f40c4dd97ce 100644 --- a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/TypeHints.kt +++ b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/TypeHints.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.idea.parameterInfo @@ -35,7 +24,7 @@ import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes //hack to separate type presentation from param info presentation const val TYPE_INFO_PREFIX = "@TYPE@" -private val typeRenderer = DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.withOptions { +val inlayHintsTypeRenderer = DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.withOptions { textFormat = RenderingFormat.PLAIN renderUnabbreviatedType = false } @@ -76,7 +65,7 @@ fun provideTypeHint(element: KtCallableDeclaration, offset: Int): List + it.startsWith("") + }""" + ) + } + + fun testSimpleThis() { + check( + """ + val x = buildString { + append("foo") + }""" + ) + } + + fun testSingleLine() { + check( + """ + val x = listOf("").filter { it.startsWith("") } + """ + ) + } +}