From 8b3f8ce7506f3fbd977bbbbd72a13e9cf2b28ddb Mon Sep 17 00:00:00 2001 From: fitermay Date: Sat, 18 Mar 2017 22:21:59 -0400 Subject: [PATCH] Inlay param/type support for kotlin --- idea/src/META-INF/plugin.xml | 1 + .../KotlinInlayParameterHintsProvider.kt | 207 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index afbce5a6333..ccb6cc4f88c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -422,6 +422,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt new file mode 100644 index 00000000000..b713c20865f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/KotlinInlayParameterHintsProvider.kt @@ -0,0 +1,207 @@ +/* + * 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. + */ + +package org.jetbrains.kotlin.idea.parameterInfo + +import com.intellij.codeInsight.hints.HintInfo +import com.intellij.codeInsight.hints.InlayInfo +import com.intellij.codeInsight.hints.InlayParameterHintsProvider +import com.intellij.codeInsight.hints.Option +import com.intellij.lang.Language +import com.intellij.lang.java.JavaLanguage +import com.intellij.psi.PsiElement +import com.intellij.psi.codeStyle.CodeStyleSettingsManager +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings +import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.renderer.RenderingFormat +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +/** + * @author Yuli Fiterman + */ + +//hack to separate type presentation from param info presentation +private const val TYPE_INFO_PREFIX = "@TYPE@" +private val typeRenderer = DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.withOptions { + textFormat = RenderingFormat.PLAIN +} + + +private enum class HintType(desc: String, enabled: Boolean) { + + PROPERTY_HINT("Show property type hints", true) { + override fun provideHints(elem: PsiElement): List { + (elem as? KtProperty)?.let { + property -> + property.nameIdentifier?.let { + ident -> + return provideTypeHint(property, ident.endOffset) + } + } + return emptyList() + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is KtProperty && elem.getReturnTypeReference() == null + }, + FUNCTION_HINT("Show function type hints", true) { + override fun provideHints(elem: PsiElement): List { + (elem as? KtNamedFunction)?.let { + namedFunc -> + namedFunc.valueParameterList?.let { + paramList -> + return provideTypeHint(namedFunc, paramList.startOffset) + } + } + return emptyList() + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is KtNamedFunction && !(elem.hasBlockBody() || elem.hasDeclaredReturnType()) + }, + PARAMETER_TYPE_HINT("Show parameter type hints ", false) { + override fun provideHints(elem: PsiElement): List { + (elem as? KtParameter)?.let { + param -> + param.nameIdentifier?.let { + ident -> + return provideTypeHint(param, ident.endOffset) + } + } + return emptyList() + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is KtParameter && elem.typeReference == null + }, + PARAMETER_HINT("Show parameter hints", true) { + override fun provideHints(elem: PsiElement): List { + (elem as? KtCallExpression)?.let { + return provideParameterInfo(it) + } + return emptyList() + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is KtCallExpression + }; + + companion object { + + fun resolve(elem: PsiElement): HintType? = HintType.values().find { it.isApplicable(elem) } + fun resolveToEnabled(elem: PsiElement?): HintType? { + + val resolved = elem?.let { resolve(it) } ?: return null + return if (resolved.enabled) { + resolved + } + else { + null + } + } + + private fun provideParameterInfo(element: KtCallExpression): List { + val ctx = element.analyze(BodyResolveMode.PARTIAL) + element.getResolvedCall(ctx)?.let { resolvedCall -> + return resolvedCall.valueArguments.mapNotNull { (valueParam: ValueParameterDescriptor, resolvedArg) -> + resolvedArg.arguments.firstOrNull()?.let { arg -> + arg.getArgumentExpression()?.let { argExp -> + if (!arg.isNamed() && !valueParam.name.isSpecial) { + return@mapNotNull InlayInfo(valueParam.name.identifier, argExp.startOffset) + } + } + } + null + } + } + + return emptyList() + } + + private fun provideTypeHint(element: KtCallableDeclaration, offset: Int): List { + val type = SpecifyTypeExplicitlyIntention.getTypeForDeclaration(element) + return if (!type.isError) { + val settings = CodeStyleSettingsManager.getInstance(element.project).currentSettings + .getCustomSettings(KotlinCodeStyleSettings::class.java) + + val declString = buildString { + append(TYPE_INFO_PREFIX) + if (settings.SPACE_BEFORE_TYPE_COLON) + append(" ") + append(":") + if (settings.SPACE_AFTER_TYPE_COLON) + append(" ") + append(typeRenderer.renderType(type)) + } + listOf(InlayInfo(declString, offset)) + } + else { + emptyList() + } + } + } + + abstract fun isApplicable(elem: PsiElement): Boolean + abstract fun provideHints(elem: PsiElement): List + val option = Option("SHOW_${this.name}", desc, enabled) + val enabled + get() = option.get() +} + +class KotlinInlayParameterHintsProvider : InlayParameterHintsProvider { + + override fun getSupportedOptions(): List