diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 033c076..247ccb3 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -4,6 +4,7 @@
     <mapping directory="" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/gui" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/tools" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/tools/sonar-rspec" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/workflows" vcs="Git" />
   </component>
 </project>
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index f6a50f2..890b7e2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -50,6 +50,7 @@ tasks.register<Copy>("copyTools") {
             "python-3.11.6-embed-amd64/**",
             "site-packages/**",
             "sonar-rspec/**",
+            "code-editor/**",
             "replace.sh"
         )
     }
diff --git a/gui b/gui
index 03089d0..d97b8a5 160000
--- a/gui
+++ b/gui
@@ -1 +1 @@
-Subproject commit 03089d049a326b32fb5d04c140c1e04e28c7a1eb
+Subproject commit d97b8a5bad9f7844f2fc2d5c939bc95a44fb98ee
diff --git a/src/main/kotlin/ai/devchat/common/CommandLine.kt b/src/main/kotlin/ai/devchat/common/CommandLine.kt
new file mode 100644
index 0000000..540a134
--- /dev/null
+++ b/src/main/kotlin/ai/devchat/common/CommandLine.kt
@@ -0,0 +1,16 @@
+package ai.devchat.common
+
+import java.io.BufferedReader
+
+
+data class CommandResult(val output: String, val errors: String, val exitCode: Int)
+
+object CommandLine {
+   fun exec(vararg command: String): CommandResult {
+        val process = ProcessBuilder(*command).start()
+        val output = process.inputStream.bufferedReader(charset=Charsets.UTF_8).use(BufferedReader::readText)
+        val errors = process.errorStream.bufferedReader(charset=Charsets.UTF_8).use(BufferedReader::readText)
+        val exitCode = process.waitFor()
+        return CommandResult(output, errors, exitCode)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/ai/devchat/common/OSInfo.kt b/src/main/kotlin/ai/devchat/common/OSInfo.kt
index f571767..1860c94 100644
--- a/src/main/kotlin/ai/devchat/common/OSInfo.kt
+++ b/src/main/kotlin/ai/devchat/common/OSInfo.kt
@@ -4,7 +4,7 @@ import java.util.*
 
 object OSInfo {
     val OS_NAME: String = System.getProperty("os.name").lowercase(Locale.getDefault())
-    val OS_ARCH: String = System.getProperty("os.arch")
+    val OS_ARCH: String = System.getProperty("os.arch").lowercase(Locale.getDefault())
 
     val isWindows: Boolean = OS_NAME.contains("win")
     val platform: String  = when {
diff --git a/src/main/kotlin/ai/devchat/common/PathUtils.kt b/src/main/kotlin/ai/devchat/common/PathUtils.kt
index dec5664..f5df365 100644
--- a/src/main/kotlin/ai/devchat/common/PathUtils.kt
+++ b/src/main/kotlin/ai/devchat/common/PathUtils.kt
@@ -1,5 +1,6 @@
 package ai.devchat.common
 
+import java.io.File
 import java.io.IOException
 import java.nio.file.*
 import java.nio.file.attribute.BasicFileAttributes
@@ -13,22 +14,41 @@ object PathUtils {
     val mambaWorkPath = Paths.get(workPath, "mamba").toString()
     val mambaBinPath = Paths.get(mambaWorkPath, "micromamba").toString()
     val toolsPath: String = Paths.get(workPath, "tools").toString()
+    val codeEditorBinary: String = "${when {
+        OSInfo.OS_ARCH.contains("aarch") || OSInfo.OS_ARCH.contains("arm") -> "aarch64"
+        else -> "x86_64"
+    }}-${when {
+        OSInfo.OS_NAME.contains("win") -> "pc-windows-msvc"
+        OSInfo.OS_NAME.contains("darwin") || OSInfo.OS_NAME.contains("mac") -> "apple-darwin"
+        OSInfo.OS_NAME.contains("linux") -> "unknown-linux-musl"
+        else -> throw RuntimeException("Unsupported OS: ${OSInfo.OS_NAME}")
+    }}-code_editor" + if (OSInfo.isWindows) ".exe" else ""
 
-    fun copyResourceDirToPath(resourceDir: String, outputDir: String, overwrite: Boolean = false): String {
-        val uri = javaClass.getResource(resourceDir)!!.toURI()
+    fun copyResourceDirToPath(resourcePath: String, outputPath: String, overwrite: Boolean = false): String {
+        val uri = javaClass.getResource(resourcePath)?.toURI() ?: throw IllegalArgumentException(
+            "Resource not found: $resourcePath"
+        )
         val sourcePath = if (uri.scheme == "jar") {
             val fileSystem = try {
                 FileSystems.newFileSystem(uri, emptyMap<String, Any>())
             } catch (e: FileSystemAlreadyExistsException) {
                 FileSystems.getFileSystem(uri)
             }
-            fileSystem.getPath("/$resourceDir")
+            fileSystem.getPath("/$resourcePath")
         } else {
             Paths.get(uri)
         }
-        val targetPath = Paths.get(outputDir)
-        if (!Files.exists(targetPath)) Files.createDirectories(targetPath)
-        if (overwrite) targetPath.toFile().deleteRecursively()
+
+        val targetPath = Paths.get(outputPath)
+        if (!Files.exists(targetPath.parent)) Files.createDirectories(targetPath.parent)
+        if (overwrite && Files.exists(targetPath)) targetPath.toFile().deleteRecursively()
+
+        // Handle single file copying
+        if (Files.isRegularFile(sourcePath)) {
+            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
+            targetPath.toFile().setExecutable(true)
+            return targetPath.toString()
+        }
 
         Files.walkFileTree(sourcePath, object : SimpleFileVisitor<Path>() {
             @Throws(IOException::class)
@@ -52,4 +72,10 @@ object PathUtils {
 
         return targetPath.toString()
     }
+
+    fun createTempFile(prefix: String, content: String): String {
+        val tempFile = File.createTempFile(prefix, "")
+        tempFile.writeText(content)
+        return tempFile.absolutePath
+    }
 }
diff --git a/src/main/kotlin/ai/devchat/installer/DevChatSetupThread.kt b/src/main/kotlin/ai/devchat/installer/DevChatSetupThread.kt
index 0d783c4..34de51c 100644
--- a/src/main/kotlin/ai/devchat/installer/DevChatSetupThread.kt
+++ b/src/main/kotlin/ai/devchat/installer/DevChatSetupThread.kt
@@ -39,6 +39,11 @@ class DevChatSetupThread : Thread() {
     private fun setup(envManager: PythonEnvManager) {
         val overwrite = devChatVersion != DevChatState.instance.lastVersion
         PathUtils.copyResourceDirToPath("/tools/site-packages", PathUtils.sitePackagePath, overwrite)
+        PathUtils.copyResourceDirToPath(
+            "/tools/code-editor/${PathUtils.codeEditorBinary}",
+            Paths.get(PathUtils.toolsPath, PathUtils.codeEditorBinary).toString(),
+            overwrite
+        )
         PathUtils.copyResourceDirToPath(
             "/tools/sonar-rspec",
             Paths.get(PathUtils.toolsPath, "sonar-rspec").toString(),
diff --git a/src/main/kotlin/ai/devchat/plugin/DiffViewerDialog.kt b/src/main/kotlin/ai/devchat/plugin/DiffViewerDialog.kt
index 384010c..f5e5e7d 100644
--- a/src/main/kotlin/ai/devchat/plugin/DiffViewerDialog.kt
+++ b/src/main/kotlin/ai/devchat/plugin/DiffViewerDialog.kt
@@ -1,5 +1,8 @@
 package ai.devchat.plugin
 
+import ai.devchat.common.CommandLine
+import ai.devchat.common.Log
+import ai.devchat.common.PathUtils
 import ai.devchat.core.DevChatActions
 import ai.devchat.core.handlers.CodeDiffApplyHandler
 import com.intellij.diff.DiffContentFactory
@@ -10,28 +13,45 @@ import com.intellij.openapi.editor.Editor
 import com.intellij.openapi.fileEditor.FileDocumentManager
 import com.intellij.openapi.ui.DialogWrapper
 import java.awt.event.ActionEvent
+import java.io.File
+import java.nio.file.Paths
 import javax.swing.Action
 import javax.swing.JComponent
 
 class DiffViewerDialog(
     val editor: Editor,
-    private val newText: String
+    private var newText: String,
+    autoEdit: Boolean = false
 ) : DialogWrapper(editor.project) {
+    private var startOffset: Int = 0
+    private var endOffset: Int = editor.document.textLength
+    private var localContent: String = editor.document.text
+
     init {
-        super.init()
         title = "Confirm Changes"
+        val selectionModel = editor.selectionModel
+        val maxIdx = editor.document.textLength
+        if (!autoEdit && selectionModel.hasSelection()) {
+            startOffset = selectionModel.selectionStart.coerceIn(0, maxIdx)
+            endOffset = selectionModel.selectionEnd.coerceIn(0, maxIdx).coerceAtLeast(startOffset)
+            localContent = editor.selectionModel.selectedText ?: ""
+        }
+        if (autoEdit) {
+            try {
+                newText = editText()
+            } catch(e: Exception) {
+                Log.warn("Failed to edit code: $e")
+            }
+        }
+        super.init()
     }
 
     override fun createCenterPanel(): JComponent {
-        val virtualFile = FileDocumentManager.getInstance().getFile(editor.document)
-        val fileType = virtualFile!!.fileType
-        val localContent = if (editor.selectionModel.hasSelection()) {
-            editor.selectionModel.selectedText
-        } else editor.document.text
+        val fileType = FileDocumentManager.getInstance().getFile(editor.document)!!.fileType
         val contentFactory = DiffContentFactory.getInstance()
         val diffRequest = SimpleDiffRequest(
             "Code Diff",
-            contentFactory.create(localContent!!, fileType),
+            contentFactory.create(localContent, fileType),
             contentFactory.create(newText, fileType),
             "Old code",
             "New code"
@@ -45,27 +65,24 @@ class DiffViewerDialog(
 
         return arrayOf(cancelAction, object: DialogWrapperAction("Apply") {
             override fun doAction(e: ActionEvent?) {
-                val selectionModel = editor.selectionModel
-                val document = editor.document
-                val startOffset: Int?
-                val endOffset: Int?
-                if (selectionModel.hasSelection()) {
-                    startOffset = selectionModel.selectionStart
-                    endOffset = selectionModel.selectionEnd
-                } else {
-                    startOffset = 0
-                    endOffset = document.textLength - 1
-                }
                 WriteCommandAction.runWriteCommandAction(editor.project) {
-                    // Ensure offsets are valid
-                    val safeStartOffset = startOffset.coerceIn(0, document.textLength)
-                    val safeEndOffset = endOffset.coerceIn(0, document.textLength).coerceAtLeast(safeStartOffset)
-                    // Replace the selected range with new text
-                    document.replaceString(safeStartOffset, safeEndOffset, newText)
+                    editor.document.replaceString(startOffset, endOffset, newText)
                 }
                 CodeDiffApplyHandler(DevChatActions.CODE_DIFF_APPLY_REQUEST,null, null).executeAction()
                 close(OK_EXIT_CODE)
             }
         })
     }
+
+    private fun editText(): String {
+        val srcTempFile = PathUtils.createTempFile("code_editor_src_", editor.document.text)
+        val newTempFile = PathUtils.createTempFile("code_editor_new_", newText)
+        val resultTempFile = PathUtils.createTempFile("code_editor_res_", "")
+        val codeEditorPath = Paths.get(PathUtils.toolsPath, PathUtils.codeEditorBinary).toString()
+        val result = CommandLine.exec(codeEditorPath, srcTempFile, newTempFile, resultTempFile)
+        require(result.exitCode == 0) {
+            throw Exception("Code editor failed with exit code ${result.exitCode}")
+        }
+        return File(resultTempFile).readText()
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/ai/devchat/plugin/IDEServer.kt b/src/main/kotlin/ai/devchat/plugin/IDEServer.kt
index b419179..42e9420 100644
--- a/src/main/kotlin/ai/devchat/plugin/IDEServer.kt
+++ b/src/main/kotlin/ai/devchat/plugin/IDEServer.kt
@@ -58,6 +58,8 @@ const val START_PORT: Int = 31800
 @Serializable
 data class ReqLocation(val abspath: String, val line: Int, val character: Int)
 @Serializable
+data class DiffApplyRequest(val filepath: String?, val content: String?, val autoedit: Boolean?)
+@Serializable
 data class Position(val line: Int, val character: Int)
 @Serializable
 data class Range(val start: Position, val end: Position)
@@ -100,7 +102,6 @@ class IDEServer(private var project: Project) {
                     call.respond(Result(definitions))
                 }
 
-
                 post("/references") {
                     val body: ReqLocation = call.receive()
                     val references = withContext(Dispatchers.IO)  {
@@ -242,20 +243,17 @@ class IDEServer(private var project: Project) {
                     } ?: call.respond(HttpStatusCode.NoContent)
                 }
                 post("/diff_apply") {
-                    val body = call.receive<Map<String, String>>()
-                    val filePath: String? = body["filepath"]
-                    var content: String? = body["content"]
-                    if (content.isNullOrEmpty() && !filePath.isNullOrEmpty()) {
-                        content = File(filePath).readText()
-                    }
-                    if (content.isNullOrEmpty()) {
-                        content = ""
-                    }
+                    val body: DiffApplyRequest = call.receive()
+                    val filePath: String? = body.filepath
+                    val content = body.content.takeUnless { it.isNullOrEmpty() }
+                        ?: filePath?.takeUnless { it.isEmpty() }?.let { File(it).readText() }
+                        ?: ""
+                    val autoEdit: Boolean = body.autoedit ?: false
                     var editor: Editor? = null
                     ApplicationManager.getApplication().invokeAndWait {
                         editor = FileEditorManager.getInstance(project).selectedTextEditor
                     }
-                    editor?.diffWith(content)
+                    editor?.diffWith(content, autoEdit)
                     call.respond(Result(true))
                 }
                 post("/ide_logging") {
@@ -348,9 +346,9 @@ fun Editor.visibleRange(): LocationWithText {
     )
 }
 
-fun Editor.diffWith(newText: String) {
+fun Editor.diffWith(newText: String, autoEdit: Boolean) {
     ApplicationManager.getApplication().invokeLater {
-        val dialog = DiffViewerDialog(this, newText)
+        val dialog = DiffViewerDialog(this, newText, autoEdit)
         dialog.show()
     }
 }
diff --git a/tools b/tools
index c4f335e..0557f55 160000
--- a/tools
+++ b/tools
@@ -1 +1 @@
-Subproject commit c4f335e486a33f8161d4866b1d0e5600391a0434
+Subproject commit 0557f55a8d5dadabcf225a2b7a24db9db120f895