From 3d70503b833bd531af99095f542717d6437a184d Mon Sep 17 00:00:00 2001 From: SerVB Date: Tue, 3 Nov 2020 23:54:40 +0300 Subject: [PATCH] PRJ-131 Inject MD preview dynamically using agent --- gradle.properties | 4 +- projector-server/build.gradle | 94 +------------ .../projector/server/ProjectorServer.kt | 51 ++++--- .../projector/server/idea/CaretInfoUpdater.kt | 1 + .../projector/server/idea/IdeColors.kt | 1 + .../projector/server/idea/IdeaState.kt | 80 ----------- .../projector/server/idea/KeymapSetter.kt | 1 + .../server/idea/MarkdownPanelUpdater.kt | 131 ------------------ .../server/idea/SettingsInitializer.kt | 1 + .../server/log/impl/DelegatingJvmLogger.kt | 2 +- settings.gradle.kts | 4 - 11 files changed, 36 insertions(+), 334 deletions(-) delete mode 100644 projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeaState.kt delete mode 100644 projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/MarkdownPanelUpdater.kt diff --git a/gradle.properties b/gradle.properties index 7b7da9fe..52915332 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,10 +17,10 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # intellijPluginVersion=0.4.14 -javassistVersion=3.26.0-GA +javassistVersion=3.27.0-GA kotlinVersion=1.4.0 mockitoKotlinVersion=2.2.0 -projectorClientVersion=766f2a6b +projectorClientVersion=c439c6c0 targetJvm=1.8 # Give JitPack some time to build projector-client: systemProp.org.gradle.internal.http.connectionTimeout=180000 diff --git a/projector-server/build.gradle b/projector-server/build.gradle index f7a78738..aa6b665f 100644 --- a/projector-server/build.gradle +++ b/projector-server/build.gradle @@ -17,8 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import groovy.json.JsonSlurper - import java.util.zip.ZipFile plugins { @@ -90,26 +88,6 @@ if (serverTargetClasspath != null && serverClassToLaunch != null) { } } -static def getIdeaVersion(path) { - def jsonFile = new File("$path/product-info.json") - def json = new JsonSlurper().parseText(jsonFile.text) - - return json.version -} - -def getIdeaConfigPaths(path, pathsSelector) { - def userHome = System.getProperty("user.home") - def version = getIdeaVersion(path) - - if (version.contains("2019.")) { - return ["$userHome/.$pathsSelector/config", - "$userHome/.$pathsSelector/config/plugins"] - } - - return ["$userHome/.config/JetBrains/$pathsSelector", - "$userHome/.local/share/JetBrains/$pathsSelector"] -} - def ideaPath = localProperties['projectorLauncher.ideaPath'] println("----------- Idea launch config ---------------") println("Idea path: $ideaPath") @@ -123,78 +101,7 @@ if (ideaPath != null) { def ideaPathsSelector = "ProjectorIntelliJIdea" - // trying to match for Linux: - // https://www.jetbrains.com/help/idea/2019.3/tuning-the-ide.html - // https://www.jetbrains.com/help/idea/2020.1/tuning-the-ide.html - // please note that IDEs installed from Toolbox can have their own dirs for plugins - - def (String ideaConfigPath, String ideaPluginsPath) = getIdeaConfigPaths(ideaPath, ideaPathsSelector) - - task disableBundledMarkdownPlugin { - group = "projector" - - doLast { - print("Disabling bundled markdown plugin (in $ideaConfigPath): ") - new File(ideaConfigPath).mkdirs() - - def disabledPluginsFilePath = "${ideaConfigPath}/disabled_plugins.txt" - - def disabledPluginsFile = new File(disabledPluginsFilePath) - - disabledPluginsFile.createNewFile() - - def bundledMarkdownPluginName = "org.intellij.plugins.markdown" - - if (bundledMarkdownPluginName in disabledPluginsFile.readLines()) { - println("already disabled") - } - else { - disabledPluginsFile.text = bundledMarkdownPluginName + "\n" + disabledPluginsFile.text - println("disabled") - } - } - } - - task deleteMarkdownPlugin(type: Delete) { - group = "projector" - - delete "${ideaPluginsPath}/projector-plugin-markdown" - } - - task copyMarkdownPlugin { - dependsOn(deleteMarkdownPlugin) - dependsOn(gradle.includedBuild("projector-markdown-plugin").task(":buildPlugin")) - - gradle.includedBuild("projector-markdown-plugin") - - group = "projector" - - doLast { - print("Copying markdown plugin (to $ideaPluginsPath): ") - - def mdProjectDir = gradle.includedBuild("projector-markdown-plugin").projectDir - def mdDistributionsDir = new File(mdProjectDir, "build/distributions") - - def zipDistribution = mdDistributionsDir.listFiles().find { file -> - file.name.startsWith("projector-markdown-plugin-") && - file.name.endsWith(".zip") - } - - def destination = file(ideaPluginsPath) - destination.mkdirs() - - copy { - from zipTree(zipDistribution) - into destination - } - - println("done") - } - } - task runIdeaServer(type: JavaExec) { - dependsOn(disableBundledMarkdownPlugin, copyMarkdownPlugin) - group = "projector" main = "org.jetbrains.projector.server.ProjectorLauncher" classpath(sourceSets.main.runtimeClasspath, jar, "$ideaClassPath", "$jdkHome/../lib/tools.jar") @@ -213,6 +120,7 @@ if (ideaPath != null) { "--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", + "-Djdk.attach.allowAttachSelf=true", ] } } diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt index 193ccce4..a2fa8a56 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt @@ -47,10 +47,15 @@ import org.jetbrains.projector.common.protocol.toServer.* import org.jetbrains.projector.server.ReadyClientSettings.TouchState import org.jetbrains.projector.server.core.ProjectorHttpWsServer import org.jetbrains.projector.server.core.convert.toAwt.toAwtKeyEvent +import org.jetbrains.projector.server.core.ij.md.IjInjectorAgentInitializer +import org.jetbrains.projector.server.core.ij.md.PanelUpdater import org.jetbrains.projector.server.core.protocol.HandshakeTypesSelector import org.jetbrains.projector.server.core.protocol.KotlinxJsonToClientHandshakeEncoder import org.jetbrains.projector.server.core.protocol.KotlinxJsonToServerHandshakeDecoder -import org.jetbrains.projector.server.idea.* +import org.jetbrains.projector.server.idea.CaretInfoUpdater +import org.jetbrains.projector.server.idea.IdeColors +import org.jetbrains.projector.server.idea.KeymapSetter +import org.jetbrains.projector.server.idea.SettingsInitializer import org.jetbrains.projector.server.log.Logger import org.jetbrains.projector.server.service.ProjectorAwtInitializer import org.jetbrains.projector.server.service.ProjectorDrawEventQueue @@ -113,7 +118,6 @@ class ProjectorServer private constructor( } caretInfoUpdater.start() - markdownPanelUpdater.setUpCallbacks() } override fun onWsMessage(connection: WebSocket, message: ByteBuffer) { @@ -136,7 +140,7 @@ class ProjectorServer private constructor( PWindow.windows.forEach(PWindow::repaint) previousWindowEvents = emptySet() caretInfoUpdater.createCaretInfoEvent() - markdownPanelUpdater.updateAll() + PanelUpdater.updateAll() } is ReadyClientSettings -> { @@ -191,20 +195,20 @@ class ProjectorServer private constructor( windowColorsEvent = ServerWindowColorsEvent(colors) } - private val markdownPanelUpdater = MarkdownPanelUpdater( - showCallback = { id, show -> + init { + PanelUpdater.showCallback = { id, show -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownShowEvent(id, show)) - }, - resizeCallback = { id, size -> + } + PanelUpdater.resizeCallback = { id, size -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownResizeEvent(id, size.toCommonIntSize())) - }, - moveCallback = { id, point -> + } + PanelUpdater.moveCallback = { id, point -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownMoveEvent(id, point.shift(PGraphicsDevice.clientShift))) - }, - disposeCallback = { id -> + } + PanelUpdater.disposeCallback = { id -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownDisposeEvent(id)) - }, - placeToWindowCallback = { id, rootComponent -> + } + PanelUpdater.placeToWindowCallback = { id, rootComponent -> rootComponent?.let { val peer = AWTAccessor.getComponentAccessor().getPeer(it) @@ -214,20 +218,20 @@ class ProjectorServer private constructor( markdownQueue.add(ServerMarkdownEvent.ServerMarkdownPlaceToWindowEvent(id, peer.pWindow.id)) } - }, - setHtmlCallback = { id, html -> + } + PanelUpdater.setHtmlCallback = { id, html -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownSetHtmlEvent(id, html)) - }, - setCssCallback = { id, css -> + } + PanelUpdater.setCssCallback = { id, css -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownSetCssEvent(id, css)) - }, - scrollCallback = { id, offset -> + } + PanelUpdater.scrollCallback = { id, offset -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownScrollEvent(id, offset)) - }, - browseUriCallback = { link -> + } + PanelUpdater.browseUriCallback = { link -> markdownQueue.add(ServerMarkdownEvent.ServerMarkdownBrowseUriEvent(link)) } - ) + } @OptIn(ExperimentalStdlibApi::class) private fun createDataToSend(): List { @@ -427,7 +431,7 @@ class ProjectorServer private constructor( clientSettings.requestedData.add(pingReply) } - is ClientOpenLinkEvent -> markdownPanelUpdater.openInExternalBrowser(message.link) + is ClientOpenLinkEvent -> PanelUpdater.openInExternalBrowser(message.link) is ClientSetKeymapEvent -> if (isAgent) { logger.info { "Client keymap was ignored (agent mode)!" } @@ -949,6 +953,7 @@ class ProjectorServer private constructor( else { setupSystemProperties() setupSingletons() + IjInjectorAgentInitializer.init() // todo: support variant for agent too } ProjectorAwtInitializer.initDefaults() // this should be done after setting classes because some headless operations can happen here diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt index ebb40087..c3b7d4a7 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt @@ -26,6 +26,7 @@ import org.jetbrains.projector.common.protocol.data.CommonRectangle import org.jetbrains.projector.common.protocol.data.Point import org.jetbrains.projector.common.protocol.toClient.ServerCaretInfoChangedEvent import org.jetbrains.projector.common.protocol.toClient.data.idea.CaretInfo +import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import org.jetbrains.projector.server.log.Logger import org.jetbrains.projector.server.util.FontCacher import org.jetbrains.projector.server.util.unprotect diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeColors.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeColors.kt index 34ee1588..a0dc80f3 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeColors.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeColors.kt @@ -20,6 +20,7 @@ package org.jetbrains.projector.server.idea import org.jetbrains.projector.common.protocol.data.PaintValue import org.jetbrains.projector.common.protocol.toClient.ServerWindowColorsEvent +import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import org.jetbrains.projector.server.log.Logger import java.awt.Color import java.lang.reflect.InvocationHandler diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeaState.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeaState.kt deleted file mode 100644 index 106de5ce..00000000 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/IdeaState.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * GNU General Public License version 2 - * - * Copyright (C) 2019-2020 JetBrains s.r.o. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -package org.jetbrains.projector.server.idea - -import org.jetbrains.projector.server.log.Logger -import kotlin.concurrent.thread - -private fun isIdeaInProperState(ideaClassLoader: ClassLoader?): Boolean { - val loadingStateClass = Class.forName("com.intellij.diagnostic.LoadingState", false, ideaClassLoader) - - val loadingState = loadingStateClass - .getDeclaredField("CONFIGURATION_STORE_INITIALIZED") - .get(null) - - return loadingStateClass - .getDeclaredMethod("isOccurred") - .invoke(loadingState) as Boolean -} - -fun invokeWhenIdeaIsInitialized( - purpose: String, - onNoIdeaFound: (() -> Unit)? = null, - onInitialized: (ideaClassLoader: ClassLoader) -> Unit, -) { - thread(isDaemon = true) { - if (onNoIdeaFound == null) { - logger.debug { "Starting attempts to $purpose" } - } - - while (true) { - try { - val ideaMainClassWithIdeaClassLoader = Class.forName("com.intellij.ide.WindowsCommandLineProcessor") - .getDeclaredField("ourMainRunnerClass") - .get(null) as Class<*>? - - if (ideaMainClassWithIdeaClassLoader != null) { // null means we run with IDEA but it's not initialized yet - val ideaClassLoader = ideaMainClassWithIdeaClassLoader.classLoader - - if (isIdeaInProperState(ideaClassLoader)) { - onInitialized(ideaClassLoader) - - if (onNoIdeaFound == null) { - logger.debug { "\"$purpose\" is done" } - } - break - } - } - } - catch (t: Throwable) { - if (onNoIdeaFound == null) { - logger.debug(t) { "Can't $purpose. It's OK if you don't run an IntelliJ platform based app." } - } - else { - onNoIdeaFound() - } - break - } - - Thread.sleep(1) - } - } -} - -private val logger = Logger("IdeaStateKt") diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/KeymapSetter.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/KeymapSetter.kt index afd98bae..07d56956 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/KeymapSetter.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/KeymapSetter.kt @@ -19,6 +19,7 @@ package org.jetbrains.projector.server.idea import org.jetbrains.projector.common.protocol.data.UserKeymap +import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import org.jetbrains.projector.server.log.Logger import javax.swing.SwingUtilities diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/MarkdownPanelUpdater.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/MarkdownPanelUpdater.kt deleted file mode 100644 index b94aae9f..00000000 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/MarkdownPanelUpdater.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * GNU General Public License version 2 - * - * Copyright (C) 2019-2020 JetBrains s.r.o. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -package org.jetbrains.projector.server.idea - -import java.awt.Component -import java.awt.Dimension -import java.awt.Point -import java.util.function.BiConsumer -import java.util.function.Consumer - -class MarkdownPanelUpdater( - private val showCallback: BiConsumer, - private val resizeCallback: BiConsumer, - private val moveCallback: BiConsumer, - private val disposeCallback: Consumer, - private val placeToWindowCallback: BiConsumer, - private val setHtmlCallback: BiConsumer, - private val setCssCallback: BiConsumer, - private val scrollCallback: BiConsumer, - private val browseUriCallback: Consumer, -) { - - private lateinit var ideaClassLoader: ClassLoader - - private val extensionPointNameClass by lazy { - Class.forName("com.intellij.openapi.extensions.ExtensionPointName", false, ideaClassLoader) - } - - private val extensionPointNameCreateMethod by lazy { - extensionPointNameClass.getDeclaredMethod("create", String::class.java) - } - - private val extensionPointNameGetExtensionsMethod by lazy { - extensionPointNameClass.getDeclaredMethod("getExtensions") - } - - private lateinit var update: () -> Unit - - private lateinit var open: (String) -> Unit - - fun setUpCallbacks() { - invokeWhenIdeaIsInitialized("set up markdown callbacks") { ideaClassLoader -> - this.ideaClassLoader = ideaClassLoader - - val extensionPointName = extensionPointNameCreateMethod.invoke(null, OUR_EXTENSION_ID) - - val extensions = extensionPointNameGetExtensionsMethod.invoke(extensionPointName) as Array<*> - - val projectorExtension = extensions.filterNotNull().single { "Projector" in it::class.java.name } - - val projectorExtensionClass = projectorExtension::class.java - - val updateAllMethod = projectorExtensionClass - .getDeclaredMethod("updateAll") - - update = { updateAllMethod.invoke(null) } - - val openInExternalBrowserMethod = projectorExtensionClass - .getDeclaredMethod("openInExternalBrowser", String::class.java) - - open = { openInExternalBrowserMethod.invoke(null, it) } - - projectorExtensionClass - .getDeclaredMethod("setShowCallback", BiConsumer::class.java) - .invoke(null, showCallback) - - projectorExtensionClass - .getDeclaredMethod("setResizeCallback", BiConsumer::class.java) - .invoke(null, resizeCallback) - - projectorExtensionClass - .getDeclaredMethod("setMoveCallback", BiConsumer::class.java) - .invoke(null, moveCallback) - - projectorExtensionClass - .getDeclaredMethod("setDisposeCallback", Consumer::class.java) - .invoke(null, disposeCallback) - - projectorExtensionClass - .getDeclaredMethod("setPlaceToWindowCallback", BiConsumer::class.java) - .invoke(null, placeToWindowCallback) - - projectorExtensionClass - .getDeclaredMethod("setSetHtmlCallback", BiConsumer::class.java) - .invoke(null, setHtmlCallback) - - projectorExtensionClass - .getDeclaredMethod("setSetCssCallback", BiConsumer::class.java) - .invoke(null, setCssCallback) - - projectorExtensionClass - .getDeclaredMethod("setScrollCallback", BiConsumer::class.java) - .invoke(null, scrollCallback) - - projectorExtensionClass - .getDeclaredMethod("setBrowseUriCallback", Consumer::class.java) - .invoke(null, browseUriCallback) - } - } - - fun updateAll() { - if (this::update.isInitialized) { - update() - } - } - - fun openInExternalBrowser(link: String) { - open(link) - } - - companion object { - - private const val OUR_EXTENSION_ID = "org.jetbrains.projector.markdown.html.panel.provider" - } -} diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/SettingsInitializer.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/SettingsInitializer.kt index a4abdbf1..d21ab754 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/SettingsInitializer.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/SettingsInitializer.kt @@ -19,6 +19,7 @@ package org.jetbrains.projector.server.idea import org.jetbrains.projector.awt.image.PGraphics2D +import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import org.jetbrains.projector.server.log.Logger import org.jetbrains.projector.server.util.unprotect import java.awt.RenderingHints diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/log/impl/DelegatingJvmLogger.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/log/impl/DelegatingJvmLogger.kt index 7ba45efc..27401fd5 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/log/impl/DelegatingJvmLogger.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/log/impl/DelegatingJvmLogger.kt @@ -19,7 +19,7 @@ package org.jetbrains.projector.server.log.impl import org.jetbrains.projector.common.misc.Do -import org.jetbrains.projector.server.idea.invokeWhenIdeaIsInitialized +import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import java.util.* import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read diff --git a/settings.gradle.kts b/settings.gradle.kts index d77e98d6..6dd28b0d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,10 +30,6 @@ if (localProperties["useLocalProjectorClient"] == "true") { } } -if (localProperties["projectorLauncher.ideaPath"] != null) { - includeBuild("../projector-markdown-plugin") -} - include("projector-agent") include("projector-awt") include("projector-plugin")