@@ -43,11 +43,14 @@ import org.eclipse.lsp4j.services.TextDocumentService
43
43
import org.gradle.declarative.dsl.schema.AnalysisSchema
44
44
import org.gradle.declarative.dsl.schema.DataClass
45
45
import org.gradle.declarative.dsl.schema.DataParameter
46
+ import org.gradle.declarative.dsl.schema.DataProperty
46
47
import org.gradle.declarative.dsl.schema.DataType
47
48
import org.gradle.declarative.dsl.schema.DataTypeRef
48
49
import org.gradle.declarative.dsl.schema.EnumClass
50
+ import org.gradle.declarative.dsl.schema.FqName
49
51
import org.gradle.declarative.dsl.schema.FunctionSemantics
50
52
import org.gradle.declarative.dsl.schema.SchemaFunction
53
+ import org.gradle.declarative.dsl.schema.SchemaMemberFunction
51
54
import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel
52
55
import org.gradle.declarative.lsp.extension.indexBasedOverlayResultFromDocuments
53
56
import org.gradle.declarative.lsp.extension.toLspRange
@@ -93,7 +96,7 @@ class DeclarativeTextDocumentService : TextDocumentService {
93
96
this .documentStore = documentStore
94
97
this .mutationRegistry = mutationRegistry
95
98
this .declarativeResources = declarativeResources
96
-
99
+
97
100
this .schemaAnalysisEvaluator = SimpleAnalysisEvaluator .withSchema(
98
101
declarativeResources.settingsInterpretationSequence,
99
102
declarativeResources.projectInterpretationSequence
@@ -183,8 +186,11 @@ class DeclarativeTextDocumentService : TextDocumentService {
183
186
?.getDataClass(dom.overlayResolutionContainer)
184
187
.let { it ? : schema.topLevelReceiverType }
185
188
.let { dataClass ->
186
- computePropertyCompletions(dataClass, schema) +
187
- computeFunctionCompletions(dataClass, schema)
189
+ computePropertyCompletions(dataClass, schema) + computePropertyByValueFactoryCompletions(
190
+ dataClass,
191
+ schema
192
+ ) +
193
+ computeFunctionCompletions(dataClass, schema)
188
194
}
189
195
}
190
196
}.orEmpty().toMutableList()
@@ -309,7 +315,7 @@ class DeclarativeTextDocumentService : TextDocumentService {
309
315
)
310
316
)
311
317
}
312
-
318
+
313
319
data class ParsedDocument (
314
320
val documentOverlayResult : DocumentOverlayResult ,
315
321
val analysisSchemas : List <AnalysisSchema >
@@ -318,7 +324,7 @@ class DeclarativeTextDocumentService : TextDocumentService {
318
324
private fun parse (uri : URI , text : String ): ParsedDocument {
319
325
val fileName = uri.path.substringAfterLast(' /' )
320
326
val analysisResult = schemaAnalysisEvaluator.evaluate(fileName, text)
321
-
327
+
322
328
// Workaround: for now, the mutation utilities cannot handle mutations that touch the underlay document content.
323
329
// To avoid that, use the utility that produces an overlay result with no real underlay content.
324
330
// This utility also takes care of multi-step resolution results and merges them, presenting .
@@ -351,14 +357,77 @@ private fun computePropertyCompletions(
351
357
dataClass : DataClass ,
352
358
analysisSchema : AnalysisSchema
353
359
): List <CompletionItem > {
354
- return dataClass.properties.map { property ->
355
- val propertyName = property.name
356
- val targetType = property.valueType.toSimpleName()
360
+ return dataClass.properties.mapNotNull { property ->
361
+ when (val resolvedType = SchemaTypeRefContext (analysisSchema).resolveRef(property.valueType)) {
362
+ is EnumClass -> completionItem(property, resolvedType)
363
+ is DataType .BooleanDataType -> completionItem(property, resolvedType)
364
+ is DataType .IntDataType -> completionItem(property, resolvedType)
365
+ is DataType .LongDataType -> completionItem(property, resolvedType)
366
+ is DataType .StringDataType -> completionItem(property, resolvedType)
367
+ else -> null
368
+ }
369
+ }
370
+ }
357
371
358
- CompletionItem (" $propertyName = $targetType " ).apply {
359
- kind = CompletionItemKind .Field
360
- insertTextFormat = InsertTextFormat .Snippet
361
- insertText = " ${property.name} = ${computeTypedPlaceholder(1 , property.valueType, analysisSchema)} "
372
+ private fun completionItem (property : DataProperty , resolvedType : DataType ) =
373
+ CompletionItem (" ${property.name} = ${property.valueType.toSimpleName()} " ).apply {
374
+ kind = CompletionItemKind .Field
375
+ insertTextFormat = InsertTextFormat .Snippet
376
+ insertText = " ${property.name} = ${computeTypedPlaceholder(1 , resolvedType)} "
377
+ }
378
+
379
+ private typealias LabelAndInsertText = Pair <String , String >
380
+
381
+ private fun computePropertyByValueFactoryCompletions (
382
+ dataClass : DataClass ,
383
+ analysisSchema : AnalysisSchema
384
+ ): List <CompletionItem > {
385
+ fun indexValueFactories (analysisSchema : AnalysisSchema , type : DataClass , namePrefix : String ): Map <FqName , List <LabelAndInsertText >> {
386
+ val factoryIndex = mutableMapOf<FqName , List <LabelAndInsertText >>()
387
+ type.memberFunctions
388
+ .filter { it.semantics is FunctionSemantics .Pure && it.returnValueType is DataTypeRef .Name }
389
+ .forEach {
390
+ val indexKey = (it.returnValueType as DataTypeRef .Name ).fqName
391
+ val labelAndInsertText = " $namePrefix${computeCompletionLabel(it)} " to " $namePrefix${
392
+ computeCompletionInsertText(
393
+ it,
394
+ analysisSchema
395
+ )
396
+ } "
397
+ factoryIndex.merge(indexKey, listOf (labelAndInsertText)) { oldVal, newVal -> oldVal + newVal }
398
+ }
399
+ type.properties.filter { it.valueType is DataTypeRef .Name }.forEach {
400
+ when (val propType = analysisSchema.dataClassTypesByFqName[(it.valueType as DataTypeRef .Name ).fqName]) {
401
+ is DataClass -> {
402
+ val propName = it.name
403
+ val propIndex = indexValueFactories(analysisSchema, propType, " $namePrefix${propName} ." )
404
+ propIndex.forEach { (key, value) ->
405
+ factoryIndex.merge(key, value) { oldVal, newVal -> oldVal + newVal }
406
+ }
407
+ }
408
+
409
+ is EnumClass -> Unit
410
+ null -> Unit
411
+ }
412
+ }
413
+ return factoryIndex
414
+ }
415
+
416
+ val factories = indexValueFactories(analysisSchema, analysisSchema.topLevelReceiverType, " " )
417
+ return dataClass.properties.flatMap { property ->
418
+ val resolvedType = SchemaTypeRefContext (analysisSchema).resolveRef(property.valueType)
419
+ if (resolvedType is DataType .ClassDataType ) {
420
+ val factoriesForProperty = factories[resolvedType.name]
421
+ factoriesForProperty
422
+ ?.map {
423
+ CompletionItem (" ${property.name} = ${it.first} " ).apply {
424
+ kind = CompletionItemKind .Field
425
+ insertTextFormat = InsertTextFormat .Snippet
426
+ insertText = " ${property.name} = ${it.second} "
427
+ }
428
+ } ? : emptyList()
429
+ } else {
430
+ emptyList()
362
431
}
363
432
}
364
433
}
@@ -368,28 +437,56 @@ private fun computeFunctionCompletions(
368
437
analysisSchema : AnalysisSchema
369
438
): List <CompletionItem > =
370
439
dataClass.memberFunctions.map { function ->
371
- val functionName = function.simpleName
372
- val parameterSignature = when (function.parameters.isEmpty()) {
373
- true -> " "
374
- false -> function.parameters.joinToString(" ," , " (" , " )" ) { it.toSignatureLabel() }
375
- }
376
- val configureBlockLabel = function.semantics.toBlockConfigurabilityLabel().orEmpty()
377
-
378
- CompletionItem (" $functionName$parameterSignature$configureBlockLabel " ).apply {
440
+ val label = computeCompletionLabel(function)
441
+ val text = computeCompletionInsertText(function, analysisSchema)
442
+ CompletionItem (label).apply {
379
443
kind = CompletionItemKind .Method
380
444
insertTextFormat = InsertTextFormat .Snippet
381
445
insertTextMode = InsertTextMode .AdjustIndentation
382
- insertText = computeCompletionInsertText(function, analysisSchema)
446
+ insertText = text
383
447
}
384
448
}
385
449
450
+ /* *
451
+ * Computes a [placeholder](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#placeholders)
452
+ * based on the given data type.
453
+ *
454
+ * If there is a specific placeholder for the given data type, it will be used.
455
+ * Otherwise, a simple indexed will be used
456
+ */
457
+ private fun computeTypedPlaceholder (
458
+ index : Int ,
459
+ resolvedType : DataType
460
+ ): String {
461
+ return when (resolvedType) {
462
+ is DataType .BooleanDataType -> " \$ {$index |true,false|}"
463
+ is EnumClass -> " \$ {$index |${resolvedType.entryNames.joinToString(" ," )} |}"
464
+ is DataType .IntDataType -> " \$ {$index :0}"
465
+ is DataType .LongDataType -> " \$ {$index :0L}"
466
+ is DataType .StringDataType -> " \" \$ {$index }\" "
467
+ else -> " \$ $index "
468
+ }
469
+ }
470
+
471
+ private fun computeCompletionLabel (function : SchemaMemberFunction ): String {
472
+ val functionName = function.simpleName
473
+ val parameterSignature = when (function.parameters.isEmpty()) {
474
+ true -> " "
475
+ false -> function.parameters.joinToString(" ," , " (" , " )" ) { it.toSignatureLabel() }
476
+ }
477
+ val configureBlockLabel = function.semantics.toBlockConfigurabilityLabel().orEmpty()
478
+
479
+ return " $functionName$parameterSignature$configureBlockLabel "
480
+ }
481
+
386
482
private fun computeCompletionInsertText (
387
483
function : SchemaFunction ,
388
- analysisSchema : AnalysisSchema
484
+ analysisSchema : AnalysisSchema ,
389
485
): String {
390
486
val parameterSnippet = function.parameters.mapIndexed { index, parameter ->
391
487
// Additional placeholders are indexed from 1
392
- computeTypedPlaceholder(index + 1 , parameter.type, analysisSchema)
488
+ val resolvedType = SchemaTypeRefContext (analysisSchema).resolveRef(parameter.type)
489
+ computeTypedPlaceholder(index + 1 , resolvedType)
393
490
}.joinToString(" , " , " (" , " )" )
394
491
395
492
return when (val semantics = function.semantics) {
@@ -416,28 +513,6 @@ private fun computeCompletionInsertText(
416
513
}
417
514
}
418
515
419
- /* *
420
- * Computes a [placeholder](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#placeholders)
421
- * based on the given data type.
422
- *
423
- * If there is a specific placeholder for the given data type, it will be used.
424
- * Otherwise, a simple indexed will be used
425
- */
426
- private fun computeTypedPlaceholder (
427
- index : Int ,
428
- type : DataTypeRef ,
429
- analysisSchema : AnalysisSchema
430
- ): String {
431
- return when (val resolvedType = SchemaTypeRefContext (analysisSchema).resolveRef(type)) {
432
- is DataType .BooleanDataType -> " \$ {$index |true,false|}"
433
- is EnumClass -> " \$ {$index |${resolvedType.entryNames.joinToString(" ," )} |}"
434
- is DataType .IntDataType -> " \$ {$index :0}"
435
- is DataType .LongDataType -> " \$ {$index :0L}"
436
- is DataType .StringDataType -> " \" \$ {$index }\" "
437
- else -> " \$ $index "
438
- }
439
- }
440
-
441
516
// Extension functions -------------------------------------------------------------------------------------------------
442
517
443
518
// TODO: this might not be the best way to resolve the type name, but it works for now
0 commit comments