Skip to content

Commit b31f560

Browse files
committed
feat(ui): add EDIT_FILE command support with diff view #408
Add support for executing EDIT_FILE commands in CodeHighlightSketch with automatic and manual execution modes. Commands now display results using DiffLangSketch for better visualization of file changes.
1 parent d2036fb commit b31f560

File tree

2 files changed

+123
-3
lines changed

2 files changed

+123
-3
lines changed

core/src/main/kotlin/cc/unitmesh/devti/command/dataprovider/BuiltinCommand.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ enum class BuiltinCommand(
2222
FILE(
2323
"file",
2424
"Read and retrieve file content from project using relative path. Essential for examining existing code, configurations, or documentation before modifications. Supports line ranges (L1-L10) and global filename search. Returns complete file content with line numbers for context understanding.",
25-
AllIcons.Actions.Copy,
25+
AllIcons.FileTypes.Any_type,
2626
true,
2727
true
2828
),
@@ -60,8 +60,7 @@ enum class BuiltinCommand(
6060
"edit_file",
6161
"Apply structured file edits using target_file, instructions, and code_edit parameters. Designed for precise code modifications with clear context markers. Use // ... existing code ... to represent unchanged sections. Ideal for targeted edits with explicit instructions.",
6262
AllIcons.Actions.Edit,
63-
false,
64-
true
63+
false
6564
),
6665
RUN(
6766
"run",

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/code/CodeHighlightSketch.kt

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import cc.unitmesh.devti.provider.BuildSystemProvider
99
import cc.unitmesh.devti.provider.RunService
1010
import cc.unitmesh.devti.sketch.AutoSketchMode
1111
import cc.unitmesh.devti.sketch.ui.LangSketch
12+
import cc.unitmesh.devti.sketch.ui.patch.DiffLangSketchProvider
13+
import cc.unitmesh.devti.util.AutoDevCoroutineScope
1214
import cc.unitmesh.devti.util.parser.CodeFence
15+
import kotlinx.coroutines.launch
1316
import com.intellij.icons.AllIcons
1417
import com.intellij.ide.scratch.ScratchRootType
1518
import com.intellij.lang.Language
@@ -19,6 +22,7 @@ import com.intellij.openapi.actionSystem.DataProvider
1922
import com.intellij.openapi.actionSystem.Presentation
2023
import com.intellij.openapi.actionSystem.impl.ActionButton
2124
import com.intellij.openapi.application.ReadAction
25+
import com.intellij.openapi.application.runInEdt
2226
import com.intellij.openapi.command.WriteCommandAction
2327
import com.intellij.openapi.diagnostic.logger
2428
import com.intellij.openapi.editor.Document
@@ -32,6 +36,7 @@ import com.intellij.openapi.project.Project
3236
import com.intellij.openapi.util.Disposer
3337
import com.intellij.openapi.util.text.StringUtil
3438
import com.intellij.openapi.vfs.VirtualFile
39+
import com.intellij.psi.PsiFile
3540
import com.intellij.psi.PsiManager
3641
import com.intellij.ui.JBColor
3742
import com.intellij.ui.components.JBLabel
@@ -389,6 +394,8 @@ open class CodeHighlightSketch(
389394
val sketch = CodeHighlightSketch(project, parse.text, language, editorLineThreshold, fileName)
390395
add(sketch)
391396
}
397+
} else if (currentText.startsWith("/" + BuiltinCommand.EDIT_FILE.commandName)) {
398+
processEditFileCommand(currentText)
392399
}
393400
}
394401

@@ -460,6 +467,120 @@ fun CodeHighlightSketch.processWriteCommand(currentText: String, fileName: Strin
460467
add(panel)
461468
}
462469

