mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[lsp] implement signature help for Kotlin
^LSP-171 fixed GitOrigin-RevId: 1b8eda93c1cc33224ff8aa501cfc7f3d26637f04
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4259ed05dd
commit
2ff9478864
@@ -3,6 +3,7 @@ package com.jetbrains.lsp.protocol
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@Serializable
|
||||
@@ -632,8 +633,14 @@ value class TextEditOrInsertReplaceEdit private constructor(val edit: JsonElemen
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class StringOrMarkupContent private constructor(val content: JsonElement) {
|
||||
constructor(content: String) : this(JsonPrimitive(content))
|
||||
constructor(content: MarkupContent) : this(LSP.json.encodeToJsonElement(MarkupContent.serializer(), content))
|
||||
constructor(content: String) : this(JsonPrimitive(content))
|
||||
constructor(content: MarkupContent) : this(LSP.json.encodeToJsonElement(MarkupContent.serializer(), content))
|
||||
|
||||
fun contentAsString(): String = when (content) {
|
||||
is JsonPrimitive -> content.content
|
||||
is JsonObject -> LSP.json.decodeFromJsonElement(MarkupContent.serializer(), content).value
|
||||
else -> error("Unexpected content type: ${content::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
class CompletionItemKindSerializer : EnumAsIntSerializer<CompletionItemKind>(
|
||||
|
||||
@@ -244,7 +244,6 @@ data class ServerCapabilities(
|
||||
|
||||
|
||||
typealias HoverOptions = Unknown
|
||||
typealias SignatureHelpOptions = Unknown
|
||||
typealias TypeDefinitionOptions = Unknown
|
||||
typealias ImplementationOptions = Unknown
|
||||
typealias DocumentHighlightOptions = Unknown
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
package com.jetbrains.lsp.protocol
|
||||
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.nullable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.listSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@Serializable
|
||||
data class SignatureHelpOptions(
|
||||
/**
|
||||
* The characters that trigger signature help
|
||||
* automatically.
|
||||
*/
|
||||
val triggerCharacters: List<String>,
|
||||
/**
|
||||
* List of characters that re-trigger signature help.
|
||||
*
|
||||
* These trigger characters are only active when signature help is already
|
||||
* showing. All trigger characters are also counted as re-trigger
|
||||
* characters.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
val retriggerCharacters: List<String>,
|
||||
override val workDoneProgress: Boolean?,
|
||||
) : WorkDoneProgressOptions
|
||||
|
||||
@Serializable
|
||||
data class SignatureHelpParams(
|
||||
/**
|
||||
* The signature help context. This is only available if the client
|
||||
* specifies to send this using the client capability
|
||||
* `textDocument.signatureHelp.contextSupport === true`
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
val context: SignatureHelpContext?,
|
||||
override val textDocument: TextDocumentIdentifier,
|
||||
override val position: Position,
|
||||
override val workDoneToken: ProgressToken?,
|
||||
) : TextDocumentPositionParams, WorkDoneProgressParams
|
||||
|
||||
/**
|
||||
* How a signature help was triggered.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
@Serializable(with = SignatureHelpTriggerKind.Serializer::class)
|
||||
enum class SignatureHelpTriggerKind(val value: Int) {
|
||||
/**
|
||||
* Signature help was invoked manually by the user or by a command.
|
||||
*/
|
||||
Invoked(1),
|
||||
|
||||
/**
|
||||
* Signature help was triggered by a trigger character.
|
||||
*/
|
||||
TriggerCharacter(2),
|
||||
|
||||
/**
|
||||
* Signature help was triggered by the cursor moving or by the document
|
||||
* content changing.
|
||||
*/
|
||||
ContentChange(3),
|
||||
|
||||
;
|
||||
|
||||
class Serializer : EnumAsIntSerializer<SignatureHelpTriggerKind>(
|
||||
serialName = SignatureHelpTriggerKind::class.simpleName!!,
|
||||
serialize = SignatureHelpTriggerKind::value,
|
||||
deserialize = { entries[it - 1] },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional information about the context in which a signature help request
|
||||
* was triggered.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
@Serializable
|
||||
data class SignatureHelpContext(
|
||||
/**
|
||||
* Action that caused signature help to be triggered.
|
||||
*/
|
||||
val triggerKind: SignatureHelpTriggerKind,
|
||||
|
||||
/**
|
||||
* Character that caused signature help to be triggered.
|
||||
*
|
||||
* This is undefined when triggerKind !==
|
||||
* SignatureHelpTriggerKind.TriggerCharacter
|
||||
*/
|
||||
val triggerCharacter: String?,
|
||||
|
||||
/**
|
||||
* `true` if signature help was already showing when it was triggered.
|
||||
*
|
||||
* Retriggers occur when the signature help is already active and can be
|
||||
* caused by actions such as typing a trigger character, a cursor move, or
|
||||
* document content changes.
|
||||
*/
|
||||
val isRetrigger: Boolean,
|
||||
|
||||
/**
|
||||
* The currently active `SignatureHelp`.
|
||||
*
|
||||
* The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field
|
||||
* updated based on the user navigating through available signatures.
|
||||
*/
|
||||
val activeSignatureHelp: SignatureHelp?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Signature help represents the signature of something
|
||||
* callable. There can be multiple signature but only one
|
||||
* active and only one active parameter.
|
||||
*/
|
||||
@Serializable
|
||||
data class SignatureHelp(
|
||||
/**
|
||||
* One or more signatures. If no signatures are available the signature help
|
||||
* request should return `null`.
|
||||
*/
|
||||
val signatures: List<SignatureInformation>,
|
||||
|
||||
/**
|
||||
* The active signature. If omitted or the value lies outside the
|
||||
* range of `signatures` the value defaults to zero or is ignore if
|
||||
* the `SignatureHelp` as no signatures.
|
||||
*
|
||||
* Whenever possible implementors should make an active decision about
|
||||
* the active signature and shouldn't rely on a default value.
|
||||
*
|
||||
* In future version of the protocol this property might become
|
||||
* mandatory to better express this.
|
||||
*/
|
||||
val activeSignature: Int?,
|
||||
|
||||
/**
|
||||
* The active parameter of the active signature. If omitted or the value
|
||||
* lies outside the range of `signatures[activeSignature].parameters`
|
||||
* defaults to 0 if the active signature has parameters. If
|
||||
* the active signature has no parameters it is ignored.
|
||||
* In future version of the protocol this property might become
|
||||
* mandatory to better express the active parameter if the
|
||||
* active signature does have any.
|
||||
*/
|
||||
val activeParameter: Int?,
|
||||
) {
|
||||
init {
|
||||
require(activeSignature == null || activeSignature >= 0) {
|
||||
"activeSignature must be non-negative but was $activeSignature"
|
||||
}
|
||||
require(activeParameter == null || activeParameter >= 0) {
|
||||
"activeParameter must be non-negative but was $activeParameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the signature of something callable. A signature
|
||||
* can have a label, like a function-name, a doc-comment, and
|
||||
* a set of parameters.
|
||||
*/
|
||||
@Serializable
|
||||
data class SignatureInformation(
|
||||
/**
|
||||
* The label of this signature. Will be shown in
|
||||
* the UI.
|
||||
*/
|
||||
val label: String,
|
||||
|
||||
/**
|
||||
* The human-readable doc-comment of this signature. Will be shown
|
||||
* in the UI but can be omitted.
|
||||
*/
|
||||
val documentation: StringOrMarkupContent?,
|
||||
|
||||
/**
|
||||
* The parameters of this signature.
|
||||
*/
|
||||
val parameters: List<ParameterInformation>?,
|
||||
|
||||
/**
|
||||
* The index of the active parameter.
|
||||
*
|
||||
* If provided, this is used in place of `SignatureHelp.activeParameter`.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
val activeParameter: Int?,
|
||||
) {
|
||||
init {
|
||||
require(activeParameter == null || activeParameter >= 0) {
|
||||
"activeParameter must be non-negative but was $activeParameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a parameter of a callable-signature. A parameter can
|
||||
* have a label and a doc-comment.
|
||||
*/
|
||||
@Serializable
|
||||
data class ParameterInformation(
|
||||
|
||||
/**
|
||||
* The label of this parameter information.
|
||||
*
|
||||
* Either a string or an inclusive start and exclusive end offsets within
|
||||
* its containing signature label. (see SignatureInformation.label). The
|
||||
* offsets are based on a UTF-16 string representation as `Position` and
|
||||
* `Range` does.
|
||||
*
|
||||
* *Note*: a label of type string should be a substring of its containing
|
||||
* signature label. Its intended use case is to highlight the parameter
|
||||
* label part in the `SignatureInformation.label`.
|
||||
*/
|
||||
val label: Label,
|
||||
|
||||
/**
|
||||
* The human-readable doc-comment of this parameter. Will be shown
|
||||
* in the UI but can be omitted.
|
||||
*/
|
||||
val documentation: StringOrMarkupContent?,
|
||||
) {
|
||||
|
||||
@Serializable(with = Label.LabelSerializer::class)
|
||||
sealed interface Label {
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class StringLabel(val value: String) : Label
|
||||
|
||||
@Serializable(with = RangeLabel.Serializer::class)
|
||||
data class RangeLabel(val start: Int, val end: Int) : Label {
|
||||
init {
|
||||
require(start >= 0) {
|
||||
"start must be non-negative but was $start"
|
||||
}
|
||||
require(end >= 0) {
|
||||
"end must be non-negative but was $end"
|
||||
}
|
||||
require(start <= end) {
|
||||
"start must be less than or equal to end but was start=$start, end=$end"
|
||||
}
|
||||
}
|
||||
|
||||
object Serializer : KSerializer<RangeLabel> {
|
||||
private val listSerializer = ListSerializer(Int.serializer())
|
||||
|
||||
override val descriptor: SerialDescriptor = listSerialDescriptor<Int>()
|
||||
|
||||
override fun serialize(encoder: Encoder, value: RangeLabel) {
|
||||
encoder.encodeSerializableValue(listSerializer, listOf(value.start, value.end))
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): RangeLabel {
|
||||
val list = decoder.decodeSerializableValue(listSerializer)
|
||||
require(list.size == 2) {
|
||||
"Expected list of size 2 for RangeLabel, got ${list.size}"
|
||||
}
|
||||
return RangeLabel(list[0], list[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We serialize [RangeLabel] as an array, and thus we cannot use default polymorphic serializer as it writes the `kind` field
|
||||
* which is not expected possible for the array case. So, we have a custom serializer.
|
||||
*/
|
||||
object LabelSerializer : JsonContentPolymorphicSerializer<Label>(Label::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Label> {
|
||||
return when (element) {
|
||||
is JsonPrimitive -> StringLabel.serializer()
|
||||
is JsonArray -> RangeLabel.serializer()
|
||||
else -> throw SerializationException("Unknown Label variant: $element")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val SignatureHelpRequest: RequestType<SignatureHelpParams, SignatureHelp?, Unit> =
|
||||
RequestType("textDocument/signatureHelp", SignatureHelpParams.serializer(), SignatureHelp.serializer().nullable, Unit.serializer())
|
||||
+259
-157
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.parameterInfo
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle
|
||||
@@ -7,6 +7,7 @@ import com.intellij.lang.parameterInfo.ParameterInfoHandlerWithTabActionSupport
|
||||
import com.intellij.lang.parameterInfo.ParameterInfoUIContext
|
||||
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.ui.Gray
|
||||
import com.intellij.ui.JBColor
|
||||
@@ -18,7 +19,9 @@ import org.jetbrains.kotlin.analysis.api.components.KaSubtypingErrorTypePolicy
|
||||
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
|
||||
import org.jetbrains.kotlin.analysis.api.signatures.KaVariableSignature
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.name
|
||||
import org.jetbrains.kotlin.analysis.api.types.KaErrorType
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
||||
@@ -34,6 +37,7 @@ import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.renderer.render
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import java.awt.Color
|
||||
import kotlin.math.min
|
||||
@@ -59,7 +63,7 @@ class KotlinHighLevelLambdaParameterInfoHandler :
|
||||
|
||||
override fun getArgumentListAllowedParentClasses(): Set<Class<KtLambdaArgument>> = setOf(KtLambdaArgument::class.java)
|
||||
|
||||
override fun getCurrentArgumentIndex(context: UpdateParameterInfoContext, argumentList: KtLambdaArgument): Int {
|
||||
override fun getCurrentArgumentIndex(offset: Int, argumentList: KtLambdaArgument): Int {
|
||||
val size = (argumentList.parent as? KtCallElement)?.valueArguments?.size ?: 1
|
||||
return size - 1
|
||||
}
|
||||
@@ -99,6 +103,9 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
|
||||
private val ANNOTATION_TARGET_TYPE = CallableId(StandardClassIds.AnnotationTarget, Name.identifier(AnnotationTarget.TYPE.name))
|
||||
private val ANNOTATION_TARGET_VALUE_PARAMETER = CallableId(StandardClassIds.AnnotationTarget, Name.identifier(AnnotationTarget.VALUE_PARAMETER.name))
|
||||
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
private val typeRenderer = KaTypeRendererForSource.WITH_SHORT_NAMES
|
||||
}
|
||||
|
||||
override fun getActualParameterDelimiterType(): KtSingleValueToken = KtTokens.COMMA
|
||||
@@ -114,9 +121,7 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
override fun findElementForParameterInfo(context: CreateParameterInfoContext): TArgumentList? {
|
||||
val file = context.file as? KtFile ?: return null
|
||||
|
||||
val token = file.findElementAt(context.offset) ?: return null
|
||||
val argumentList = PsiTreeUtil.getParentOfType(token, argumentListClass.java, true, *STOP_SEARCH_CLASSES.toTypedArray())
|
||||
?: return null
|
||||
val argumentList = findElementForParameterInfo(file, context.offset) ?: return null
|
||||
|
||||
val callElement = argumentList.parent as? KtElement ?: return null
|
||||
return analyze(callElement) {
|
||||
@@ -138,6 +143,11 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
}
|
||||
}
|
||||
|
||||
fun findElementForParameterInfo(file: KtFile, offset: Int): TArgumentList? {
|
||||
val token = file.findElementAt(offset) ?: return null
|
||||
return PsiTreeUtil.getParentOfType(token, argumentListClass.java, true, *STOP_SEARCH_CLASSES.toTypedArray())
|
||||
}
|
||||
|
||||
override fun findElementForUpdatingParameterInfo(context: UpdateParameterInfoContext): TArgumentList? {
|
||||
val element = context.file.findElementAt(context.offset) ?: return null
|
||||
return PsiTreeUtil.getParentOfType(element, argumentListClass.java)
|
||||
@@ -148,10 +158,25 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
if (context.parameterOwner !== argumentList) {
|
||||
context.removeHint()
|
||||
}
|
||||
val currentArgumentIndex = getCurrentArgumentIndex(context, argumentList)
|
||||
val currentArgumentIndex = getCurrentArgumentIndex(context.offset, argumentList)
|
||||
context.setCurrentParameter(currentArgumentIndex)
|
||||
|
||||
val callElement = argumentList.parent as? KtElement ?: return
|
||||
val callInfos = createCallInfos(argumentList, currentArgumentIndex) ?: return
|
||||
for ((index, objectToView) in context.objectsToView.withIndex()) {
|
||||
val candidateInfo = objectToView as? CandidateInfo ?: continue
|
||||
|
||||
if (index >= callInfos.size) {
|
||||
// Number of candidates somehow changed while UI is shown, which should NOT be possible. Bail out to be safe.
|
||||
return
|
||||
}
|
||||
candidateInfo.callInfo = callInfos[index]
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
fun createCallInfos(argumentList: TArgumentList, currentArgumentIndex: Int): List<CallInfo>? {
|
||||
val callElement = argumentList.parent as? KtElement ?: return null
|
||||
val result = mutableListOf<CallInfo>()
|
||||
analyze(callElement) {
|
||||
if (callElement !is KtCallElement && callElement !is KtArrayAccessExpression) return@analyze
|
||||
|
||||
@@ -160,14 +185,7 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
|
||||
val candidates = collectCallCandidates(callElement)
|
||||
val hasMultipleApplicableBestCandidates = candidates.count { candidate -> candidate.withMapping.isApplicableBestCandidate } > 1
|
||||
|
||||
for ((index, objectToView) in context.objectsToView.withIndex()) {
|
||||
val candidateInfo = objectToView as? CandidateInfo ?: continue
|
||||
|
||||
if (index >= candidates.size) {
|
||||
// Number of candidates somehow changed while UI is shown, which should NOT be possible. Bail out to be safe.
|
||||
return
|
||||
}
|
||||
for ((index, objectToView) in candidates.withIndex()) {
|
||||
val (candidateSignature, argumentMapping, isApplicableBestCandidate) = candidates[index].withMapping
|
||||
|
||||
// For array set calls, we only want the index arguments in brackets, which are all except the last (the value to set).
|
||||
@@ -232,7 +250,32 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
else -> null
|
||||
}
|
||||
|
||||
candidateInfo.callInfo = CallInfo(
|
||||
val representation = CallStringRepresentation(
|
||||
buildString {
|
||||
candidateSignature.receiverType?.let {
|
||||
append(it.render(typeRenderer, position = Variance.IN_VARIANCE))
|
||||
append(".")
|
||||
}
|
||||
val name = when(val symbol = candidateSignature.symbol) {
|
||||
is KaConstructorSymbol -> (symbol.containingDeclaration as? KaClassSymbol)?.name
|
||||
else -> symbol.name
|
||||
}
|
||||
append(name?.render())
|
||||
},
|
||||
buildString {
|
||||
when(val symbol = candidateSignature.symbol) {
|
||||
is KaConstructorSymbol ->{}
|
||||
else -> {
|
||||
append(": ")
|
||||
append(candidateSignature.returnType.render(typeRenderer, position = Variance.OUT_VARIANCE))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
result += CallInfo(
|
||||
candidateSignature.symbol.psi,
|
||||
callElement,
|
||||
valueArguments,
|
||||
firstArgumentInNamedMode,
|
||||
@@ -244,13 +287,14 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
hasTypeMismatchBeforeCurrent,
|
||||
highlightParameterIndex,
|
||||
isDeprecated = candidateSignature.symbol.deprecationStatus != null,
|
||||
representation,
|
||||
)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
protected open fun getCurrentArgumentIndex(context: UpdateParameterInfoContext, argumentList: TArgumentList): Int {
|
||||
val offset = context.offset
|
||||
open fun getCurrentArgumentIndex(offset: Int, argumentList: TArgumentList): Int {
|
||||
return argumentList.allChildren
|
||||
.takeWhile { it.startOffset < offset }
|
||||
.count { it.node.elementType == KtTokens.COMMA }
|
||||
@@ -284,7 +328,7 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
}
|
||||
|
||||
val returnType = parameter.returnType.takeUnless { it is KaErrorType } ?: parameter.symbol.returnType
|
||||
append(returnType.render(KaTypeRendererForSource.WITH_SHORT_NAMES, position = Variance.INVARIANT))
|
||||
append(returnType.render(typeRenderer, position = Variance.INVARIANT))
|
||||
|
||||
parameter.symbol.defaultValue?.let { defaultValue ->
|
||||
append(" = ")
|
||||
@@ -389,153 +433,205 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
if (!argumentListClass.java.isInstance(context.parameterOwner)) return false
|
||||
|
||||
val callInfo = itemToShow.callInfo ?: return false
|
||||
val currentArgumentIndex = min(context.currentParameterIndex, callInfo.arguments.size)
|
||||
if (currentArgumentIndex < 0) return false
|
||||
with(callInfo) {
|
||||
var highlightStartOffset = -1
|
||||
var highlightEndOffset = -1
|
||||
var isDisabledBeforeHighlight = false
|
||||
var hasUnmappedArgument = false
|
||||
var hasUnmappedArgumentBeforeCurrent = false
|
||||
var lastMappedArgumentIndex = -1
|
||||
var namedMode = false
|
||||
val usedParameterIndices = HashSet<Int>()
|
||||
val text = buildString {
|
||||
var argumentIndex = 0
|
||||
val parameterDelimiterIndexes = mutableListOf<Int>()
|
||||
|
||||
fun appendParameter(
|
||||
parameterIndex: Int,
|
||||
shouldHighlight: Boolean = false,
|
||||
isNamed: Boolean = false,
|
||||
markUsedUnusedParameterBorder: Boolean = false
|
||||
) {
|
||||
argumentIndex++
|
||||
|
||||
if (length > 0) {
|
||||
append(", ")
|
||||
parameterDelimiterIndexes.add(length)
|
||||
if (markUsedUnusedParameterBorder) {
|
||||
// This is used to "disable" the used parameters, when in "named mode" and there are more unused parameters.
|
||||
// See NamedParameter3.kt test. Disabling them gives a visual cue that they are already used.
|
||||
|
||||
// Highlight something as bold to show text before as disabled
|
||||
highlightStartOffset = length - 1
|
||||
highlightEndOffset = length - 1
|
||||
isDisabledBeforeHighlight = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldHighlight) {
|
||||
highlightStartOffset = length
|
||||
}
|
||||
|
||||
val surroundInBrackets = isNamed || namedMode
|
||||
if (surroundInBrackets) {
|
||||
append("[")
|
||||
}
|
||||
append(parameterIndexToText[parameterIndex])
|
||||
if (surroundInBrackets) {
|
||||
append("]")
|
||||
}
|
||||
|
||||
if (shouldHighlight) {
|
||||
highlightEndOffset = length
|
||||
}
|
||||
}
|
||||
|
||||
if (valueArguments != null) {
|
||||
for (valueArgument in valueArguments) {
|
||||
val parameterIndex = argumentToParameterIndex[valueArgument.getArgumentExpression()]
|
||||
if (valueArgument == firstArgumentInNamedMode){
|
||||
namedMode = true
|
||||
}
|
||||
|
||||
if (parameterIndex == null) {
|
||||
hasUnmappedArgument = true
|
||||
if (argumentIndex < currentArgumentIndex) {
|
||||
hasUnmappedArgumentBeforeCurrent = true
|
||||
}
|
||||
argumentIndex++
|
||||
continue
|
||||
}
|
||||
lastMappedArgumentIndex = argumentIndex
|
||||
if (!usedParameterIndices.add(parameterIndex)) continue
|
||||
|
||||
val shouldHighlight = parameterIndex == highlightParameterIndex
|
||||
appendParameter(parameterIndex, shouldHighlight, valueArgument.isNamed())
|
||||
}
|
||||
} else {
|
||||
// This is for array get/set calls which don't have KtValueArguments.
|
||||
for (argument in arguments) {
|
||||
val parameterIndex = argumentToParameterIndex[argument]
|
||||
if (parameterIndex == null) {
|
||||
hasUnmappedArgument = true
|
||||
if (argumentIndex < currentArgumentIndex) {
|
||||
hasUnmappedArgumentBeforeCurrent = true
|
||||
}
|
||||
argumentIndex++
|
||||
continue
|
||||
}
|
||||
lastMappedArgumentIndex = argumentIndex
|
||||
if (!usedParameterIndices.add(parameterIndex)) continue
|
||||
|
||||
val shouldHighlight = parameterIndex == highlightParameterIndex
|
||||
appendParameter(parameterIndex, shouldHighlight)
|
||||
}
|
||||
}
|
||||
|
||||
for (parameterIndex in 0 until valueParameterCount) {
|
||||
if (parameterIndex !in usedParameterIndices) {
|
||||
// Highlight the first unused parameter if it is in the correct position
|
||||
val shouldHighlight = !namedMode && highlightStartOffset == -1
|
||||
appendParameter(
|
||||
parameterIndex,
|
||||
shouldHighlight,
|
||||
markUsedUnusedParameterBorder = namedMode && highlightStartOffset == -1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
append(CodeInsightBundle.message("parameter.info.no.parameters"))
|
||||
} else {
|
||||
val useMultilineParameters = Registry.`is`("kotlin.multiline.function.parameters.info")
|
||||
if (useMultilineParameters && argumentIndex > SINGLE_LINE_PARAMETERS_COUNT) {
|
||||
parameterDelimiterIndexes.forEach { offset ->
|
||||
replace(offset - 1, offset, "\n")
|
||||
}
|
||||
val uiModel = callInfo.toUiModel(context.currentParameterIndex) ?: return false
|
||||
val backgroundColor = if (callInfo.shouldHighlightGreen) GREEN_BACKGROUND else context.defaultParameterColor
|
||||
val signature = uiModel.signatureModel
|
||||
val text = buildString {
|
||||
signature.parts.forEach { piece -> append(piece.text) }
|
||||
val useMultilineParameters = Registry.`is`("kotlin.multiline.function.parameters.info", false)
|
||||
if (useMultilineParameters && signature.parts.size > SINGLE_LINE_PARAMETERS_COUNT) {
|
||||
signature.parts.forEachIndexed { i, piece ->
|
||||
if (piece is SignaturePart.Parameter) {
|
||||
val offset = signature.getRange(i).first
|
||||
replace(offset - 1, offset, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundColor = if (shouldHighlightGreen) GREEN_BACKGROUND else context.defaultParameterColor
|
||||
|
||||
// Disabled when there are too many arguments.
|
||||
val allParametersUsed = usedParameterIndices.size == valueParameterCount
|
||||
val supportsTrailingCommas = callElement.languageVersionSettings.supportsFeature(LanguageFeature.TrailingCommas)
|
||||
val afterTrailingComma = arguments.isNotEmpty() && currentArgumentIndex == arguments.size
|
||||
val isInPositionToEnterArgument = !supportsTrailingCommas && afterTrailingComma
|
||||
val isAfterMappedArgs = currentArgumentIndex > lastMappedArgumentIndex
|
||||
val tooManyArgs = allParametersUsed && (isInPositionToEnterArgument || hasUnmappedArgument) && (isAfterMappedArgs || namedMode)
|
||||
|
||||
val isDisabled = tooManyArgs || hasTypeMismatchBeforeCurrent || hasUnmappedArgumentBeforeCurrent
|
||||
|
||||
context.setupUIComponentPresentation(
|
||||
text,
|
||||
highlightStartOffset,
|
||||
highlightEndOffset,
|
||||
isDisabled,
|
||||
/*strikeout=*/ isDeprecated,
|
||||
isDisabledBeforeHighlight,
|
||||
backgroundColor
|
||||
)
|
||||
}
|
||||
val highlightedRange =run {
|
||||
val index= signature.parts.withIndex()
|
||||
.firstOrNull { (it.value as? SignaturePart.Parameter)?.isHighlighted == true }?.index
|
||||
?: return@run Pair(-1, -1)
|
||||
signature.getRange(index)
|
||||
}
|
||||
context.setupUIComponentPresentation(
|
||||
text,
|
||||
highlightedRange.first,
|
||||
highlightedRange.second,
|
||||
uiModel.isDisabled,
|
||||
/*strikeout=*/ callInfo.isDeprecated,
|
||||
uiModel.isDisabledBeforeHighlight,
|
||||
backgroundColor
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
sealed interface SignaturePart {
|
||||
val text: String
|
||||
|
||||
data class Text(override val text: String) : SignaturePart
|
||||
data class Parameter(override val text: String, val isHighlighted: Boolean) : SignaturePart
|
||||
}
|
||||
|
||||
data class UiModel(
|
||||
val signatureModel: SignatureModel,
|
||||
val isDisabled: Boolean,
|
||||
val isDisabledBeforeHighlight: Boolean,
|
||||
)
|
||||
|
||||
data class SignatureModel(
|
||||
val parts: List<SignaturePart>,
|
||||
) {
|
||||
val text: String get() = parts.joinToString("") { it.text }
|
||||
|
||||
private val ranges by lazy {
|
||||
var offset = 0
|
||||
val result = mutableListOf<Pair<Int, Int>>()
|
||||
for (piece in parts) {
|
||||
result += Pair(offset, offset + piece.text.length)
|
||||
offset += piece.text.length
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fun getRange(index: Int): Pair<Int, Int> {
|
||||
return ranges[index]
|
||||
}
|
||||
}
|
||||
|
||||
fun CallInfo.toUiModel(
|
||||
currentParameterIndex: Int,
|
||||
appendNoParametersMessage: Boolean = true,
|
||||
): UiModel? {
|
||||
val currentArgumentIndex = min(currentParameterIndex, arguments.size)
|
||||
if (currentArgumentIndex < 0) return null
|
||||
var wasParameterHighlighted = false
|
||||
var isDisabledBeforeHighlight = false
|
||||
var hasUnmappedArgument = false
|
||||
var hasUnmappedArgumentBeforeCurrent = false
|
||||
var lastMappedArgumentIndex = -1
|
||||
var namedMode = false
|
||||
val usedParameterIndices = HashSet<Int>()
|
||||
|
||||
val signatureParts = mutableListOf<SignaturePart>()
|
||||
|
||||
var argumentIndex = 0
|
||||
|
||||
fun appendParameter(
|
||||
parameterIndex: Int,
|
||||
shouldHighlight: Boolean = false,
|
||||
isNamed: Boolean = false,
|
||||
markUsedUnusedParameterBorder: Boolean = false
|
||||
) {
|
||||
argumentIndex++
|
||||
|
||||
if (signatureParts.isNotEmpty()) {
|
||||
signatureParts.add(SignaturePart.Text(","))
|
||||
if (markUsedUnusedParameterBorder) {
|
||||
// This is used to "disable" the used parameters, when in "named mode" and there are more unused parameters.
|
||||
// See NamedParameter3.kt test. Disabling them gives a visual cue that they are already used.
|
||||
signatureParts.add(SignaturePart.Parameter("", isHighlighted = true))
|
||||
isDisabledBeforeHighlight = true
|
||||
wasParameterHighlighted = true
|
||||
}
|
||||
signatureParts.add(SignaturePart.Text(" "))
|
||||
}
|
||||
|
||||
val surroundInBrackets = isNamed || namedMode
|
||||
val parameterText = buildString {
|
||||
if (surroundInBrackets) {
|
||||
append("[")
|
||||
}
|
||||
append(parameterIndexToText[parameterIndex])
|
||||
if (surroundInBrackets) {
|
||||
append("]")
|
||||
}
|
||||
}
|
||||
if (shouldHighlight) {
|
||||
wasParameterHighlighted = true
|
||||
}
|
||||
signatureParts.add(SignaturePart.Parameter(parameterText, shouldHighlight))
|
||||
}
|
||||
|
||||
if (valueArguments != null) {
|
||||
for (valueArgument in valueArguments) {
|
||||
val parameterIndex = argumentToParameterIndex[valueArgument.getArgumentExpression()]
|
||||
if (valueArgument == firstArgumentInNamedMode) {
|
||||
namedMode = true
|
||||
}
|
||||
|
||||
if (parameterIndex == null) {
|
||||
hasUnmappedArgument = true
|
||||
if (argumentIndex < currentArgumentIndex) {
|
||||
hasUnmappedArgumentBeforeCurrent = true
|
||||
}
|
||||
argumentIndex++
|
||||
continue
|
||||
}
|
||||
lastMappedArgumentIndex = argumentIndex
|
||||
if (!usedParameterIndices.add(parameterIndex)) continue
|
||||
|
||||
val shouldHighlight = parameterIndex == highlightParameterIndex
|
||||
appendParameter(parameterIndex, shouldHighlight, valueArgument.isNamed())
|
||||
}
|
||||
} else {
|
||||
// This is for array get/set calls which don't have KtValueArguments.
|
||||
for (argument in arguments) {
|
||||
val parameterIndex = argumentToParameterIndex[argument]
|
||||
if (parameterIndex == null) {
|
||||
hasUnmappedArgument = true
|
||||
if (argumentIndex < currentArgumentIndex) {
|
||||
hasUnmappedArgumentBeforeCurrent = true
|
||||
}
|
||||
argumentIndex++
|
||||
continue
|
||||
}
|
||||
lastMappedArgumentIndex = argumentIndex
|
||||
if (!usedParameterIndices.add(parameterIndex)) continue
|
||||
|
||||
val shouldHighlight = parameterIndex == highlightParameterIndex
|
||||
appendParameter(parameterIndex, shouldHighlight)
|
||||
}
|
||||
}
|
||||
|
||||
for (parameterIndex in 0 until valueParameterCount) {
|
||||
if (parameterIndex !in usedParameterIndices) {
|
||||
// Highlight the first unused parameter if it is in the correct position
|
||||
val shouldHighlight = !namedMode && !wasParameterHighlighted
|
||||
appendParameter(
|
||||
parameterIndex,
|
||||
shouldHighlight,
|
||||
markUsedUnusedParameterBorder = namedMode && !wasParameterHighlighted
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (appendNoParametersMessage && signatureParts.isEmpty()) {
|
||||
signatureParts.add(SignaturePart.Text(CodeInsightBundle.message("parameter.info.no.parameters")))
|
||||
}
|
||||
|
||||
|
||||
// Disabled when there are too many arguments.
|
||||
val allParametersUsed = usedParameterIndices.size == valueParameterCount
|
||||
val supportsTrailingCommas = callElement.languageVersionSettings.supportsFeature(LanguageFeature.TrailingCommas)
|
||||
val afterTrailingComma = arguments.isNotEmpty() && currentArgumentIndex == arguments.size
|
||||
val isInPositionToEnterArgument = !supportsTrailingCommas && afterTrailingComma
|
||||
val isAfterMappedArgs = currentArgumentIndex > lastMappedArgumentIndex
|
||||
val tooManyArgs = allParametersUsed && (isInPositionToEnterArgument || hasUnmappedArgument) && (isAfterMappedArgs || namedMode)
|
||||
|
||||
val isDisabled = tooManyArgs || hasTypeMismatchBeforeCurrent || hasUnmappedArgumentBeforeCurrent
|
||||
|
||||
return UiModel(
|
||||
SignatureModel(signatureParts),
|
||||
isDisabled,
|
||||
isDisabledBeforeHighlight,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
data class CallInfo(
|
||||
val target: PsiElement?,
|
||||
val callElement: KtElement,
|
||||
val valueArguments: List<KtValueArgument>?,
|
||||
val firstArgumentInNamedMode: KtValueArgument?,
|
||||
@@ -547,6 +643,12 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
|
||||
val hasTypeMismatchBeforeCurrent: Boolean,
|
||||
val highlightParameterIndex: Int?,
|
||||
val isDeprecated: Boolean,
|
||||
val representation: CallStringRepresentation,
|
||||
)
|
||||
|
||||
data class CallStringRepresentation(
|
||||
val beforeParameters: String,
|
||||
val afterParameters: String,
|
||||
)
|
||||
|
||||
data class CandidateInfo(
|
||||
|
||||
Reference in New Issue
Block a user