Skip to content

Commit

Permalink
feat(devins-compiler): add support for writing and auto commands #101
Browse files Browse the repository at this point in the history
Add WriteAutoCommand to allow writing content to files. Also, process WRITE commands in DevInsCompiler and execute WriteAutoCommand properly. This enables the handling of /write commands in DevIns files.
  • Loading branch information
phodal committed Mar 16, 2024
1 parent ff4269d commit d7232d8
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 9 deletions.
@@ -1,12 +1,15 @@
package cc.unitmesh.devti.language.compiler

import cc.unitmesh.devti.language.completion.BuiltinCommand
import cc.unitmesh.devti.language.parser.CodeBlockElement
import cc.unitmesh.devti.language.psi.DevInCode
import cc.unitmesh.devti.language.psi.DevInFile
import cc.unitmesh.devti.language.psi.DevInTypes
import cc.unitmesh.devti.language.psi.DevInUsed
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType

data class CompileResult(
Expand All @@ -15,17 +18,28 @@ data class CompileResult(
)

class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Editor? = null) {
private var skipNextCode: Boolean = false
private val logger = logger<DevInsCompiler>()
private val result = CompileResult()
private val output: StringBuilder = StringBuilder()

/**
* Todo: build AST tree, then compile
*/
fun compile(): CompileResult {
file.children.forEach {
when (it.elementType) {
DevInTypes.TEXT_SEGMENT -> output.append(it.text)
DevInTypes.NEWLINE -> output.append("\n")
// todo: add lazy to process code
DevInTypes.CODE -> output.append(it.text)
DevInTypes.CODE -> {
if (skipNextCode) {
skipNextCode = false
return@forEach
}

output.append(it.text)
}

DevInTypes.USED -> processUsed(it as DevInUsed)
else -> {
output.append(it.text)
Expand Down Expand Up @@ -60,7 +74,7 @@ class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Ed
return
}

processingCommand(command, propElement!!.text, fallbackText = used.text)
processingCommand(command, propElement!!.text, used, fallbackText = used.text)
}

DevInTypes.AGENT_START -> {
Expand All @@ -82,7 +96,7 @@ class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Ed
}
}

private fun processingCommand(commandNode: BuiltinCommand, prop: String, fallbackText: String) {
private fun processingCommand(commandNode: BuiltinCommand, prop: String, used: DevInUsed, fallbackText: String) {
val command: AutoCommand = when (commandNode) {
BuiltinCommand.FILE -> {
FileAutoCommand(myProject, prop)
Expand All @@ -98,7 +112,28 @@ class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Ed

BuiltinCommand.WRITE -> {
result.isLocalCommand = true
PrintAutoCommand("/" + commandNode.agentName + ":" + prop)
// val devInCode = used.nextSibling.nextSibling as? DevInCode
// lookup in code
val devInCode: CodeBlockElement?
var next: PsiElement? = used
while (true) {
next = next?.nextSibling
if (next == null) {
devInCode = null
break
}

if (next.elementType == DevInTypes.CODE) {
devInCode = next as CodeBlockElement
break
}
}

if (devInCode == null) {
PrintAutoCommand("/" + commandNode.agentName + ":" + prop)
} else {
WriteAutoCommand(myProject, prop, devInCode.text)
}
}

BuiltinCommand.PATCH -> {
Expand All @@ -112,8 +147,17 @@ class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Ed
}
}

val result = command.execute() ?: fallbackText
val execResult = command.execute()
val result = if (execResult?.contains("<DevliError>") == false) {
if (commandNode == BuiltinCommand.WRITE) {
skipNextCode = true
}

execResult
} else {
execResult ?: fallbackText
}

output.append(result)
}

}
}
Expand Up @@ -13,7 +13,9 @@ class FileAutoCommand(private val myProject: Project, private val prop: String)
override fun execute(): String? {
val range: LineInfo? = LineInfo.fromString(prop)

val virtualFile = myProject.lookupFile(prop.trim())
// prop name can be src/file.name#L1-L2
val filename = prop.split("#")[0]
val virtualFile = myProject.lookupFile(filename)

val contentsToByteArray = virtualFile?.contentsToByteArray()
if (contentsToByteArray == null) {
Expand Down
@@ -0,0 +1,46 @@
package cc.unitmesh.devti.language.compiler

import cc.unitmesh.devti.language.compiler.data.LineInfo
import cc.unitmesh.devti.language.compiler.utils.lookupFile
import cc.unitmesh.devti.util.parser.Code
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager

class WriteAutoCommand(val myProject: Project, val prop: String, val content: String) : AutoCommand {
override fun execute(): String? {
val content = Code.parse(content).text

val range: LineInfo? = LineInfo.fromString(prop)

// prop name can be src/file.name#L1-L2
val filename = prop.split("#")[0]

try {
val virtualFile = myProject.lookupFile(filename) ?: return "<DevliError>: File not found: $prop"
val psiFile = PsiManager.getInstance(myProject).findFile(virtualFile)
?: return "<DevliError>: File not found: $prop"
val document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile)
?: return "<DevliError>: File not found: $prop"

ApplicationManager.getApplication().invokeLater {
WriteCommandAction.runWriteCommandAction(myProject) {
val startLine = range?.startLine ?: 0
val endLine = range?.endLine ?: document.lineCount

val startOffset = document.getLineStartOffset(startLine)
val endOffset = document.getLineEndOffset(endLine - 1)

document.replaceString(startOffset, endOffset, content)
}
}

return "Writing to file: $prop"
} catch (e: Exception) {
return "<DevliError>: ${e.message}"
}
}
}
Expand Up @@ -11,5 +11,15 @@ class DevInCompilerTest : LightJavaCodeInsightFixtureTestCase() {
val compile = DevInsCompiler(project, file as DevInFile, myFixture.editor).compile()
assertEquals("Normal String /", compile.output)
}

fun testForWriting() {
// add fake code to project
myFixture.configureByText("Sample.text", "Sample Text")
val code = "/write:Sample.text#L1-L2\n```devin\nNormal String /\n```"
val file = myFixture.configureByText("test.devin", code)

val compile = DevInsCompiler(project, file as DevInFile, myFixture.editor).compile()
println(compile.output)
}
}

0 comments on commit d7232d8

Please sign in to comment.