Skip to content

Commit

Permalink
refactor: Refactor tasks.depends completion contributor (#125)
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
134130 authored Jan 5, 2025
1 parent 1ba18ef commit 6dd9615
Show file tree
Hide file tree
Showing 17 changed files with 2,084 additions and 53 deletions.
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,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,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,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

0 comments on commit 6dd9615

Please sign in to comment.