Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Refactor tasks.depends completion contributor #125

Merged
merged 4 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {

bundledPlugin("com.intellij.java")
bundledPlugin("org.jetbrains.plugins.terminal")
bundledPlugin("org.toml.lang")

jetbrainsRuntime()
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.l34130.mise.core.lang

import com.github.l34130.mise.core.icon.MiseIcons
import com.intellij.openapi.fileTypes.LanguageFileType
import javax.swing.Icon

object MiseTomlFileType : LanguageFileType(MiseTomlLanguage) {
override fun getName(): String = "mise"

override fun getDescription(): String = "Mise Configuration file"

override fun getDefaultExtension(): String = "toml"

override fun getIcon(): Icon = MiseIcons.DEFAULT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.l34130.mise.core.lang

import com.intellij.lang.Language
import org.toml.lang.TomlLanguage

object MiseTomlLanguage : Language(TomlLanguage, "MiseToml")
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.l34130.mise.core.lang.completion

import com.github.l34130.mise.core.lang.psi.MiseTomlPsiPatterns
import com.intellij.codeInsight.completion.CompletionContributor
import com.intellij.codeInsight.completion.CompletionType

class MiseTomlCompletionContributor : CompletionContributor() {
init {
extend(CompletionType.BASIC, MiseTomlPsiPatterns.inTaskDependsArray, MiseTomlTaskCompletionProvider())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.github.l34130.mise.core.lang.completion

import com.github.l34130.mise.core.lang.psi.MiseTomlFile
import com.github.l34130.mise.core.lang.psi.allTasks
import com.github.l34130.mise.core.lang.psi.stringValue
import com.github.l34130.mise.core.lang.psi.taskName
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.psi.util.parentOfType
import com.intellij.util.ProcessingContext
import org.toml.lang.psi.TomlArray
import org.toml.lang.psi.TomlTable

/**
* ```
* [tasks.foo]
* ...
*
* [tasks.<task-name>]
* depends = [ "f<caret>" ]
* #^ Provides completion for "foo"
*/
class MiseTomlTaskCompletionProvider : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet,
) {
val element = parameters.position
val miseTomlFile = element.containingFile as? MiseTomlFile ?: return

val dependsArray = (element.parent.parent as? TomlArray) ?: return

val parentTable = element.parentOfType<TomlTable>() ?: return
val parentTaskName = parentTable.taskName

for (task in miseTomlFile.allTasks()) {
val taskName = task.name ?: continue
if (dependsArray.elements.any { it.stringValue == taskName }) continue
if (taskName == parentTaskName) continue

result.addElement(
LookupElementBuilder
.createWithSmartPointer(taskName, task)
.withInsertHandler(StringLiteralInsertionHandler()),
)
}
}

// TODO: Need to support literal string completion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.github.l34130.mise.core.lang.completion

import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.PsiTreeUtil
import org.toml.lang.psi.TomlElementTypes
import org.toml.lang.psi.TomlLiteral
import org.toml.lang.psi.ext.elementType

class StringLiteralInsertionHandler : InsertHandler<LookupElement> {
override fun handleInsert(
context: InsertionContext,
item: LookupElement,
) {
val leaf = context.getElementOfType<PsiElement>() ?: return
val elementType = (leaf.parent as? TomlLiteral)?.elementType

val hasQuotes = elementType in tokenSet
if (!hasQuotes) {
context.document.insertString(context.startOffset, "\"")
context.document.insertString(context.selectionEndOffset, "\"")
}
}

private val tokenSet =
TokenSet.create(
TomlElementTypes.BASIC_STRING,
TomlElementTypes.LITERAL_STRING,
TomlElementTypes.LITERAL,
)
}

inline fun <reified T : PsiElement> InsertionContext.getElementOfType(strict: Boolean = false): T? =
PsiTreeUtil.findElementOfClassAtOffset(file, tailOffset - 1, T::class.java, strict)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.l34130.mise.core.lang.json

import com.github.l34130.mise.core.lang.MiseTomlFileType
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider
import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory
import com.jetbrains.jsonSchema.extension.SchemaType

class MiseTomlJsonSchemaFileProviderFactory :
JsonSchemaProviderFactory,
DumbAware {
override fun getProviders(project: Project): List<JsonSchemaFileProvider> = listOf(MiseTomlJsonSchemaFileProvider())

class MiseTomlJsonSchemaFileProvider : JsonSchemaFileProvider {
override fun isAvailable(file: VirtualFile): Boolean = file.fileType is MiseTomlFileType

override fun getName(): String = "mise"

override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema

override fun isUserVisible(): Boolean = false

override fun getSchemaFile(): VirtualFile? = JsonSchemaProviderFactory.getResourceFile(javaClass, "/schemas/mise.json")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.l34130.mise.core.lang.json

import com.github.l34130.mise.core.lang.MiseTomlLanguage
import com.intellij.psi.PsiElement
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalkerFactory
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
import org.toml.ide.json.TomlJsonPsiWalker

class MiseTomlPsiWalkerFactory : JsonLikePsiWalkerFactory {
override fun handles(element: PsiElement): Boolean = element.containingFile.language is MiseTomlLanguage

override fun create(schemaObject: JsonSchemaObject): JsonLikePsiWalker = TomlJsonPsiWalker
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.l34130.mise.core.lang.psi

import com.github.l34130.mise.core.lang.MiseTomlFileType
import com.github.l34130.mise.core.lang.MiseTomlLanguage
import com.intellij.extapi.psi.PsiFileBase
import com.intellij.psi.FileViewProvider

class MiseTomlFile(
viewProvider: FileViewProvider,
) : PsiFileBase(viewProvider, MiseTomlLanguage) {
override fun getFileType() = MiseTomlFileType

override fun toString() = "Mise Toml File"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.github.l34130.mise.core.lang.psi

import com.github.l34130.mise.core.lang.MiseTomlLanguage
import com.intellij.lang.ASTNode
import com.intellij.lang.ParserDefinition
import com.intellij.lang.PsiParser
import com.intellij.lexer.Lexer
import com.intellij.openapi.project.Project
import com.intellij.psi.FileViewProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.tree.IFileElementType
import com.intellij.psi.tree.TokenSet
import org.toml.lang.lexer.TomlLexer
import org.toml.lang.parse.TomlParser
import org.toml.lang.psi.TOML_COMMENTS

class MiseTomlParserDefinition : ParserDefinition {
override fun createLexer(project: Project): Lexer = TomlLexer()

override fun createParser(project: Project): PsiParser = TomlParser()

override fun getFileNodeType(): IFileElementType = FILE

override fun getCommentTokens(): TokenSet = TOML_COMMENTS

override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY

override fun createElement(node: ASTNode): PsiElement = throw UnsupportedOperationException()

override fun createFile(viewProvider: FileViewProvider): PsiFile = MiseTomlFile(viewProvider)

companion object {
val FILE = IFileElementType(MiseTomlLanguage)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.github.l34130.mise.core.lang.psi

import com.github.l34130.mise.core.lang.MiseTomlFileType
import com.intellij.patterns.ObjectPattern
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.PlatformPatterns
import com.intellij.patterns.PsiElementPattern
import com.intellij.patterns.StandardPatterns
import com.intellij.patterns.VirtualFilePattern
import com.intellij.psi.PsiElement
import com.intellij.util.ProcessingContext
import org.toml.lang.psi.TomlArray
import org.toml.lang.psi.TomlKeyValue
import org.toml.lang.psi.TomlLiteral
import org.toml.lang.psi.TomlTable
import org.toml.lang.psi.TomlTableHeader
import org.toml.lang.psi.ext.TomlLiteralKind
import org.toml.lang.psi.ext.kind
import org.toml.lang.psi.ext.name

object MiseTomlPsiPatterns {
private inline fun <reified I : PsiElement> miseTomlPsiElement(): PsiElementPattern.Capture<I> =
psiElement<I>().inVirtualFile(
VirtualFilePattern().ofType(MiseTomlFileType),
)

fun miseTomlStringLiteral() = miseTomlPsiElement<TomlLiteral>().with("stringLiteral") { e, _ -> e.kind is TomlLiteralKind.String }

private val onSpecificTaskTable =
miseTomlPsiElement<TomlTable>()
.withChild(
psiElement<TomlTableHeader>()
.with("specificTaskCondition") { header, _ ->
header.isSpecificTaskTableHeader
},
)

/**
* ```
* [tasks]
* foo = { $name = [] }
* #^
* ```
*
* ```
* [tasks.foo]
* $name = []
* #^
* ```
*/
private fun taskProperty(name: String) =
psiElement<TomlKeyValue>()
.with("name") { e, _ -> e.key.name == name }
.withParent(
onSpecificTaskTable,
// onSpecificTaskTable.andOr(
// psiElement<TomlInlineTable>().withSuperParent(2, onTaskTable),
// ),
)

/**
* ```
* [tasks]
* foo = { version = "*", depends = [] }
* #^
* ```
*
* ```
* [tasks.foo]
* depends = []
* #^
* ```
*/
private val onTaskDependsArray =
StandardPatterns.or(
psiElement<TomlArray>()
.withParent(taskProperty("depends")),
psiElement<TomlArray>()
.withParent(taskProperty("depends_post")),
)
val inTaskDependsArray = miseTomlPsiElement<PsiElement>().inside(onTaskDependsArray)

inline fun <reified I : PsiElement> psiElement() = PlatformPatterns.psiElement(I::class.java)

fun <T : Any, Self : ObjectPattern<T, Self>> ObjectPattern<T, Self>.with(
name: String,
cond: (T, ProcessingContext?) -> Boolean,
): Self =
with(
object : PatternCondition<T>(name) {
override fun accepts(
t: T,
context: ProcessingContext?,
): Boolean = cond(t, context)
},
)
}
Loading
Loading