Skip to content

Commit 5cb3be6

Browse files
committed
wip
1 parent 1ba18ef commit 5cb3be6

16 files changed

+2110
-1
lines changed

modules/core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515

1616
bundledPlugin("com.intellij.java")
1717
bundledPlugin("org.jetbrains.plugins.terminal")
18+
bundledPlugin("org.toml.lang")
1819

1920
jetbrainsRuntime()
2021
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.github.l34130.mise.core.lang
2+
3+
import com.github.l34130.mise.core.icon.MiseIcons
4+
import com.intellij.openapi.fileTypes.LanguageFileType
5+
import javax.swing.Icon
6+
7+
object MiseTomlFileType : LanguageFileType(MiseTomlLanguage) {
8+
override fun getName(): String = "mise"
9+
10+
override fun getDescription(): String = "Mise Configuration file"
11+
12+
override fun getDefaultExtension(): String = "toml"
13+
14+
override fun getIcon(): Icon = MiseIcons.DEFAULT
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.github.l34130.mise.core.lang
2+
3+
import com.intellij.lang.Language
4+
import org.toml.lang.TomlLanguage
5+
6+
object MiseTomlLanguage : Language(TomlLanguage, "MiseToml")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.l34130.mise.core.lang.completion
2+
3+
import com.github.l34130.mise.core.lang.psi.MiseTomlPsiPatterns
4+
import com.intellij.codeInsight.completion.CompletionContributor
5+
import com.intellij.codeInsight.completion.CompletionType
6+
7+
class MiseTomlCompletionContributor : CompletionContributor() {
8+
init {
9+
extend(CompletionType.BASIC, MiseTomlPsiPatterns.inTaskDependsArray, MiseTomlTaskCompletionProvider())
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.github.l34130.mise.core.lang.completion
2+
3+
import com.github.l34130.mise.core.lang.psi.MiseTomlFile
4+
import com.github.l34130.mise.core.lang.psi.allTasks
5+
import com.github.l34130.mise.core.lang.psi.stringValue
6+
import com.github.l34130.mise.core.lang.psi.taskName
7+
import com.intellij.codeInsight.completion.CompletionParameters
8+
import com.intellij.codeInsight.completion.CompletionProvider
9+
import com.intellij.codeInsight.completion.CompletionResultSet
10+
import com.intellij.codeInsight.lookup.LookupElementBuilder
11+
import com.intellij.psi.util.parentOfType
12+
import com.intellij.util.ProcessingContext
13+
import org.toml.lang.psi.TomlArray
14+
import org.toml.lang.psi.TomlTable
15+
16+
/**
17+
* ```
18+
* [tasks.foo]
19+
* ...
20+
*
21+
* [tasks.<task-name>]
22+
* depends = [ "f<caret>" ]
23+
* #^ Provides completion for "foo"
24+
*/
25+
class MiseTomlTaskCompletionProvider : CompletionProvider<CompletionParameters>() {
26+
override fun addCompletions(
27+
parameters: CompletionParameters,
28+
context: ProcessingContext,
29+
result: CompletionResultSet,
30+
) {
31+
val element = parameters.position
32+
val miseTomlFile = element.containingFile as? MiseTomlFile ?: return
33+
34+
val dependsArray = (element.parent.parent as? TomlArray) ?: return
35+
36+
val parentTable = element.parentOfType<TomlTable>() ?: return
37+
val parentTaskName = parentTable.taskName
38+
39+
for (task in miseTomlFile.allTasks()) {
40+
val taskName = task.name ?: continue
41+
if (dependsArray.elements.any { it.stringValue == taskName }) continue
42+
if (taskName == parentTaskName) continue
43+
44+
result.addElement(
45+
LookupElementBuilder
46+
.createWithSmartPointer(taskName, task)
47+
.withInsertHandler(StringLiteralInsertionHandler()),
48+
)
49+
}
50+
}
51+
52+
// TODO: Need to support literal string completion
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.github.l34130.mise.core.lang.completion
2+
3+
import com.intellij.codeInsight.completion.InsertHandler
4+
import com.intellij.codeInsight.completion.InsertionContext
5+
import com.intellij.codeInsight.lookup.LookupElement
6+
import com.intellij.psi.PsiElement
7+
import com.intellij.psi.tree.TokenSet
8+
import com.intellij.psi.util.PsiTreeUtil
9+
import org.toml.lang.psi.TomlElementTypes
10+
import org.toml.lang.psi.TomlLiteral
11+
import org.toml.lang.psi.ext.elementType
12+
13+
class StringLiteralInsertionHandler : InsertHandler<LookupElement> {
14+
override fun handleInsert(
15+
context: InsertionContext,
16+
item: LookupElement,
17+
) {
18+
val leaf = context.getElementOfType<PsiElement>() ?: return
19+
val elementType = (leaf.parent as? TomlLiteral)?.elementType
20+
21+
val hasQuotes = elementType in tokenSet
22+
if (!hasQuotes) {
23+
context.document.insertString(context.startOffset, "\"")
24+
context.document.insertString(context.selectionEndOffset, "\"")
25+
}
26+
}
27+
28+
private val tokenSet =
29+
TokenSet.create(
30+
TomlElementTypes.BASIC_STRING,
31+
TomlElementTypes.LITERAL_STRING,
32+
TomlElementTypes.LITERAL,
33+
)
34+
}
35+
36+
inline fun <reified T : PsiElement> InsertionContext.getElementOfType(strict: Boolean = false): T? =
37+
PsiTreeUtil.findElementOfClassAtOffset(file, tailOffset - 1, T::class.java, strict)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.l34130.mise.core.lang.json
2+
3+
import com.github.l34130.mise.core.lang.MiseTomlFileType
4+
import com.intellij.openapi.project.DumbAware
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.vfs.VirtualFile
7+
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider
8+
import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory
9+
import com.jetbrains.jsonSchema.extension.SchemaType
10+
11+
class MiseTomlJsonSchemaFileProviderFactory :
12+
JsonSchemaProviderFactory,
13+
DumbAware {
14+
override fun getProviders(project: Project): List<JsonSchemaFileProvider> = listOf(MiseTomlJsonSchemaFileProvider())
15+
16+
class MiseTomlJsonSchemaFileProvider : JsonSchemaFileProvider {
17+
override fun isAvailable(file: VirtualFile): Boolean = file.fileType is MiseTomlFileType
18+
19+
override fun getName(): String = "mise"
20+
21+
override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema
22+
23+
override fun isUserVisible(): Boolean = false
24+
25+
override fun getSchemaFile(): VirtualFile? = JsonSchemaProviderFactory.getResourceFile(javaClass, "/schemas/mise.json")
26+
}
27+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.github.l34130.mise.core.lang.json
2+
3+
import com.github.l34130.mise.core.lang.MiseTomlLanguage
4+
import com.intellij.psi.PsiElement
5+
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker
6+
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalkerFactory
7+
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
8+
import org.toml.ide.json.TomlJsonPsiWalker
9+
10+
class MiseTomlPsiWalkerFactory : JsonLikePsiWalkerFactory {
11+
override fun handles(element: PsiElement): Boolean = element.containingFile.language is MiseTomlLanguage
12+
13+
override fun create(schemaObject: JsonSchemaObject): JsonLikePsiWalker = TomlJsonPsiWalker
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.github.l34130.mise.core.lang.psi
2+
3+
import com.github.l34130.mise.core.lang.MiseTomlFileType
4+
import com.github.l34130.mise.core.lang.MiseTomlLanguage
5+
import com.intellij.extapi.psi.PsiFileBase
6+
import com.intellij.psi.FileViewProvider
7+
8+
class MiseTomlFile(
9+
viewProvider: FileViewProvider,
10+
) : PsiFileBase(viewProvider, MiseTomlLanguage) {
11+
override fun getFileType() = MiseTomlFileType
12+
13+
override fun toString() = "Mise Toml File"
14+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.l34130.mise.core.lang.psi
2+
3+
import com.github.l34130.mise.core.lang.MiseTomlLanguage
4+
import com.intellij.lang.ASTNode
5+
import com.intellij.lang.ParserDefinition
6+
import com.intellij.lang.PsiParser
7+
import com.intellij.lexer.Lexer
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.psi.FileViewProvider
10+
import com.intellij.psi.PsiElement
11+
import com.intellij.psi.PsiFile
12+
import com.intellij.psi.tree.IFileElementType
13+
import com.intellij.psi.tree.TokenSet
14+
import org.toml.lang.lexer.TomlLexer
15+
import org.toml.lang.parse.TomlParser
16+
import org.toml.lang.psi.TOML_COMMENTS
17+
18+
class MiseTomlParserDefinition : ParserDefinition {
19+
override fun createLexer(project: Project): Lexer = TomlLexer()
20+
21+
override fun createParser(project: Project): PsiParser = TomlParser()
22+
23+
override fun getFileNodeType(): IFileElementType = FILE
24+
25+
override fun getCommentTokens(): TokenSet = TOML_COMMENTS
26+
27+
override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
28+
29+
override fun createElement(node: ASTNode): PsiElement = throw UnsupportedOperationException()
30+
31+
override fun createFile(viewProvider: FileViewProvider): PsiFile = MiseTomlFile(viewProvider)
32+
33+
companion object {
34+
val FILE = IFileElementType(MiseTomlLanguage)
35+
}
36+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.github.l34130.mise.core.lang.psi
2+
3+
import com.github.l34130.mise.core.lang.MiseTomlFileType
4+
import com.intellij.patterns.ObjectPattern
5+
import com.intellij.patterns.PatternCondition
6+
import com.intellij.patterns.PlatformPatterns
7+
import com.intellij.patterns.PsiElementPattern
8+
import com.intellij.patterns.StandardPatterns
9+
import com.intellij.patterns.VirtualFilePattern
10+
import com.intellij.psi.PsiElement
11+
import com.intellij.util.ProcessingContext
12+
import org.toml.lang.psi.TomlArray
13+
import org.toml.lang.psi.TomlKeyValue
14+
import org.toml.lang.psi.TomlLiteral
15+
import org.toml.lang.psi.TomlTable
16+
import org.toml.lang.psi.TomlTableHeader
17+
import org.toml.lang.psi.ext.TomlLiteralKind
18+
import org.toml.lang.psi.ext.kind
19+
import org.toml.lang.psi.ext.name
20+
21+
object MiseTomlPsiPatterns {
22+
private inline fun <reified I : PsiElement> miseTomlPsiElement(): PsiElementPattern.Capture<I> =
23+
psiElement<I>().inVirtualFile(
24+
VirtualFilePattern().ofType(MiseTomlFileType),
25+
)
26+
27+
fun miseTomlStringLiteral() = miseTomlPsiElement<TomlLiteral>().with("stringLiteral") { e, _ -> e.kind is TomlLiteralKind.String }
28+
29+
private val onSpecificTaskTable =
30+
miseTomlPsiElement<TomlTable>()
31+
.withChild(
32+
psiElement<TomlTableHeader>()
33+
.with("specificTaskCondition") { header, _ ->
34+
header.isSpecificTaskTableHeader
35+
},
36+
)
37+
38+
/**
39+
* ```
40+
* [tasks]
41+
* foo = { $name = [] }
42+
* #^
43+
* ```
44+
*
45+
* ```
46+
* [tasks.foo]
47+
* $name = []
48+
* #^
49+
* ```
50+
*/
51+
private fun taskProperty(name: String) =
52+
psiElement<TomlKeyValue>()
53+
.with("name") { e, _ -> e.key.name == name }
54+
.withParent(
55+
onSpecificTaskTable,
56+
// onSpecificTaskTable.andOr(
57+
// psiElement<TomlInlineTable>().withSuperParent(2, onTaskTable),
58+
// ),
59+
)
60+
61+
/**
62+
* ```
63+
* [tasks]
64+
* foo = { version = "*", depends = [] }
65+
* #^
66+
* ```
67+
*
68+
* ```
69+
* [tasks.foo]
70+
* depends = []
71+
* #^
72+
* ```
73+
*/
74+
private val onTaskDependsArray =
75+
StandardPatterns.or(
76+
psiElement<TomlArray>()
77+
.withParent(taskProperty("depends")),
78+
psiElement<TomlArray>()
79+
.withParent(taskProperty("depends_post")),
80+
)
81+
val inTaskDependsArray = miseTomlPsiElement<PsiElement>().inside(onTaskDependsArray)
82+
83+
inline fun <reified I : PsiElement> psiElement() = PlatformPatterns.psiElement(I::class.java)
84+
85+
fun <T : Any, Self : ObjectPattern<T, Self>> ObjectPattern<T, Self>.with(
86+
name: String,
87+
cond: (T, ProcessingContext?) -> Boolean,
88+
): Self =
89+
with(
90+
object : PatternCondition<T>(name) {
91+
override fun accepts(
92+
t: T,
93+
context: ProcessingContext?,
94+
): Boolean = cond(t, context)
95+
},
96+
)
97+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.github.l34130.mise.core.lang.psi
2+
3+
import com.intellij.psi.util.childrenOfType
4+
import org.toml.lang.psi.TomlInlineTable
5+
import org.toml.lang.psi.TomlKeySegment
6+
import org.toml.lang.psi.TomlKeyValueOwner
7+
import org.toml.lang.psi.TomlLiteral
8+
import org.toml.lang.psi.TomlTable
9+
import org.toml.lang.psi.TomlTableHeader
10+
import org.toml.lang.psi.TomlValue
11+
import org.toml.lang.psi.ext.TomlLiteralKind
12+
import org.toml.lang.psi.ext.kind
13+
14+
fun MiseTomlFile.allTasks(): Sequence<TomlKeySegment> {
15+
val explicitTasks = hashSetOf<String>()
16+
17+
return childrenOfType<TomlTable>()
18+
.asSequence()
19+
.flatMap { table ->
20+
val header = table.header
21+
when {
22+
// [tasks]
23+
// <task-name> = { ... }
24+
header.isTaskListHeader -> {
25+
table.entries
26+
.asSequence()
27+
.filter { (it.value as? TomlInlineTable) != null }
28+
.mapNotNull { it.key.segments.singleOrNull() }
29+
.filter { it.name !in explicitTasks }
30+
}
31+
32+
// [tasks.<task-name>]
33+
header.isSpecificTaskTableHeader -> {
34+
val lastKey = header.key?.segments?.last()
35+
if (lastKey != null && lastKey.name !in explicitTasks) {
36+
sequenceOf(lastKey)
37+
} else {
38+
emptySequence()
39+
}
40+
}
41+
else -> emptySequence()
42+
}
43+
}.constrainOnce()
44+
}
45+
46+
val TomlTable.taskName: String?
47+
get() {
48+
if (header.isSpecificTaskTableHeader) {
49+
val headerKey = header.key ?: return null
50+
return headerKey.segments.lastOrNull()?.name
51+
}
52+
return null
53+
}
54+
55+
val TomlTableHeader.isTaskListHeader: Boolean
56+
get() = key?.segments?.lastOrNull()?.name == "tasks"
57+
58+
val TomlTableHeader.isSpecificTaskTableHeader: Boolean
59+
get() {
60+
val names = key?.segments.orEmpty()
61+
return names.getOrNull(names.size - 2)?.name == "tasks"
62+
}
63+
64+
fun TomlKeyValueOwner.getValueWithKey(key: String): TomlValue? = entries.find { it.key.text == key }?.value
65+
66+
val TomlValue.stringValue: String?
67+
get() {
68+
val kind = (this as? TomlLiteral)?.kind
69+
return (kind as? TomlLiteralKind.String)?.value
70+
}

0 commit comments

Comments
 (0)