Skip to content

Commit

Permalink
Implement simple signature help
Browse files Browse the repository at this point in the history
  • Loading branch information
hegyibalint committed Sep 18, 2024
1 parent 7eb0d42 commit 8d4b21c
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.services.LanguageClientAware
import org.eclipse.lsp4j.services.TextDocumentService
import org.gradle.declarative.dsl.schema.DataClass
import org.gradle.declarative.dsl.schema.DataParameter
import org.gradle.declarative.dsl.schema.DataType
import org.gradle.declarative.dsl.schema.DataTypeRef
import org.gradle.declarative.dsl.schema.FunctionSemantics
import org.gradle.declarative.dsl.schema.SchemaFunction
import org.gradle.declarative.lsp.build.model.ResolvedDeclarativeResourcesModel
import org.gradle.declarative.lsp.visitor.BestFittingNodeVisitor
import org.gradle.declarative.lsp.visitor.SemanticErrorToDiagnosticVisitor
import org.gradle.declarative.lsp.visitor.SyntaxErrorToDiagnosticVisitor
import org.gradle.declarative.lsp.visitor.visit
import org.gradle.internal.declarativedsl.analysis.SchemaTypeRefContext
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
import org.gradle.internal.declarativedsl.dom.DocumentResolution
import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult
Expand All @@ -57,6 +61,8 @@ import java.io.File
import java.net.URI
import java.util.concurrent.CompletableFuture

private val LOGGER = LoggerFactory.getLogger(DeclarativeTextDocumentService::class.java)

class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware {
private val documentStore = VersionedDocumentStore()

Expand Down Expand Up @@ -153,9 +159,9 @@ class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware
params.position,
DeclarativeDocument.DocumentNode.ElementNode::class
)
).matchingNode?.withDataClass(dom.overlayResolutionContainer) { dataClass ->
).matchingNode?.getDataClass(dom.overlayResolutionContainer)?.let { dataClass ->
computePropertyCompletions(dataClass) + computeFunctionCompletions(dataClass)
}.orEmpty()
}
}
}.orEmpty().toMutableList()
return CompletableFuture.completedFuture(Either.forLeft(completions))
Expand All @@ -168,19 +174,22 @@ class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware
val uri = URI(params.textDocument.uri)
withDom(uri) { dom ->
val position = params.position
val matchingNode = dom.document.visit(
val matchingNodes = dom.document.visit(
BestFittingNodeVisitor(
position,
DeclarativeDocument.DocumentNode::class
DeclarativeDocument.DocumentNode.ElementNode::class
)
).matchingNode

matchingNode?.withDataClass(dom.overlayResolutionContainer) { node ->
node.constructors.map { ctor ->
SignatureInformation(ctor.simpleName).apply {
parameters = ctor.parameters.map { parameter ->
ParameterInformation(parameter.name)
}
).matchingNodes

val targetNode = matchingNodes[matchingNodes.size - 1]
val containerNode = matchingNodes[matchingNodes.size - 2]

containerNode.getDataClass(dom.overlayResolutionContainer)?.memberFunctions?.filter { function ->
function.simpleName == targetNode.name
}?.map { function ->
SignatureInformation(computeFunctionSignature(function)).apply {
parameters = function.parameters.map { parameter ->
ParameterInformation(computeParameterSignature(parameter))
}
}
}
Expand Down Expand Up @@ -258,11 +267,15 @@ class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware
}
}

companion object {
private val LOGGER = LoggerFactory.getLogger(DeclarativeTextDocumentService::class.java)
private fun DataTypeRef.resolve(): DataType {
val schemaTypeRefContext = SchemaTypeRefContext(resources.analysisSchema)
return schemaTypeRefContext.resolveRef(this)
}

}

// Pure functions supporting LSP functions -----------------------------------------------------------------------------

private fun computePropertyCompletions(
dataClass: DataClass
): List<CompletionItem> =
Expand Down Expand Up @@ -303,13 +316,24 @@ private fun computeCompletionInsertText(function: SchemaFunction): String {
return "${function.simpleName}($parameterSnippet)$0"
}

private fun computeFunctionSignature(function: SchemaFunction): String {
val parameterSignatures = function.parameters.map { parameter ->
computeParameterSignature(parameter)
}.joinToString(", ")

return "${function.simpleName}(${parameterSignatures})"
}

private fun computeCompletionParameterLabel(schemaFunction: SchemaFunction): String? {
val parameterNames = schemaFunction.parameters.joinToString(", ") { parameter ->
"${parameter.name}: ${parameter.type}"
computeParameterSignature(parameter)
}
return if (parameterNames.isNotEmpty()) "($parameterNames)" else null
}

private fun computeParameterSignature(parameter: DataParameter) =
"${parameter.name}: ${parameter.type}"

private fun computeCompletionConfigurabilityLabel(semantics: FunctionSemantics): String? = when (semantics) {
is FunctionSemantics.ConfigureSemantics -> when (semantics.configureBlockRequirement) {
is FunctionSemantics.ConfigureSemantics.ConfigureBlockRequirement.Required -> " { this: ${semantics.configuredType} }"
Expand All @@ -320,19 +344,20 @@ private fun computeCompletionConfigurabilityLabel(semantics: FunctionSemantics):
else -> null
}

// Extension functions -------------------------------------------------------------------------------------------------

/**
* Calls the specified function block if the element resolution was successful, and the element type is a data class.
* Tries to resolve the data class of the given node. If the resolution fails, returns `null`.
*/
private fun <N : DeclarativeDocument.DocumentNode, R> N.withDataClass(
resolutionContainer: DocumentResolutionContainer,
function: (DataClass) -> R
): R? = when (val nodeType = resolutionContainer.data(this)) {
private fun <N : DeclarativeDocument.DocumentNode> N.getDataClass(
resolutionContainer: DocumentResolutionContainer
): DataClass? = when (val nodeType = resolutionContainer.data(this)) {
is DocumentResolution.ElementResolution.SuccessfulElementResolution -> {
when (val elementType = nodeType.elementType) {
is DataClass -> function(elementType)
is DataClass -> elementType
else -> null
}
}

else -> null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ class BestFittingNodeVisitor<T: DeclarativeDocument.Node>(
) : DocumentVisitor() {

/** The node best-fitting the cursor position. */
var matchingNode: T? = null
var matchingNodes: List<T> = mutableListOf()

// Property to get the last node
val matchingNode: T?
get() = matchingNodes.lastOrNull()

override fun visitNode(node: DeclarativeDocument.Node) {
val nodeRange = node.sourceData.toLspRange()
if (nodeType.isInstance(node) && Ranges.containsPosition(nodeRange, position))
matchingNode = nodeType.cast(node)
matchingNodes += nodeType.cast(node)
}

}

0 comments on commit 8d4b21c

Please sign in to comment.