diff --git a/build.gradle.kts b/build.gradle.kts index f5abc093..8564e7c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ repositories { val kotlin_version = "1.9.21" val jetty_version = "11.0.18" val slf4j_version = "2.0.9" -val skyenet_version = "1.0.48" +val skyenet_version = "1.0.49" val remoterobot_version = "0.11.21" dependencies { diff --git a/gradle.properties b/gradle.properties index 61ff304b..fde6a575 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ pluginName=intellij-aicoder pluginRepositoryUrl=https://github.com/SimiaCryptus/intellij-aicoder -pluginVersion=1.2.28 +pluginVersion=1.2.29 jvmArgs=-Xmx8g org.gradle.jvmargs=-Xmx8g 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 7e9475df..31baf72d 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/FileContextAction.kt @@ -26,7 +26,7 @@ abstract class FileContextAction( abstract fun processSelection(state: SelectionState, config: T?): Array final override fun handle(e: AnActionEvent) { - val config = getConfig(e.project) + val config = getConfig(e.project, e) val virtualFile = UITools.getSelectedFile(e) ?: UITools.getSelectedFolder(e) ?: return val project = e.project ?: return val projectRoot = File(project.basePath!!).toPath() @@ -67,7 +67,7 @@ abstract class FileContextAction( }.start() } - open fun getConfig(project: Project?): T? = null + open fun getConfig(project: Project?, e: AnActionEvent): T? = null private var isDevAction = false override fun isEnabled(event: AnActionEvent): Boolean { 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 3b26c1d3..1613f0cc 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 @@ -52,7 +52,7 @@ class AnalogueFileAction : FileContextAction() { val project: Project? = null ) - override fun getConfig(project: Project?): Settings { + override fun getConfig(project: Project?, e: AnActionEvent): Settings { return Settings( UITools.showDialog( project, diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt new file mode 100644 index 00000000..bad75f0c --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt @@ -0,0 +1,284 @@ +package com.github.simiacryptus.aicoder.actions.generic + +import com.github.simiacryptus.aicoder.ApplicationEvents +import com.github.simiacryptus.aicoder.actions.BaseAction +import com.github.simiacryptus.aicoder.actions.dev.AppServer +import com.github.simiacryptus.aicoder.util.ComputerLanguage +import com.github.simiacryptus.aicoder.util.UITools +import com.github.simiacryptus.aicoder.util.addApplyDiffLinks +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.proxy.ValidatedObject +import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.skyenet.AgentPatterns +import com.simiacryptus.skyenet.core.actors.ActorSystem +import com.simiacryptus.skyenet.core.actors.BaseActor +import com.simiacryptus.skyenet.core.actors.ParsedActor +import com.simiacryptus.skyenet.core.actors.SimpleActor +import com.simiacryptus.skyenet.core.platform.* +import com.simiacryptus.skyenet.core.platform.file.DataStorage +import com.simiacryptus.skyenet.webui.application.ApplicationInterface +import com.simiacryptus.skyenet.webui.application.ApplicationServer +import com.simiacryptus.skyenet.webui.chat.ChatServer +import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import org.slf4j.LoggerFactory +import java.awt.Desktop +import java.io.File + +class AutoDevAction : BaseAction() { + + val path = "/autodev" + + override fun handle(e: AnActionEvent) { + val session = StorageInterface.newGlobalID() + val storage = ApplicationServices.dataStorageFactory(DiffChatAction.root) as DataStorage? + val selectedFile = UITools.getSelectedFolder(e) + if (null != storage && null != selectedFile) { + DataStorage.sessionPaths[session] = selectedFile.toFile + } + agents[session] = AutoDevApp(event = e) + val server = AppServer.getServer(e.project) + val app = initApp(server, path) + app.sessions[session] = app.newSession(null, session) + Thread { + Thread.sleep(500) + try { + Desktop.getDesktop().browse(server.server.uri.resolve("$path/#$session")) + } catch (e: Throwable) { + log.warn("Error opening browser", e) + } + }.start() + } + + open class AutoDevApp( + applicationName: String = "Auto Dev Assistant v1.1", + open val symbols: Map = mapOf(), + val temperature: Double = 0.1, + val event: AnActionEvent, + ) : ApplicationServer( + applicationName = applicationName, + path = "/autodev", + ) { + override fun userMessage( + session: Session, + user: User?, + userMessage: String, + ui: ApplicationInterface, + api: API + ) { + val settings = getSettings(session, user) ?: Settings() + if (api is ClientManager.MonitoredClient) api.budget = settings.budget ?: 2.00 + AutoDevAgent( + api = api, + dataStorage = dataStorage, + session = session, + user = user, + ui = ui, + tools = settings.tools, + model = settings.model, + event = event, + ).start( + userMessage = userMessage, + ) + } + + data class Settings( + val budget: Double? = 2.00, + val tools: List = emptyList(), + val model: ChatModels = ChatModels.GPT4Turbo, + ) + + override val settingsClass: Class<*> get() = Settings::class.java + + @Suppress("UNCHECKED_CAST") + override fun initSettings(session: Session): T? = Settings() as T + } + + class AutoDevAgent( + val api: API, + dataStorage: StorageInterface, + session: Session, + user: User?, + val ui: ApplicationInterface, + val model: ChatModels = ChatModels.GPT35Turbo, + val tools: List = emptyList(), + val actorMap: Map> = mapOf( + ActorTypes.DesignActor to ParsedActor( + parserClass = TaskListParser::class.java, + prompt = """ + Translate the user directive into an action plan for the project. + Break the user's request into a list of simple tasks to be performed. + For each task, provide a list of files to be modified and a description of the changes to be made. + """.trimIndent(), + model = model, + parsingModel = model, + ), + ActorTypes.TaskCodingActor to SimpleActor( + prompt = """ + Implement the changes to the codebase as described in the task list. + + Response should use one or more code patches in diff format within ```diff code blocks. + Each diff should be preceded by a header that identifies the file being modified. + The diff format should use + for line additions, - for line deletions. + The diff should include 2 lines of context before and after every change. + + Example: + + Explanation text + + ### scripts/filename.js + ```diff + - const b = 2; + + const a = 1; + ``` + + Continued text + """.trimIndent(), + model = model + ), + ), + val event: AnActionEvent, + ) : ActorSystem(actorMap, dataStorage, user, session) { + enum class ActorTypes { + DesignActor, + TaskCodingActor, + } + + val designActor by lazy { getActor(ActorTypes.DesignActor) as ParsedActor } + val taskActor by lazy { getActor(ActorTypes.TaskCodingActor) as SimpleActor } + + fun start( + userMessage: String, + ) { + val dataContext = event.dataContext + val virtualFiles = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext) + val languages = + virtualFiles?.associate { it to ComputerLanguage.findByExtension(it.extension ?: "")?.name } ?: mapOf() + val virtualFileMap = virtualFiles?.associate { it.toNioPath() to it } ?: mapOf() + val codeFiles = mutableMapOf() + val root = virtualFiles?.map { file -> + file.toNioPath() + }?.toTypedArray()?.commonRoot()!! + val paths = virtualFiles.associate { file -> + val relative = root.relativize(file.toNioPath()) + val path = relative.toString() + val language = languages[file] ?: "plaintext" + val code = file.contentsToByteArray().toString(Charsets.UTF_8) + codeFiles[path] = code + path to language + } + + fun codeSummary() = codeFiles.entries.joinToString("\n\n") { (path, code) -> + "# $path\n```${ + path.split('.').last() + }\n$code\n```" + } + + + val architectureResponse = AgentPatterns.iterate( + input = userMessage, + heading = userMessage, + actor = designActor, + toInput = { listOf(codeSummary(), it) }, + api = api, + ui = ui, + outputFn = { task, design -> + task.add(MarkdownUtil.renderMarkdown("${design.text}\n\n```json\n${JsonUtil.toJson(design.obj)}\n```")) + } + ) + + + val task = ui.newTask() + try { + architectureResponse.obj.tasks.forEach { (paths, description) -> + task.complete(ui.hrefLink("Task: $description") { + val task = ui.newTask() + task.header("Task: $description") + task.complete( + MarkdownUtil.renderMarkdown( + ui.socketManager.addApplyDiffLinks( + codeFiles.filter { (path, _) -> paths?.contains(path) == true }, + taskActor.answer(listOf( + userMessage, + architectureResponse.text, + codeFiles.filter { (path, _) -> paths?.contains(path) == true }.entries.joinToString("\n\n") { + "# ${it.key}\n```${it.key.split('.').last()}\n${it.value}\n```" + }, + "Provide a change for ${paths?.joinToString(",") { it } ?: ""} ($description)" + ), api) + ) { newCodeMap -> + newCodeMap.forEach { (path, newCode) -> + val prev = codeFiles[path] + if (prev != newCode) { + codeFiles[path] = newCode + task.complete( + "$path Updated" + ) + } + } + }) + ) + }) + + } + } catch (e: Throwable) { + log.warn("Error", e) + task.error(ui, e) + } + } + + + } + + companion object { + private val log = LoggerFactory.getLogger(AutoDevAction::class.java) + private val agents = mutableMapOf() + val root: File get() = File(ApplicationEvents.pluginHome, "code_chat") + private fun initApp(server: AppServer, path: String): ChatServer { + server.appRegistry[path]?.let { return it } + val socketServer = object : ApplicationServer(applicationName = "Code Chat", path = path) { + override val singleInput = true + override val stickyInput = false + override fun newSession(user: User?, session: Session) = agents[session]!!.newSession(user, session) + } + server.addApp(path, socketServer) + return socketServer + } + + interface TaskListParser : java.util.function.Function { + @Description("Parse out a list of tasks to be performed in this project") + override fun apply(text: String): TaskList + } + + data class TaskList( + @Description("List of tasks to be performed in this project") + val tasks: List = emptyList() + ) : ValidatedObject { + override fun validate(): String? = when { + tasks.isEmpty() -> "Resources are required" + tasks.any { it.validate() != null } -> "Invalid resource" + else -> null + } + } + + data class Task( + val paths: List? = null, + val description: String? = null + ) : ValidatedObject { + override fun validate(): String? = when { + paths.isNullOrEmpty() -> "Paths are required" + paths.any { it.isBlank() } -> "Invalid path" + else -> null + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DocumentationCompilerAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DocumentationCompilerAction.kt index be38ee39..77d4e34c 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DocumentationCompilerAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DocumentationCompilerAction.kt @@ -9,6 +9,8 @@ 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.ui.CheckBoxList +import com.intellij.ui.components.JBScrollPane import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList import org.apache.commons.io.IOUtils @@ -35,10 +37,22 @@ class DocumentationCompilerAction : FileContextAction = listOf(), ) class Settings( @@ -46,18 +60,35 @@ class DocumentationCompilerAction : FileContextAction = CheckBoxList() + val files = Files.walk(root) + .filter { Files.isRegularFile(it) && !Files.isDirectory(it) } + .toList().filterNotNull().toTypedArray() + filesToProcess.setItems(files.toMutableList()) { path -> + root?.relativize(path)?.toString() ?: path.toString() + } + files.forEach { path -> + filesToProcess.setItemSelected(path, true) + } + val settingsUI = SettingsUI().apply { + filesToProcessScrollPane.setViewportView(filesToProcess) + } + val settings: UserSettings = UITools.showDialog2( project, - SettingsUI::class.java, + settingsUI, UserSettings::class.java, "Compile Documentation" - ), project - ) + ) { } + settings.filesToProcess = files.filter { path -> filesToProcess.isItemSelected(path) }.toList() + //.map { path -> return@map root?.resolve(path) }.filterNotNull() + return Settings(settings, project) + } override fun processSelection(state: SelectionState, config: Settings?): Array { val root = state.selectedFile.toPath() - var outputPath = root.resolve("compiled_documentation.md") + var outputPath = root.resolve(config?.settings?.outputFilename ?: "compiled_documentation.md") if (outputPath.toFile().exists()) { val extension = outputPath.toString().split(".").last() val name = outputPath.toString().split(".").dropLast(1).joinToString(".") @@ -71,8 +102,11 @@ class DocumentationCompilerAction : FileContextAction> { path -> executorService.submit { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt new file mode 100644 index 00000000..3a521a6d --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt @@ -0,0 +1,173 @@ +package com.github.simiacryptus.aicoder.actions.generic + +import com.github.simiacryptus.aicoder.ApplicationEvents +import com.github.simiacryptus.aicoder.actions.BaseAction +import com.github.simiacryptus.aicoder.actions.dev.AppServer +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.util.ComputerLanguage +import com.github.simiacryptus.aicoder.util.addApplyDiffLinks +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.simiacryptus.skyenet.core.platform.ApplicationServices +import com.simiacryptus.skyenet.core.platform.Session +import com.simiacryptus.skyenet.core.platform.StorageInterface +import com.simiacryptus.skyenet.core.platform.User +import com.simiacryptus.skyenet.webui.application.ApplicationServer +import com.simiacryptus.skyenet.webui.chat.ChatServer +import com.simiacryptus.skyenet.webui.chat.ChatSocketManager +import com.simiacryptus.skyenet.webui.session.SocketManager +import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import org.slf4j.LoggerFactory +import java.awt.Desktop +import java.io.File +import java.nio.file.Path + +class MultiDiffChatAction : BaseAction() { + + val path = "/diffChat" + + override fun handle(e: AnActionEvent) { + + val dataContext = e.dataContext + val virtualFiles = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext) + val languages = + virtualFiles?.associate { it to ComputerLanguage.findByExtension(it.extension ?: "")?.name } ?: mapOf() + val virtualFileMap = virtualFiles?.associate { it.toNioPath() to it } ?: mapOf() + val codeFiles = mutableMapOf() + val root = virtualFiles?.map { file -> + file.toNioPath() + }?.toTypedArray()?.commonRoot()!! + val paths = virtualFiles.associate { file -> + val relative = root.relativize(file.toNioPath()) + val path = relative.toString() + val language = languages[file] ?: "plaintext" + val code = file.contentsToByteArray().toString(Charsets.UTF_8) + codeFiles[path] = code + path to language + } + + fun codeSummary() = codeFiles.entries.joinToString("\n\n") { (path, code) -> + "# $path\n```${ + path.split('.').last() + }\n$code\n```" + } + val session = StorageInterface.newGlobalID() + //DataStorage.sessionPaths[session] = root.toFile() + + agents[session] = object : ChatSocketManager( + session = session, + model = AppSettingsState.instance.defaultChatModel(), + userInterfacePrompt = """ + | + |${codeSummary()} + | + """.trimMargin().trim(), + systemPrompt = """ + You are a helpful AI that helps people with coding. + + You will be answering questions about the following code: + + ${codeSummary()} + + Response should use one or more code patches in diff format within ```diff code blocks. + Each diff should be preceded by a header that identifies the file being modified. + The diff format should use + for line additions, - for line deletions. + The diff should include 2 lines of context before and after every change. + + Example: + + Explanation text + + ### scripts/filename.js + ```diff + - const b = 2; + + const a = 1; + ``` + + Continued text + """.trimIndent(), + api = api, + applicationClass = ApplicationServer::class.java, + storage = ApplicationServices.dataStorageFactory(DiffChatAction.root), + ) { + override fun renderResponse(response: String): String { + val html = renderMarkdown(addApplyDiffLinks(codeFiles, response) { newCodeMap -> + newCodeMap.map { (path, newCode) -> + val prev = codeFiles[path] + if (prev != newCode) { + codeFiles[path] = newCode + root.resolve(path).let { file -> + file.toFile().writeText(newCode) + val virtualFile = virtualFileMap.get(file) + if(null != virtualFile) FileDocumentManager.getInstance().getDocument(virtualFile)?.let { doc -> + WriteCommandAction.runWriteCommandAction(e.project) { + doc.setText(newCode) + } + } + } + "$path Updated" + } else { +// "$path Unchanged" + "" + } + } + }) + return """
$html
""" + } + } + + val server = AppServer.getServer(e.project) + val app = initApp(server, path) + app.sessions[session] = app.newSession(null, session) + + Thread { + Thread.sleep(500) + try { + Desktop.getDesktop().browse(server.server.uri.resolve("$path/#$session")) + } catch (e: Throwable) { + log.warn("Error opening browser", e) + } + }.start() + } + + override fun isEnabled(event: AnActionEvent) = true + + companion object { + private val log = LoggerFactory.getLogger(MultiDiffChatAction::class.java) + private val agents = mutableMapOf() + val root: File get() = File(ApplicationEvents.pluginHome, "mdiff_chat") + private fun initApp(server: AppServer, path: String): ChatServer { + server.appRegistry[path]?.let { return it } + val socketServer = object : ApplicationServer("Multi-file Diff Chat", path) { + override val singleInput = false + override val stickyInput = true + override fun newSession(user: User?, session: Session) = agents[session]!! + } + server.addApp(path, socketServer) + return socketServer + } + + } +} + +fun Array.commonRoot() = this.reduce { a, b -> + if (a.startsWith(b)) b + else if (b.startsWith(a)) a + else { + val common = a.commonPrefixWith(b) + if (common == a) a + else if (common == b) b + else common.toAbsolutePath() + } +} + +private fun Path.commonPrefixWith(b: Path): Path { + val a = this + val aParts = a.toAbsolutePath().toString().split(File.separator) + val bParts = b.toAbsolutePath().toString().split(File.separator) + val common = aParts.zip(bParts).takeWhile { (a, b) -> a == b }.map { it.first } + return File(File.separator + common.joinToString(File.separator)).toPath() +} + diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt index 5b323d95..3ed0dd57 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt @@ -32,7 +32,7 @@ import java.awt.Desktop import java.io.File import java.util.concurrent.atomic.AtomicBoolean -private val VirtualFile.toFile: File get() = File(this.path) +val VirtualFile.toFile: File get() = File(this.path) class WebDevAction : BaseAction() { @@ -191,9 +191,10 @@ class WebDevAction : BaseAction() { val javascriptActor by lazy { getActor(ActorTypes.JavascriptCodingActor) as SimpleActor } val cssActor by lazy { getActor(ActorTypes.CssCodingActor) as SimpleActor } val codeReviewer by lazy { getActor(ActorTypes.CodeReviewer) as SimpleActor } - val codeFiles = mutableMapOf() val etcActor by lazy { getActor(ActorTypes.EtcCodingActor) as SimpleActor } + val codeFiles = mutableMapOf() + fun start( userMessage: String, ) { 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 a7d9eb42..6784afbe 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.0049 + private val version = 2.0070 fun edit(superChildren: Array): Array { val children = superChildren.toList().toMutableList() diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt index 71c894f4..ffc4a428 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt @@ -1,6 +1,4 @@ -@file:Suppress("UNNECESSARY_SAFE_CALL", "UNCHECKED_CAST") - -package com.github.simiacryptus.aicoder.util +package com.github.simiacryptus.aicoder.util import com.github.simiacryptus.aicoder.config.ActionSettingsRegistry import com.github.simiacryptus.aicoder.config.AppSettingsState @@ -62,6 +60,8 @@ import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaType +private val ListModel.elements get() = (0 until this.size).forEach { this.getElementAt(it) } + object UITools { val retry = WeakHashMap() @@ -258,6 +258,32 @@ object UITools { if (uiVal is JScrollPane) { uiVal = uiVal.viewport.view } +// // Handle JBList bound to ArrayList +// if (uiVal is JBList<*> && ArrayList::class.java.isAssignableFrom(settingsField.returnType.javaType as Class<*>) && settingsField.returnType.arguments[0].type?.javaType?.typeName == "java.nio.file.Path") { +// val model = uiVal.model +// newSettingsValue = ArrayList().apply { +// for (i in 0 until model.size) { +// val element = model.getElementAt(i) +// if (element is String) { +// add(Paths.get(element)) +// } +// } +// } +// } +// // Handle JBList bound to List +// if (uiVal is JBList<*> && List::class.java.isAssignableFrom(settingsField.returnType.javaClass)) { +// newSettingsValue = uiVal.model.elements +// } +// // Handle CheckBoxList bound to List +// if (uiVal is CheckBoxList<*> && List::class.java.isAssignableFrom(settingsField.returnType.javaClass)) { +// val model = uiVal.model +// val checkBoxListValues = ArrayList() +// for (i in 0 until model.size) { +// checkBoxListValues.add( +// model.getElementAt(i)?.let { item -> item to uiVal.isItemSelected(item as Nothing?) }) +// } +// newSettingsValue = checkBoxListValues +// } when (settingsField.returnType.javaType.typeName) { "java.lang.String" -> if (uiVal is JTextComponent) { newSettingsValue = uiVal.text @@ -318,14 +344,47 @@ object UITools { for (settingsField in publicProperties) { val fieldName = settingsField.name try { - if (!declaredUIFields.contains(fieldName)) continue - val uiField: KProperty1 = - (componentClass.kotlin.memberProperties.find { it.name == fieldName } as KProperty1?)!! + if (!declaredUIFields.contains(fieldName)) { + log.warn("Field not found: $fieldName") + continue + } + val uiField: KProperty1 = + (componentClass.kotlin.memberProperties.find { it.name == fieldName } as KProperty1?)!! val settingsVal = settingsField.get(settings) ?: continue var uiVal = uiField.get(component) if (uiVal is JScrollPane) { uiVal = uiVal.viewport.view } +// // Handle JBList bound to ArrayList +// if (uiVal is JBList<*> && settingsVal is List<*> && settingsVal.all { it is Path }) { +// val listModel = DefaultListModel() +// settingsVal.forEach { path -> +// if (path is Path) { +// listModel.addElement(path.toString()) +// } +// } +// uiVal.model = listModel +// } +// // Handle JBList bound to List +// if (uiVal is JBList<*> && settingsVal is List<*>) { +// val listModel = DefaultListModel() +// settingsVal.forEach { listModel.addElement(it) } +// uiVal.model = listModel +// } +// // Handle CheckBoxList bound to List> +// val checkBoxListValues = ArrayList>() +// if (uiVal is CheckBoxList<*> && settingsVal is List<*>) { +// val listModel = DefaultListModel() +// settingsVal.forEach { item -> +// if (item is Pair<*, *>) { +// listModel.addElement(item.first) +// uiVal.setItemSelected(item.first as Nothing?, item.second as Boolean) +// } +// } +// settingsVal.forEach { listModel.addElement(it) } +// @Suppress("UNCHECKED_CAST") +// uiVal.model = listModel as ListModel +// } when (settingsField.returnType.javaType.typeName) { "java.lang.String" -> if (uiVal is JTextComponent) { uiVal.text = settingsVal.toString() @@ -557,9 +616,31 @@ object UITools { configClass: Class, title: String = "Generate Project", onComplete: (C) -> Unit = { _ -> }, - ): C? { - val component = uiClass.getConstructor().newInstance() - val config = configClass.getConstructor().newInstance() + ) = showDialog(project, uiClass, configClass.getConstructor().newInstance(), title, onComplete) + + fun showDialog2( + project: Project?, + component: T, + configClass: Class, + title: String = "Generate Project", + onComplete: (C) -> Unit = { _ -> }, + ) = showDialog(project, component, configClass.getConstructor().newInstance(), title, onComplete) + + fun showDialog( + project: Project?, + uiClass: Class, + config: C, + title: String, + onComplete: (C) -> Unit + ) = showDialog(project, uiClass.getConstructor().newInstance(), config, title, onComplete) + + fun showDialog( + project: Project?, + component: T, + config: C, + title: String, + onComplete: (C) -> Unit + ): C { val dialog = object : DialogWrapper(project) { init { this.init() diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d20708e8..0c880ab9 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -119,6 +119,15 @@ --> + + + + @@ -129,6 +138,15 @@ --> + + + +