diff --git a/build.gradle.kts b/build.gradle.kts index 00f01bac..3ec16c6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,8 @@ val skyenet_version = "1.0.47" val remoterobot_version = "0.11.21" dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation(group = "com.simiacryptus.skyenet", name = "kotlin", version = skyenet_version) { exclude(group = "org.jetbrains.kotlin", module = "") diff --git a/gradle.properties b/gradle.properties index 70b9a585..ee75f0a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ pluginName=intellij-aicoder pluginRepositoryUrl=https://github.com/SimiaCryptus/intellij-aicoder -pluginVersion=1.2.25 +pluginVersion=1.2.26 jvmArgs=-Xmx8g org.gradle.jvmargs=-Xmx8g diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/BaseAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/BaseAction.kt index ba633684..bb96c680 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/BaseAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/BaseAction.kt @@ -9,36 +9,43 @@ import org.slf4j.LoggerFactory import javax.swing.Icon abstract class BaseAction( - name: String? = null, - description: String? = null, - icon: Icon? = null, + name: String? = null, + description: String? = null, + icon: Icon? = null, ) : AnAction(name, description, icon) { - private val log by lazy { LoggerFactory.getLogger(javaClass) } - //override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + private val log by lazy { LoggerFactory.getLogger(javaClass) } + //override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT - val api: OpenAIClient - get() = IdeaOpenAIClient.instance + val api: OpenAIClient + get() = IdeaOpenAIClient.instance - final override fun update(event: AnActionEvent) { - event.presentation.isEnabledAndVisible = isEnabled(event) - super.update(event) - } + final override fun update(event: AnActionEvent) { + event.presentation.isEnabledAndVisible = isEnabled(event) + super.update(event) + } - abstract fun handle(e: AnActionEvent) + abstract fun handle(e: AnActionEvent) - final override fun actionPerformed(e: AnActionEvent) { - UITools.logAction(""" + final override fun actionPerformed(e: AnActionEvent) { + UITools.logAction( + """ |Action: ${javaClass.simpleName} - """.trimMargin().trim()) - IdeaOpenAIClient.lastEvent = e - try { - handle(e) - } catch (e: Throwable) { - UITools.error(log, "Error in Action ${javaClass.simpleName}", e) - } + """.trimMargin().trim() + ) + IdeaOpenAIClient.lastEvent = e + try { + handle(e) + } catch (e: Throwable) { + UITools.error(log, "Error in Action ${javaClass.simpleName}", e) } + } + + open fun isEnabled(event: AnActionEvent): Boolean = true - open fun isEnabled(event: AnActionEvent): Boolean = true + companion object { + val log by lazy { LoggerFactory.getLogger(javaClass) } + val scheduledPool = java.util.concurrent.Executors.newScheduledThreadPool(1) + } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt index d2202ba5..7e9475df 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt @@ -3,12 +3,15 @@ package com.github.simiacryptus.aicoder.actions import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.util.UITools import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import org.slf4j.LoggerFactory import java.io.File +import java.nio.file.Path +import java.util.concurrent.TimeUnit abstract class FileContextAction( private val supportsFiles: Boolean = true, @@ -42,18 +45,16 @@ abstract class FileContextAction( if (it.isCanceled) throw InterruptedException() } UITools.writeableFn(e) { - val files = newFiles.map { file -> + val files = newFiles.mapNotNull { file -> val localFileSystem = LocalFileSystem.getInstance() - localFileSystem.findFileByIoFile(file.parentFile)?.refresh(false, true) val generatedFile = localFileSystem.findFileByIoFile(file) if (generatedFile == null) { log.warn("Generated file not found: ${file.path}") } else { - generatedFile.refresh(false, false) - FileEditorManager.getInstance(project).openFile(generatedFile, true) + open(project, file.toPath()) } generatedFile - }.filter { it != null }.toTypedArray() + }.toTypedArray() Runnable { files.forEach { it?.delete(this@FileContextAction) } } @@ -78,6 +79,33 @@ abstract class FileContextAction( companion object { private val log = LoggerFactory.getLogger(FileContextAction::class.java) + + fun open(project: Project, outputPath: Path) { + lateinit var function: () -> Unit + function = { + val file = outputPath.toFile() + if (file.exists()) { + // Ensure the IDE is ready for file operations + ApplicationManager.getApplication().invokeLater { + val ioFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file) + if (false == (ioFile?.let { FileEditorManager.getInstance(project).isFileOpen(it) })) { + val localFileSystem = LocalFileSystem.getInstance() + // Refresh the file system to ensure the file is visible + val virtualFile = localFileSystem.refreshAndFindFileByIoFile(file) + virtualFile?.let { + FileEditorManager.getInstance(project).openFile(it, true) + } ?: scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } else { + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + } + } else { + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + } + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.kt index 27faa9ed..3b26c1d3 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.kt @@ -4,8 +4,11 @@ import com.github.simiacryptus.aicoder.actions.FileContextAction import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.config.Name import com.github.simiacryptus.aicoder.util.UITools +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.ApiModel.ChatMessage import com.simiacryptus.jopenai.ApiModel.Role @@ -14,6 +17,8 @@ import org.apache.commons.io.FileUtils import org.apache.commons.io.IOUtils import java.io.File import java.io.FileInputStream +import java.nio.file.Path +import java.util.concurrent.TimeUnit import javax.swing.JTextArea class AnalogueFileAction : FileContextAction() { @@ -38,39 +43,48 @@ class AnalogueFileAction : FileContextAction() { ) } - class Settings ( - var directive: String = "" + class UserSettings( + var directive: String = "", ) - override fun getConfig(project: Project?): Settings? { - return UITools.showDialog( - project, - SettingsUI::class.java, - Settings::class.java, - "Create Analogue File" + class Settings( + val settings: UserSettings? = null, + val project: Project? = null + ) + + override fun getConfig(project: Project?): Settings { + return Settings( + UITools.showDialog( + project, + SettingsUI::class.java, + UserSettings::class.java, + "Create Analogue File" + ), project ) } override fun processSelection(state: SelectionState, config: Settings?): Array { + val root = getModuleRootForFile(state.selectedFile).toPath() + val selectedFile = state.selectedFile val analogue = generateFile( ProjectFile( - path = state.projectRoot.toPath().relativize(state.selectedFile.toPath()).toString(), - code = IOUtils.toString(FileInputStream(state.selectedFile), "UTF-8") + path = root.relativize(selectedFile.toPath()).toString(), + code = IOUtils.toString(FileInputStream(selectedFile), "UTF-8") ), - config?.directive ?: "" + config?.settings?.directive ?: "" ) - var outputPath = state.projectRoot.toPath().resolve(analogue.path) + var outputPath = root.resolve(analogue.path) if (outputPath.toFile().exists()) { val extension = outputPath.toString().split(".").last() val name = outputPath.toString().split(".").dropLast(1).joinToString(".") val fileIndex = (1..Int.MAX_VALUE).find { - !File(state.projectRoot, "$name.$it.$extension").exists() + !root.resolve("$name.$it.$extension").toFile().exists() } - outputPath = state.projectRoot.toPath().resolve("$name.$fileIndex.$extension") + outputPath = root.resolve("$name.$fileIndex.$extension") } outputPath.parent.toFile().mkdirs() FileUtils.write(outputPath.toFile(), analogue.code, "UTF-8") - Thread.sleep(100) + open(config?.project!!, outputPath) return arrayOf(outputPath.toFile()) } @@ -103,10 +117,7 @@ class AnalogueFileAction : FileContextAction() { ) ) - val response = api.chat( - chatRequest, - AppSettingsState.instance.defaultChatModel() - ).choices.first().message?.content?.trim() + val response = api.chat(chatRequest, model).choices.first().message?.content?.trim() var outputPath = baseFile.path val header = response?.split("\n")?.first() var body = response?.split("\n")?.drop(1)?.joinToString("\n")?.trim() @@ -123,4 +134,43 @@ class AnalogueFileAction : FileContextAction() { code = body ?: "" ) } + + companion object { + fun open(project: Project, outputPath: Path) { + lateinit var function: () -> Unit + function = { + val file = outputPath.toFile() + if (file.exists()) { + // Ensure the IDE is ready for file operations + ApplicationManager.getApplication().invokeLater { + val ioFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file) + if (false == (ioFile?.let { FileEditorManager.getInstance(project).isFileOpen(it) })) { + val localFileSystem = LocalFileSystem.getInstance() + // Refresh the file system to ensure the file is visible + val virtualFile = localFileSystem.refreshAndFindFileByIoFile(file) + virtualFile?.let { + FileEditorManager.getInstance(project).openFile(it, true) + } ?: scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } else { + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + } + } else { + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + } + scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS) + } + + fun getModuleRootForFile(file: File): File { + var current = file + while (current.parentFile != null) { + if (current.resolve(".git").exists()) { + return current + } + current = current.parentFile + } + return file + } + } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt index bd0d790c..f10a8f2d 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt @@ -11,7 +11,7 @@ import java.util.stream.Collectors class ActionSettingsRegistry { val actionSettings: MutableMap = HashMap() - private val version = 2.0027 + private val version = 2.0040 fun edit(superChildren: Array): Array { val children = superChildren.toList().toMutableList() diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index fc7e2571..09b9c295 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -81,7 +81,7 @@ text="_Chat Append Text" description="Append text using the Chat API"> @@ -91,7 +91,7 @@ text="_Create File" description="Create file"> @@ -105,6 +105,15 @@ --> + + + +