[param hints] refactored + preserve existing hints in some cases [IDEA-169381]

This commit is contained in:
Yaroslav Lepenkin
2017-03-28 10:57:00 +03:00
parent 1c8ee7eb03
commit 90407ca29f
2 changed files with 152 additions and 69 deletions
@@ -60,23 +60,24 @@ object JavaInlayHintsProvider {
}
private fun createHintSet(info: CallInfo, substitutor: PsiSubstitutor): Set<InlayInfo> {
val args = info.regularArgs.filter { it.isAssignable(substitutor) }
val resultSet = mutableSetOf<InlayInfo>()
with(resultSet) {
getVarArgInlay(info)?.let { add(it) }
val hintsProvider = JavaInlayParameterHintsProvider.getInstance()
if (hintsProvider.isShowForParamsWithSameType.get()) {
addAll(createSameTypeInlays(args))
}
addAll(createUnclearInlays(args))
val varargInlay = info.createVarargInlay(substitutor)
if (varargInlay != null) {
resultSet.add(varargInlay)
}
if (isShowForParamsWithSameType()) {
resultSet.addAll(info.createSameTypeInlays())
}
resultSet.addAll(info.createUnclearInlays(substitutor))
return resultSet
}
private fun isShowForParamsWithSameType() = JavaInlayParameterHintsProvider.getInstance().isShowForParamsWithSameType.get()
private fun isMethodToShow(method: PsiMethod, callExpression: PsiCallExpression): Boolean {
val params = method.parameterList.parameters
if (params.isEmpty()) return false
@@ -95,22 +96,8 @@ object JavaInlayHintsProvider {
}
return true
}
private fun createUnclearInlays(args: List<CallArgumentInfo>): List<InlayInfo> {
return args
.filter { isUnclearExpression(it.argument) }
.mapNotNull { createInlayInfo(it.argument, it.parameter) }
}
private fun getVarArgInlay(info: CallInfo): InlayInfo? {
if (info.varArg == null || info.varArgExpressions.isEmpty()) return null
val hasUnclearExpressions = info.varArgExpressions.find { isUnclearExpression(it) } != null
if (hasUnclearExpressions) {
return createInlayInfo(info.varArgExpressions.first(), info.varArg)
}
return null
}
private fun isBuilderLike(expression: PsiCallExpression, method: PsiMethod): Boolean {
if (expression is PsiNewExpression) return false
@@ -127,26 +114,7 @@ object JavaInlayHintsProvider {
}
return false
}
private fun createSameTypeInlays(args: List<CallArgumentInfo>): List<InlayInfo> {
val all = args.map { it.parameter.typeText() }
val duplicated = all.toMutableList()
all.distinct().forEach {
duplicated.remove(it)
}
return args
.filter { duplicated.contains(it.parameter.typeText()) }
.mapNotNull { createInlayInfo(it.argument, it.parameter) }
}
private fun createInlayInfo(callArgument: PsiExpression, methodParam: PsiParameter): InlayInfo? {
val paramName = methodParam.name ?: return null
val paramToShow = (if (methodParam.type is PsiEllipsisType) "..." else "") + paramName
return InlayInfo(paramToShow, callArgument.textRange.startOffset)
}
private fun getCallInfo(callExpression: PsiCallExpression, method: PsiMethod): CallInfo {
val params = method.parameterList.parameters
val hasVarArg = params.lastOrNull()?.isVarArgs ?: false
@@ -164,35 +132,99 @@ object JavaInlayHintsProvider {
return CallInfo(regularArgInfos, varargParam, varargExpressions)
}
private fun isUnclearExpression(callArgument: PsiElement): Boolean {
val isShowHint = when (callArgument) {
is PsiLiteralExpression -> true
is PsiThisExpression -> true
is PsiBinaryExpression -> true
is PsiPolyadicExpression -> true
is PsiPrefixExpression -> {
val tokenType = callArgument.operationTokenType
val isLiteral = callArgument.operand is PsiLiteralExpression
isLiteral && (JavaTokenType.MINUS == tokenType || JavaTokenType.PLUS == tokenType)
}
private fun createInlayInfo(info: CallArgumentInfo, showOnlyIfExistedBefore: Boolean = false): InlayInfo? {
return createInlayInfo(info.argument, info.parameter, showOnlyIfExistedBefore)
}
private fun createInlayInfo(callArgument: PsiExpression, methodParam: PsiParameter, showOnlyIfExistedBefore: Boolean = false): InlayInfo? {
val paramName = methodParam.name ?: return null
val paramToShow = (if (methodParam.type is PsiEllipsisType) "..." else "") + paramName
return InlayInfo(paramToShow, callArgument.textRange.startOffset, showOnlyIfExistedBefore)
}
private fun isUnclearExpression(callArgument: PsiElement): Boolean {
val isShowHint = when (callArgument) {
is PsiLiteralExpression -> true
is PsiThisExpression -> true
is PsiBinaryExpression -> true
is PsiPolyadicExpression -> true
is PsiPrefixExpression -> {
val tokenType = callArgument.operationTokenType
val isLiteral = callArgument.operand is PsiLiteralExpression
isLiteral && (JavaTokenType.MINUS == tokenType || JavaTokenType.PLUS == tokenType)
}
else -> false
}
return isShowHint
}
private class CallInfo(val regularArgs: List<CallArgumentInfo>, val varArg: PsiParameter?, val varArgExpressions: List<PsiExpression>) {
fun createUnclearInlays(substitutor: PsiSubstitutor): List<InlayInfo> {
val inlays = mutableListOf<InlayInfo>()
for (callInfo in regularArgs) {
val inlay = when {
isUnclearExpression(callInfo.argument) -> createInlayInfo(callInfo)
!callInfo.isAssignable(substitutor) -> createInlayInfo(callInfo, showOnlyIfExistedBefore = true)
else -> null
}
else -> false
inlay?.let { inlays.add(inlay) }
}
return inlays
}
fun createSameTypeInlays(): List<InlayInfo> {
val all = regularArgs.map { it.parameter.typeText() }
val duplicated = all.toMutableList()
all.distinct().forEach {
duplicated.remove(it)
}
return isShowHint
return regularArgs
.filter { duplicated.contains(it.parameter.typeText()) }
.mapNotNull { createInlayInfo(it) }
}
fun createVarargInlay(substitutor: PsiSubstitutor): InlayInfo? {
if (varArg == null) return null
var hasUnassignable = false
for (expr in varArgExpressions) {
if (isUnclearExpression(expr)) {
return createInlayInfo(varArgExpressions.first(), varArg)
}
hasUnassignable = hasUnassignable || !varArg.isAssignable(expr, substitutor)
}
return if (hasUnassignable) createInlayInfo(varArgExpressions.first(), varArg, showOnlyIfExistedBefore = true) else null
}
}
private class CallArgumentInfo(val parameter: PsiParameter, val argument: PsiExpression) {
fun isAssignable(substitutor: PsiSubstitutor): Boolean {
return parameter.isAssignable(argument, substitutor)
}
}
private class CallInfo(val regularArgs: List<CallArgumentInfo>,
val varArg: PsiParameter?,
val varArgExpressions: List<PsiExpression>)
private class CallArgumentInfo(val parameter: PsiParameter, val argument: PsiExpression)
private fun CallArgumentInfo.isAssignable(substitutor: PsiSubstitutor): Boolean {
val substitutedType = substitutor.substitute(parameter.type)
private fun PsiParameter.isAssignable(argument: PsiExpression, substitutor: PsiSubstitutor = PsiSubstitutor.EMPTY): Boolean {
val substitutedType = substitutor.substitute(type)
if (PsiPolyExpressionUtil.isPolyExpression(argument)) return true
return argument.type?.isAssignableTo(substitutedType) ?: false
}
@@ -15,10 +15,14 @@
*/
package com.intellij.codeInsight.daemon.inlays
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager
import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider
import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.editor.Inlay
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.assertj.core.api.Assertions.assertThat
class JavaInlayParameterHintsTest : LightCodeInsightFixtureTestCase() {
@@ -762,4 +766,51 @@ class X {
""")
}
fun `test preserved inlays`() {
myFixture.configureByText(JavaFileType.INSTANCE,
"""
class Test {
void main() {
test(<caret>);
}
void test(int fooo) {}
}
""")
myFixture.type("100")
myFixture.doHighlighting()
assertSingleInlayWithText("fooo:")
myFixture.type("\b\b\b")
myFixture.doHighlighting()
assertSingleInlayWithText("fooo:")
myFixture.type("yyy")
myFixture.doHighlighting()
assertSingleInlayWithText("fooo:")
myFixture.checkResult(
"""
class Test {
void main() {
test(yyy);
}
void test(int fooo) {}
}
""")
}
fun assertSingleInlayWithText(expectedText: String) {
val inlays = myFixture.editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength)
assertThat(inlays).hasSize(1)
val realText = getHintText(inlays[0])
assertThat(realText).isEqualTo(expectedText)
}
fun getHintText(inlay: Inlay): String {
return ParameterHintsPresentationManager.getInstance().getHintText(inlay)
}
}