470+
/**
471+
* Add Edit File Command Action
472+
*/
473+
fun CodeHighlightSketch.processEditFileCommand(currentText: String) {
474+
val isAutoSketchMode = AutoSketchMode.getInstance(project).isEnable
475+
476+
if (isAutoSketchMode) {
477+
// In AutoSketchMode, automatically execute the edit_file command
478+
val button = JButton("Auto Executing...", AutoDevIcons.LOADING).apply {
479+
isEnabled = false
480+
preferredSize = JBUI.size(150, 30)
481+
}
482+
483+
val panel = JPanel()
484+
panel.layout = BoxLayout(panel, BoxLayout.X_AXIS)
485+
panel.add(button)
486+
add(panel)
487+
488+
// Execute automatically
489+
AutoDevCoroutineScope.scope(project).launch {
490+
executeEditFileCommand(project, currentText) { result ->
491+
runInEdt {
492+
if (result != null && !result.startsWith("DEVINS_ERROR")) {
493+
// Create DiffLangSketch to show the result
494+
val diffSketch = DiffLangSketchProvider().create(project, result)
495+
add(diffSketch.getComponent())
496+
button.text = "Executed"
497+
button.icon = AllIcons.Actions.Checked
498+
} else {
499+
button.text = "Failed"
500+
button.icon = AllIcons.General.Error
501+
AutoDevNotifications.warn(project, result ?: "Unknown error occurred")
502+
}
503+
}
504+
}
505+
}
506+
} else {
507+
val executeButton = JButton("Execute Edit File", AllIcons.Actions.Execute).apply {
508+
preferredSize = JBUI.size(120, 30)
509+
addActionListener {
510+
this.isEnabled = false
511+
this.text = "Executing..."
512+
this.icon = AutoDevIcons.LOADING
513+
514+
AutoDevCoroutineScope.scope(project).launch {
515+
executeEditFileCommand(project, currentText) { result ->
516+
runInEdt {
517+
if (result != null && !result.startsWith("DEVINS_ERROR")) {
518+
// Create DiffLangSketch to show the result
519+
val diffSketch = DiffLangSketchProvider().create(project, result)
520+
add(diffSketch.getComponent())
521+
this@apply.text = "Executed"
522+
this@apply.icon = AllIcons.Actions.Checked
523+
} else {
524+
this@apply.text = "Failed"
525+
this@apply.icon = AllIcons.General.Error
526+
AutoDevNotifications.warn(project, result ?: "Unknown error occurred")
527+
}
528+
}
529+
}
530+
}
531+
}
532+
}
533+
534+
val panel = JPanel()
535+
panel.layout = BoxLayout(panel, BoxLayout.X_AXIS)
536+
panel.add(executeButton)
537+
add(panel)
538+
}
539+
}
540+
541+
private suspend fun executeEditFileCommand(project: Project, editFileContent: String, callback: (String?) -> Unit) {
542+
try {
543+
val newFileName = "EditFile-${System.currentTimeMillis()}.devin"
544+
val language = Language.findLanguageByID("DevIn")
545+
val file = ScratchRootType.getInstance()
546+
.createScratchFile(project, newFileName, language, editFileContent)
547+
548+
if (file == null) {
549+
callback("DEVINS_ERROR: Failed to create temporary file")
550+
return
551+
}
552+
553+
val psiFile = PsiManager.getInstance(project).findFile(file)
554+
if (psiFile == null) {
555+
callback("DEVINS_ERROR: Failed to find PSI file")
556+
return
557+
}
558+
559+
// Use RunService to execute the DevIn file
560+
val result = executeDevInFile(project, file, psiFile)
561+
callback(result)
562+
} catch (e: Exception) {
563+
callback("DEVINS_ERROR: ${e.message}")
564+
}
565+
}
566+
567+
private fun executeDevInFile(project: Project, file: VirtualFile, psiFile: PsiFile): String? {
568+
return try {
569+
// Use RunService to execute the DevIn file
570+
val runService = RunService.provider(project, file)
571+
if (runService != null) {
572+
runService.runFile(project, file, psiFile, isFromToolAction = true)
573+
"Edit file command executed successfully"
574+
} else {
575+
// Fallback to RunService.runInCli
576+
RunService.runInCli(project, psiFile)
577+
"Edit file command executed via CLI"
578+
}
579+
} catch (e: Exception) {
580+
"DEVINS_ERROR: Failed to execute DevIn file: ${e.message}"
581+
}
582+
}
583+
463584
@RequiresReadLock
464585
fun VirtualFile.findDocument(): Document? {
465586
return ReadAction.compute<Document, Throwable> {

0 commit comments

Comments
 (0)