[toml] IDEA-288468 Provide completion for values

GitOrigin-RevId: b46aaa32f7f4c93b46d6d8545f0ea41b32dcb3bc
This commit is contained in:
Aleksei Trifonov
2022-02-21 22:01:19 +00:00
committed by intellij-monorepo-bot
parent 17b6900b88
commit 679bc3f560
6 changed files with 188 additions and 0 deletions
@@ -69,4 +69,26 @@ class TomlCompletionFixture(
error("Expected completions that don't contain $variants, but got ${lookups.map { it.render() }}")
}
}
private fun executeSoloCompletion() {
val lookups = myFixture.completeBasic()
if (lookups != null) {
if (lookups.size == 1) {
// for cases like `frob/*caret*/nicate()`,
// completion won't be selected automatically.
myFixture.type('\n')
return
}
fun LookupElement.debug(): String = "$lookupString ($psiElement)"
error("Expected a single completion, but got ${lookups.size}\n"
+ lookups.joinToString("\n") { it.debug() })
}
}
fun doSingleCompletion(code: String, after: String) {
myFixture.configureByText(defaultFileName, code.trimIndent())
executeSoloCompletion()
myFixture.checkResult(after.trimIndent())
}
}
@@ -29,4 +29,9 @@ abstract class TomlJsonSchemaCompletionTestBase : TomlJsonSchemaTestBase() {
@Language("TOML") code: String,
render: LookupElement.() -> String = { lookupString }
) = completionFixture.checkNotContainsCompletion(variants, code, render)
fun doSingleCompletion(
@Language("TOML") before: String,
@Language("TOML") after: String
) = completionFixture.doSingleCompletion(before, after)
}
@@ -0,0 +1,93 @@
package org.toml.ide.json
class TomlJsonSchemaValueCompletionTest : TomlJsonSchemaCompletionTestBase() {
fun `test enum variants`() = checkContainsCompletion(setOf("\"2015\"", "\"2018\"", "\"2021\""), """
[package]
edition = <caret>
""")
fun `test variants inside array table`() = checkContainsCompletion(setOf("\"\""), """
[[bin]]
name = <caret>
""")
fun `test string literal inside literal variants`() = checkNotContainsCompletion(setOf("\"\""), """
[package]
name = "<caret>"
""")
fun `test boolean value variants`() = checkContainsCompletion(setOf("true", "false"), """
[[bin]]
test = <caret>
""")
// TODO: Support value completion in inline tables
fun `test variants inside inline table`() {
assertThrows(IllegalStateException::class.java) {
checkContainsCompletion(setOf("\"\""), """
package = { name = <caret> }
""")
}
}
fun `test inside inside inline array`() {
assertThrows(IllegalStateException::class.java) {
checkContainsCompletion(setOf("{}"), """
bin = [<caret>]
""")
}
}
fun `test variants inside inline array and table`() {
assertThrows(IllegalStateException::class.java) {
checkContainsCompletion(setOf("\"\""), """
bin = [{ name = <caret> }]
""")
}
}
fun `test enum string value completion in literal`() = doSingleCompletion("""
[package]
edition = "21<caret>"
""", """
[package]
edition = "2021<caret>"
""")
fun `test enum string value completion`() = doSingleCompletion("""
[package]
edition = 21<caret>
""", """
[package]
edition = "2021"<caret>
""")
fun `test array completion`() = doSingleCompletion("""
[package]
authors = <caret>
""", """
[package]
authors = [<caret>]
""")
fun `test inline table completion`() = doSingleCompletion("""
[dependencies]
foo = <caret>
""", """
[dependencies]
foo = {<caret>}
""")
fun `test string literal completion`() = doSingleCompletion("""
[package]
name = <caret>
""", """
[package]
name = "<caret>"
""")
fun `test number enum variants`() = checkContainsCompletion(setOf("1", "2"), """
[foo]
number-enum = <caret>
""")
}
@@ -39,6 +39,9 @@
},
"path": {
"type": "string"
},
"test": {
"type": "boolean"
}
}
}
@@ -96,6 +99,10 @@
},
"a": {
"type": "integer"
},
"number-enum": {
"type": "number",
"enum": [1, 2, 3]
}
}
}
@@ -51,6 +51,19 @@ object TomlJsonPsiWalker : JsonLikePsiWalker {
val parentKey = parent.header.key ?: break
// add table header segments to process all the previous siblings to handle nested array tables cases
parentKey.segments.mapTo(tableHeaderSegments) { it.name }
// TODO: Workaround, should be fixed in TOML grammar
// if it is a request from inspections, it walks only properties,
// so we don't have path calculation from key or value
if (element is TomlKeyValue) {
val currentKey = current.key
for (segment in currentKey.segments.asReversed()) {
if (segment != element || forceLastTransition) {
position.addPrecedingStep(segment.name)
}
}
}
}
current is TomlValue && parent is TomlArray -> {
if (current != element || forceLastTransition) {
@@ -11,7 +11,9 @@ import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.icons.AllIcons
import com.intellij.openapi.editor.EditorModificationUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentOfType
import com.intellij.util.Consumer
@@ -24,7 +26,10 @@ import com.jetbrains.jsonSchema.impl.JsonSchemaObject
import com.jetbrains.jsonSchema.impl.JsonSchemaResolver
import com.jetbrains.jsonSchema.impl.JsonSchemaType
import org.toml.ide.experiments.TomlExperiments
import org.toml.lang.psi.TomlLiteral
import org.toml.lang.psi.TomlTableHeader
import org.toml.lang.psi.ext.TomlLiteralKind
import org.toml.lang.psi.ext.kind
class TomlJsonSchemaCompletionContributor : CompletionContributor() {
override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
@@ -68,6 +73,10 @@ class TomlJsonSchemaCompletionContributor : CompletionContributor() {
val schemaProperties = schema.properties
addAllPropertyVariants(properties, adapter, schemaProperties, knownNames, originalPosition)
}
if (isName != ThreeState.YES) {
suggestValues(schema, isName == ThreeState.NO)
}
}
for (variant in variants) {
@@ -118,6 +127,45 @@ class TomlJsonSchemaCompletionContributor : CompletionContributor() {
variants.add(lookupElement)
}
private val isInsideStringLiteral: Boolean
get() = (position.parent as? TomlLiteral)?.kind is TomlLiteralKind.String
private fun suggestValues(schema: JsonSchemaObject, isSurelyValue: Boolean) {
val enumVariants = schema.enum
if (enumVariants != null) {
for (o in enumVariants) {
if (isInsideStringLiteral && o !is String) continue
val variant = if (isInsideStringLiteral) {
StringUtil.unquoteString(o.toString())
} else {
o.toString()
}
variants.add(LookupElementBuilder.create(variant))
}
} else if (isSurelyValue) {
variants.addAll(suggestValuesByType(schema.guessType()))
}
}
private fun suggestValuesByType(type: JsonSchemaType?): List<LookupElement> = when (type) {
JsonSchemaType._object -> listOf(buildPairLookupElement("{}"))
JsonSchemaType._array -> listOf(buildPairLookupElement("[]"))
JsonSchemaType._string -> if (isInsideStringLiteral) {
emptyList()
} else {
listOf(buildPairLookupElement("\"\""))
}
JsonSchemaType._boolean -> listOf("true", "false").map { LookupElementBuilder.create(it) }
else -> emptyList()
}
private fun buildPairLookupElement(element: String): LookupElementBuilder =
LookupElementBuilder.create(element)
.withInsertHandler { context, _ ->
EditorModificationUtil.moveCaretRelatively(context.editor, -1)
}
private fun getIconForType(type: JsonSchemaType?) = when (type) {
JsonSchemaType._object -> AllIcons.Json.Object
JsonSchemaType._array -> AllIcons.Json.Array