Skip to content

Commit d7232d8

Browse files
committed
feat(devins-compiler): add support for writing and auto commands #101
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.
1 parent ff4269d commit d7232d8

File tree

4 files changed

+111
-9
lines changed

4 files changed

+111
-9
lines changed

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/DevInsCompiler.kt

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package cc.unitmesh.devti.language.compiler
22

33
import cc.unitmesh.devti.language.completion.BuiltinCommand
4+
import cc.unitmesh.devti.language.parser.CodeBlockElement
5+
import cc.unitmesh.devti.language.psi.DevInCode
46
import cc.unitmesh.devti.language.psi.DevInFile
57
import cc.unitmesh.devti.language.psi.DevInTypes
68
import cc.unitmesh.devti.language.psi.DevInUsed
79
import com.intellij.openapi.diagnostic.logger
810
import com.intellij.openapi.editor.Editor
911
import com.intellij.openapi.project.Project
12+
import com.intellij.psi.PsiElement
1013
import com.intellij.psi.util.elementType
1114

1215
data class CompileResult(
@@ -15,17 +18,28 @@ data class CompileResult(
1518
)
1619

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

26+
/**
27+
* Todo: build AST tree, then compile
28+
*/
2229
fun compile(): CompileResult {
2330
file.children.forEach {
2431
when (it.elementType) {
2532
DevInTypes.TEXT_SEGMENT -> output.append(it.text)
2633
DevInTypes.NEWLINE -> output.append("\n")
27-
// todo: add lazy to process code
28-
DevInTypes.CODE -> output.append(it.text)
34+
DevInTypes.CODE -> {
35+
if (skipNextCode) {
36+
skipNextCode = false
37+
return@forEach
38+
}
39+
40+
output.append(it.text)
41+
}
42+
2943
DevInTypes.USED -> processUsed(it as DevInUsed)
3044
else -> {
3145
output.append(it.text)
@@ -60,7 +74,7 @@ class DevInsCompiler(val myProject: Project, val file: DevInFile, val editor: Ed
6074
return
6175
}
6276

63-
processingCommand(command, propElement!!.text, fallbackText = used.text)
77+
processingCommand(command, propElement!!.text, used, fallbackText = used.text)
6478
}
6579

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

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

99113
BuiltinCommand.WRITE -> {
100114
result.isLocalCommand = true
101-
PrintAutoCommand("/" + commandNode.agentName + ":" + prop)
115+
// val devInCode = used.nextSibling.nextSibling as? DevInCode
116+
// lookup in code
117+
val devInCode: CodeBlockElement?
118+
var next: PsiElement? = used
119+
while (true) {
120+
next = next?.nextSibling
121+
if (next == null) {
122+
devInCode = null
123+
break
124+
}
125+
126+
if (next.elementType == DevInTypes.CODE) {
127+
devInCode = next as CodeBlockElement
128+
break
129+
}
130+
}
131+
132+
if (devInCode == null) {
133+
PrintAutoCommand("/" + commandNode.agentName + ":" + prop)
134+
} else {
135+
WriteAutoCommand(myProject, prop, devInCode.text)
136+
}
102137
}
103138

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

115-
val result = command.execute() ?: fallbackText
150+
val execResult = command.execute()
151+
val result = if (execResult?.contains("<DevliError>") == false) {
152+
if (commandNode == BuiltinCommand.WRITE) {
153+
skipNextCode = true
154+
}
155+
156+
execResult
157+
} else {
158+
execResult ?: fallbackText
159+
}
160+
116161
output.append(result)
117162
}
118-
119-
}
163+
}

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/FileAutoCommand.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class FileAutoCommand(private val myProject: Project, private val prop: String)
1313
override fun execute(): String? {
1414
val range: LineInfo? = LineInfo.fromString(prop)
1515

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

1820
val contentsToByteArray = virtualFile?.contentsToByteArray()
1921
if (contentsToByteArray == null) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cc.unitmesh.devti.language.compiler
2+
3+
import cc.unitmesh.devti.language.compiler.data.LineInfo
4+
import cc.unitmesh.devti.language.compiler.utils.lookupFile
5+
import cc.unitmesh.devti.util.parser.Code
6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.application.runWriteAction
8+
import com.intellij.openapi.command.WriteCommandAction
9+
import com.intellij.openapi.project.Project
10+
import com.intellij.psi.PsiDocumentManager
11+
import com.intellij.psi.PsiManager
12+
13+
class WriteAutoCommand(val myProject: Project, val prop: String, val content: String) : AutoCommand {
14+
override fun execute(): String? {
15+
val content = Code.parse(content).text
16+
17+
val range: LineInfo? = LineInfo.fromString(prop)
18+
19+
// prop name can be src/file.name#L1-L2
20+
val filename = prop.split("#")[0]
21+
22+
try {
23+
val virtualFile = myProject.lookupFile(filename) ?: return "<DevliError>: File not found: $prop"
24+
val psiFile = PsiManager.getInstance(myProject).findFile(virtualFile)
25+
?: return "<DevliError>: File not found: $prop"
26+
val document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile)
27+
?: return "<DevliError>: File not found: $prop"
28+
29+
ApplicationManager.getApplication().invokeLater {
30+
WriteCommandAction.runWriteCommandAction(myProject) {
31+
val startLine = range?.startLine ?: 0
32+
val endLine = range?.endLine ?: document.lineCount
33+
34+
val startOffset = document.getLineStartOffset(startLine)
35+
val endOffset = document.getLineEndOffset(endLine - 1)
36+
37+
document.replaceString(startOffset, endOffset, content)
38+
}
39+
}
40+
41+
return "Writing to file: $prop"
42+
} catch (e: Exception) {
43+
return "<DevliError>: ${e.message}"
44+
}
45+
}
46+
}

exts/devins-lang/src/test/kotlin/cc/unitmesh/devti/language/compiler/DevInCompilerTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,15 @@ class DevInCompilerTest : LightJavaCodeInsightFixtureTestCase() {
1111
val compile = DevInsCompiler(project, file as DevInFile, myFixture.editor).compile()
1212
assertEquals("Normal String /", compile.output)
1313
}
14+
15+
fun testForWriting() {
16+
// add fake code to project
17+
myFixture.configureByText("Sample.text", "Sample Text")
18+
val code = "/write:Sample.text#L1-L2\n```devin\nNormal String /\n```"
19+
val file = myFixture.configureByText("test.devin", code)
20+
21+
val compile = DevInsCompiler(project, file as DevInFile, myFixture.editor).compile()
22+
println(compile.output)
23+
}
1424
}
1525

0 commit comments

Comments
 (0)