From 4e665f7bf8d8d0f4e692eb9b19e68e04e1f1d41f Mon Sep 17 00:00:00 2001 From: shield Date: Mon, 28 Aug 2023 10:56:55 +0800 Subject: [PATCH] fix intellij version (#28) * idea plugin baseline * selection code send ok * selection code replace ready * clean when plugin upgrade * remove same not necessary --- .../fastRequestCurrentProjectConfig.json | 24 +++++ .../fastRequestCurrentProjectEnvironment.json | 3 + packages/gpt-runner-intellij/CHANGELOG.md | 10 ++ packages/gpt-runner-intellij/build.gradle.kts | 24 +++-- .../gpt-runner-intellij/gradle.properties | 2 +- .../nicepkg/gptrunner/intellij/LangBundle.kt | 21 ++++ .../intellij/actions/OpenInEditorMode.kt | 24 +++++ .../intellij/listeners/ClientEventName.kt | 22 +++++ .../listeners/EditorMouseListenerImpl.kt | 42 ++++++++ .../listeners/PluginInstallerStateListener.kt | 16 ++++ .../listeners/ProjectStateListener.kt | 28 ++++++ .../handler/BaseBrowserEventHandler.kt | 9 ++ .../listeners/handler/BrowserEventHandlers.kt | 45 +++++++++ .../listeners/handler/CodeReplaceHandler.kt | 29 ++++++ .../intellij/listeners/publish/AppEvent.kt | 32 +++++++ .../listeners/publish/AppEventPublisher.kt | 32 +++++++ .../intellij/services/AbstractService.kt | 9 ++ .../services/IGPTRunnerExecutableService.kt | 9 ++ .../intellij/services/IGPTRunnerService.kt | 11 +++ .../services/RunnerAgentCommandLine.kt | 27 ++++++ .../impl/GPTRunnerExecutableService.kt | 17 +++- .../services/impl/GPTRunnerService.kt | 14 ++- .../intellij/ui/windows/GPTRunnerWindow.kt | 24 ++--- .../ui/windows/GPTRunnerWindowFactory.kt | 95 +++++++++++++++++++ .../ui/windows/GPTRunnerWindowHolder.kt | 13 +++ .../intellij/utils/RunnerAgentUtil.kt | 15 +++ .../src/main/resources/META-INF/plugin.xml | 6 ++ .../messages/LangBundle_en.properties | 6 ++ 28 files changed, 578 insertions(+), 31 deletions(-) create mode 100644 packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectConfig.json create mode 100644 packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectEnvironment.json create mode 100644 packages/gpt-runner-intellij/CHANGELOG.md create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/LangBundle.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/actions/OpenInEditorMode.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ClientEventName.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/EditorMouseListenerImpl.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/PluginInstallerStateListener.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ProjectStateListener.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BaseBrowserEventHandler.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BrowserEventHandlers.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/CodeReplaceHandler.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEvent.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEventPublisher.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/AbstractService.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerExecutableService.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerService.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/RunnerAgentCommandLine.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowFactory.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowHolder.kt create mode 100644 packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/utils/RunnerAgentUtil.kt create mode 100644 packages/gpt-runner-intellij/src/main/resources/messages/LangBundle_en.properties diff --git a/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectConfig.json b/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectConfig.json new file mode 100644 index 0000000..af2ef5d --- /dev/null +++ b/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectConfig.json @@ -0,0 +1,24 @@ +{ + "dataList":[], + "envList":[], + "headerList":[], + "maxDescriptionLength":-1, + "postScript":"", + "preScript":"", + "projectList":[], + "syncModel":{ + "branch":"master", + "domain":"https://github.com", + "enabled":false, + "namingPolicy":"byDoc", + "owner":"", + "repo":"", + "repoUrl":"", + "syncAfterRun":false, + "token":"", + "type":"github" + }, + "urlEncodedKeyValueList":[], + "urlParamsKeyValueList":[], + "urlSuffix":"" +} \ No newline at end of file diff --git a/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectEnvironment.json b/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectEnvironment.json new file mode 100644 index 0000000..6b0e7ab --- /dev/null +++ b/packages/gpt-runner-intellij/.fastRequest/config/fastRequestCurrentProjectEnvironment.json @@ -0,0 +1,3 @@ +{ + "environment":{} +} \ No newline at end of file diff --git a/packages/gpt-runner-intellij/CHANGELOG.md b/packages/gpt-runner-intellij/CHANGELOG.md new file mode 100644 index 0000000..8d8396e --- /dev/null +++ b/packages/gpt-runner-intellij/CHANGELOG.md @@ -0,0 +1,10 @@ + + +# Changelog + +## [Unreleased] + +## [0.0.1] - 2023-06-30 + +### Added +- Initial project scaffold diff --git a/packages/gpt-runner-intellij/build.gradle.kts b/packages/gpt-runner-intellij/build.gradle.kts index b16c1c1..22ab5ac 100755 --- a/packages/gpt-runner-intellij/build.gradle.kts +++ b/packages/gpt-runner-intellij/build.gradle.kts @@ -9,12 +9,12 @@ fun environment(key: String) = providers.environmentVariable(key) plugins { id("java") // Java support - alias(libs.plugins.kotlin) // Kotlin support - alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin - alias(libs.plugins.changelog) // Gradle Changelog Plugin - alias(libs.plugins.qodana) // Gradle Qodana Plugin - alias(libs.plugins.kover) // Gradle Kover Plugin - alias(libs.plugins.nodeGradle) // Gradle Node Plugin + id("org.jetbrains.kotlin.jvm") version "1.9.0" // Kotlin support + id("org.jetbrains.intellij") version "1.15.0" // Gradle IntelliJ Plugin + id("org.jetbrains.changelog") version "2.1.2" // Gradle Changelog Plugin + id("org.jetbrains.qodana") version "0.1.13" // Gradle Qodana Plugin + id("org.jetbrains.kotlinx.kover") version "0.7.3" // Gradle Kover Plugin + id("com.github.node-gradle.node") version "7.0.0" // Gradle Node Plugin } group = properties("pluginGroup").get() @@ -22,13 +22,17 @@ version = properties("pluginVersion").get() // Configure project's dependencies repositories { + maven { + url = uri("https://maven.aliyun.com/repository/public") + } mavenCentral() } // Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog dependencies { // implementation(libs.annotations) - implementation(libs.coroutines) +// implementation(libs.coroutines) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") } // Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. @@ -83,8 +87,8 @@ node { // nodeProjectDir.set(File("../gpt-runner-web")) // workDir.set(File("../gpt-runner-web")) - nodeProjectDir.set(File("./")) - workDir.set(File("./")) + nodeProjectDir.set(File("../gpt-runner-web")) + workDir.set(File("../gpt-runner-web")) } val buildGPTRunnerWebClientTask = tasks.register("buildGPTRunnerWebClient", PnpmTask::class) { @@ -94,7 +98,7 @@ val buildGPTRunnerWebClientTask = tasks.register("buildGPTRunnerWebClient", Pnpm val runGPTRunnerServerTask = tasks.register("runGPTRunnerServerTask", NodeTask::class) { dependsOn(tasks.pnpmInstall) - script.set(File("../gpt-runner-web/dist/start-server.mjs")) + script.set(File("../gpt-runner-web/dist/start-server.cjs")) } tasks.withType().configureEach { diff --git a/packages/gpt-runner-intellij/gradle.properties b/packages/gpt-runner-intellij/gradle.properties index fe7ddf7..9247cc8 100755 --- a/packages/gpt-runner-intellij/gradle.properties +++ b/packages/gpt-runner-intellij/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = cn.nicepkg.gptrunner.intellij pluginName = GPT-Runner pluginRepositoryUrl = https://github.com/nicepkg/gpt-runner # SemVer format -> https://semver.org -pluginVersion = 0.0.2 +pluginVersion = 0.0.3 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 222 diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/LangBundle.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/LangBundle.kt new file mode 100644 index 0000000..7bcc783 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/LangBundle.kt @@ -0,0 +1,21 @@ +package cn.nicepkg.gptrunner.intellij + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.NonNls +import org.jetbrains.annotations.PropertyKey + +@NonNls +private const val BUNDLE = "messages.LangBundle" + +object LangBundle : DynamicBundle(BUNDLE) { + + @Suppress("SpreadOperator") + @JvmStatic + fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + getMessage(key, *params) + + @Suppress("SpreadOperator", "unused") + @JvmStatic + fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + getLazyMessage(key, *params) +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/actions/OpenInEditorMode.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/actions/OpenInEditorMode.kt new file mode 100644 index 0000000..0b9ea28 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/actions/OpenInEditorMode.kt @@ -0,0 +1,24 @@ +package cn.nicepkg.gptrunner.intellij.actions + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent + +// TODO: impl it +class OpenInEditorMode : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + if (e.project == null) return +// val project = e.project!! +// val gptRunnerService = project.service() +// project.projectFile + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = e.project != null + super.update(e) + } + + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.EDT + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ClientEventName.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ClientEventName.kt new file mode 100644 index 0000000..85c0fb4 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ClientEventName.kt @@ -0,0 +1,22 @@ +package cn.nicepkg.gptrunner.intellij.listeners + +/** + * 事件名称 + */ +enum class ClientEventName(val value: String) { + InitSuccess("initSuccess"), + RefreshTree("refreshTree"), + RefreshChatTree("refreshChatTree"), + RefreshFileTree("refreshFileTree"), + InsertCodes("insertCodes"), + DiffCodes("diffCodes"), + UpdateIdeOpeningFiles("updateIdeOpeningFiles"), + UpdateIdeActiveFilePath("updateIdeActiveFilePath"), + UpdateUserSelectedText("updateUserSelectedText"), + OpenFileInIde("openFileInIde"), + OpenFileInFileEditor("openFileInFileEditor"), + GoToChatPanel("goToChatPanel") + + + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/EditorMouseListenerImpl.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/EditorMouseListenerImpl.kt new file mode 100644 index 0000000..e14e9de --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/EditorMouseListenerImpl.kt @@ -0,0 +1,42 @@ +package cn.nicepkg.gptrunner.intellij.listeners + +import cn.nicepkg.gptrunner.intellij.listeners.publish.AppEventPublisher +import com.intellij.openapi.editor.event.EditorMouseEvent +import com.intellij.openapi.editor.event.EditorMouseListener +import com.intellij.openapi.editor.impl.EditorImpl +import java.util.regex.Pattern + +/** + * monitor selected text + */ +class EditorMouseListenerImpl : EditorMouseListener { + + override fun mouseReleased(event: EditorMouseEvent) { + super.mouseReleased(event) + val selectedText = event.editor.selectionModel.getSelectedText() + val length = selectedText?.trim()?.length + if (length != null && length > 0) { + // Markdown format + val editorImpl = event.editor as EditorImpl + val fileExtension = getFileExtension(editorImpl.virtualFile.name) + val fullPrompt = getFullPrompt(fileExtension, selectedText) + + // send + AppEventPublisher.updateUserSelectedText(fullPrompt) + } + } + + + private fun getFileExtension(filename: String): String { + val pattern = Pattern.compile("[^.]+$") + val matcher = pattern.matcher(filename) + if (matcher.find()) { + return matcher.group() + } + return "" + } + + private fun getFullPrompt(fileExtension: String?, selectedText: String): String { + return String.format("\n```%s\n%s\n```", fileExtension, selectedText) + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/PluginInstallerStateListener.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/PluginInstallerStateListener.kt new file mode 100644 index 0000000..5ad9fd0 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/PluginInstallerStateListener.kt @@ -0,0 +1,16 @@ +package cn.nicepkg.gptrunner.intellij.listeners + +import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerExecutableService +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.plugins.PluginStateListener +import com.intellij.openapi.components.service + +class PluginInstallerStateListener : PluginStateListener { + override fun install(descriptor: IdeaPluginDescriptor) { + service() + } + + override fun uninstall(descriptor: IdeaPluginDescriptor) { + super.uninstall(descriptor) + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ProjectStateListener.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ProjectStateListener.kt new file mode 100644 index 0000000..61f6f51 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/ProjectStateListener.kt @@ -0,0 +1,28 @@ +package cn.nicepkg.gptrunner.intellij.listeners + +import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerService +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManagerListener +import kotlinx.coroutines.runBlocking +import kotlin.concurrent.thread + +class ProjectStateListener : ProjectManagerListener { + override fun projectOpened(project: Project) { + thisLogger().debug("project: ${project.name}") + thisLogger().debug("project.isInitialized: ${project.isInitialized}") + thisLogger().debug("project.isOpen: ${project.isOpen}") + thread { + runBlocking { + project.service().startNodeServer() + } + } + super.projectOpened(project) + } + + override fun projectClosed(project: Project) { + runBlocking { project.service().closeNodeServer() } + super.projectClosed(project) + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BaseBrowserEventHandler.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BaseBrowserEventHandler.kt new file mode 100644 index 0000000..e35c0f6 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BaseBrowserEventHandler.kt @@ -0,0 +1,9 @@ +package cn.nicepkg.gptrunner.intellij.listeners.handler + +import com.intellij.openapi.project.Project +import com.intellij.ui.jcef.JBCefJSQuery + +abstract class BaseBrowserEventHandler(val project: Project) : java.util.function.Function { + + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BrowserEventHandlers.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BrowserEventHandlers.kt new file mode 100644 index 0000000..10d6e4a --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/BrowserEventHandlers.kt @@ -0,0 +1,45 @@ +package cn.nicepkg.gptrunner.intellij.listeners.handler + +import cn.nicepkg.gptrunner.intellij.listeners.ClientEventName +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.intellij.openapi.project.Project +import com.intellij.ui.jcef.JBCefJSQuery +import java.util.* +import kotlin.collections.HashMap + +/** + * handle browser events + */ +class BrowserEventHandlers(project: Project) : BaseBrowserEventHandler(project) { + + private val handlers: HashMap> = HashMap() + + @Volatile + var initFlag: Boolean = false + + override fun apply(t: String): JBCefJSQuery.Response { + if (!initFlag) { + synchronized(this) { + if (!initFlag) { + handlers[ClientEventName.InsertCodes.value] = CodeReplaceHandler(project) + } + initFlag = true + } + } + + val gson = Gson() + val any = gson.fromJson(t, Any::class.java) + if (any is JsonArray) { + val eventName = any.get(0) + if (handlers.contains(eventName.asString)){ + return handlers.get(eventName.asString)!!.apply(any.get(1)) + } + } + + return JBCefJSQuery.Response("Done") + } + + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/CodeReplaceHandler.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/CodeReplaceHandler.kt new file mode 100644 index 0000000..ad633f7 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/handler/CodeReplaceHandler.kt @@ -0,0 +1,29 @@ +package cn.nicepkg.gptrunner.intellij.listeners.handler + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.ui.jcef.JBCefJSQuery + +/** + * TODO 验证 + * + * @author shield + */ +class CodeReplaceHandler(project: Project) : BaseBrowserEventHandler(project) { + + override fun apply(t: JsonElement): JBCefJSQuery.Response { + val editor = FileEditorManager.getInstance(project).getSelectedTextEditor() + if (editor != null) { + val selectionModel = editor.selectionModel + + if (t is JsonObject && t.has("codes")) { + val content = t.get("codes").asString + editor.document.replaceString(selectionModel.selectionStart, selectionModel.selectionEnd, content) + } + } + + return JBCefJSQuery.Response("Done") + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEvent.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEvent.kt new file mode 100644 index 0000000..3a90be9 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEvent.kt @@ -0,0 +1,32 @@ +package cn.nicepkg.gptrunner.intellij.listeners.publish + +import cn.nicepkg.gptrunner.intellij.listeners.ClientEventName +import kotlinx.serialization.Serializable + + +@Serializable +class AppEvent(value: String?, eventData: Any?, type: String?) { + + private val eventName: String? = null + private val type: String? = null + private val eventData: Any? = null + + + private var text: String? = null + private val codes: String? = null + + companion object { + fun of(eventName: ClientEventName, eventData: Any, type: String): AppEvent { + return AppEvent(eventName.value, eventData, type) + } + + fun of(text: String): AppEvent { + val event = AppEvent() + event.text = text + return event + } + } + + constructor() : this(null, null, null) + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEventPublisher.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEventPublisher.kt new file mode 100644 index 0000000..89f300f --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/listeners/publish/AppEventPublisher.kt @@ -0,0 +1,32 @@ +package cn.nicepkg.gptrunner.intellij.listeners.publish + +import cn.nicepkg.gptrunner.intellij.listeners.ClientEventName +import cn.nicepkg.gptrunner.intellij.ui.windows.GPTRunnerWindowHolder +import com.google.gson.JsonObject + +/** + * push event to web + */ +class AppEventPublisher { + + companion object { + + + fun updateUserSelectedText(text: String) { + val jsonObject = JsonObject() + jsonObject.addProperty("text", text) + + publish(ClientEventName.UpdateUserSelectedText.value, jsonObject) + } + + + private fun publish(eventName: String, eventData: JsonObject) { + GPTRunnerWindowHolder.jBCefBrowser!!.cefBrowser.executeJavaScript( + "window.__emitter__.emit( \"${eventName}\", $eventData )", + null, + 0 + ) + } + } + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/AbstractService.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/AbstractService.kt new file mode 100644 index 0000000..c390a28 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/AbstractService.kt @@ -0,0 +1,9 @@ +package cn.nicepkg.gptrunner.intellij.services + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId + +abstract class AbstractService { + val pluginId = "cn.nicepkg.gptrunner.intellij" + val plugin = PluginManagerCore.getPlugin(PluginId.getId(pluginId))!! +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerExecutableService.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerExecutableService.kt new file mode 100644 index 0000000..4f998f9 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerExecutableService.kt @@ -0,0 +1,9 @@ +package cn.nicepkg.gptrunner.intellij.services + +import java.nio.file.Path + +interface IGPTRunnerExecutableService { + val userHome: Path + val gptRunnerExecutablesDir: Path + val gptRunnerExecutableDir: Path +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerService.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerService.kt new file mode 100644 index 0000000..7daf1e2 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/IGPTRunnerService.kt @@ -0,0 +1,11 @@ +package cn.nicepkg.gptrunner.intellij.services + +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import org.apache.commons.lang3.SystemUtils + +interface IGPTRunnerService : Disposable { + val port: Int + suspend fun startNodeServer() + suspend fun closeNodeServer() +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/RunnerAgentCommandLine.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/RunnerAgentCommandLine.kt new file mode 100644 index 0000000..4c9b31b --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/RunnerAgentCommandLine.kt @@ -0,0 +1,27 @@ +package cn.nicepkg.gptrunner.intellij.services + +import com.intellij.execution.configurations.PathEnvironmentVariableUtil +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.SystemInfoRt +import java.nio.file.Files +import java.nio.file.Path + +object RunnerAgentCommandLine { + private val LOG = Logger.getInstance(RunnerAgentCommandLine::class.java) + + fun getNodeExecutablePath(): Path? { + val path = PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("node") + if (path == null) { + LOG.debug("在 PATH 中找不到 node 可执行文件") + return null + } + val nioPath = path.toPath() + if (SystemInfoRt.isUnix && !Files.isExecutable(nioPath)) { + LOG.warn("node 可执行文件没有执行权限: $nioPath") + return null + } + LOG.debug("在 $nioPath 找到了 node 可执行文件") + return nioPath + } + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerExecutableService.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerExecutableService.kt index c3cd48c..1cd590e 100755 --- a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerExecutableService.kt +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerExecutableService.kt @@ -2,17 +2,18 @@ package cn.nicepkg.gptrunner.intellij.services.impl import cn.nicepkg.gptrunner.intellij.services.AbstractService import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerExecutableService +import com.intellij.openapi.util.io.NioFiles import org.apache.commons.lang3.SystemUtils -import java.io.File import java.net.HttpURLConnection import java.net.URL import java.nio.file.Files import java.nio.file.StandardCopyOption -import java.nio.file.attribute.FileAttribute import java.util.zip.ZipEntry import java.util.zip.ZipInputStream -import kotlin.io.path.* -import kotlin.reflect.jvm.internal.impl.builtins.StandardNames.FqNames.target +import kotlin.io.path.createDirectories +import kotlin.io.path.exists +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.notExists class GPTRunnerExecutableService : AbstractService(), @@ -21,8 +22,14 @@ class GPTRunnerExecutableService : AbstractService(), override val gptRunnerExecutablesDir = userHome.resolve(".gpt-runner") override val gptRunnerExecutableDir = gptRunnerExecutablesDir.resolve("GPT-Runner-${plugin.version}") - init { + init { if (gptRunnerExecutableDir.notExists()) { + if (gptRunnerExecutablesDir.exists()) { + // clean + NioFiles.deleteRecursively(gptRunnerExecutablesDir) + } + + // install gptRunnerExecutableDir.createDirectories() unzipGPTRunnerExecutable() } else if (gptRunnerExecutableDir.listDirectoryEntries().isEmpty()) { diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerService.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerService.kt index 7617953..d2bc0fd 100755 --- a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerService.kt +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/services/impl/GPTRunnerService.kt @@ -3,6 +3,8 @@ package cn.nicepkg.gptrunner.intellij.services.impl import cn.nicepkg.gptrunner.intellij.services.AbstractService import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerExecutableService import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerService +import cn.nicepkg.gptrunner.intellij.services.RunnerAgentCommandLine +import cn.nicepkg.gptrunner.intellij.utils.RunnerAgentUtil import com.intellij.codeInsight.hint.HintManager import com.intellij.ide.DataManager import com.intellij.notification.NotificationDisplayType @@ -17,6 +19,8 @@ import kotlinx.coroutines.* import java.io.File import kotlin.concurrent.thread import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.util.io.exists +import kotlin.io.path.isExecutable // TODO: 需要提供几个action去启动/停止server @@ -62,7 +66,8 @@ class GPTRunnerService(project: Project) : AbstractService(), port.toString(), "--client-dist-path", "browser" - ).directory(executableService.gptRunnerExecutableDir.toFile()) // Update this line + ).directory(executableService.gptRunnerExecutableDir.toFile().resolve("dist")) + // Update this line .start() // val serverExecutablePath = executableService.gptRunnerExecutableDir.toFile().path+"/server-executable"; // process = ProcessBuilder( @@ -123,6 +128,13 @@ class GPTRunnerService(project: Project) : AbstractService(), fun getMyNodePath(): String { + // 优先查找系统环境变量 - node + val nodeExecutablePath = RunnerAgentCommandLine.getNodeExecutablePath() + if (nodeExecutablePath != null && nodeExecutablePath.exists() && nodeExecutablePath.isExecutable()){ + return nodeExecutablePath.toString(); + } + + // 其他 val osName = System.getProperty("os.name").toLowerCase() if (osName.contains("mac") || osName.contains("nix") || osName.contains("nux")) { val nodePath = getNodePath() diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindow.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindow.kt index 483476b..730be4c 100755 --- a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindow.kt +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindow.kt @@ -2,25 +2,15 @@ package cn.nicepkg.gptrunner.intellij.ui.windows import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerService -import com.intellij.ide.plugins.PluginManager -import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.LafManagerListener import com.intellij.ide.ui.laf.UIThemeBasedLookAndFeelInfo import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service -import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.Project import com.intellij.ui.jcef.JBCefBrowser -import com.intellij.ui.jcef.JBCefBrowserBuilder +import com.intellij.ui.jcef.JBCefClient import com.intellij.ui.jcef.executeJavaScriptAsync -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Paths -import java.util.* -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import javax.swing.JComponent class GPTRunnerWindow(project: Project, disposable: Disposable) { private val jBCefBrowser = @@ -29,15 +19,21 @@ class GPTRunnerWindow(project: Project, disposable: Disposable) { init { println("init windows") - println("project.basePath="+project.basePath) - ApplicationManager.getApplication().messageBus.connect(disposable) + println("project.basePath=" + project.basePath) + val messageBusConnection = ApplicationManager.getApplication().messageBus.connect(disposable) + messageBusConnection .subscribe(LafManagerListener.TOPIC, LafManagerListener { val isDark = (it.currentLookAndFeel as UIThemeBasedLookAndFeelInfo).theme.isDark jBCefBrowser.executeJavaScriptAsync("document.body.dataset.theme = `${if (isDark) "jetbrainsDark" else "jetbrainsLight"}`") }) -// window.setAdditionalGearActions(); + +// window.setAdditionalGearActions() +// jBCefBrowser.jbCefClient.setProperty(JBCefClient.Properties.JS_QUERY_POOL_SIZE, 3) + } fun getContent() = jBCefBrowser.component + + fun getBrowser() = jBCefBrowser } diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowFactory.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowFactory.kt new file mode 100644 index 0000000..60a3d3b --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowFactory.kt @@ -0,0 +1,95 @@ +package cn.nicepkg.gptrunner.intellij.ui.windows + +import cn.nicepkg.gptrunner.intellij.listeners.ClientEventName +import cn.nicepkg.gptrunner.intellij.listeners.handler.BrowserEventHandlers +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowFactory +import com.intellij.ui.content.ContentFactory +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import org.cef.browser.CefBrowser +import org.cef.browser.CefFrame +import org.cef.handler.CefLoadHandler +import org.cef.network.CefRequest + +class GPTRunnerWindowFactory : ToolWindowFactory { + + init { + thisLogger().info("GPTRunnerWindowFactory init.") + } + + override fun createToolWindowContent(project: Project, window: ToolWindow) { + val gptRunnerWindow = GPTRunnerWindow(project, window.disposable) + val content = ContentFactory.getInstance().createContent( + gptRunnerWindow.getContent(), null, true + ) + window.contentManager.addContent(content) + val jbCefBrowser = gptRunnerWindow.getBrowser() + val jsQuery = JBCefJSQuery.create(jbCefBrowser as JBCefBrowserBase) + + jsQuery.addHandler { t -> + println("监听js传参 $t") + + BrowserEventHandlers(project).apply(t) + } + + + jbCefBrowser.jbCefClient.addLoadHandler(object : CefLoadHandler { + override fun onLoadingStateChange(browser: CefBrowser?, isLoading: Boolean, canGoBack: Boolean, canGoForward: Boolean) { + } + + override fun onLoadStart(browser: CefBrowser?, frame: CefFrame?, transitionType: CefRequest.TransitionType?) { + } + + override fun onLoadEnd(browser: CefBrowser?, frame: CefFrame?, httpStatusCode: Int) { + println("onLoadEnd" + frame?.url) + + browser?.executeJavaScript( + "window.addEventListener('message', event => {\n" + + "const message = event.data || {};\n" + + "console.log(event)\n" + + "const {eventName, eventData} = message\n" + + "const strEvent = JSON.stringify(message) \n" + +// jsQuery.inject("strEvent") + + "window.__emitter__.emit(eventName, eventData, \"ReceiveMessage\")" + + " })", + null, + 0 + ) + + browser?.executeJavaScript( + "oldEmit = window.__emitter__.emit\n" + + " window.__emitter__.emit = function (...args) {\n" + + " const [eventName, eventData, type] = args\n" + + " if (type !== \"ReceiveMessage\") {\n" + + " const strEvent = JSON.stringify(args) \n" + + jsQuery.inject("strEvent") + + " }\n" + + " return oldEmit.apply(this, args)\n" + + " }", + null, + 1 + ) + + } + + override fun onLoadError(browser: CefBrowser?, frame: CefFrame?, errorCode: CefLoadHandler.ErrorCode?, errorText: String?, failedUrl: String?) { + } + + }, gptRunnerWindow.getBrowser().cefBrowser) + + GPTRunnerWindowHolder.jBCefBrowser = jbCefBrowser + GPTRunnerWindowHolder.jsQuery = jsQuery + } + + override fun shouldBeAvailable(project: Project) = true + + override fun init(toolWindow: ToolWindow) { + super.init(toolWindow) + } +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowHolder.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowHolder.kt new file mode 100644 index 0000000..8e19218 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/ui/windows/GPTRunnerWindowHolder.kt @@ -0,0 +1,13 @@ +package cn.nicepkg.gptrunner.intellij.ui.windows + +import com.intellij.ui.jcef.JBCefBrowser +import com.intellij.ui.jcef.JBCefJSQuery + +class GPTRunnerWindowHolder { + + companion object { + var jsQuery: JBCefJSQuery? = null + var jBCefBrowser: JBCefBrowser? = null + } + +} diff --git a/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/utils/RunnerAgentUtil.kt b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/utils/RunnerAgentUtil.kt new file mode 100644 index 0000000..44c2807 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/kotlin/cn/nicepkg/gptrunner/intellij/utils/RunnerAgentUtil.kt @@ -0,0 +1,15 @@ +package cn.nicepkg.gptrunner.intellij.utils + +import cn.nicepkg.gptrunner.intellij.services.IGPTRunnerExecutableService +import com.intellij.openapi.application.ApplicationManager +import java.nio.file.Path + +object RunnerAgentUtil { + + + fun agentDirectoryPath(): Path { + val executableService = ApplicationManager.getApplication().getService(IGPTRunnerExecutableService::class.java) + return executableService.gptRunnerExecutableDir.resolve("dist") + } + +} diff --git a/packages/gpt-runner-intellij/src/main/resources/META-INF/plugin.xml b/packages/gpt-runner-intellij/src/main/resources/META-INF/plugin.xml index 968d8df..3bb508a 100755 --- a/packages/gpt-runner-intellij/src/main/resources/META-INF/plugin.xml +++ b/packages/gpt-runner-intellij/src/main/resources/META-INF/plugin.xml @@ -39,4 +39,10 @@ + + + + + diff --git a/packages/gpt-runner-intellij/src/main/resources/messages/LangBundle_en.properties b/packages/gpt-runner-intellij/src/main/resources/messages/LangBundle_en.properties new file mode 100644 index 0000000..84fd2f3 --- /dev/null +++ b/packages/gpt-runner-intellij/src/main/resources/messages/LangBundle_en.properties @@ -0,0 +1,6 @@ +name=GPT-Runner Intellij + +messages.openGPTRunnerInEditorMode=Open GPT-Runner in Editor Mode. + +action.cn.nicepkg.gptrunner.intellij.actions.OpenInEditorMode.text=Open GPT-Runner in Editor Mode. +