diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb4b54919..3a967722d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,8 @@ jobs: # Check out current repository - name: Fetch Sources uses: actions/checkout@v2.4.0 + with: + submodules: true # Validate wrapper - name: Gradle Wrapper Validation @@ -108,9 +110,11 @@ jobs: name: pluginVerifier-result path: ${{ github.workspace }}/plugin/build/reports/pluginVerifier - # Run Qodana inspections - - name: Qodana - Code Inspection - uses: JetBrains/qodana-action@v4.2.3 +# # Run Qodana inspections +# - name: Qodana - Code Inspection +# uses: JetBrains/qodana-action@v4.2.3 +# with: +# upload-result: false # Prepare plugin archive content for creating artifact - name: Prepare Plugin Artifact diff --git a/.gitmodules b/.gitmodules index 3c581c63a..2f4f4d9a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "test/e2e/example-web-app"] path = test/e2e/example-web-app url = https://github.com/sourceplusplus/example-web-app +[submodule "commander"] + path = commander + url = https://github.com/sourceplusplus/jetbrains-commander diff --git a/build.gradle.kts b/build.gradle.kts index 1b6f2d6b7..114987696 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.diffplug.spotless") apply false id("com.avast.gradle.docker-compose") id("org.jetbrains.kotlin.jvm") apply false - id("io.gitlab.arturbosch.detekt") +// id("io.gitlab.arturbosch.detekt") apply false id("maven-publish") } @@ -25,22 +25,18 @@ repositories { } subprojects { - ext { - set("kotlinVersion", "1.5.0") - } - repositories { mavenCentral() maven(url = "https://jitpack.io") } apply() - apply() +// apply() tasks { - withType { - parallel = true - buildUponDefaultConfig = true - } +// withType { +// parallel = true +// buildUponDefaultConfig = true +// } withType { sourceCompatibility = "1.8" @@ -70,7 +66,7 @@ subprojects { apply(plugin = "com.diffplug.spotless") configure { kotlin { - targetExclude("**/generated/**") + targetExclude("**/generated/**", "**/liveplugin/**") if (file("../LICENSE-HEADER.txt").exists()) { licenseHeaderFile(file("../LICENSE-HEADER.txt")) } else { diff --git a/commander b/commander new file mode 160000 index 000000000..a9ef4be36 --- /dev/null +++ b/commander @@ -0,0 +1 @@ +Subproject commit a9ef4be36a09d17b02d42a6cbdacd8c7b178e0dd diff --git a/gradle.properties b/gradle.properties index 096f341d0..4e6ab1e38 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,25 +4,27 @@ kotlin.code.style=official pluginGroup = spp.jetbrains pluginName = Source++ -projectVersion=0.4.7 -pluginSinceBuild = 202.4357 +projectVersion=0.5.0 +pluginSinceBuild = 221.5080.210 # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl # See https://jb.gg/intellij-platform-builds-list for available build versions -pluginVerifierIdeVersions = 2020.2.4, 2021.3.3 +pluginVerifierIdeVersions = 2022.1, 2022.1.1 platformType = IC -ideVersion = 2021.3.3 +ideVersion = 2022.1.1 platformDownloadSources = true # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = java, Groovy, Kotlin, PythonCore:213.5744.223, org.intellij.scala:2021.3.18 +platformPlugins = java, Groovy, Kotlin, PythonCore:221.5591.52, org.intellij.scala:2022.1.14 # Opt-out flag for bundling Kotlin standard library. # See https://kotlinlang.org/docs/reference/using-gradle.html#dependency-on-the-standard-library for details. kotlin.stdlib.default.dependency = true apolloVersion=3.3.0 -vertxVersion=4.2.6 +vertxVersion=4.3.0 slf4jVersion=1.7.33 jacksonVersion=2.13.1 joorVersion=0.9.13 + +kotlinVersion=1.6.10 diff --git a/marker/build.gradle.kts b/marker/build.gradle.kts index b72d62368..7c84f585a 100644 --- a/marker/build.gradle.kts +++ b/marker/build.gradle.kts @@ -1,25 +1,13 @@ plugins { id("org.jetbrains.kotlin.jvm") - id("maven-publish") } -val kotlinVersion = ext.get("kotlinVersion") +val kotlinVersion: String by project val pluginGroup: String by project val projectVersion: String by project val slf4jVersion: String by project val joorVersion: String by project - -publishing { - publications { - create("maven") { - groupId = pluginGroup - artifactId = "marker" - version = projectVersion - - from(components["java"]) - } - } -} +val vertxVersion: String by project repositories { maven(url = "https://www.jetbrains.com/intellij-repository/releases") { name = "intellij-releases" } @@ -27,11 +15,12 @@ repositories { } dependencies { - val intellijVersion = "213.7172.25" + val intellijVersion = "221.5080.210" + compileOnly("io.vertx:vertx-core:$vertxVersion") compileOnly("org.jooq:joor:$joorVersion") compileOnly("com.github.sourceplusplus.protocol:protocol:$projectVersion") - compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinVersion") + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8") compileOnly("com.google.guava:guava:31.1-jre") compileOnly("org.jetbrains:annotations:23.0.0") diff --git a/marker/jvm-marker/build.gradle.kts b/marker/jvm-marker/build.gradle.kts index bcf543f11..699d9518e 100644 --- a/marker/jvm-marker/build.gradle.kts +++ b/marker/jvm-marker/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("org.jetbrains.kotlin.jvm") } -val kotlinVersion = ext.get("kotlinVersion") +val kotlinVersion: String by project val vertxVersion: String by project val projectVersion: String by project val slf4jVersion: String by project @@ -22,9 +22,9 @@ dependencies { compileOnly(project(":monitor")) } compileOnly("com.github.sourceplusplus.protocol:protocol:$projectVersion") - val intellijVersion = "213.7172.25" + val intellijVersion = "221.5080.210" - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.google.guava:guava:31.1-jre") implementation("org.jetbrains:annotations:23.0.0") diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMArtifactNamingService.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMArtifactNamingService.kt index 0e24adab3..46b546d81 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMArtifactNamingService.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMArtifactNamingService.kt @@ -20,10 +20,7 @@ package spp.jetbrains.marker.jvm import com.intellij.psi.PsiClassOwner import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import org.jetbrains.uast.UClass -import org.jetbrains.uast.UExpression -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.toUElement +import org.jetbrains.uast.* import spp.jetbrains.marker.ArtifactNamingService import spp.jetbrains.marker.source.JVMMarkerUtils import spp.protocol.artifact.ArtifactQualifiedName @@ -42,6 +39,7 @@ class JVMArtifactNamingService : ArtifactNamingService { is UClass -> JVMMarkerUtils.getFullyQualifiedName(uElement) is UMethod -> JVMMarkerUtils.getFullyQualifiedName(uElement) is UExpression -> JVMMarkerUtils.getFullyQualifiedName(element) + is UDeclaration -> JVMMarkerUtils.getFullyQualifiedName(element) else -> TODO("Not yet implemented") } } diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMGuideProvider.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMGuideProvider.kt index 0e919da8d..03f032779 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMGuideProvider.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMGuideProvider.kt @@ -19,6 +19,7 @@ package spp.jetbrains.marker.jvm import com.intellij.openapi.application.ApplicationManager import com.intellij.psi.JavaRecursiveElementVisitor +import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNameIdentifierOwner import spp.jetbrains.marker.plugin.SourceGuideProvider @@ -29,15 +30,22 @@ class JVMGuideProvider : SourceGuideProvider { override fun determineGuideMarks(fileMarker: SourceFileMarker) { fileMarker.psiFile.acceptChildren(object : JavaRecursiveElementVisitor() { - override fun visitMethod(method: PsiMethod) { - super.visitMethod(method) - - ApplicationManager.getApplication().runReadAction { - fileMarker.createMethodSourceMark( - method as PsiNameIdentifierOwner, SourceMark.Type.GUIDE - ).apply(true) + override fun visitElement(element: PsiElement) { + super.visitElement(element) + if (element is PsiMethod) { + makeMethodGuideMark(fileMarker, element) + } else if (element::class.java.name == "org.jetbrains.kotlin.psi.KtNamedFunction") { + makeMethodGuideMark(fileMarker, element) } } }) } + + private fun makeMethodGuideMark(fileMarker: SourceFileMarker, element: PsiElement) { + ApplicationManager.getApplication().runReadAction { + fileMarker.createMethodSourceMark( + element as PsiNameIdentifierOwner, SourceMark.Type.GUIDE + ).apply(true) + } + } } diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMLineMarkerProvider.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMLineMarkerProvider.kt index c07900cf2..43961044d 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMLineMarkerProvider.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMLineMarkerProvider.kt @@ -20,11 +20,12 @@ package spp.jetbrains.marker.jvm import com.intellij.codeInsight.daemon.GutterIconNavigationHandler import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.openapi.editor.markup.GutterIconRenderer -import com.intellij.psi.* +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiIdentifier +import com.intellij.psi.PsiJavaFile import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.plugins.groovy.lang.psi.GroovyFile -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.uast.UClass import org.jetbrains.uast.UMethod import org.jetbrains.uast.toUElement @@ -58,15 +59,7 @@ abstract class JVMLineMarkerProvider : SourceLineMarkerProvider() { ): LineMarkerInfo? { return when { parent is PsiClass && element === parent.nameIdentifier -> getClassGutterMark(element) - parent is PsiMethod && element === parent.nameIdentifier -> getMethodGutterMark(element) - parent?.javaClass?.simpleName?.equals("GrMethod") == true - && (parent is GrMethod && element === parent.nameIdentifierGroovy) -> { - getMethodGutterMark(element) - } - parent?.javaClass?.simpleName?.equals("KtNamedFunction") == true - && (parent is KtNamedFunction && element === parent.nameIdentifier) -> { - getMethodGutterMark(element) - } + element == JVMMarkerUtils.getNameIdentifier(parent) -> getMethodGutterMark(element) else -> null } } diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMSourceInlayHintProvider.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMSourceInlayHintProvider.kt index 7c473241d..b432bd05a 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMSourceInlayHintProvider.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMSourceInlayHintProvider.kt @@ -19,13 +19,9 @@ package spp.jetbrains.marker.jvm import com.intellij.codeInsight.hints.InlayHintsSink import com.intellij.codeInsight.hints.presentation.InlayPresentation -import com.intellij.openapi.editor.BlockInlayPriority import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.PsiStatement -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.psiUtil.getParentOfType -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.uast.UExpression import org.jetbrains.uast.UMethod import org.jetbrains.uast.toUElement @@ -46,8 +42,7 @@ class JVMSourceInlayHintProvider : SourceInlayHintProvider() { override fun createInlayMarkIfNecessary(element: PsiElement): InlayMark? { val parent = element.parent if ((parent is PsiMethod && element === parent.nameIdentifier) - || (parent is GrMethod && element === parent.nameIdentifierGroovy) - || (parent is KtNamedFunction && element === parent.nameIdentifier) + || (JVMMarkerUtils.getNameIdentifier(parent) === element) ) { val fileMarker = SourceMarker.getSourceFileMarker(element.containingFile)!! val artifactQualifiedName = JVMMarkerUtils.getFullyQualifiedName(parent.toUElement() as UMethod) @@ -106,7 +101,7 @@ class JVMSourceInlayHintProvider : SourceInlayHintProvider() { startOffset, virtualText.relatesToPrecedingText, virtualText.showAbove, - BlockInlayPriority.CODE_VISION, + 0, representation ) } diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMVariableSimpleNode.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMVariableSimpleNode.kt index de37e9e02..e87815e37 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMVariableSimpleNode.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/JVMVariableSimpleNode.kt @@ -18,8 +18,8 @@ package spp.jetbrains.marker.jvm import com.intellij.icons.AllIcons -import com.intellij.ide.highlighter.JavaHighlightingColors import com.intellij.ide.projectView.PresentationData +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.treeStructure.SimpleNode import com.intellij.xdebugger.impl.ui.DebuggerUIUtil @@ -98,12 +98,12 @@ class JVMVariableSimpleNode(val variable: LiveVariable) : SimpleNode() { } else if (variable.liveClazz == "java.lang.String") { presentation.addText( "\"" + variable.value + "\"", - SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(JavaHighlightingColors.STRING)) + SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(DefaultLanguageHighlighterColors.STRING)) ) } else if (numerals.contains(variable.liveClazz)) { presentation.addText( variable.value.toString(), - SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(JavaHighlightingColors.NUMBER)) + SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(DefaultLanguageHighlighterColors.NUMBER)) ) } presentation.setIcon(AllIcons.Debugger.Db_primitive) @@ -121,12 +121,12 @@ class JVMVariableSimpleNode(val variable: LiveVariable) : SimpleNode() { if (variable.value is Number) { presentation.addText( variable.value.toString(), - SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(JavaHighlightingColors.NUMBER)) + SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(DefaultLanguageHighlighterColors.NUMBER)) ) } else { presentation.addText( "\"" + variable.value + "\"", - SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(JavaHighlightingColors.STRING)) + SimpleTextAttributes.fromTextAttributes(scheme.getAttributes(DefaultLanguageHighlighterColors.STRING)) ) } diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/source/JVMMarkerUtils.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/source/JVMMarkerUtils.kt index 608e78095..611c44495 100755 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/source/JVMMarkerUtils.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/source/JVMMarkerUtils.kt @@ -17,12 +17,10 @@ */ package spp.jetbrains.marker.source -import com.intellij.lang.Language import com.intellij.psi.* import com.intellij.psi.util.PsiUtil -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.uast.* +import org.jooq.tools.reflect.Reflect import org.slf4j.LoggerFactory import spp.jetbrains.marker.SourceMarkerUtils import spp.jetbrains.marker.source.mark.api.SourceMark @@ -146,7 +144,7 @@ object JVMMarkerUtils { return if (inlayMark == null) { val uExpression = element.toUElement() - if (uExpression !is UExpression) return null + if (uExpression !is UExpression && uExpression !is UDeclaration) return null inlayMark = fileMarker.createExpressionSourceMark( element, SourceMark.Type.INLAY @@ -483,25 +481,14 @@ object JVMMarkerUtils { } } - /** - * todo: description. - * - * @since 0.1.0 - */ - @JvmStatic - fun getNameIdentifier(nameIdentifierOwner: PsiNameIdentifierOwner): PsiElement? { - return when { - nameIdentifierOwner.language === Language.findLanguageByID("kotlin") -> { - when (nameIdentifierOwner) { - is KtNamedFunction -> nameIdentifierOwner.nameIdentifier - else -> (nameIdentifierOwner.navigationElement as KtNamedFunction).nameIdentifier - } - } - nameIdentifierOwner.language === Language.findLanguageByID("Groovy") -> { - (nameIdentifierOwner.navigationElement as GrMethod).nameIdentifierGroovy //todo: why can't be null? - } - else -> nameIdentifierOwner.nameIdentifier + fun getNameIdentifier(element: PsiElement?): PsiElement? { + if (element?.javaClass?.simpleName?.equals("GrMethod") == true) { + return Reflect.on(element).call("getNameIdentifierGroovy").get() + } + if (element?.javaClass?.simpleName?.equals("KtNamedFunction") == true) { + return Reflect.on(element).call("getNameIdentifier").get() } + return null } /** @@ -541,9 +528,12 @@ object JVMMarkerUtils { @JvmStatic fun getFullyQualifiedName(element: PsiElement): ArtifactQualifiedName { val expression = element.toUElement()!! - val qualifiedMethodName = expression.getContainingUMethod()?.let { getFullyQualifiedName(it) } + var parentIdentifier = expression.getContainingUMethod()?.let { getFullyQualifiedName(it) } + if (parentIdentifier == null) { + parentIdentifier = expression.getContainingUClass()?.let { getFullyQualifiedName(it) } + } return ArtifactQualifiedName( - """${qualifiedMethodName!!.identifier}#${ + """${parentIdentifier!!.identifier}#${ Base64.getEncoder().encodeToString(expression.toString().toByteArray()) }""", type = ArtifactType.EXPRESSION, diff --git a/marker/py-marker/build.gradle.kts b/marker/py-marker/build.gradle.kts index 6c8bd66a3..5570b9ca4 100644 --- a/marker/py-marker/build.gradle.kts +++ b/marker/py-marker/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("org.jetbrains.kotlin.jvm") } -val kotlinVersion = ext.get("kotlinVersion") +val kotlinVersion: String by project val vertxVersion: String by project val projectVersion: String by project val slf4jVersion: String by project @@ -19,9 +19,9 @@ dependencies { compileOnly(project(":marker")) } compileOnly("com.github.sourceplusplus.protocol:protocol:$projectVersion") - val intellijVersion = "213.7172.25" + val intellijVersion = "221.5080.210" - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.google.guava:guava:31.1-jre") implementation("org.jetbrains:annotations:23.0.0") @@ -41,3 +41,7 @@ dependencies { compileOnly("io.vertx:vertx-core:$vertxVersion") } + +tasks.withType { + kotlinOptions.jvmTarget = "11" +} diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/plugin/SourceInlayHintProvider.kt b/marker/src/main/kotlin/spp/jetbrains/marker/plugin/SourceInlayHintProvider.kt index 5be1fe4ce..0b711f70d 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/plugin/SourceInlayHintProvider.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/plugin/SourceInlayHintProvider.kt @@ -57,6 +57,7 @@ import javax.swing.JPanel * @author [Brandon Fergerson](mailto:bfergerson@apache.org) */ @Suppress("UnstableApiUsage") +@JvmDefaultWithoutCompatibility abstract class SourceInlayHintProvider : InlayHintsProvider { companion object { diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ClassSourceMark.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ClassSourceMark.kt index d95aee0aa..ea8cf1166 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ClassSourceMark.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ClassSourceMark.kt @@ -117,6 +117,7 @@ abstract class ClassSourceMark( } private val userData = HashMap() + override fun getUserData() = userData override fun getUserData(key: SourceKey): T? = userData[key] as T? override fun putUserData(key: SourceKey, value: T?) { if (value != null) { diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ExpressionSourceMark.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ExpressionSourceMark.kt index 7c0bc09ef..1b013708e 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ExpressionSourceMark.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/ExpressionSourceMark.kt @@ -121,6 +121,7 @@ abstract class ExpressionSourceMark( } private val userData = HashMap() + override fun getUserData() = userData override fun getUserData(key: SourceKey): T? = userData[key] as T? override fun putUserData(key: SourceKey, value: T?) { if (value != null) { diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/MethodSourceMark.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/MethodSourceMark.kt index 2e1278616..8e6851a2b 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/MethodSourceMark.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/MethodSourceMark.kt @@ -118,6 +118,7 @@ abstract class MethodSourceMark( } private val userData = HashMap() + override fun getUserData() = userData override fun getUserData(key: SourceKey): T? = userData[key] as T? override fun putUserData(key: SourceKey, value: T?) { if (value != null) { diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/SourceMark.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/SourceMark.kt index 14cdc0bb7..317c2109c 100755 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/SourceMark.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/SourceMark.kt @@ -252,6 +252,7 @@ interface SourceMark : JBPopupListener, MouseMotionListener, VisibleAreaListener clearEventListeners() } + fun getUserData(): Map fun getUserData(key: SourceKey): T? fun putUserData(key: SourceKey, value: T?) fun hasUserData(): Boolean diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/SourceMarkSingleJcefComponentProvider.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/SourceMarkSingleJcefComponentProvider.kt index 0e45359ad..ccc71f507 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/SourceMarkSingleJcefComponentProvider.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/SourceMarkSingleJcefComponentProvider.kt @@ -31,7 +31,16 @@ import spp.jetbrains.marker.source.mark.gutter.event.GutterMarkEventCode */ class SourceMarkSingleJcefComponentProvider : SourceMarkJcefComponentProvider(), SourceMarkEventListener { - private val jcefComponent: SourceMarkJcefComponent by lazy { + companion object { + var singleton: SourceMarkSingleJcefComponentProvider? = null + } + + init { + singleton?.jcefComponent?.dispose() + singleton = this + } + + val jcefComponent: SourceMarkJcefComponent by lazy { SourceMarkJcefComponent(defaultConfiguration.copy()) } diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/config/SourceMarkJcefComponentConfiguration.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/config/SourceMarkJcefComponentConfiguration.kt index 0d5da08f0..9a09717eb 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/config/SourceMarkJcefComponentConfiguration.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/component/jcef/config/SourceMarkJcefComponentConfiguration.kt @@ -31,7 +31,7 @@ import java.awt.Dimension @Suppress("MagicNumber") class SourceMarkJcefComponentConfiguration : SourceMarkComponentConfiguration() { - var preloadJcefBrowser: Boolean = true + var preloadJcefBrowser: Boolean = false var currentUrl: String = "about:blank" var initialUrl: String = "about:blank" var initialHtml: String? = null @@ -63,6 +63,7 @@ class SourceMarkJcefComponentConfiguration : SourceMarkComponentConfiguration() copy.componentWidth = componentWidth copy.componentHeight = componentHeight copy.autoDisposeBrowser = autoDisposeBrowser + copy.zoomLevel = zoomLevel copy.browserLoadingListener = browserLoadingListener return copy } diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/event/SourceMarkEventCode.kt b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/event/SourceMarkEventCode.kt index 94000788d..65da6a186 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/event/SourceMarkEventCode.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/source/mark/api/event/SourceMarkEventCode.kt @@ -41,4 +41,10 @@ enum class SourceMarkEventCode(private val code: Int) : IEventCode { override fun code(): Int { return this.code } + + companion object { + fun fromName(name: String): SourceMarkEventCode? { + return values().firstOrNull { it.name == name } + } + } } diff --git a/monitor/build.gradle.kts b/monitor/build.gradle.kts index fc8ee2255..424187a34 100644 --- a/monitor/build.gradle.kts +++ b/monitor/build.gradle.kts @@ -4,24 +4,32 @@ plugins { } val vertxVersion: String by project -val kotlinVersion = ext.get("kotlinVersion") +val kotlinVersion: String by project val apolloVersion: String by project val projectVersion: String by project val slf4jVersion: String by project +repositories { + maven(url = "https://www.jetbrains.com/intellij-repository/releases") { name = "intellij-releases" } + maven(url = "https://cache-redirector.jetbrains.com/intellij-dependencies/") { name = "intellij-dependencies" } +} + dependencies { implementation("com.github.sourceplusplus.protocol:protocol:$projectVersion") implementation("org.slf4j:slf4j-api:$slf4jVersion") implementation("com.apollographql.apollo3:apollo-runtime:$apolloVersion") api("com.apollographql.apollo3:apollo-api:$apolloVersion") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") implementation("io.vertx:vertx-core:$vertxVersion") implementation("io.vertx:vertx-lang-kotlin:$vertxVersion") implementation("io.vertx:vertx-lang-kotlin-coroutines:$vertxVersion") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.3") implementation("io.dropwizard.metrics:metrics-core:4.2.9") implementation("eu.geekplace.javapinning:java-pinning-core:1.2.0") + + val intellijVersion = "221.5080.210" + compileOnly("com.jetbrains.intellij.platform:ide:$intellijVersion") } apollo { diff --git a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingClient.kt b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingClient.kt index 869f873d1..dfe3cf9bc 100644 --- a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingClient.kt +++ b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingClient.kt @@ -25,6 +25,7 @@ import monitor.skywalking.protocol.general.GetVersionQuery import monitor.skywalking.protocol.log.QueryLogsQuery import monitor.skywalking.protocol.metadata.GetAllServicesQuery import monitor.skywalking.protocol.metadata.GetServiceInstancesQuery +import monitor.skywalking.protocol.metadata.GetTimeInfoQuery import monitor.skywalking.protocol.metadata.SearchEndpointQuery import monitor.skywalking.protocol.metrics.GetLinearIntValuesQuery import monitor.skywalking.protocol.metrics.GetMultipleLinearIntValuesQuery @@ -100,6 +101,25 @@ class SkywalkingClient( } } + suspend fun getTimeInfo(): GetTimeInfoQuery.Data { + metricRegistry.timer("getTimeInfo").time().use { + if (log.isTraceEnabled) { + log.trace("Get time info request") + } + + val response = apolloClient.query( + GetTimeInfoQuery() + ).execute() + if (response.hasErrors()) { + response.errors!!.forEach { log.error(it.message) } + throw IOException(response.errors!![0].message) + } else { + if (log.isTraceEnabled) log.trace("Get time info response: {}", response.data!!.result) + return response.data!! + } + } + } + suspend fun queryTraceStack( traceId: String, ): QueryTraceQuery.Result? { diff --git a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitor.kt b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitor.kt index 5767daf7f..cd4d40a86 100644 --- a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitor.kt +++ b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitor.kt @@ -19,6 +19,8 @@ package spp.jetbrains.monitor.skywalking import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.network.okHttpClient +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key import eu.geekplace.javapinning.JavaPinning import eu.geekplace.javapinning.pin.Pin import io.vertx.kotlin.coroutines.CoroutineVerticle @@ -27,9 +29,13 @@ import monitor.skywalking.protocol.metadata.GetTimeInfoQuery import okhttp3.OkHttpClient import org.slf4j.LoggerFactory import spp.jetbrains.monitor.skywalking.bridge.* +import spp.jetbrains.monitor.skywalking.impl.SkywalkingMonitorServiceImpl import spp.jetbrains.monitor.skywalking.service.SWLiveService import spp.jetbrains.monitor.skywalking.service.SWLiveViewService import spp.protocol.SourceServices +import spp.protocol.service.LiveInstrumentService +import spp.protocol.service.LiveService +import spp.protocol.service.LiveViewService import java.security.SecureRandom import java.security.cert.X509Certificate import javax.net.ssl.SSLContext @@ -47,11 +53,16 @@ class SkywalkingMonitor( private val jwtToken: String? = null, private val certificatePins: List = emptyList(), private val verifyHost: Boolean, - private val currentService: String? = null + private val currentService: String? = null, + private val project: Project ) : CoroutineVerticle() { companion object { private val log = LoggerFactory.getLogger(SkywalkingMonitor::class.java) + + val LIVE_SERVICE = Key.create("SPP_LIVE_SERVICE") + val LIVE_VIEW_SERVICE = Key.create("SPP_LIVE_VIEW_SERVICE") + val LIVE_INSTRUMENT_SERVICE = Key.create("SPP_LIVE_INSTRUMENT_SERVICE") } override suspend fun start() { @@ -115,12 +126,18 @@ class SkywalkingMonitor( val swLiveService = SWLiveService() vertx.deployVerticle(swLiveService).await() SourceServices.Instance.liveService = swLiveService + + project.putUserData(LIVE_SERVICE, swLiveService) } if (SourceServices.Instance.liveView == null) { val swLiveViewService = SWLiveViewService() vertx.deployVerticle(swLiveViewService).await() SourceServices.Instance.liveView = swLiveViewService + + project.putUserData(LIVE_VIEW_SERVICE, swLiveViewService) } + + project.putUserData(SkywalkingMonitorService.KEY, SkywalkingMonitorServiceImpl(skywalkingClient)) } } } diff --git a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitorService.kt b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitorService.kt new file mode 100644 index 000000000..040e8c614 --- /dev/null +++ b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/SkywalkingMonitorService.kt @@ -0,0 +1,60 @@ +/* + * Source++, the open-source live coding platform. + * Copyright (C) 2022 CodeBrig, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package spp.jetbrains.monitor.skywalking + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import io.vertx.core.AsyncResult +import monitor.skywalking.protocol.metadata.GetServiceInstancesQuery +import monitor.skywalking.protocol.metadata.GetTimeInfoQuery +import monitor.skywalking.protocol.metadata.SearchEndpointQuery +import monitor.skywalking.protocol.metrics.GetLinearIntValuesQuery +import monitor.skywalking.protocol.metrics.GetMultipleLinearIntValuesQuery +import spp.jetbrains.monitor.skywalking.bridge.LogsBridge +import spp.jetbrains.monitor.skywalking.model.GetEndpointMetrics +import spp.jetbrains.monitor.skywalking.model.GetEndpointTraces +import spp.jetbrains.monitor.skywalking.model.GetMultipleEndpointMetrics +import spp.protocol.artifact.log.LogResult +import spp.protocol.artifact.trace.TraceResult +import spp.protocol.artifact.trace.TraceSpanStackQueryResult +import spp.protocol.platform.general.Service + +abstract class SkywalkingMonitorService { + companion object { + val KEY = Key.create("SPP_SKYWALKING_MONITOR_SERVICE") + + fun getInstance(project: Project): SkywalkingMonitorService { + return project.getUserData(KEY)!! + } + } + + abstract suspend fun getVersion(): String + abstract suspend fun getTimeInfo(): GetTimeInfoQuery.Data + abstract suspend fun searchExactEndpoint(keyword: String): SearchEndpointQuery.Result? + abstract suspend fun getEndpoints(serviceId: String? = null, limit: Int): List + abstract suspend fun getMetrics(request: GetEndpointMetrics): List + abstract suspend fun getMultipleMetrics(request: GetMultipleEndpointMetrics): List + abstract suspend fun getTraces(request: GetEndpointTraces): TraceResult + abstract suspend fun getTraceStack(traceId: String): TraceSpanStackQueryResult + abstract suspend fun queryLogs(query: LogsBridge.GetEndpointLogs): AsyncResult + abstract suspend fun getCurrentService(): Service + abstract suspend fun getActiveServices(): List + abstract suspend fun getCurrentServiceInstance(): GetServiceInstancesQuery.Result? + abstract suspend fun getActiveServiceInstances(): List + abstract suspend fun getServiceInstances(serviceId: String): List +} diff --git a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/bridge/GeneralBridge.kt b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/bridge/GeneralBridge.kt index aae0dfc49..744479d3d 100644 --- a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/bridge/GeneralBridge.kt +++ b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/bridge/GeneralBridge.kt @@ -27,7 +27,7 @@ import spp.jetbrains.monitor.skywalking.SkywalkingClient /** * todo: description. * - * @since 0.5.0 + * @since 0.4.7 * @author [Brandon Fergerson](mailto:bfergerson@apache.org) */ class GeneralBridge(private val skywalkingClient: SkywalkingClient) : CoroutineVerticle() { diff --git a/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/impl/SkywalkingMonitorServiceImpl.kt b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/impl/SkywalkingMonitorServiceImpl.kt new file mode 100644 index 000000000..3b5f39064 --- /dev/null +++ b/monitor/src/main/kotlin/spp/jetbrains/monitor/skywalking/impl/SkywalkingMonitorServiceImpl.kt @@ -0,0 +1,96 @@ +/* + * Source++, the open-source live coding platform. + * Copyright (C) 2022 CodeBrig, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package spp.jetbrains.monitor.skywalking.impl + +import io.vertx.core.AsyncResult +import monitor.skywalking.protocol.metadata.GetServiceInstancesQuery +import monitor.skywalking.protocol.metadata.GetTimeInfoQuery +import monitor.skywalking.protocol.metadata.SearchEndpointQuery +import monitor.skywalking.protocol.metrics.GetLinearIntValuesQuery +import monitor.skywalking.protocol.metrics.GetMultipleLinearIntValuesQuery +import spp.jetbrains.monitor.skywalking.SkywalkingClient +import spp.jetbrains.monitor.skywalking.SkywalkingMonitorService +import spp.jetbrains.monitor.skywalking.bridge.* +import spp.jetbrains.monitor.skywalking.model.GetEndpointMetrics +import spp.jetbrains.monitor.skywalking.model.GetEndpointTraces +import spp.jetbrains.monitor.skywalking.model.GetMultipleEndpointMetrics +import spp.protocol.artifact.log.LogResult +import spp.protocol.artifact.trace.TraceResult +import spp.protocol.artifact.trace.TraceSpanStackQueryResult +import spp.protocol.platform.general.Service + +class SkywalkingMonitorServiceImpl( + private val skywalkingClient: SkywalkingClient +) : SkywalkingMonitorService() { + + override suspend fun getVersion(): String { + return skywalkingClient.getVersion()!! + } + + override suspend fun getTimeInfo(): GetTimeInfoQuery.Data { + return skywalkingClient.getTimeInfo() + } + + override suspend fun queryLogs(query: LogsBridge.GetEndpointLogs): AsyncResult { + return LogsBridge.queryLogs(query, skywalkingClient.vertx) + } + + override suspend fun searchExactEndpoint(keyword: String): SearchEndpointQuery.Result? { + return EndpointBridge.searchExactEndpoint(keyword, skywalkingClient.vertx) + } + + override suspend fun getEndpoints(serviceId: String?, limit: Int): List { + return EndpointBridge.getEndpoints(serviceId, limit, skywalkingClient.vertx) + } + + override suspend fun getMetrics(request: GetEndpointMetrics): List { + return EndpointMetricsBridge.getMetrics(request, skywalkingClient.vertx) + } + + override suspend fun getMultipleMetrics(request: GetMultipleEndpointMetrics): List { + return EndpointMetricsBridge.getMultipleMetrics(request, skywalkingClient.vertx) + } + + override suspend fun getTraces(request: GetEndpointTraces): TraceResult { + return EndpointTracesBridge.getTraces(request, skywalkingClient.vertx) + } + + override suspend fun getTraceStack(traceId: String): TraceSpanStackQueryResult { + return EndpointTracesBridge.getTraceStack(traceId, skywalkingClient.vertx) + } + + override suspend fun getCurrentService(): Service { + return ServiceBridge.getCurrentService(skywalkingClient.vertx) + } + + override suspend fun getActiveServices(): List { + return ServiceBridge.getActiveServices(skywalkingClient.vertx) + } + + override suspend fun getCurrentServiceInstance(): GetServiceInstancesQuery.Result? { + return ServiceInstanceBridge.getCurrentServiceInstance(skywalkingClient.vertx) + } + + override suspend fun getActiveServiceInstances(): List { + return ServiceInstanceBridge.getActiveServiceInstances(skywalkingClient.vertx) + } + + override suspend fun getServiceInstances(serviceId: String): List { + return ServiceInstanceBridge.getServiceInstances(serviceId, skywalkingClient.vertx) + } +} diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 526c98816..6ebf325ab 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -12,7 +12,7 @@ plugins { val joorVersion: String by project val jacksonVersion: String by project val vertxVersion: String by project -val kotlinVersion = ext.get("kotlinVersion") +val kotlinVersion: String by project val projectVersion: String by project // Import variables from gradle.properties file @@ -37,9 +37,11 @@ intellij { updateSinceUntilBuild.set(false) plugins.set(platformPlugins.split(',').map(String::trim).filter(String::isNotEmpty).toMutableList()) + //plugins.add("com.intellij.zh:202.413") //test chinese locale } tasks.getByName("buildSearchableOptions").onlyIf { false } //todo: figure out how to remove tasks.getByName("runIde") { + //systemProperty("sourcemarker.debug.unblocked_threads", true) systemProperty("ide.enable.slow.operations.in.edt", false) systemProperty("ide.browser.jcef.contextMenu.devTools.enabled", true) } @@ -50,18 +52,30 @@ changelog { dependencies { if (findProject(":interfaces:jetbrains") != null) { + implementation(project(":interfaces:jetbrains:commander")) { + exclude(group = "org.jetbrains.kotlin") + } + implementation(project(":interfaces:jetbrains:commander:kotlin-compiler-wrapper")) { + exclude(group = "org.jetbrains.kotlin") + } implementation(project(":interfaces:jetbrains:marker")) implementation(project(":interfaces:jetbrains:marker:jvm-marker")) implementation(project(":interfaces:jetbrains:marker:py-marker")) implementation(project(":interfaces:jetbrains:monitor")) - implementation(project(":interfaces:portal")) + implementation(project(":interfaces:booster-ui")) implementation(project(":protocol")) } else { + implementation(project(":commander")) { + exclude(group = "org.jetbrains.kotlin") + } + implementation(project(":commander:kotlin-compiler-wrapper")) { + exclude(group = "org.jetbrains.kotlin") + } implementation(project(":marker")) implementation(project(":marker:jvm-marker")) implementation(project(":marker:py-marker")) implementation(project(":monitor")) - implementation("com.github.sourceplusplus.interface-portal:portal-jvm:$projectVersion") { isTransitive = false } + implementation("com.github.sourceplusplus:interface-booster-ui:$projectVersion") implementation("com.github.sourceplusplus.protocol:protocol:$projectVersion") } @@ -69,7 +83,7 @@ dependencies { implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion") implementation("org.apache.commons:commons-text:1.9") implementation("com.github.sh5i:git-stein:v0.5.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") implementation("io.vertx:vertx-core:$vertxVersion") implementation("io.vertx:vertx-lang-kotlin:$vertxVersion") implementation("io.vertx:vertx-lang-kotlin-coroutines:$vertxVersion") @@ -136,9 +150,31 @@ tasks { runPluginVerifier { ideVersions.set(pluginVerifierIdeVersions.split(",").map { it.trim() }) } -} -tasks { + runIde { + dependsOn("getKotlinCompilerWrapper") + jvmArgs = listOf("-Xmx2G") + } + + register("getKotlinCompilerWrapper") { + mustRunAfter("prepareSandbox") + if (findProject(":interfaces:jetbrains") != null) { + dependsOn(":interfaces:jetbrains:commander:kotlin-compiler-wrapper:installDist") + } else { + dependsOn(":commander:kotlin-compiler-wrapper:installDist") + } + val wrapperBuildDir = if (findProject(":interfaces:jetbrains") != null) { + project(":interfaces:jetbrains:commander:kotlin-compiler-wrapper").buildDir + } else { + project(":commander:kotlin-compiler-wrapper").buildDir + } + from(File(wrapperBuildDir, "install/kotlin-compiler-wrapper/lib")) + into(File(buildDir, "idea-sandbox/plugins/interface-jetbrains/kotlin-compiler")) + } + getByName("buildPlugin") { + dependsOn("getKotlinCompilerWrapper") + } + register("getPluginChangelog") { doFirst { val pluginChangesHeader = "### [JetBrains Plugin](https://github.com/sourceplusplus/interface-jetbrains)\n" diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/ControlBar.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/ControlBar.java index d26325acd..5e0b20749 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/ControlBar.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/ControlBar.java @@ -6,13 +6,13 @@ import com.intellij.util.ui.UIUtil; import info.debatty.java.stringsimilarity.JaroWinkler; import net.miginfocom.swing.MigLayout; +import spp.command.LiveCommand; import spp.jetbrains.marker.source.mark.api.ExpressionSourceMark; import spp.jetbrains.marker.source.mark.inlay.InlayMark; -import spp.jetbrains.sourcemarker.command.AutocompleteFieldRow; import spp.jetbrains.sourcemarker.command.ControlBarController; -import spp.jetbrains.sourcemarker.command.LiveControlCommand; import spp.jetbrains.sourcemarker.status.util.AutocompleteField; import spp.jetbrains.sourcemarker.status.util.ControlBarCellRenderer; +import spp.jetbrains.sourcemarker.status.util.LiveCommandFieldRow; import spp.protocol.artifact.ArtifactNameUtils; import javax.swing.*; @@ -22,6 +22,7 @@ import java.awt.*; import java.awt.event.*; import java.util.List; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -32,20 +33,24 @@ public class ControlBar extends JPanel implements VisibleAreaListener { private static final JaroWinkler sift4 = new JaroWinkler(1.0d); - private final List availableCommands; - private final Function> lookup; + private final List availableCommands; + private final Function> lookup; private final Editor editor; private final InlayMark inlayMark; private boolean disposed = false; - public ControlBar(Editor editor, InlayMark inlayMark, List availableCommands) { + public ControlBar(Editor editor, InlayMark inlayMark, List availableCommands) { this.editor = editor; this.inlayMark = inlayMark; - this.availableCommands = availableCommands; - this.lookup = text -> availableCommands.stream() + + List commands = availableCommands.stream() + .map(it -> new LiveCommandFieldRow(it, Objects.requireNonNull(editor.getProject()))) + .collect(Collectors.toList()); + this.availableCommands = commands; + this.lookup = text -> commands.stream() .sorted((c1, c2) -> { - String c1Command = c1.getCommand().replace("_", "").toLowerCase(); - String c2Command = c2.getCommand().replace("_", "").toLowerCase(); + String c1Command = c1.getText().toLowerCase(); + String c2Command = c2.getText().toLowerCase(); double c1Distance = sift4.distance(text.toLowerCase(), c1Command); double c2Distance = sift4.distance(text.toLowerCase(), c2Command); if (c1Command.contains(text.toLowerCase())) { @@ -117,11 +122,12 @@ public void keyTyped(KeyEvent e) { if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { dispose(); } else if (e.getKeyChar() == KeyEvent.VK_ENTER) { + if (!textField1.getReady()) return; String autoCompleteText = textField1.getSelectedText(); if (autoCompleteText != null) { - ControlBarController.INSTANCE.handleCommandInput(autoCompleteText, editor); + ControlBarController.INSTANCE.handleCommandInput(autoCompleteText, textField1.getActualText(), editor); } else if (!textField1.getText().isEmpty()) { - List commands = lookup.apply(textField1.getText()); + List commands = lookup.apply(textField1.getText()); if (commands.isEmpty()) { ControlBarController.INSTANCE.handleCommandInput(textField1.getText(), editor); } else { @@ -238,7 +244,7 @@ private void initComponents() { // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JLabel label1; - private AutocompleteField textField1; + private AutocompleteField textField1; private JLabel label2; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/PluginUI.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/PluginUI.java index 5fa11acd3..350268a6a 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/PluginUI.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/PluginUI.java @@ -13,10 +13,12 @@ public class PluginUI { public static final LineBorder PANEL_BORDER = new LineBorder(Gray._85); public static Font ROBOTO_LIGHT_BOLD_14; + private static Font ROBOTO_LIGHT_PLAIN_13; private static Font ROBOTO_LIGHT_PLAIN_14; private static Font ROBOTO_LIGHT_PLAIN_15; private static Font ROBOTO_LIGHT_PLAIN_16; private static Font ROBOTO_LIGHT_PLAIN_17; + private static Font MICROSOFT_YAHEI_PLAIN_12; private static Font MICROSOFT_YAHEI_PLAIN_13; public static Font MICROSOFT_YAHEI_PLAIN_14; private static Font MICROSOFT_YAHEI_PLAIN_15; @@ -26,12 +28,14 @@ public class PluginUI { try { Font ROBOTO_LIGHT = Font.createFont(Font.TRUETYPE_FONT, PluginUI.class.getResourceAsStream("/fonts/Roboto-Light.ttf")); ROBOTO_LIGHT_BOLD_14 = ROBOTO_LIGHT.deriveFont(Font.BOLD).deriveFont(14f); + ROBOTO_LIGHT_PLAIN_13 = ROBOTO_LIGHT.deriveFont(Font.PLAIN).deriveFont(13f); ROBOTO_LIGHT_PLAIN_14 = ROBOTO_LIGHT.deriveFont(Font.PLAIN).deriveFont(14f); ROBOTO_LIGHT_PLAIN_15 = ROBOTO_LIGHT.deriveFont(Font.PLAIN).deriveFont(15f); ROBOTO_LIGHT_PLAIN_16 = ROBOTO_LIGHT.deriveFont(Font.PLAIN).deriveFont(16f); ROBOTO_LIGHT_PLAIN_17 = ROBOTO_LIGHT.deriveFont(Font.PLAIN).deriveFont(17f); Font YAHEI = Font.createFont(Font.TRUETYPE_FONT, PluginUI.class.getResourceAsStream("/fonts/chinese.msyh.ttf")); + MICROSOFT_YAHEI_PLAIN_12 = YAHEI.deriveFont(Font.PLAIN).deriveFont(12f); MICROSOFT_YAHEI_PLAIN_13 = YAHEI.deriveFont(Font.PLAIN).deriveFont(13f); MICROSOFT_YAHEI_PLAIN_14 = YAHEI.deriveFont(Font.PLAIN).deriveFont(14f); MICROSOFT_YAHEI_PLAIN_15 = YAHEI.deriveFont(Font.PLAIN).deriveFont(15f); @@ -45,6 +49,7 @@ public class PluginUI { public static final Font SMALL_FONT = (PluginBundle.INSTANCE.getLOCALE().getLanguage().equals("zh")) ? MICROSOFT_YAHEI_PLAIN_15 : ROBOTO_LIGHT_PLAIN_16; public static final Font SMALLER_FONT = (PluginBundle.INSTANCE.getLOCALE().getLanguage().equals("zh")) ? MICROSOFT_YAHEI_PLAIN_14 : ROBOTO_LIGHT_PLAIN_15; public static final Font SMALLEST_FONT = (PluginBundle.INSTANCE.getLOCALE().getLanguage().equals("zh")) ? MICROSOFT_YAHEI_PLAIN_13 : ROBOTO_LIGHT_PLAIN_14; + public static final Font SUPER_SMALLEST_FONT = (PluginBundle.INSTANCE.getLOCALE().getLanguage().equals("zh")) ? MICROSOFT_YAHEI_PLAIN_12 : ROBOTO_LIGHT_PLAIN_13; public static final Color PANEL_BACKGROUND_COLOR = Gray._37; public static final Color LABEL_FOREGROUND_COLOR = new Color(152, 118, 170); public static final Color LABEL_FOREGROUND_COLOR1 = new Color(106, 135, 89); diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/element/LiveControlBarRow.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/element/LiveControlBarRow.java index 199587ef5..f1c7156e5 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/element/LiveControlBarRow.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/element/LiveControlBarRow.java @@ -117,7 +117,7 @@ private void initComponents() { //---- descriptionLabel ---- descriptionLabel.setBackground(null); - descriptionLabel.setFont(SMALLER_FONT); + descriptionLabel.setFont(SUPER_SMALLEST_FONT); descriptionLabel.setContentType("text/html"); descriptionLabel.setEditable(false); descriptionLabel.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true); diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.form b/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.form index 1a8e100e1..a0ff1670c 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.form +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.form @@ -21,7 +21,7 @@ - + @@ -74,14 +74,6 @@ - - - - - - - - @@ -177,6 +169,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.java index 56c8a8209..6ec5fa549 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/settings/PluginConfigurationPanel.java @@ -15,10 +15,7 @@ import javax.swing.*; import java.awt.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import static spp.jetbrains.sourcemarker.PluginBundle.message; @@ -37,12 +34,13 @@ public class PluginConfigurationPanel { private JComboBox serviceComboBox; private JCheckBox verifyHostCheckBox; private JLabel verifyHostLabel; - private JCheckBox autoDisplayEndpointQuickStatsCheckBox; private JLabel hostLabel; private JLabel accessTokenLabel; private JLabel certificatePinsLabel; private JLabel serviceLabel; private JLabel rootSourcePackageLabel; + private JPanel myPortalSettingsPanel; + private JSpinner portalZoomSpinner; private SourceMarkerConfig config; private CertificatePinPanel myCertificatePins; @@ -50,6 +48,8 @@ public PluginConfigurationPanel(SourceMarkerConfig config) { this.config = config; myServiceSettingsPanel.setBorder(IdeBorderFactory.createTitledBorder(message("service_settings"))); myGlobalSettingsPanel.setBorder(IdeBorderFactory.createTitledBorder(message("plugin_settings"))); + myPortalSettingsPanel.setBorder(IdeBorderFactory.createTitledBorder(message("portal_settings"))); + portalZoomSpinner.setModel(new SpinnerNumberModel(1.0, 0.5, 2.0, 0.1)); if (INSTANCE.getLiveService() != null) { INSTANCE.getLiveService().getServices().onComplete(it -> { @@ -74,7 +74,6 @@ public PluginConfigurationPanel(SourceMarkerConfig config) { rootSourcePackageLabel.setText(message("root_source_package")); debugConsoleCheckBox.setText(message("debug_console")); autoResolveEndpointNamesCheckBox.setText(message("auto_resolve_endpoint_names")); - autoDisplayEndpointQuickStatsCheckBox.setText(message("auto_display_endpoint_quick_stats")); } private void setUIEnabled(boolean enabled) { @@ -85,7 +84,7 @@ private void setUIEnabled(boolean enabled) { accessTokenTextField.setEnabled(enabled); serviceComboBox.setEnabled(enabled); verifyHostCheckBox.setEnabled(enabled); - autoDisplayEndpointQuickStatsCheckBox.setEnabled(enabled); + portalZoomSpinner.setEnabled(enabled); } public JComponent getContentPane() { @@ -120,7 +119,7 @@ boolean isModified() { !(config.getServiceName() == null && Objects.equals(serviceComboBox.getSelectedItem(), "All Services"))) { return true; } - if (!Objects.equals(autoDisplayEndpointQuickStatsCheckBox.isSelected(), config.getAutoDisplayEndpointQuickStats())) { + if (!Objects.equals(portalZoomSpinner.getValue(), config.getPortalConfig().getZoomLevel())) { return true; } return false; @@ -143,8 +142,9 @@ public SourceMarkerConfig getPluginConfig() { null, verifyHostCheckBox.isSelected(), currentService, - autoDisplayEndpointQuickStatsCheckBox.isSelected(), - false + false, + new PortalConfig((Double) portalZoomSpinner.getValue()), + new HashMap<>() ); } @@ -156,12 +156,13 @@ public void applySourceMarkerConfig(SourceMarkerConfig config) { serviceHostTextField.setText(config.getServiceHost()); accessTokenTextField.setText(config.getAccessToken()); verifyHostCheckBox.setSelected(config.getVerifyHost()); - autoDisplayEndpointQuickStatsCheckBox.setSelected(config.getAutoDisplayEndpointQuickStats()); myCertificatePins = new CertificatePinPanel(!config.getOverride()); myCertificatePins.listModel.addAll(config.getCertificatePins()); testPanel.add(myCertificatePins); + portalZoomSpinner.setValue(config.getPortalConfig().getZoomLevel()); + setUIEnabled(!config.getOverride()); } diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/status/BreakpointStatusBar.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/status/BreakpointStatusBar.java index 56e108d66..4632dfd11 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/status/BreakpointStatusBar.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/status/BreakpointStatusBar.java @@ -20,7 +20,7 @@ import spp.jetbrains.marker.source.mark.inlay.InlayMark; import spp.jetbrains.sourcemarker.PluginIcons; import spp.jetbrains.sourcemarker.PluginUI; -import spp.jetbrains.sourcemarker.command.AutocompleteFieldRow; +import spp.jetbrains.sourcemarker.status.util.AutocompleteFieldRow; import spp.jetbrains.sourcemarker.mark.SourceMarkKeys; import spp.jetbrains.sourcemarker.service.InstrumentEventListener; import spp.jetbrains.sourcemarker.service.instrument.breakpoint.BreakpointHitColumnInfo; @@ -97,7 +97,11 @@ public String getDescription() { return null; } - public Icon getIcon() { + public Icon getSelectedIcon() { + return PluginIcons.Nodes.variable; + } + + public Icon getUnselectedIcon() { return PluginIcons.Nodes.variable; } }).collect(Collectors.toList()); @@ -114,7 +118,11 @@ public String getDescription() { return null; } - public Icon getIcon() { + public Icon getSelectedIcon() { + return PluginIcons.Nodes.variable; + } + + public Icon getUnselectedIcon() { return PluginIcons.Nodes.variable; } }) @@ -612,7 +620,7 @@ private void initComponents() { private JLabel configLabel; private JLabel configDropdownLabel; private JPanel mainPanel; - private AutocompleteField breakpointConditionField; + private AutocompleteField breakpointConditionField; private JLabel label1; private JBIntSpinner hitLimitSpinner; private JLabel timeLabel; diff --git a/plugin/src/main/java/spp/jetbrains/sourcemarker/status/LogStatusBar.java b/plugin/src/main/java/spp/jetbrains/sourcemarker/status/LogStatusBar.java index 33b176a8b..4d633d9a4 100644 --- a/plugin/src/main/java/spp/jetbrains/sourcemarker/status/LogStatusBar.java +++ b/plugin/src/main/java/spp/jetbrains/sourcemarker/status/LogStatusBar.java @@ -19,7 +19,7 @@ import spp.jetbrains.marker.source.mark.inlay.InlayMark; import spp.jetbrains.sourcemarker.PluginIcons; import spp.jetbrains.sourcemarker.PluginUI; -import spp.jetbrains.sourcemarker.command.AutocompleteFieldRow; +import spp.jetbrains.sourcemarker.status.util.AutocompleteFieldRow; import spp.jetbrains.sourcemarker.mark.SourceMarkKeys; import spp.jetbrains.sourcemarker.service.InstrumentEventListener; import spp.jetbrains.sourcemarker.service.ViewEventListener; @@ -123,7 +123,11 @@ public String getDescription() { return null; } - public Icon getIcon() { + public Icon getSelectedIcon() { + return PluginIcons.Nodes.variable; + } + + public Icon getUnselectedIcon() { return PluginIcons.Nodes.variable; } }).collect(Collectors.toList()); @@ -138,7 +142,11 @@ public String getDescription() { return null; } - public Icon getIcon() { + public Icon getSelectedIcon() { + return PluginIcons.Nodes.variable; + } + + public Icon getUnselectedIcon() { return PluginIcons.Nodes.variable; } }) diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/PluginBundle.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/PluginBundle.kt index 3b1ddb6f4..445f7fc0f 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/PluginBundle.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/PluginBundle.kt @@ -52,6 +52,10 @@ object PluginBundle : AbstractBundle(BUNDLE) { @Suppress("SpreadOperator") @JvmStatic fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String { - return LOCALE_BUNDLE.getString(key) ?: getMessage(key, *params) + return try { + LOCALE_BUNDLE.getString(key) ?: getMessage(key, *params) + } catch (e: MissingResourceException) { + key // no translation found + } } } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/SourceMarkerPlugin.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/SourceMarkerPlugin.kt index 2ba7a346d..eeed5847c 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/SourceMarkerPlugin.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/SourceMarkerPlugin.kt @@ -27,7 +27,6 @@ import com.intellij.notification.NotificationType import com.intellij.notification.Notifications import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFileManager @@ -52,17 +51,14 @@ import io.vertx.servicediscovery.ServiceDiscoveryOptions import io.vertx.servicediscovery.impl.DiscoveryImpl import io.vertx.serviceproxy.ServiceProxyBuilder import kotlinx.coroutines.* +import liveplugin.implementation.command.LiveCommandService import org.apache.commons.text.CaseUtils import org.slf4j.LoggerFactory import spp.jetbrains.marker.SourceMarker import spp.jetbrains.marker.jvm.* import spp.jetbrains.marker.plugin.SourceInlayHintProvider import spp.jetbrains.marker.py.* -import spp.jetbrains.marker.source.mark.api.component.api.config.ComponentSizeEvaluator -import spp.jetbrains.marker.source.mark.api.component.api.config.SourceMarkComponentConfiguration -import spp.jetbrains.marker.source.mark.api.component.jcef.SourceMarkSingleJcefComponentProvider import spp.jetbrains.marker.source.mark.api.filter.CreateSourceMarkFilter -import spp.jetbrains.marker.source.mark.guide.config.GuideMarkConfiguration import spp.jetbrains.monitor.skywalking.SkywalkingMonitor import spp.jetbrains.sourcemarker.PluginBundle.message import spp.jetbrains.sourcemarker.activities.PluginSourceMarkerStartupActivity.Companion.INTELLIJ_PRODUCT_CODES @@ -79,14 +75,11 @@ import spp.jetbrains.sourcemarker.settings.getServicePortNormalized import spp.jetbrains.sourcemarker.settings.isSsl import spp.jetbrains.sourcemarker.settings.serviceHostNormalized import spp.jetbrains.sourcemarker.status.LiveStatusManager -import spp.jetbrains.sourcemarker.view.ActivityQuickStatsIndicator -import spp.jetbrains.sourcemarker.view.FailingEndpointIndicator import spp.protocol.SourceServices import spp.protocol.SourceServices.Instance import spp.protocol.service.LiveInstrumentService import spp.protocol.service.LiveService import spp.protocol.service.LiveViewService -import java.awt.Dimension import java.io.File import javax.net.ssl.SSLHandshakeException @@ -193,7 +186,7 @@ object SourceMarkerPlugin { var connectedMonitor = false try { initServices(project, config) - initMonitor(config) + initMonitor(project, config) connectedMonitor = true if (notifySuccessfulConnection) { @@ -270,6 +263,7 @@ object SourceMarkerPlugin { if (connectedMonitor) { initUI(config) initMarker(config, project) + project.getUserData(LiveCommandService.LIVE_COMMAND_LOADER)!!.invoke() } } } @@ -278,13 +272,14 @@ object SourceMarkerPlugin { if (project.basePath != null) { val configFile = File(project.basePath, SPP_PLUGIN_YML_PATH) if (configFile.exists()) { - val config = JsonObject( + var config = JsonObject( ObjectMapper().writeValueAsString(YAMLMapper().readValue(configFile, Object::class.java)) ) - config.fieldNames().toList().forEach { - val value = config.remove(it) - config.put(CaseUtils.toCamelCase(it, false, '_'), value) - } + + val commandConfig = config.remove("command_config") + config = convertConfigToCamelCase(config) + config.put("commandConfig", commandConfig) + return try { Json.decodeValue(config.toString(), SourceMarkerConfig::class.java) } catch (ex: DecodeException) { @@ -296,6 +291,19 @@ object SourceMarkerPlugin { return null } + private fun convertConfigToCamelCase(jsonObject: JsonObject): JsonObject { + val result = JsonObject(jsonObject.toString()) + result.fieldNames().toList().forEach { + val value = result.remove(it) + if (value is JsonObject) { + result.put(CaseUtils.toCamelCase(it, false, '_'), convertConfigToCamelCase(value)) + } else { + result.put(CaseUtils.toCamelCase(it, false, '_'), value) + } + } + return result + } + fun getConfig(project: Project): SourceMarkerConfig { val fileConfig = loadSppPluginFileConfiguration(project) val config = if (fileConfig != null && fileConfig.override) { @@ -353,6 +361,7 @@ object SourceMarkerPlugin { .apply { config.serviceToken?.let { setToken(it) } } .setAddress(SourceServices.Utilize.LIVE_SERVICE) .build(LiveService::class.java) + project.putUserData(SkywalkingMonitor.LIVE_SERVICE, Instance.liveService) } else { log.warn("Live service unavailable") } @@ -366,6 +375,8 @@ object SourceMarkerPlugin { .apply { config.serviceToken?.let { setToken(it) } } .setAddress(SourceServices.Utilize.LIVE_INSTRUMENT) .build(LiveInstrumentService::class.java) + project.putUserData(SkywalkingMonitor.LIVE_INSTRUMENT_SERVICE, Instance.liveInstrument) + ApplicationManager.getApplication().invokeLater { BreakpointHitWindowService.getInstance(project).showEventsWindow() } @@ -384,6 +395,7 @@ object SourceMarkerPlugin { .apply { config.serviceToken?.let { setToken(it) } } .setAddress(SourceServices.Utilize.LIVE_VIEW) .build(LiveViewService::class.java) + project.putUserData(SkywalkingMonitor.LIVE_VIEW_SERVICE, Instance.liveView) val viewListener = LiveViewManager(config) GlobalScope.launch(vertx.dispatcher()) { @@ -528,7 +540,7 @@ object SourceMarkerPlugin { } } - private suspend fun initMonitor(config: SourceMarkerConfig) { + private suspend fun initMonitor(project: Project, config: SourceMarkerConfig) { val scheme = if (config.isSsl()) "https" else "http" val skywalkingHost = "$scheme://${config.serviceHostNormalized}:${config.getServicePortNormalized()}/graphql" val certificatePins = mutableListOf() @@ -536,7 +548,7 @@ object SourceMarkerPlugin { deploymentIds.add( vertx.deployVerticle( SkywalkingMonitor( - skywalkingHost, config.serviceToken, certificatePins, config.verifyHost, config.serviceName + skywalkingHost, config.serviceToken, certificatePins, config.verifyHost, config.serviceName, project ) ).await() ) @@ -550,30 +562,8 @@ object SourceMarkerPlugin { log.info("Initializing marker") SourceMarker.addGlobalSourceMarkEventListener(SourceInlayHintProvider.EVENT_LISTENER) SourceMarker.addGlobalSourceMarkEventListener(PluginSourceMarkEventListener()) - SourceMarker.addGlobalSourceMarkEventListener(ActivityQuickStatsIndicator(config)) - SourceMarker.addGlobalSourceMarkEventListener(FailingEndpointIndicator(config)) - - val guideMarkConfig = GuideMarkConfiguration() - guideMarkConfig.activateOnKeyboardShortcut = true - val componentProvider = SourceMarkSingleJcefComponentProvider().apply { - defaultConfiguration.preloadJcefBrowser = false - defaultConfiguration.componentSizeEvaluator = object : ComponentSizeEvaluator() { - override fun getDynamicSize( - editor: Editor, - configuration: SourceMarkComponentConfiguration - ): Dimension { - var portalWidth = (editor.contentComponent.width * 0.8).toInt() - if (portalWidth > 775) { - portalWidth = 775 - } - return Dimension(portalWidth, 250) - } - } - } - guideMarkConfig.componentProvider = componentProvider - SourceMarker.configuration.guideMarkConfiguration = guideMarkConfig - SourceMarker.configuration.inlayMarkConfiguration.componentProvider = componentProvider + SourceMarker.configuration.guideMarkConfiguration.activateOnKeyboardShortcut = true SourceMarker.configuration.inlayMarkConfiguration.strictlyManualCreation = true if (config.rootSourcePackages.isNotEmpty()) { diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/ControlBarController.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/ControlBarController.kt index 7230801be..62980f934 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/ControlBarController.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/ControlBarController.kt @@ -17,29 +17,26 @@ */ package spp.jetbrains.sourcemarker.command -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiDeclarationStatement import com.intellij.psi.PsiDocumentManager -import io.vertx.kotlin.coroutines.await +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLocalVariable import kotlinx.coroutines.runBlocking +import liveplugin.implementation.command.LiveCommandService +import liveplugin.implementation.common.toFilePath +import org.joor.Reflect import org.slf4j.LoggerFactory +import spp.command.LiveCommand +import spp.command.LiveCommandContext import spp.jetbrains.marker.SourceMarker.creationService -import spp.jetbrains.marker.jvm.psi.EndpointDetector import spp.jetbrains.marker.source.SourceFileMarker -import spp.jetbrains.marker.source.mark.api.MethodSourceMark import spp.jetbrains.marker.source.mark.api.SourceMark import spp.jetbrains.marker.source.mark.api.component.swing.SwingSourceMarkComponentProvider -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode.* import spp.jetbrains.marker.source.mark.inlay.ExpressionInlayMark import spp.jetbrains.marker.source.mark.inlay.InlayMark import spp.jetbrains.sourcemarker.ControlBar -import spp.jetbrains.sourcemarker.command.LiveControlCommand.* -import spp.jetbrains.sourcemarker.mark.SourceMarkKeys import spp.jetbrains.sourcemarker.mark.SourceMarkSearch -import spp.jetbrains.sourcemarker.status.LiveStatusManager -import spp.jetbrains.sourcemarker.view.ActivityQuickStatsIndicator -import spp.protocol.SourceServices -import spp.protocol.instrument.LiveInstrumentType.* import java.awt.BorderLayout import javax.swing.JComponent import javax.swing.JPanel @@ -54,7 +51,7 @@ object ControlBarController { private val log = LoggerFactory.getLogger(ControlBarController::class.java) private var previousControlBar: InlayMark? = null - private val availableCommands: MutableList = mutableListOf() + private val availableCommands: MutableList = mutableListOf() fun clearAvailableCommands() { availableCommands.clear() @@ -63,191 +60,93 @@ object ControlBarController { private suspend fun syncAvailableCommands() { availableCommands.clear() - val selfInfo = SourceServices.Instance.liveService!!.getSelf().await() - availableCommands.addAll(LiveControlCommand.values().toList().filter { - @Suppress("UselessCallOnCollection") //unknown enums are null - selfInfo.permissions.filterNotNull().map { it.name }.contains(it.name) - }) - //availableCommands.add(VIEW_OVERVIEW) //todo: remove after v0.4.3 +// val selfInfo = SourceServices.Instance.liveService!!.getSelf().await() +// availableCommands.addAll(LiveControlCommand.values().toList().filter { +// @Suppress("UselessCallOnCollection") //unknown enums are null +// selfInfo.permissions.filterNotNull().map { it.name }.contains(it.name) +// }) } - private fun determineAvailableCommandsAtLocation(inlayMark: ExpressionInlayMark): List { + private fun determineAvailableCommandsAtLocation(inlayMark: ExpressionInlayMark): List { if (availableCommands.isEmpty()) { runBlocking { syncAvailableCommands() } } val availableCommandsAtLocation = availableCommands.toMutableSet() - availableCommandsAtLocation.remove(SHOW_QUICK_STATS) - - val parentMark = inlayMark.getParentSourceMark() - if (parentMark is MethodSourceMark) { - val loggerDetector = parentMark.getUserData(SourceMarkKeys.LOGGER_DETECTOR) - if (loggerDetector != null) { - runBlocking { - val detectedLogs = loggerDetector.getOrFindLoggerStatements(parentMark) - val logOnCurrentLine = detectedLogs.find { it.lineLocation == inlayMark.lineNumber } - if (logOnCurrentLine != null) { - availableCommandsAtLocation.add(WATCH_LOG) - } - } - } - - if (parentMark.getUserData(EndpointDetector.ENDPOINT_ID) != null) { - val existingQuickStats = parentMark.sourceFileMarker.getSourceMarks().find { - it.artifactQualifiedName == parentMark.artifactQualifiedName - && it.getUserData(ActivityQuickStatsIndicator.SHOWING_QUICK_STATS) == true - } - if (existingQuickStats == null) { - availableCommandsAtLocation.add(SHOW_QUICK_STATS) - } else { - availableCommandsAtLocation.add(HIDE_QUICK_STATS) - } - } - } +// availableCommandsAtLocation.remove(SHOW_QUICK_STATS) + availableCommandsAtLocation.addAll( + LiveCommandService.getInstance(inlayMark.project).getRegisteredLiveCommands() + ) + +// val parentMark = inlayMark.getParentSourceMark() +// if (parentMark is MethodSourceMark) { +// val loggerDetector = parentMark.getUserData(SourceMarkKeys.LOGGER_DETECTOR) +// if (loggerDetector != null) { +// runBlocking { +// val detectedLogs = loggerDetector.getOrFindLoggerStatements(parentMark) +// val logOnCurrentLine = detectedLogs.find { it.lineLocation == inlayMark.lineNumber } +// if (logOnCurrentLine != null) { +// availableCommandsAtLocation.add(WATCH_LOG) +// } +// } +// } +// +// if (parentMark.getUserData(EndpointDetector.ENDPOINT_ID) != null) { +// val existingQuickStats = parentMark.sourceFileMarker.getSourceMarks().find { +// it.artifactQualifiedName == parentMark.artifactQualifiedName +// && it.getUserData(ActivityQuickStatsIndicator.SHOWING_QUICK_STATS) == true +// } +// if (existingQuickStats == null) { +// availableCommandsAtLocation.add(SHOW_QUICK_STATS) +// } else { +// availableCommandsAtLocation.add(HIDE_QUICK_STATS) +// } +// } +// } return availableCommandsAtLocation.toList() } fun handleCommandInput(input: String, editor: Editor) { - log.info("Processing command input: {}", input) - when (input) { - SHOW_QUICK_STATS.command -> handleQuickStatsCommand(editor, SHOW_QUICK_STATS) - HIDE_QUICK_STATS.command -> handleQuickStatsCommand(editor, HIDE_QUICK_STATS) - VIEW_OVERVIEW.command -> handleViewPortalCommand(editor, VIEW_OVERVIEW) - VIEW_ACTIVITY.command -> handleViewPortalCommand(editor, VIEW_ACTIVITY) - VIEW_TRACES.command -> handleViewPortalCommand(editor, VIEW_TRACES) - VIEW_LOGS.command -> handleViewPortalCommand(editor, VIEW_LOGS) - WATCH_LOG.command -> { - //replace command inlay with log status inlay - val prevCommandBar = previousControlBar!! - previousControlBar!!.dispose() - previousControlBar = null - - ApplicationManager.getApplication().runWriteAction { - LiveStatusManager.showLogStatusBar(editor, prevCommandBar.lineNumber, true) - } - } - ADD_LIVE_BREAKPOINT.command -> { - //replace command inlay with breakpoint status inlay - val prevCommandBar = previousControlBar!! - previousControlBar!!.dispose() - previousControlBar = null - - ApplicationManager.getApplication().runWriteAction { - LiveStatusManager.showBreakpointStatusBar(editor, prevCommandBar.lineNumber) - } - } - ADD_LIVE_LOG.command -> { - //replace command inlay with log status inlay - val prevCommandBar = previousControlBar!! - previousControlBar!!.dispose() - previousControlBar = null - - ApplicationManager.getApplication().runWriteAction { - LiveStatusManager.showLogStatusBar(editor, prevCommandBar.lineNumber, false) - } - } - ADD_LIVE_METER.command -> { - //replace command inlay with meter status inlay - val prevCommandBar = previousControlBar!! - previousControlBar!!.dispose() - previousControlBar = null + handleCommandInput(input, input, editor) + } - ApplicationManager.getApplication().runWriteAction { - LiveStatusManager.showMeterStatusBar(editor, prevCommandBar.lineNumber) - } - } - ADD_LIVE_SPAN.command -> { - //replace command inlay with span status inlay + fun handleCommandInput(input: String, fullText: String, editor: Editor) { + log.info("Processing command input: {}", input) + (availableCommands + LiveCommandService.getInstance(editor.project!!) + .getRegisteredLiveCommands()).find { it.name == input } + ?.let { val prevCommandBar = previousControlBar!! previousControlBar!!.dispose() previousControlBar = null - ApplicationManager.getApplication().runWriteAction { - LiveStatusManager.showSpanStatusBar(editor, prevCommandBar.lineNumber) - } - } - CLEAR_LIVE_BREAKPOINTS.command -> { - previousControlBar!!.dispose() - previousControlBar = null - - SourceServices.Instance.liveInstrument!!.clearLiveInstruments(BREAKPOINT).onComplete { - if (it.failed()) { - log.error("Failed to clear live breakpoints", it.cause()) - } - } - } - CLEAR_LIVE_LOGS.command -> { - previousControlBar!!.dispose() - previousControlBar = null + val argsString = substringAfterIgnoreCase(fullText, input).trim() + val args = if (argsString.isEmpty()) emptyList() else argsString.split(" ") - SourceServices.Instance.liveInstrument!!.clearLiveInstruments(LOG).onComplete { - if (it.failed()) { - log.error("Failed to clear live logs", it.cause()) - } - } - } - CLEAR_LIVE_METERS.command -> { - previousControlBar!!.dispose() - previousControlBar = null - - SourceServices.Instance.liveInstrument!!.clearLiveInstruments(METER).onComplete { - if (it.failed()) { - log.error("Failed to clear live meters", it.cause()) - } - } - } - CLEAR_LIVE_SPANS.command -> { - previousControlBar!!.dispose() - previousControlBar = null - - SourceServices.Instance.liveInstrument!!.clearLiveInstruments(SPAN).onComplete { - if (it.failed()) { - log.error("Failed to clear live spans", it.cause()) + val variableName = if (prevCommandBar.getPsiElement() is PsiDeclarationStatement) { + val localVar = prevCommandBar.getPsiElement().firstChild as? PsiLocalVariable + if (localVar != null) { + localVar.name + } else { + null } + } else { + null } - } - CLEAR_LIVE_INSTRUMENTS.command -> { - previousControlBar!!.dispose() - previousControlBar = null - SourceServices.Instance.liveInstrument!!.clearLiveInstruments(null).onComplete { - if (it.failed()) { - log.error("Failed to clear live instruments", it.cause()) - } - } + val sourceMark = SourceMarkSearch.getClosestSourceMark(prevCommandBar.sourceFileMarker, editor) + it.trigger( + LiveCommandContext( + args, + prevCommandBar.sourceFileMarker.psiFile.virtualFile.toFilePath().toFile(), + prevCommandBar.lineNumber, + prevCommandBar.artifactQualifiedName, + prevCommandBar.sourceFileMarker, + sourceMark, + prevCommandBar.getPsiElement(), + variableName + ) + ) } - else -> throw UnsupportedOperationException("Command input: $input") - } - } - - private fun handleQuickStatsCommand(editor: Editor, command: LiveControlCommand) { - val sourceMark = SourceMarkSearch.getClosestSourceMark(previousControlBar!!.sourceFileMarker, editor) - if (sourceMark != null) { - sourceMark.triggerEvent(CUSTOM_EVENT, listOf(command)) - } else { - log.warn("No source mark found for command: {}", command) - } - - previousControlBar!!.dispose() - previousControlBar = null - } - - private fun handleViewPortalCommand(editor: Editor, command: LiveControlCommand) { - val sourceMark = SourceMarkSearch.getClosestSourceMark(previousControlBar!!.sourceFileMarker, editor) - if (sourceMark != null) { - sourceMark.triggerEvent(UPDATE_PORTAL_CONFIG, listOf(command)) { - sourceMark.triggerEvent(PORTAL_OPENING, listOf(PORTAL_OPENING)) - } - } else { - log.warn("No source mark found for command: {}", command) - } - - previousControlBar!!.dispose() - previousControlBar = null - } - - fun canShowControlBar(fileMarker: SourceFileMarker, lineNumber: Int): Boolean { - return creationService.getOrCreateExpressionInlayMark(fileMarker, lineNumber).isPresent } /** @@ -267,7 +166,7 @@ object ControlBarController { } val findInlayMark = creationService.getOrCreateExpressionInlayMark(fileMarker, lineNumber) - if (findInlayMark.isPresent) { + if (findInlayMark.isPresent && canShowControlBar(findInlayMark.get().getPsiElement())) { val inlayMark = findInlayMark.get() if (fileMarker.containsSourceMark(inlayMark)) { if (!tryingAboveLine) { @@ -300,4 +199,27 @@ object ControlBarController { showControlBar(editor, lineNumber - 1, true) } } + + fun canShowControlBar(fileMarker: SourceFileMarker, lineNumber: Int): Boolean { + val expressionInlayMark = creationService.getOrCreateExpressionInlayMark(fileMarker, lineNumber) + return expressionInlayMark.isPresent && canShowControlBar(expressionInlayMark.get().getPsiElement()) + } + + private fun canShowControlBar(psiElement: PsiElement): Boolean { + return when (psiElement::class.java.name) { + "org.jetbrains.kotlin.psi.KtObjectDeclaration" -> false + "org.jetbrains.kotlin.psi.KtProperty" -> { + Reflect.on(psiElement).call("isLocal").get() == true + } + else -> true + } + } + + private fun substringAfterIgnoreCase(str: String, search: String): String { + val index = str.indexOf(search, ignoreCase = true) + if (index == -1) { + return str + } + return str.substring(index + search.length) + } } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/LiveControlCommand.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/LiveControlCommand.kt deleted file mode 100644 index cf77d4ff3..000000000 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/LiveControlCommand.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Source++, the open-source live coding platform. - * Copyright (C) 2022 CodeBrig, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package spp.jetbrains.sourcemarker.command - -import spp.jetbrains.sourcemarker.PluginBundle.message -import spp.jetbrains.sourcemarker.PluginIcons -import spp.jetbrains.sourcemarker.PluginUI.getCommandHighlightColor -import spp.jetbrains.sourcemarker.PluginUI.getCommandTypeColor -import javax.swing.Icon - -/** - * todo: description. - * - * @since 0.3.0 - * @author [Brandon Fergerson](mailto:bfergerson@apache.org) - */ -@Suppress("unused", "MaxLineLength") -enum class LiveControlCommand( - val command: String, - private val description: () -> String, - val selectedIcon: Icon? = null, - val unselectedIcon: Icon? = null -) : AutocompleteFieldRow { - - VIEW_OVERVIEW( - message("view_overview"), - { "" + message("live_view") + " ➛ " + message("overview") + " ➛ " + message("scope") + ": " + message("class") + "" }, - PluginIcons.Command.viewOverviewSelected, - PluginIcons.Command.viewOverviewUnSelected - ), - VIEW_ACTIVITY( - message("view_activity"), - { "" + message("live_view") + " ➛ " + message("activity") + " ➛ " + message("scope") + ": " + message("method") + "" }, - PluginIcons.Command.viewActivitySelected, - PluginIcons.Command.viewActivityUnSelected - ), - VIEW_TRACES( - message("view_traces"), - { "" + message("live_view") + " ➛ " + message("traces") + " ➛ " + message("scope") + ": " + message("method") + "" }, - PluginIcons.Command.viewTracesSelected, - PluginIcons.Command.viewTracesUnSelected - ), - VIEW_LOGS( - message("view_logs"), - { "" + message("live_view") + " ➛ " + message("logs") + " ➛ " + message("scope") + ": " + message("method") + "" }, - PluginIcons.Command.viewLogsSelected, - PluginIcons.Command.viewLogsUnSelected - ), - SHOW_QUICK_STATS( - message("show_quick_stats"), - { "" + message("live_view") + " ➛ " + message("quick_stats") + " ➛ " + message("scope") + ": " + message("endpoint") + "" }, - PluginIcons.Command.quickStatsSelected, - PluginIcons.Command.quickStatsUnSelected - ), - HIDE_QUICK_STATS( - message("hide_quick_stats"), - { "" + message("live_view") + " ➛ " + message("quick_stats") + " ➛ " + message("scope") + ": " + message("endpoint") + "" }, - PluginIcons.Command.quickStatsSelected, - PluginIcons.Command.quickStatsUnSelected - ), - WATCH_LOG( - message("watch_log"), - { "" + message("live_view") + " ➛ " + message("log") + " ➛ " + message("scope") + ": " + message("Expression") + "" }, - PluginIcons.Command.watchLogSelected, - PluginIcons.Command.watchLogUnSelected - ), -// WATCH_VARIABLE( -// "watch", -// "Manual Tracing ➛ Watched Variables ➛ Scope: Local / Add *variable* to watched variables" -// ), -// TRACE_METHOD( -// "trace", -// "Add method to distributed tracing system" -// ), - ADD_LIVE_BREAKPOINT( - message("add_breakpoint"), - { "" + message("live_instrument") + " ➛ " + message("add") + " ➛ " + message("location") +": "+ message("on_line") + " *lineNumber*" }, - PluginIcons.Command.liveBreakpointSelected, - PluginIcons.Command.liveBreakpointUnSelected - ), - ADD_LIVE_LOG( - message("add_log"), - { "" + message("live_instrument") + " ➛ " + message("add") + " ➛ " + message("location") + ": "+ message("on_line") + " *lineNumber*" }, - PluginIcons.Command.liveLogSelected, - PluginIcons.Command.liveLogUnSelected - ), - ADD_LIVE_METER( - message("add_meter"), - { "" + message("live_instrument") + " ➛ " + message("add") + " ➛ " + message("location") + ": " + message("on_line") + " *lineNumber*" }, - PluginIcons.Command.liveMeterSelected, - PluginIcons.Command.liveMeterUnSelected - ), - ADD_LIVE_SPAN( - message("add_span"), - { "" + message("live_instrument") + " ➛ " + message("add") + " ➛ " + message("location") + ": " + message("on_method") + " *methodName*" }, - PluginIcons.Command.liveSpanSelected, - PluginIcons.Command.liveSpanUnSelected - ), - CLEAR_LIVE_INSTRUMENTS( - message("clear_instruments"), - { "" + message("live_instrument") + " ➛ " + message("clear_all") + "" }, - PluginIcons.Command.clearInstrumentSelected, - PluginIcons.Command.clearInstrumentUnSelected - ), - CLEAR_LIVE_BREAKPOINTS( - message("clear_breakpoints"), - { "Clear all self-created live breakpoints" }, - PluginIcons.Command.clearInstrumentSelected, - PluginIcons.Command.clearInstrumentUnSelected - ), - CLEAR_LIVE_LOGS( - message("clear_logs"), - { "Clear all self-created live logs" }, - PluginIcons.Command.clearInstrumentSelected, - PluginIcons.Command.clearInstrumentUnSelected - ), - CLEAR_LIVE_METERS( - message("clear_meters"), - { "Clear all self-created live meters" }, - PluginIcons.Command.clearInstrumentSelected, - PluginIcons.Command.clearInstrumentUnSelected - ), - CLEAR_LIVE_SPANS( - message("clear_spans"), - { "Clear all self-created live spans" }, - PluginIcons.Command.clearInstrumentSelected, - PluginIcons.Command.clearInstrumentUnSelected - ); - - override fun getText(): String { - return command - } - - override fun getDescription(): String? { - return description.invoke() - } - - override fun getIcon(): Icon? { - return unselectedIcon - } -} diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalController.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalController.kt index 83e16ed6f..686e77770 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalController.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalController.kt @@ -20,26 +20,31 @@ package spp.jetbrains.sourcemarker.portal import com.fasterxml.jackson.databind.module.SimpleModule import com.intellij.ide.ui.laf.IntelliJLaf import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor import io.vertx.core.json.JsonObject import io.vertx.core.json.jackson.DatabindCodec import io.vertx.kotlin.coroutines.CoroutineVerticle import io.vertx.kotlin.coroutines.await import kotlinx.datetime.Instant import org.slf4j.LoggerFactory +import spp.booster.PortalServer +import spp.booster.SourcePortal import spp.jetbrains.marker.SourceMarker -import spp.jetbrains.marker.source.mark.api.component.jcef.SourceMarkJcefComponent +import spp.jetbrains.marker.source.mark.api.component.api.config.ComponentSizeEvaluator +import spp.jetbrains.marker.source.mark.api.component.api.config.SourceMarkComponentConfiguration +import spp.jetbrains.marker.source.mark.api.component.jcef.SourceMarkSingleJcefComponentProvider +import spp.jetbrains.marker.source.mark.api.component.jcef.config.BrowserLoadingListener +import spp.jetbrains.marker.source.mark.api.component.jcef.config.SourceMarkJcefComponentConfiguration import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode import spp.jetbrains.marker.source.mark.guide.GuideMark -import spp.jetbrains.portal.SourcePortal -import spp.jetbrains.portal.backend.PortalServer -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RenderPage -import spp.jetbrains.portal.protocol.portal.PageType -import spp.jetbrains.sourcemarker.command.LiveControlCommand -import spp.jetbrains.sourcemarker.command.LiveControlCommand.* import spp.jetbrains.sourcemarker.mark.SourceMarkKeys import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig import spp.protocol.marshall.KSerializers +import java.awt.Dimension +import java.util.concurrent.atomic.AtomicReference import javax.swing.UIManager +import kotlin.math.ceil +import kotlin.math.floor class PortalController(private val markerConfig: SourceMarkerConfig) : CoroutineVerticle() { @@ -53,9 +58,40 @@ class PortalController(private val markerConfig: SourceMarkerConfig) : Coroutine module.addDeserializer(Instant::class.java, KSerializers.KotlinInstantDeserializer()) DatabindCodec.mapper().registerModule(module) - val portalServer = PortalServer(0) + log.info("Initializing portal server") + val portalServer = PortalServer() vertx.deployVerticle(portalServer).await() - vertx.deployVerticle(PortalEventListener(markerConfig)).await() + log.info("Portal server initialized") + + val initialUrl = AtomicReference("") + val componentProvider = SourceMarkSingleJcefComponentProvider().apply { + defaultConfiguration.browserLoadingListener = object: BrowserLoadingListener() { + override fun beforeBrowserCreated(configuration: SourceMarkJcefComponentConfiguration) { + configuration.initialUrl = initialUrl.get() + } + } + defaultConfiguration.zoomLevel = markerConfig.portalConfig.zoomLevel + defaultConfiguration.componentSizeEvaluator = object : ComponentSizeEvaluator() { + override fun getDynamicSize( + editor: Editor, + configuration: SourceMarkComponentConfiguration + ): Dimension { + val widthDouble = 963 * markerConfig.portalConfig.zoomLevel + val heightDouble = 350 * markerConfig.portalConfig.zoomLevel + var width: Int = widthDouble.toInt() + if (ceil(widthDouble) != floor(widthDouble)) { + width = ceil(widthDouble).toInt() + 1 + } + var height = heightDouble.toInt() + if (ceil(heightDouble) != floor(heightDouble)) { + height = ceil(heightDouble).toInt() + 1 + } + return Dimension(width, height) + } + } + } + SourceMarker.configuration.guideMarkConfiguration.componentProvider = componentProvider + SourceMarker.configuration.inlayMarkConfiguration.componentProvider = componentProvider SourceMarker.addGlobalSourceMarkEventListener { if (it.eventCode == SourceMarkEventCode.MARK_BEFORE_ADDED && it.sourceMark is GuideMark) { @@ -64,49 +100,25 @@ class PortalController(private val markerConfig: SourceMarkerConfig) : Coroutine SourcePortal.register(it.sourceMark.artifactQualifiedName, false) )!! it.sourceMark.putUserData(SourceMarkKeys.PORTAL_CONFIGURATION, portal.configuration) - portal.configuration.config["visibleOverview"] = it.sourceMark.isClassMark - portal.configuration.config["visibleActivity"] = true - portal.configuration.config["visibleTraces"] = true - portal.configuration.config["visibleLogs"] = true - portal.configuration.config["visibleConfiguration"] = false - val genUrl = "http://localhost:${portalServer.serverPort}?portalUuid=${portal.portalUuid}" it.sourceMark.addEventListener { if (it.eventCode == SourceMarkEventCode.UPDATE_PORTAL_CONFIG) { - val newPage = when (val command = it.params.first() as LiveControlCommand) { - VIEW_OVERVIEW -> PageType.OVERVIEW - VIEW_ACTIVITY -> PageType.ACTIVITY - VIEW_TRACES -> PageType.TRACES - VIEW_LOGS -> PageType.LOGS - else -> throw UnsupportedOperationException("Command input: $command") - } - - if (newPage != portal.configuration.config["currentPage"]) { - log.info("Setting portal page to $newPage") - portal.configuration.config["currentPage"] = newPage + if (it.params.first() is String && it.params.first() == "setPage") { + initialUrl.set("http://localhost:${portalServer.serverPort}${it.params.get(1)}") + vertx.eventBus().publish( + "portal.SetCurrentPage", + JsonObject().put("page", it.params.get(1) as String) + ) } } else if (it.eventCode == SourceMarkEventCode.PORTAL_OPENING) { - SourcePortal.getPortals().filter { it.portalUuid != portal.portalUuid }.forEach { - it.configuration.config["active"] = false - } - portal.configuration.config["active"] = true - - val jcefComponent = it.sourceMark.sourceMarkComponent as SourceMarkJcefComponent portal.configuration.darkMode = UIManager.getLookAndFeel() !is IntelliJLaf - - if (jcefComponent.configuration.currentUrl == "about:blank") { - jcefComponent.configuration.initialUrl = genUrl - jcefComponent.configuration.currentUrl = genUrl - jcefComponent.getBrowser().cefBrowser.executeJavaScript( - "window.location.href = '$genUrl';", genUrl, 0 - ) - } portal.configuration.config["portal_uuid"] = portal.portalUuid - vertx.eventBus().publish(RenderPage, JsonObject.mapFrom(portal.configuration)) ApplicationManager.getApplication().invokeLater(it.sourceMark::displayPopup) } } } } + + log.info("Portal initialized") } } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalEventListener.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalEventListener.kt deleted file mode 100644 index c05a1aa22..000000000 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/portal/PortalEventListener.kt +++ /dev/null @@ -1,863 +0,0 @@ -/* - * Source++, the open-source live coding platform. - * Copyright (C) 2022 CodeBrig, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package spp.jetbrains.sourcemarker.portal - -import com.intellij.ide.ui.laf.IntelliJLaf -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runReadAction -import com.intellij.openapi.project.ProjectManager -import io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND -import io.vertx.core.eventbus.ReplyException -import io.vertx.core.json.Json -import io.vertx.core.json.JsonArray -import io.vertx.core.json.JsonObject -import io.vertx.ext.auth.impl.jose.JWT -import io.vertx.kotlin.coroutines.CoroutineVerticle -import io.vertx.kotlin.coroutines.dispatcher -import kotlinx.coroutines.launch -import kotlinx.datetime.Instant -import kotlinx.datetime.toJavaInstant -import kotlinx.datetime.toKotlinInstant -import org.slf4j.LoggerFactory -import spp.jetbrains.marker.SourceMarker -import spp.jetbrains.marker.jvm.ArtifactNavigator -import spp.jetbrains.marker.source.SourceFileMarker -import spp.jetbrains.marker.source.mark.api.ClassSourceMark -import spp.jetbrains.marker.source.mark.api.MethodSourceMark -import spp.jetbrains.marker.source.mark.api.SourceMark -import spp.jetbrains.marker.source.mark.api.component.jcef.SourceMarkJcefComponent -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode -import spp.jetbrains.marker.source.mark.guide.MethodGuideMark -import spp.jetbrains.monitor.skywalking.SkywalkingClient -import spp.jetbrains.monitor.skywalking.average -import spp.jetbrains.monitor.skywalking.bridge.EndpointMetricsBridge -import spp.jetbrains.monitor.skywalking.bridge.EndpointTracesBridge -import spp.jetbrains.monitor.skywalking.bridge.GeneralBridge -import spp.jetbrains.monitor.skywalking.bridge.LogsBridge -import spp.jetbrains.monitor.skywalking.bridge.LogsBridge.GetEndpointLogs -import spp.jetbrains.monitor.skywalking.model.GetEndpointMetrics -import spp.jetbrains.monitor.skywalking.model.GetEndpointTraces -import spp.jetbrains.monitor.skywalking.model.ZonedDuration -import spp.jetbrains.monitor.skywalking.toProtocol -import spp.jetbrains.portal.SourcePortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.ArtifactLogUpdated -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.ArtifactMetricsUpdated -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.ArtifactTracesUpdated -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.CanNavigateToArtifact -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.ClickedStackTraceElement -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.ClosePortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.FindAndOpenPortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.FindPortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.GetPortalConfiguration -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.GetPortalTranslations -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.NavigateToArtifact -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.OpenPortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.QueryTraceStack -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RefreshActivity -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RefreshLogs -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RefreshOverview -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RefreshPortal -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.RefreshTraces -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.SetCurrentPage -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.TraceSpanUpdated -import spp.jetbrains.portal.protocol.ProtocolAddress.Global.UpdateEndpoints -import spp.jetbrains.portal.protocol.artifact.endpoint.EndpointResult -import spp.jetbrains.portal.protocol.artifact.endpoint.EndpointType -import spp.jetbrains.portal.protocol.artifact.metrics.ArtifactSummarizedMetrics -import spp.jetbrains.portal.protocol.artifact.metrics.ArtifactSummarizedResult -import spp.jetbrains.portal.protocol.portal.PageType -import spp.jetbrains.sourcemarker.PluginBundle -import spp.jetbrains.sourcemarker.SourceMarkerPlugin -import spp.jetbrains.sourcemarker.mark.SourceMarkKeys -import spp.jetbrains.sourcemarker.mark.SourceMarkKeys.ENDPOINT_DETECTOR -import spp.jetbrains.sourcemarker.mark.SourceMarkSearch -import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig -import spp.protocol.SourceServices.Instance -import spp.protocol.SourceServices.Provide.toLiveViewSubscriberAddress -import spp.protocol.artifact.ArtifactQualifiedName -import spp.protocol.artifact.QueryTimeFrame -import spp.protocol.artifact.exception.LiveStackTraceElement -import spp.protocol.artifact.log.Log -import spp.protocol.artifact.log.LogOrderType -import spp.protocol.artifact.log.LogResult -import spp.protocol.artifact.metrics.ArtifactMetricResult -import spp.protocol.artifact.metrics.ArtifactMetrics -import spp.protocol.artifact.metrics.MetricType -import spp.protocol.artifact.trace.Trace -import spp.protocol.artifact.trace.TraceOrderType -import spp.protocol.artifact.trace.TraceResult -import spp.protocol.artifact.trace.TraceSpan -import spp.protocol.instrument.LiveSourceLocation -import spp.protocol.view.LiveViewConfig -import spp.protocol.view.LiveViewEvent -import spp.protocol.view.LiveViewSubscription -import java.net.URI -import java.net.URISyntaxException -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatterBuilder -import java.time.temporal.ChronoUnit -import javax.swing.UIManager - -/** - * todo: description. - * - * @since 0.1.0 - * @author [Brandon Fergerson](mailto:bfergerson@apache.org) - */ -@Suppress("MagicNumber") -class PortalEventListener( - private val markerConfig: SourceMarkerConfig, - private val hostTranslations: Boolean = true -) : CoroutineVerticle() { - - companion object { - private val log = LoggerFactory.getLogger(PortalEventListener::class.java) - } - - private val formatter = DateTimeFormatterBuilder() - .appendPattern("yyyyMMddHHmm") - .toFormatter() - .withZone(ZoneOffset.UTC) //todo: load from SkywalkingMonitor - private var lastDisplayedInternalPortal: SourcePortal? = null - - override suspend fun start() { - //listen for theme changes - UIManager.addPropertyChangeListener { - if (lastDisplayedInternalPortal != null) { - lastDisplayedInternalPortal!!.configuration.darkMode = (it.newValue !is IntelliJLaf) - val sourceMark = SourceMarker.getSourceMark( - lastDisplayedInternalPortal!!.viewingArtifact, SourceMark.Type.GUIDE - ) - if (sourceMark != null) { - val jcefComponent = sourceMark.sourceMarkComponent as SourceMarkJcefComponent - jcefComponent.getBrowser().cefBrowser.reload() - } - } - } - - //listen to live view events - var developer = "system" - if (markerConfig.serviceToken != null) { - val json = JWT.parse(markerConfig.serviceToken) - developer = json.getJsonObject("payload").getString("developer_id") - } - vertx.eventBus().consumer(toLiveViewSubscriberAddress(developer)) { - val event = Json.decodeValue(it.body().toString(), LiveViewEvent::class.java) - when (event.viewConfig.viewName) { - "LOGS" -> launch(vertx.dispatcher()) { consumeLogsViewEvent(event) } - "TRACES" -> consumeTracesViewEvent(event) - "ACTIVITY" -> consumeActivityViewEvent(event) - } - } - - vertx.eventBus().consumer(RefreshPortal) { - val portal = if (it.body() is String) { - SourcePortal.getPortal(it.body() as String) - } else { - it.body() as SourcePortal - }!! - when (portal.configuration.config["currentPage"]) { - PageType.OVERVIEW -> vertx.eventBus().send(RefreshOverview, portal) - PageType.ACTIVITY -> vertx.eventBus().send(RefreshActivity, portal) - PageType.LOGS -> vertx.eventBus().send(RefreshLogs, portal) - PageType.TRACES -> vertx.eventBus().send(RefreshTraces, portal) - PageType.CONFIGURATION -> TODO() - } - } - vertx.eventBus().consumer(SetCurrentPage) { - if (it.body() is JsonObject) { - val body = (it.body() as JsonObject) - val portalUuid = body.getString("portalUuid") - val pageType = PageType.valueOf(body.getString("pageType")) - val portal = SourcePortal.getPortal(portalUuid)!! - portal.configuration.config["currentPage"] = pageType - it.reply(JsonObject.mapFrom(portal.configuration)) - log.info("Set portal ${portal.portalUuid} page type to $pageType") - vertx.eventBus().publish(RefreshPortal, portal) - } else { - val portal = it.body() as SourcePortal - if (lastDisplayedInternalPortal == null) { - configureDisplayedPortal(portal) - } else { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - val jcefComponent = sourceMark!!.sourceMarkComponent as SourceMarkJcefComponent - val port = vertx.sharedData().getLocalMap("portal")["http.port"]!! - val host = "http://localhost:$port" - val currentUrl = "$host/?portalUuid=${portal.portalUuid}" - jcefComponent.getBrowser().cefBrowser.executeJavaScript( - "window.location.href = '$currentUrl';", currentUrl, 0 - ) - } - it.reply(JsonObject.mapFrom(portal.configuration)) - log.info("Updated portal ${portal.portalUuid} current page") - } - } - vertx.eventBus().consumer(GetPortalConfiguration) { - val portalUuid = it.body() - if (!portalUuid.isNullOrEmpty()) { - log.info("Getting portal configuration. Portal UUID: $portalUuid") - val portal = SourcePortal.getPortal(portalUuid) - if (portal == null) { - log.error("Failed to find portal: $portalUuid") - it.fail(NOT_FOUND.code(), "Portal $portalUuid does not exist") - } else { - it.reply(JsonObject.mapFrom(portal.configuration)) - } - } else { - log.error("Failed to get portal configuration. Missing portalUuid"); - } - } - if (hostTranslations) { - vertx.eventBus().consumer(GetPortalTranslations) { - val map = HashMap() - val keys = PluginBundle.LOCALE_BUNDLE.keys - while (keys.hasMoreElements()) { - val key = keys.nextElement() - map[key] = PluginBundle.LOCALE_BUNDLE.getString(key) - } - it.reply(JsonObject.mapFrom(map)) - } - } - vertx.eventBus().consumer(FindPortal) { -// val artifactQualifiedName = it.body() -// val portals = SourcePortal.getPortals(artifactQualifiedName) -// if (portals.isNotEmpty()) { -// it.reply(portals.first()) -// } else { -// launch(vertx.dispatcher()) { -// val classArtifact = findArtifact( -// vertx, artifactQualifiedName.copy( -// identifier = ArtifactNameUtils.getQualifiedClassName(artifactQualifiedName.identifier)!!, -// operationName = null, -// type = ArtifactType.CLASS -// ) -// ) -// val fileMarker = SourceMarker.getSourceFileMarker(classArtifact!!.containingFile)!! -// val searchArtifact = findArtifact(vertx, artifactQualifiedName) as PsiNameIdentifierOwner -// runReadAction { -// val gutterMark = creationService.getOrCreateMethodGutterMark( -// fileMarker, searchArtifact.nameIdentifier!! -// )!! -// println(gutterMark) -// //it.reply(gutterMark.getUserData(SourceMarkKeys.SOURCE_PORTAL)!!) -// } -// } -// } - } - vertx.eventBus().consumer(FindAndOpenPortal) { -// val artifactQualifiedName = it.body() -// runReadAction { -// val sourceMarks = SourceMarker.getSourceMarks(artifactQualifiedName) -// if (sourceMarks.isNotEmpty()) { -// val sourceMark = sourceMarks[0] -// ApplicationManager.getApplication().invokeLater { -// PsiNavigateUtil.navigate(sourceMark.getPsiElement()) -// -// val portals = SourcePortal.getPortals(artifactQualifiedName) -// openPortal(portals.first()) -// it.reply(portals.first()) -// } -// } else { -// log.warn("Failed to find portal for artifact: $artifactQualifiedName") -// } -// } - } - vertx.eventBus().consumer(OpenPortal) { openPortal(it.body()); it.reply(it.body()) } - vertx.eventBus().consumer(ClosePortal) { closePortal(it.body()) } - vertx.eventBus().consumer(RefreshOverview) { - runReadAction { - val fileMarker = SourceMarker.getSourceFileMarker(it.body().viewingArtifact)!! - launch(vertx.dispatcher()) { - refreshOverview(fileMarker, it.body()) - } - - //todo: update subscriptions - } - } - vertx.eventBus().consumer(RefreshActivity) { - val portal = it.body() - //pull from skywalking - launch(vertx.dispatcher()) { - pullLatestActivity(portal) - } - - //update subscriptions - launch(vertx.dispatcher()) { - val sourceMark = SourceMarker.getSourceMark( - portal.viewingArtifact, SourceMark.Type.GUIDE - ) ?: return@launch - val endpointName = sourceMark.getUserData( - ENDPOINT_DETECTOR - )?.getOrFindEndpointName(sourceMark) ?: return@launch - val endpointId = sourceMark.getUserData( - ENDPOINT_DETECTOR - )?.getOrFindEndpointId(sourceMark) ?: return@launch - - val swVersion = GeneralBridge.getVersion(SourceMarkerPlugin.vertx) - val fetchMetricTypes = if (swVersion.startsWith("9")) { - listOf("endpoint_cpm", "endpoint_resp_time", "endpoint_sla") - } else { - listOf("endpoint_cpm", "endpoint_avg", "endpoint_sla") - } - Instance.liveView!!.addLiveViewSubscription( - LiveViewSubscription( - null, - listOf(endpointName), - portal.viewingArtifact, - LiveSourceLocation(portal.viewingArtifact.identifier, 0), //todo: fix - LiveViewConfig("ACTIVITY", fetchMetricTypes) - ) - ).onComplete { - if (it.succeeded()) { - val subscriptionId = it.result().subscriptionId!! - if (portal.configuration.config["subscriptionId"] != null) { - Instance.liveView!!.removeLiveViewSubscription( - portal.configuration.config["subscriptionId"].toString() - ) - } - portal.configuration.config["subscriptionId"] = subscriptionId - - sourceMark.addEventListener { - if (it.eventCode == SourceMarkEventCode.PORTAL_CLOSED) { - Instance.liveView!!.removeLiveViewSubscription(subscriptionId) - } - } - } else { - log.error("Failed to add live view subscription", it.cause()) - } - } - } - } - vertx.eventBus().consumer(RefreshTraces) { - val portal = it.body() - //pull from skywalking - launch(vertx.dispatcher()) { - pullLatestTraces(it.body()) - } - - //update subscriptions - launch(vertx.dispatcher()) { - val sourceMark = SourceMarker.getSourceMark( - portal.viewingArtifact, SourceMark.Type.GUIDE - ) ?: return@launch - val endpointName = sourceMark.getUserData( - ENDPOINT_DETECTOR - )?.getOrFindEndpointName(sourceMark) ?: return@launch - val endpointId = sourceMark.getUserData( - ENDPOINT_DETECTOR - )?.getOrFindEndpointId(sourceMark) ?: return@launch - - Instance.liveView!!.addLiveViewSubscription( - LiveViewSubscription( - null, - listOf(endpointName), - portal.viewingArtifact, - LiveSourceLocation(portal.viewingArtifact.identifier, 0), //todo: fix - LiveViewConfig("TRACES", listOf("endpoint_traces")) - ) - ).onComplete { - if (it.succeeded()) { - val subscriptionId = it.result().subscriptionId!! - if (portal.configuration.config["subscriptionId"] != null) { - Instance.liveView!!.removeLiveViewSubscription( - portal.configuration.config["subscriptionId"].toString() - ) - } - portal.configuration.config["subscriptionId"] = subscriptionId - - sourceMark.addEventListener { - if (it.eventCode == SourceMarkEventCode.PORTAL_CLOSED) { - Instance.liveView!!.removeLiveViewSubscription(subscriptionId) - } - } - } else { - log.error("Failed to add live view subscription", it.cause()) - } - } - } - } - vertx.eventBus().consumer(RefreshLogs) { - val portal = it.body() - //pull from skywalking - launch(vertx.dispatcher()) { - pullLatestLogs(portal) - } - - //update subscriptions - launch(vertx.dispatcher()) { - val sourceMark = SourceMarker.getSourceMark( - portal.viewingArtifact, SourceMark.Type.GUIDE - ) ?: return@launch - val logPatterns = if (sourceMark is ClassSourceMark) { - sourceMark.sourceFileMarker.getSourceMarks().filterIsInstance() - .flatMap { - it.getUserData(SourceMarkKeys.LOGGER_DETECTOR)!! - .getOrFindLoggerStatements(it) - }.map { it.logPattern } - } else if (sourceMark is MethodSourceMark) { - sourceMark.getUserData(SourceMarkKeys.LOGGER_DETECTOR)!! - .getOrFindLoggerStatements(sourceMark).map { it.logPattern } - } else { - throw IllegalStateException("Unsupported source mark type") - } - - Instance.liveView!!.addLiveViewSubscription( - LiveViewSubscription( - null, - logPatterns, - portal.viewingArtifact, - LiveSourceLocation(portal.viewingArtifact.identifier, 0), //todo: fix - LiveViewConfig("LOGS", listOf("endpoint_logs")) - ) - ).onComplete { - if (it.succeeded()) { - val subscriptionId = it.result().subscriptionId!! - if (portal.configuration.config["subscriptionId"] != null) { - Instance.liveView!!.removeLiveViewSubscription( - portal.configuration.config["subscriptionId"].toString() - ) - } - portal.configuration.config["subscriptionId"] = subscriptionId - - sourceMark.addEventListener { - if (it.eventCode == SourceMarkEventCode.PORTAL_CLOSED) { - Instance.liveView!!.removeLiveViewSubscription(subscriptionId) - } - } - } else { - log.error("Failed to add live view subscription", it.cause()) - } - } - } - } - vertx.eventBus().consumer(QueryTraceStack) { handler -> - val traceId = handler.body() - launch(vertx.dispatcher()) { - handler.reply(EndpointTracesBridge.getTraceStack(traceId, vertx)) - } - } - vertx.eventBus().consumer(ClickedStackTraceElement) { handler -> - val message = handler.body() - val portalUuid = message.getString("portalUuid") - val portal = SourcePortal.getPortal(portalUuid)!! - if (!portal.configuration.external) vertx.eventBus().send(ClosePortal, portal) - - val element = Json.decodeValue( - message.getJsonObject("stackTraceElement").toString(), - LiveStackTraceElement::class.java - ) - log.info("Clicked stack trace element: $element") - - val project = ProjectManager.getInstance().openProjects[0] - ArtifactNavigator.navigateTo(project, element) - } - vertx.eventBus().consumer(CanNavigateToArtifact) { - val artifactQualifiedName = it.body() - val project = ProjectManager.getInstance().openProjects[0] - launch(vertx.dispatcher()) { - it.reply(ArtifactNavigator.canNavigateTo(project, artifactQualifiedName)) - } - } - vertx.eventBus().consumer(NavigateToArtifact) { msg -> - launch(vertx.dispatcher()) { - ArtifactNavigator.navigateTo(vertx, msg.body()) { - if (it.succeeded()) { - log.info("Navigated to artifact $it") - msg.reply(it.result()) - } else { - log.error("Failed to navigate to artifact", it.cause()) - msg.fail(500, it.cause().message) - } - } - } - } - } - - private suspend fun pullLatestTraces(portal: SourcePortal) { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - if (sourceMark != null && sourceMark is MethodSourceMark) { - val endpointId = sourceMark.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointId(sourceMark) - if (endpointId != null) { - val traceResult = EndpointTracesBridge.getTraces( - GetEndpointTraces( - artifactQualifiedName = portal.viewingArtifact, - endpointId = endpointId, - zonedDuration = ZonedDuration( - ZonedDateTime.now().minusHours(24), - ZonedDateTime.now(), - SkywalkingClient.DurationStep.MINUTE - ), - orderType = portal.tracesView.orderType, - pageSize = portal.tracesView.viewTraceAmount, - pageNumber = portal.tracesView.pageNumber - ), vertx - ) - vertx.eventBus().send(ArtifactTracesUpdated, traceResult) - - if (markerConfig.autoResolveEndpointNames) { - autoResolveEndpointNames(traceResult, portal) - } - } - } - } - - private suspend fun autoResolveEndpointNames(traceResult: TraceResult, portal: SourcePortal) { - //todo: only try to auto resolve endpoint names with dynamic ids - //todo: support multiple operationsNames/traceIds - traceResult.traces.forEach { - if (!portal.tracesView.resolvedEndpointNames.containsKey(it.traceIds[0])) { - val traceStack = EndpointTracesBridge.getTraceStack(it.traceIds[0], vertx) - val entrySpan: TraceSpan? = traceStack.traceSpans.firstOrNull { it.type == "Entry" } - if (entrySpan != null) { - val url = entrySpan.tags["url"] - val httpMethod = entrySpan.tags["http.method"] - if (url != null && httpMethod != null) { - try { - val updatedEndpointName = "$httpMethod:${URI(url).path}" - vertx.eventBus().send( - TraceSpanUpdated, entrySpan.copy( - endpointName = updatedEndpointName, - artifactQualifiedName = portal.viewingArtifact - ) - ) - } catch (e: URISyntaxException) { - log.warn("Failed to parse url $url") - } - } - } - } - } - } - - private suspend fun pullLatestLogs(portal: SourcePortal) { - if (log.isTraceEnabled) log.trace("Refreshing logs. Portal: {}", portal.portalUuid) - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - val logsResult = LogsBridge.queryLogs( - GetEndpointLogs( - endpointId = if (sourceMark is MethodSourceMark) { - sourceMark.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointId(sourceMark) - } else null, - zonedDuration = ZonedDuration( - ZonedDateTime.now().minusMinutes(15), //todo: method filtering in skywalking - ZonedDateTime.now(), - SkywalkingClient.DurationStep.MINUTE - ), - orderType = portal.logsView.orderType, - pageSize = portal.logsView.viewLogAmount * 25, //todo: method filtering in skywalking - pageNumber = portal.logsView.pageNumber - ), vertx - ) - if (logsResult.succeeded()) { - //todo: impl method filtering in skywalking - for ((content, logs) in logsResult.result().logs.groupBy { it.content }) { - SourceMarkSearch.findInheritedSourceMarks(content).forEach { - vertx.eventBus().send( - ArtifactLogUpdated, logsResult.result().copy( - artifactQualifiedName = it.artifactQualifiedName, - total = logs.size, - logs = logs, - ) - ) - } - } - } else { - val replyException = logsResult.cause() as ReplyException - if (replyException.failureCode() == 404) { - log.warn("Failed to fetch logs. Service(s) unavailable") - } else { - log.error("Failed to fetch logs", logsResult.cause()) - } - } - } - - private suspend fun refreshOverview(fileMarker: SourceFileMarker, portal: SourcePortal) { - val endpointMarks = fileMarker.getSourceMarks().filterIsInstance().filter { - it.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointId(it) != null - } - - val swVersion = GeneralBridge.getVersion(SourceMarkerPlugin.vertx) - val fetchMetricTypes = if (swVersion.startsWith("9")) { - listOf("endpoint_cpm", "endpoint_resp_time", "endpoint_sla") - } else { - listOf("endpoint_cpm", "endpoint_avg", "endpoint_sla") - } - val requestDuration = ZonedDuration( - ZonedDateTime.now().minusMinutes(portal.overviewView.timeFrame.minutes.toLong()), - ZonedDateTime.now(), - SkywalkingClient.DurationStep.MINUTE - ) - val endpointMetricResults = mutableListOf() - endpointMarks.forEach { - val metricsRequest = GetEndpointMetrics( - fetchMetricTypes, - it.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointId(it)!!, - requestDuration - ) - val metrics = EndpointMetricsBridge.getMetrics(metricsRequest, vertx) - val endpointName = it.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointName(it)!! - - val summarizedMetrics = mutableListOf() - for (i in metrics.indices) { - val avg = metrics[i].values.average() - val metricType = MetricType.realValueOf(fetchMetricTypes[i]) - summarizedMetrics.add(ArtifactSummarizedMetrics(metricType, avg)) - } - - endpointMetricResults.add( - ArtifactSummarizedResult( - it.artifactQualifiedName.copy(operationName = endpointName), - summarizedMetrics, - EndpointType.HTTP - ) - ) - } - - vertx.eventBus().send( - UpdateEndpoints, - JsonObject( - Json.encode( - EndpointResult( - portal.overviewView.timeFrame, - start = Instant.fromEpochMilliseconds(requestDuration.start.toInstant().toEpochMilli()), - stop = Instant.fromEpochMilliseconds(requestDuration.stop.toInstant().toEpochMilli()), - step = requestDuration.step.name, - endpointMetricResults - ) - ) - ) - ) - } - - private suspend fun pullLatestActivity(portal: SourcePortal) { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - if (sourceMark != null && sourceMark is MethodSourceMark) { - val endpointId = sourceMark.getUserData(ENDPOINT_DETECTOR)!!.getOrFindEndpointId(sourceMark) - if (endpointId != null) { - pullLatestActivity(portal, endpointId) - } - } - } - - private suspend fun pullLatestActivity(portal: SourcePortal, endpointId: String) { - val swVersion = GeneralBridge.getVersion(SourceMarkerPlugin.vertx) - val fetchMetricTypes = if (swVersion.startsWith("9")) { - listOf("endpoint_cpm", "endpoint_resp_time", "endpoint_sla") - } else { - listOf("endpoint_cpm", "endpoint_avg", "endpoint_sla") - } - val endTime = ZonedDateTime.now().plusMinutes(1).truncatedTo(ChronoUnit.MINUTES) - val startTime = endTime.minusMinutes(portal.activityView.timeFrame.minutes.toLong()) - val metricsRequest = GetEndpointMetrics( - fetchMetricTypes, - endpointId, - ZonedDuration(startTime, endTime, SkywalkingClient.DurationStep.MINUTE) - ) - val metrics = EndpointMetricsBridge.getMetrics(metricsRequest, vertx) - val metricResult = toProtocol( - portal.viewingArtifact, - portal.activityView.timeFrame, - portal.activityView.activeChartMetric, - metricsRequest, - metrics - ) - - val finalArtifactMetrics = metricResult.artifactMetrics.toMutableList() - vertx.eventBus().send(ArtifactMetricsUpdated, metricResult.copy(artifactMetrics = finalArtifactMetrics)) - } - - private fun openPortal(portal: SourcePortal) { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - if (sourceMark != null) { - configureDisplayedPortal(portal) - ApplicationManager.getApplication().invokeLater(sourceMark::displayPopup) - } - } - - private fun configureDisplayedPortal(portal: SourcePortal) { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - if (sourceMark != null) { - val jcefComponent = sourceMark.sourceMarkComponent as SourceMarkJcefComponent - if (portal != lastDisplayedInternalPortal) { - portal.configuration.darkMode = UIManager.getLookAndFeel() !is IntelliJLaf - - val externalEndpoint = sourceMark.getUserData(ENDPOINT_DETECTOR)?.isExternalEndpoint(sourceMark) == true - if (externalEndpoint) { - portal.configuration.config["visibleActivity"] = true - portal.configuration.config["visibleTraces"] = true - portal.configuration.config["visibleLogs"] = true //todo: can hide based on if there is logs - } else { - //non-endpoint artifact; hide activity/traces till manually shown - portal.configuration.config["visibleActivity"] = false - portal.configuration.config["visibleTraces"] = portal.tracesView.innerTraceStack - - //default to logs if method - if (sourceMark is MethodSourceMark && !(portal.configuration.config["visibleTraces"] as Boolean)) { - portal.configuration.config["currentPage"] = PageType.LOGS - } - - //hide overview if class and no child endpoints and default to logs - if (sourceMark is ClassSourceMark) { - val hasChildEndpoints = sourceMark.sourceFileMarker.getSourceMarks().firstOrNull { - it.getUserData(ENDPOINT_DETECTOR)?.getEndpointId(it) != null - } != null - portal.configuration.config["visibleOverview"] = hasChildEndpoints - if (!hasChildEndpoints) { - portal.configuration.config["currentPage"] = PageType.LOGS - } - } - } - - val port = vertx.sharedData().getLocalMap("portal")["http.port"]!! - val host = "http://localhost:$port" - val currentUrl = "$host/?portalUuid=${portal.portalUuid}" - - if (lastDisplayedInternalPortal == null) { - jcefComponent.configuration.initialUrl = currentUrl - } else { - jcefComponent.getBrowser().cefBrowser.executeJavaScript( - "window.location.href = '$currentUrl';", currentUrl, 0 - ) - } - lastDisplayedInternalPortal = portal - } - } - } - - private fun consumeTracesViewEvent(event: LiveViewEvent) { - val portal = SourcePortal.getPortals().find { - it.configuration.config["subscriptionId"] == event.subscriptionId - } ?: return - - val rawMetrics = JsonObject(event.metricsData) - val trace = Json.decodeValue(rawMetrics.getJsonObject("trace").toString(), Trace::class.java) - val traceResult = TraceResult( - portal.viewingArtifact, - null, - TraceOrderType.LATEST_TRACES, - trace.start, - trace.start.toJavaInstant().minusMillis(trace.duration.toLong()).toKotlinInstant(), - "minute", - listOf(trace), - Int.MAX_VALUE - ) - vertx.eventBus().send(ArtifactTracesUpdated, traceResult) - - if (markerConfig.autoResolveEndpointNames) { - //S++ adds trace meta to avoid additional query for auto-resolve endpoints - val url = trace.meta["url"] - val httpMethod = trace.meta["http.method"] - val entrySpanJson = trace.meta["entrySpan"] - if (url != null && httpMethod != null && entrySpanJson != null) { - val updatedEndpointName = "$httpMethod:${URI(url).path}" - val entrySpan = Json.decodeValue(entrySpanJson, TraceSpan::class.java) - vertx.eventBus().send( - TraceSpanUpdated, entrySpan.copy( - endpointName = updatedEndpointName, - artifactQualifiedName = event.artifactQualifiedName - ) - ) - } else { - launch(vertx.dispatcher()) { - autoResolveEndpointNames(traceResult, portal) - } - } - } - } - - private suspend fun consumeLogsViewEvent(event: LiveViewEvent) { - val rawMetrics = JsonObject(event.metricsData) - val logData = Json.decodeValue(rawMetrics.getJsonObject("log").toString(), Log::class.java) - val logsResult = LogResult( - event.artifactQualifiedName, - LogOrderType.NEWEST_LOGS, - logData.timestamp, - listOf(logData), - Int.MAX_VALUE - ) - for ((content, logs) in logsResult.logs.groupBy { it.content }) { - SourceMarkSearch.findInheritedSourceMarks(content).forEach { - vertx.eventBus().send( - ArtifactLogUpdated, logsResult.copy( - artifactQualifiedName = it.artifactQualifiedName, - total = logs.size, - logs = logs, - ) - ) - } - } - } - - private fun consumeActivityViewEvent(event: LiveViewEvent) { - val portal = SourcePortal.getPortals().find { - it.configuration.config["subscriptionId"] == event.subscriptionId - } ?: return - - val artifactMetrics = toArtifactMetrics(event) - val metricResult = ArtifactMetricResult( - portal.viewingArtifact, - QueryTimeFrame.valueOf(1), - portal.activityView.activeChartMetric, //todo: assumes activity view - formatter.parse(event.timeBucket, java.time.Instant::from).toKotlinInstant(), - formatter.parse(event.timeBucket, java.time.Instant::from).plusSeconds(60).toKotlinInstant(), - "minute", - artifactMetrics, - true - ) - vertx.eventBus().send(ArtifactMetricsUpdated, metricResult) - } - - private fun toArtifactMetrics(event: LiveViewEvent): List { - val rawMetrics = mutableListOf() - if (event.viewConfig.viewMetrics.size > 1) { - val multiMetrics = JsonArray(event.metricsData) - for (i in 0 until multiMetrics.size()) { - val metricsName = multiMetrics.getJsonObject(i).getJsonObject("meta").getString("metricsName") - val value = when (MetricType.realValueOf(metricsName)) { - MetricType.Throughput_Average -> multiMetrics.getJsonObject(i) - .getInteger("value") - MetricType.ResponseTime_Average -> multiMetrics.getJsonObject(i) - .getInteger("value") - MetricType.ServiceLevelAgreement_Average -> multiMetrics.getJsonObject(i) - .getInteger("percentage") - else -> TODO(metricsName) - } - rawMetrics.add(value) - } - } else { - val value = when (val metricType = MetricType.realValueOf(event.viewConfig.viewMetrics.first())) { - MetricType.Throughput_Average -> JsonObject(event.metricsData).getInteger("value") - MetricType.ResponseTime_Average -> JsonObject(event.metricsData).getInteger("value") - MetricType.ServiceLevelAgreement_Average -> JsonObject(event.metricsData).getInteger("percentage") - else -> TODO(metricType.name) - } - rawMetrics.add(value) - } - val artifactMetrics = rawMetrics.mapIndexed { i: Int, it: Int -> - ArtifactMetrics(MetricType.realValueOf(event.viewConfig.viewMetrics[i]), listOf(it.toDouble())) - } - return artifactMetrics - } - - private fun closePortal(portal: SourcePortal) { - val sourceMark = SourceMarker.getSourceMark(portal.viewingArtifact, SourceMark.Type.GUIDE) - if (sourceMark != null) { - ApplicationManager.getApplication().invokeLater(sourceMark::closePopup) - } - } -} diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/service/instrument/breakpoint/ui/BreakpointHitWindow.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/service/instrument/breakpoint/ui/BreakpointHitWindow.kt index a0bd1ce08..28d36fc1b 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/service/instrument/breakpoint/ui/BreakpointHitWindow.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/service/instrument/breakpoint/ui/BreakpointHitWindow.kt @@ -20,7 +20,6 @@ package spp.jetbrains.sourcemarker.service.instrument.breakpoint.ui import com.intellij.execution.ui.RunnerLayoutUi import com.intellij.execution.ui.layout.PlaceInGrid import com.intellij.icons.AllIcons -import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.Disposable import com.intellij.openapi.application.ReadAction import com.intellij.openapi.project.Project @@ -29,12 +28,11 @@ import com.intellij.openapi.util.Disposer import com.intellij.ui.content.Content import com.intellij.util.concurrency.AppExecutorUtil import com.intellij.xdebugger.impl.ui.ExecutionPointHighlighter -import io.vertx.core.json.Json +import spp.jetbrains.sourcemarker.SourceMarkerPlugin import spp.jetbrains.sourcemarker.service.instrument.breakpoint.DebugStackFrameListener import spp.jetbrains.sourcemarker.service.instrument.breakpoint.ExecutionPointManager import spp.jetbrains.sourcemarker.service.instrument.breakpoint.LiveBreakpointConstants import spp.jetbrains.sourcemarker.service.instrument.breakpoint.StackFrameManager -import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig import spp.protocol.artifact.exception.LiveStackTrace import spp.protocol.artifact.exception.LiveStackTraceElement import java.util.concurrent.CopyOnWriteArrayList @@ -77,10 +75,7 @@ class BreakpointHitWindow( } private fun addFramesTab() { - val projectSettings = PropertiesComponent.getInstance(ProjectManager.getInstance().openProjects[0]) - val config = Json.decodeValue( - projectSettings.getValue("sourcemarker_plugin_config"), SourceMarkerConfig::class.java - ) + val config = SourceMarkerPlugin.getConfig(ProjectManager.getInstance().openProjects[0]) val framesTab = FramesTab(this, config) val content = layoutUi.createContent( LiveBreakpointConstants.LIVE_RECORDER_STACK_FRAMES, framesTab.component, "Frames", diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/PortalConfig.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/PortalConfig.kt new file mode 100644 index 000000000..83245edcf --- /dev/null +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/PortalConfig.kt @@ -0,0 +1,22 @@ +/* + * Source++, the open-source live coding platform. + * Copyright (C) 2022 CodeBrig, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package spp.jetbrains.sourcemarker.settings + +data class PortalConfig( + val zoomLevel: Double = 1.0 +) diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/SourceMarkerConfig.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/SourceMarkerConfig.kt index 1177bafac..6f964f167 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/SourceMarkerConfig.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/settings/SourceMarkerConfig.kt @@ -34,8 +34,9 @@ data class SourceMarkerConfig( var serviceToken: String? = null, var verifyHost: Boolean = true, val serviceName: String? = null, - var autoDisplayEndpointQuickStats: Boolean = true, - val override: Boolean = false + val override: Boolean = false, + val portalConfig: PortalConfig = PortalConfig(), + val commandConfig: Map> = emptyMap(), ) { companion object { const val DEFAULT_SERVICE_PORT = 12800 diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/LiveStatusManager.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/LiveStatusManager.kt index b953b4c89..f67ded78b 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/LiveStatusManager.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/LiveStatusManager.kt @@ -17,7 +17,6 @@ */ package spp.jetbrains.sourcemarker.status -import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager @@ -38,6 +37,7 @@ import spp.jetbrains.marker.source.mark.api.event.SourceMarkEvent import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventListener import spp.jetbrains.marker.source.mark.inlay.InlayMark +import spp.jetbrains.sourcemarker.SourceMarkerPlugin import spp.jetbrains.sourcemarker.SourceMarkerPlugin.vertx import spp.jetbrains.sourcemarker.icons.SourceMarkerIcons.LIVE_METER_COUNT_ICON import spp.jetbrains.sourcemarker.icons.SourceMarkerIcons.LIVE_METER_GAUGE_ICON @@ -49,7 +49,6 @@ import spp.jetbrains.sourcemarker.mark.SourceMarkKeys.VIEW_EVENT_LISTENERS import spp.jetbrains.sourcemarker.mark.SourceMarkKeys.VIEW_SUBSCRIPTION_ID import spp.jetbrains.sourcemarker.service.InstrumentEventListener import spp.jetbrains.sourcemarker.service.ViewEventListener -import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig import spp.jetbrains.sourcemarker.status.util.CircularList import spp.protocol.SourceServices import spp.protocol.SourceServices.Provide.toLiveViewSubscriberAddress @@ -133,10 +132,7 @@ object LiveStatusManager : SourceMarkEventListener { val wrapperPanel = JPanel() wrapperPanel.layout = BorderLayout() - val config = Json.decodeValue( - PropertiesComponent.getInstance(editor.project!!).getValue("sourcemarker_plugin_config"), - SourceMarkerConfig::class.java - ) + val config = SourceMarkerPlugin.getConfig(editor.project!!) val statusBar = BreakpointStatusBar( LiveSourceLocation( namingService.getQualifiedClassNames(fileMarker.psiFile)[0].identifier, lineNumber, @@ -179,10 +175,7 @@ object LiveStatusManager : SourceMarkEventListener { val wrapperPanel = JPanel() wrapperPanel.layout = BorderLayout() - val config = Json.decodeValue( - PropertiesComponent.getInstance(editor.project!!).getValue("sourcemarker_plugin_config"), - SourceMarkerConfig::class.java - ) + val config = SourceMarkerPlugin.getConfig(editor.project!!) val statusBar = LogStatusBar( LiveSourceLocation( namingService.getQualifiedClassNames(fileMarker.psiFile)[0].identifier, @@ -275,10 +268,7 @@ object LiveStatusManager : SourceMarkEventListener { val wrapperPanel = JPanel() wrapperPanel.layout = BorderLayout() - val config = Json.decodeValue( - PropertiesComponent.getInstance(editor.project!!).getValue("sourcemarker_plugin_config"), - SourceMarkerConfig::class.java - ) + val config = SourceMarkerPlugin.getConfig(editor.project!!) val statusBar = MeterStatusBar( LiveSourceLocation( namingService.getQualifiedClassNames(fileMarker.psiFile)[0].identifier, lineNumber, @@ -316,10 +306,7 @@ object LiveStatusManager : SourceMarkEventListener { val wrapperPanel = JPanel() wrapperPanel.layout = BorderLayout() - val config = Json.decodeValue( - PropertiesComponent.getInstance(editor.project!!).getValue("sourcemarker_plugin_config"), - SourceMarkerConfig::class.java - ) + val config = SourceMarkerPlugin.getConfig(editor.project!!) val statusBar = SpanStatusBar( LiveSourceLocation( inlayMark.artifactQualifiedName.identifier.substringBefore("#"), lineNumber, diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutoCompleteCellRenderer.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutoCompleteCellRenderer.kt index 84d481dd2..f9a4f3e44 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutoCompleteCellRenderer.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutoCompleteCellRenderer.kt @@ -18,8 +18,6 @@ package spp.jetbrains.sourcemarker.status.util import spp.jetbrains.sourcemarker.PluginUI.BGND_FOCUS_COLOR -import spp.jetbrains.sourcemarker.command.AutocompleteFieldRow -import spp.jetbrains.sourcemarker.command.LiveControlCommand import spp.jetbrains.sourcemarker.element.AutocompleteRow import spp.protocol.artifact.ArtifactNameUtils.getShortFunctionSignature import spp.protocol.artifact.ArtifactQualifiedName @@ -44,7 +42,7 @@ class AutoCompleteCellRenderer(private val artifactQualifiedName: ArtifactQualif val entry = value as AutocompleteFieldRow val row = AutocompleteRow() row.setCommandName(entry.getText()) - row.setCommandIcon(entry.getIcon()) + row.setCommandIcon(entry.getUnselectedIcon()) if (entry.getDescription() != null) { row.setDescription( entry.getDescription()!! @@ -54,10 +52,8 @@ class AutoCompleteCellRenderer(private val artifactQualifiedName: ArtifactQualif } if (isSelected) { - row.background = BGND_FOCUS_COLOR; - if (entry is LiveControlCommand) { - row.setCommandIcon(entry.selectedIcon) - } + row.background = BGND_FOCUS_COLOR + row.setCommandIcon(entry.getSelectedIcon()) } return row } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteField.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteField.kt index 8bf6f71ef..53015ff2f 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteField.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteField.kt @@ -23,7 +23,6 @@ import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import spp.jetbrains.sourcemarker.PluginIcons import spp.jetbrains.sourcemarker.PluginUI.* -import spp.jetbrains.sourcemarker.command.AutocompleteFieldRow import spp.jetbrains.sourcemarker.service.instrument.log.VariableParser import spp.protocol.artifact.ArtifactQualifiedName import java.awt.* @@ -43,10 +42,10 @@ import javax.swing.text.* * @author [Brandon Fergerson](mailto:bfergerson@apache.org) */ @Suppress("MagicNumber") -class AutocompleteField( +class AutocompleteField( var placeHolderText: String?, - private val allLookup: List, - private val lookup: Function>? = null, + private val allLookup: List, + private val lookup: Function>? = null, internal val artifactQualifiedName: ArtifactQualifiedName, private val replaceCommandOnTab: Boolean = false, private val autocompleteOnEnter: Boolean = true, @@ -67,8 +66,12 @@ class AutocompleteField( var canShowSaveButton = true var varPattern: Pattern = Pattern.compile("") var includeCurlyPattern: Boolean = false + var actualText: String = "" + private set + var ready: Boolean = false + private set - val matchAndApplyStyle = { m: Matcher -> + private val matchAndApplyStyle = { m: Matcher -> if (varPattern.pattern().isNotEmpty()) { while (m.find()) { val variable: String = m.group(1) @@ -236,7 +239,9 @@ class AutocompleteField( override fun keyPressed(e: KeyEvent) { if (e.keyCode == KeyEvent.VK_SPACE && hasControlHeld) { results.clear() - results.addAll(allLookup) + results.addAll(allLookup + .filter { it.getText().toLowerCase().contains(text) } + .sortedBy { it.getText() }) model.updateView() list.visibleRowCount = results.size.coerceAtMost(10) if (results.size > 0) { @@ -260,20 +265,50 @@ class AutocompleteField( if (index != -1 && list.model.size > index + 1) { list.selectedIndex = index + 1 } - } else if (e.keyCode == KeyEvent.VK_TAB || e.keyCode == KeyEvent.VK_ENTER) { - if (e.keyCode == KeyEvent.VK_ENTER && !autocompleteOnEnter) { + } else if (e.keyCode == KeyEvent.VK_TAB) { + if (text.isBlank() || list.selectedValue == null || !replaceCommandOnTab) return + val autocompleteRow = list.selectedValue + if (autocompleteRow is LiveCommandFieldRow && autocompleteRow.liveCommand.params.isNotEmpty()) { + if (getText().toLowerCase().startsWith(autocompleteRow.liveCommand.name.toLowerCase() + " ")) { + return //do nothing + } + setText(autocompleteRow.getText() + " ") + } else { + setText(autocompleteRow.getText()) + } + caretPosition = getText().length + } else if (e.keyCode == KeyEvent.VK_ENTER) { + if (!autocompleteOnEnter) { + ready = true + actualText = text hideAutocompletePopup() return } - - val text = list.selectedValue - if (text != null) { - if (replaceCommandOnTab) { - setText(text.getText()) - caretPosition = getText().length + actualText = text + + val text = if (isPopupVisible()) list.selectedValue else null + if (text is LiveCommandFieldRow) { + val liveCommand = text.liveCommand + if (liveCommand.params.isNotEmpty()) { + if (!getText().toLowerCase().startsWith(liveCommand.name.toLowerCase() + " ")) { + setText(text.getText() + " ") + caretPosition = getText().length + } else { + val params = substringAfterIgnoreCase(getText(), liveCommand.name) + .split(" ").filter { it.isNotEmpty() } + if (params.size < liveCommand.params.size) { + setText(getText().trimEnd() + " ") + caretPosition = getText().length + } else { + ready = true + } + } } else { - addAutoCompleteToInput(text) + ready = true } + } else if (text != null) { + addAutoCompleteToInput(text) + ready = true } } } @@ -350,6 +385,28 @@ class AutocompleteField( pG.getFontMetrics().maxAscent + insets.top ) } + + //paint live command argument hints + if (list.selectedValue is LiveCommandFieldRow) { + val liveCommand = (list.selectedValue as LiveCommandFieldRow).liveCommand + if (!text.toLowerCase().startsWith(liveCommand.name.toLowerCase())) return + val params = substringAfterIgnoreCase(text, liveCommand.name).split(" ").filter { it.isNotEmpty() } + + var textOffset = 0 + for ((index, param) in liveCommand.params.withIndex()) { + if (index < params.size) continue + + val paramTextX = insets.left + pG.getFontMetrics().stringWidth(text.trimEnd() + " ") + textOffset + g.color = Color( + UIUtil.getTextFieldForeground().red, + UIUtil.getTextFieldForeground().green, + UIUtil.getTextFieldForeground().blue, + 75 + ) + g.drawString("[$param]", paramTextX, pG.getFontMetrics().maxAscent + insets.top) + textOffset += pG.getFontMetrics().stringWidth("[$param] ") + } + } } override fun mouseDragged(e: MouseEvent) { @@ -414,4 +471,12 @@ class AutocompleteField( fun interface SaveListener { fun onSave() } + + private fun substringAfterIgnoreCase(str: String, search: String): String { + val index = str.indexOf(search, ignoreCase = true) + if (index == -1) { + return str + } + return str.substring(index + search.length) + } } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/AutocompleteFieldRow.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteFieldRow.kt similarity index 89% rename from plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/AutocompleteFieldRow.kt rename to plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteFieldRow.kt index 5761c0aeb..95e73a8da 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/command/AutocompleteFieldRow.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/AutocompleteFieldRow.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package spp.jetbrains.sourcemarker.command +package spp.jetbrains.sourcemarker.status.util import javax.swing.Icon @@ -28,5 +28,6 @@ import javax.swing.Icon interface AutocompleteFieldRow { fun getText(): String fun getDescription(): String? - fun getIcon(): Icon? + fun getSelectedIcon(): Icon? + fun getUnselectedIcon(): Icon? } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/CircularList.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/CircularList.kt index 9bcf740a6..cb729ae91 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/CircularList.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/CircularList.kt @@ -1,3 +1,20 @@ +/* + * Source++, the open-source live coding platform. + * Copyright (C) 2022 CodeBrig, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package spp.jetbrains.sourcemarker.status.util import java.util.ArrayList @@ -17,4 +34,4 @@ class CircularList(val maxCapacity: Int) : ArrayList() { } super.add(index, element) } -} \ No newline at end of file +} diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/ControlBarCellRenderer.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/ControlBarCellRenderer.kt index 2adf98b43..9c78c1ed3 100644 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/ControlBarCellRenderer.kt +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/ControlBarCellRenderer.kt @@ -18,9 +18,7 @@ package spp.jetbrains.sourcemarker.status.util import spp.jetbrains.marker.source.mark.api.ExpressionSourceMark -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode.UPDATE_PORTAL_CONFIG import spp.jetbrains.sourcemarker.PluginUI.BGND_FOCUS_COLOR -import spp.jetbrains.sourcemarker.command.LiveControlCommand import spp.jetbrains.sourcemarker.element.LiveControlBarRow import spp.protocol.artifact.ArtifactNameUtils.getShortFunctionSignature import java.awt.Component @@ -34,7 +32,8 @@ import javax.swing.JList * @author [Brandon Fergerson](mailto:bfergerson@apache.org) */ class ControlBarCellRenderer( - private val autocompleteField: AutocompleteField, val sourceMark: ExpressionSourceMark + private val autocompleteField: AutocompleteField, + val sourceMark: ExpressionSourceMark ) : DefaultListCellRenderer() { init { isOpaque = false @@ -43,30 +42,27 @@ class ControlBarCellRenderer( override fun getListCellRendererComponent( list: JList<*>, value: Any, index: Int, isSelected: Boolean, cellHasFocus: Boolean ): Component { - val entry = value as LiveControlCommand + val rowValue = value as LiveCommandFieldRow + val entry = rowValue.liveCommand + val row = LiveControlBarRow() - row.setCommandName(entry.getText(), autocompleteField.text) - row.setCommandIcon(entry.getIcon()) - if (entry.getDescription() != null) { - row.setDescription( - entry.getDescription()!! - .replace( - "*lineNumber*", - autocompleteField.artifactQualifiedName.lineNumber.toString() - ) - .replace( - "*methodName*", - getShortFunctionSignature(autocompleteField.artifactQualifiedName.identifier) - ) - ) - } + row.setCommandName(entry.name, autocompleteField.text) + row.setCommandIcon(rowValue.getUnselectedIcon()) + row.setDescription( + entry.description + .replace( + "*lineNumber*", + autocompleteField.artifactQualifiedName.lineNumber.toString() + ) + .replace( + "*methodName*", + getShortFunctionSignature(autocompleteField.artifactQualifiedName.identifier) + ) + ) if (isSelected) { row.background = BGND_FOCUS_COLOR - row.setCommandIcon(entry.selectedIcon) - if (entry.name.startsWith("VIEW_")) { - sourceMark.getParentSourceMark()!!.triggerEvent(UPDATE_PORTAL_CONFIG, listOf(entry)) - } + row.setCommandIcon(rowValue.getSelectedIcon()) } return row } diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/LiveCommandFieldRow.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/LiveCommandFieldRow.kt new file mode 100644 index 000000000..ba8da60c8 --- /dev/null +++ b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/status/util/LiveCommandFieldRow.kt @@ -0,0 +1,61 @@ +/* + * Source++, the open-source live coding platform. + * Copyright (C) 2022 CodeBrig, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package spp.jetbrains.sourcemarker.status.util + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.IconLoader +import com.intellij.openapi.util.Key +import spp.command.LiveCommand +import java.io.File +import javax.swing.Icon + +class LiveCommandFieldRow(val liveCommand: LiveCommand, val project: Project) : AutocompleteFieldRow { + + private val basePath = project.basePath?.let { File(it, ".spp${File.separatorChar}commands").absolutePath } ?: "" + private val internalBasePath = Key.findKeyByName("SPP_COMMANDS_LOCATION") + ?.let { key -> project.getUserData(key).toString() } ?: "" + + override fun getText(): String = liveCommand.name + override fun getDescription(): String = liveCommand.description + + override fun getSelectedIcon(): Icon? { + return liveCommand.selectedIcon?.let { + val iconPath = if (File(internalBasePath, it).exists()) { + internalBasePath + File.separator + it + } else if (File(basePath, it).exists()) { + basePath + File.separator + it + } else { + it + } + IconLoader.findIcon(File(iconPath).toURL()) + } + } + + override fun getUnselectedIcon(): Icon? { + return liveCommand.unselectedIcon?.let { + val iconPath = if (File(internalBasePath, it).exists()) { + internalBasePath + File.separator + it + } else if (File(basePath, it).exists()) { + basePath + File.separator + it + } else { + it + } + IconLoader.findIcon(File(iconPath).toURL()) + } + } +} diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/ActivityQuickStatsIndicator.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/ActivityQuickStatsIndicator.kt deleted file mode 100644 index 6c8e4de34..000000000 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/ActivityQuickStatsIndicator.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Source++, the open-source live coding platform. - * Copyright (C) 2022 CodeBrig, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package spp.jetbrains.sourcemarker.view - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.ui.JBColor -import io.vertx.core.json.Json -import io.vertx.core.json.JsonArray -import io.vertx.core.json.JsonObject -import io.vertx.kotlin.coroutines.dispatcher -import kotlinx.coroutines.runBlocking -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import spp.jetbrains.marker.SourceMarker -import spp.jetbrains.marker.jvm.psi.EndpointDetector -import spp.jetbrains.marker.source.mark.api.MethodSourceMark -import spp.jetbrains.marker.source.mark.api.SourceMark -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEvent -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode.CUSTOM_EVENT -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventListener -import spp.jetbrains.marker.source.mark.api.key.SourceKey -import spp.jetbrains.marker.source.mark.guide.GuideMark -import spp.jetbrains.marker.source.mark.inlay.config.InlayMarkVirtualText -import spp.jetbrains.monitor.skywalking.SkywalkingClient -import spp.jetbrains.monitor.skywalking.bridge.EndpointMetricsBridge -import spp.jetbrains.monitor.skywalking.bridge.GeneralBridge -import spp.jetbrains.monitor.skywalking.model.GetEndpointMetrics -import spp.jetbrains.monitor.skywalking.model.ZonedDuration -import spp.jetbrains.monitor.skywalking.toProtocol -import spp.jetbrains.sourcemarker.PluginBundle -import spp.jetbrains.sourcemarker.PluginBundle.message -import spp.jetbrains.sourcemarker.PluginUI -import spp.jetbrains.sourcemarker.SourceMarkerPlugin.vertx -import spp.jetbrains.sourcemarker.command.LiveControlCommand.HIDE_QUICK_STATS -import spp.jetbrains.sourcemarker.command.LiveControlCommand.SHOW_QUICK_STATS -import spp.jetbrains.sourcemarker.mark.SourceMarkSearch -import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig -import spp.protocol.SourceServices -import spp.protocol.SourceServices.Provide.toLiveViewSubscriberAddress -import spp.protocol.artifact.QueryTimeFrame -import spp.protocol.artifact.metrics.ArtifactMetricResult -import spp.protocol.artifact.metrics.MetricType -import spp.protocol.instrument.LiveSourceLocation -import spp.protocol.utils.fromPerSecondToPrettyFrequency -import spp.protocol.view.LiveViewConfig -import spp.protocol.view.LiveViewEvent -import spp.protocol.view.LiveViewSubscription -import java.awt.Color -import java.time.ZonedDateTime -import java.time.temporal.ChronoUnit - -/** - * Adds activity quick stats as inlay marks above recognized endpoint methods. - * Uses a two-minute delay to ensure metrics have been fully collected. - */ -class ActivityQuickStatsIndicator(val config: SourceMarkerConfig) : SourceMarkEventListener { - - companion object { - private val log: Logger = LoggerFactory.getLogger(ActivityQuickStatsIndicator::class.java) - private val inlayForegroundColor = JBColor(Color.decode("#3e464a"), Color.decode("#87939a")) - val SHOWING_QUICK_STATS = SourceKey("SHOWING_QUICK_STATS") - } - - override fun handleEvent(event: SourceMarkEvent) { - if (config.autoDisplayEndpointQuickStats && event.eventCode == SourceMarkEventCode.MARK_USER_DATA_UPDATED) { - if (event.sourceMark is GuideMark && event.sourceMark.getUserData(EndpointDetector.ENDPOINT_ID) != null) { - val existingMarks = SourceMarkSearch.findSourceMarks(event.sourceMark.artifactQualifiedName) - if (existingMarks.find { it.getUserData(SHOWING_QUICK_STATS) == true } != null) return - - displayQuickStatsInlay(event.sourceMark) - } - } else if (event.eventCode == CUSTOM_EVENT && event.params.first() == SHOW_QUICK_STATS) { - if (event.sourceMark.getUserData(EndpointDetector.ENDPOINT_ID) != null) { - displayQuickStatsInlay(event.sourceMark) - } - } else if (event.eventCode == CUSTOM_EVENT && event.params.first() == HIDE_QUICK_STATS) { - val existingQuickStats = event.sourceMark.sourceFileMarker.getSourceMarks().find { - it.artifactQualifiedName == event.sourceMark.artifactQualifiedName - && it.getUserData(SHOWING_QUICK_STATS) == true - } - existingQuickStats?.dispose() - } - } - - private fun displayQuickStatsInlay(sourceMark: SourceMark) = ApplicationManager.getApplication().runReadAction { - log.info("Displaying quick stats inlay for {}", sourceMark.artifactQualifiedName.identifier) - val swVersion = runBlocking(vertx.dispatcher()) { GeneralBridge.getVersion(vertx) } - val listenMetrics = if (swVersion.startsWith("9")) { - listOf("endpoint_cpm", "endpoint_resp_time", "endpoint_sla") - } else { - listOf("endpoint_cpm", "endpoint_avg", "endpoint_sla") - } - val endTime = ZonedDateTime.now().minusMinutes(1).truncatedTo(ChronoUnit.MINUTES) //exclusive - val startTime = endTime.minusMinutes(2) - val metricsRequest = GetEndpointMetrics( - listenMetrics, - sourceMark.getUserData(EndpointDetector.ENDPOINT_ID)!!, - ZonedDuration(startTime, endTime, SkywalkingClient.DurationStep.MINUTE) - ) - - val currentMetrics = runBlocking(vertx.dispatcher()) { EndpointMetricsBridge.getMetrics(metricsRequest, vertx) } - val metricResult = toProtocol( - sourceMark.artifactQualifiedName, - QueryTimeFrame.LAST_15_MINUTES, //todo: don't need - MetricType.ResponseTime_Average, //todo: dont need - metricsRequest, - currentMetrics - ) - - val inlay = SourceMarker.creationService.createMethodInlayMark( - sourceMark.sourceFileMarker, - (sourceMark as MethodSourceMark).getPsiElement().nameIdentifier!!, - false - ) - inlay.putUserData(SHOWING_QUICK_STATS, true) - inlay.configuration.virtualText = InlayMarkVirtualText(inlay, formatMetricResult(metricResult)) - inlay.configuration.virtualText!!.textAttributes.foregroundColor = inlayForegroundColor - if (PluginBundle.LOCALE.language == "zh") { - inlay.configuration.virtualText!!.font = PluginUI.MICROSOFT_YAHEI_PLAIN_14 - inlay.configuration.virtualText!!.xOffset = 15 - } - inlay.configuration.activateOnMouseClick = false - inlay.apply(true) - - SourceServices.Instance.liveView!!.addLiveViewSubscription( - LiveViewSubscription( - null, - listOf(sourceMark.getUserData(EndpointDetector.ENDPOINT_NAME)!!), - sourceMark.artifactQualifiedName, - LiveSourceLocation(sourceMark.artifactQualifiedName.identifier, 0), //todo: don't need - LiveViewConfig("ACTIVITY", listenMetrics, -1) - ) - ).onComplete { - if (it.succeeded()) { - val subscriptionId = it.result().subscriptionId!! - val previousMetrics = mutableMapOf() - vertx.eventBus().consumer(toLiveViewSubscriberAddress(subscriptionId)) { - val viewEvent = Json.decodeValue(it.body().toString(), LiveViewEvent::class.java) - log.trace("Received updated quick stats for {}", sourceMark.artifactQualifiedName.identifier) - consumeLiveEvent(viewEvent, previousMetrics) - - val twoMinAgoValue = previousMetrics[viewEvent.timeBucket.toLong() - 2] - if (twoMinAgoValue != null) { - inlay.configuration.virtualText!!.updateVirtualText(twoMinAgoValue) - } - } - inlay.addEventListener { - if (it.eventCode == SourceMarkEventCode.MARK_REMOVED) { - SourceServices.Instance.liveView!!.removeLiveViewSubscription(subscriptionId) - } - } - } else { - log.error("Failed to add live view subscription", it.cause()) - } - } - } - - private fun formatMetricResult(result: ArtifactMetricResult): String { - val sb = StringBuilder() - val resp = result.artifactMetrics.find { it.metricType == MetricType.Throughput_Average }!! - val respValue = (resp.values.last() / 60.0).fromPerSecondToPrettyFrequency({ message(it) }) - sb.append(message(resp.metricType.simpleName)).append(": ").append(respValue).append(" | ") - val cpm = result.artifactMetrics.find { it.metricType == MetricType.ResponseTime_Average }!! - sb.append(message(cpm.metricType.simpleName)).append(": ").append(cpm.values.last().toInt()).append(message("ms")).append(" | ") - val sla = result.artifactMetrics.find { it.metricType == MetricType.ServiceLevelAgreement_Average }!! - sb.append(message(sla.metricType.simpleName)).append(": ").append(sla.values.last().toDouble() / 100.0).append("%") - return "/#/ " + sb.toString() + " \\#\\" - } - - private fun consumeLiveEvent(event: LiveViewEvent, previousMetrics: MutableMap) { - val metrics = JsonArray(event.metricsData) - val sb = StringBuilder() - for (i in 0 until metrics.size()) { - val metric = metrics.getJsonObject(i) - var value: String? = null - if (metric.getNumber("percentage") != null) { - value = (metric.getNumber("percentage").toDouble() / 100.0).toString() + "%" - } - if (value == null) value = metric.getNumber("value").toString() - - val metricType = MetricType.realValueOf(metric.getJsonObject("meta").getString("metricsName")) - if (metricType == MetricType.Throughput_Average) { - value = (metric.getNumber("value").toDouble() / 60.0).fromPerSecondToPrettyFrequency({ message(it) }) - } - if (metricType == MetricType.ResponseTime_Average) { - value += message("ms") - } - sb.append("${message(metricType.simpleName)}: $value") - if (i < metrics.size() - 1) { - sb.append(" | ") - } - } - previousMetrics[event.timeBucket.toLong()] = "/#/ " + sb.toString() + " \\#\\" - } -} diff --git a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/FailingEndpointIndicator.kt b/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/FailingEndpointIndicator.kt deleted file mode 100644 index 31c35faeb..000000000 --- a/plugin/src/main/kotlin/spp/jetbrains/sourcemarker/view/FailingEndpointIndicator.kt +++ /dev/null @@ -1,132 +0,0 @@ -package spp.jetbrains.sourcemarker.view - -import com.intellij.openapi.application.ApplicationManager -import io.vertx.core.json.Json -import io.vertx.core.json.JsonObject -import io.vertx.kotlin.coroutines.dispatcher -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import spp.jetbrains.marker.SourceMarker.creationService -import spp.jetbrains.marker.jvm.psi.EndpointDetector.Companion.ENDPOINT_ID -import spp.jetbrains.marker.source.mark.api.MethodSourceMark -import spp.jetbrains.marker.source.mark.api.SourceMark -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEvent -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventCode.MARK_USER_DATA_UPDATED -import spp.jetbrains.marker.source.mark.api.event.SourceMarkEventListener -import spp.jetbrains.marker.source.mark.api.key.SourceKey -import spp.jetbrains.marker.source.mark.guide.GuideMark -import spp.jetbrains.marker.source.mark.gutter.GutterMark -import spp.jetbrains.monitor.skywalking.SkywalkingClient -import spp.jetbrains.monitor.skywalking.bridge.EndpointMetricsBridge -import spp.jetbrains.monitor.skywalking.model.GetEndpointMetrics -import spp.jetbrains.monitor.skywalking.model.ZonedDuration -import spp.jetbrains.sourcemarker.SourceMarkerPlugin.vertx -import spp.jetbrains.sourcemarker.icons.SourceMarkerIcons -import spp.jetbrains.sourcemarker.settings.SourceMarkerConfig -import spp.protocol.SourceServices -import spp.protocol.SourceServices.Provide.toLiveViewSubscriberAddress -import spp.protocol.instrument.LiveSourceLocation -import spp.protocol.view.LiveViewConfig -import spp.protocol.view.LiveViewEvent -import spp.protocol.view.LiveViewSubscription -import java.time.ZonedDateTime -import java.time.temporal.ChronoUnit - -/** - * Adds a gutter icon to the editor indicating that the endpoint is currently failing. - */ -class FailingEndpointIndicator(val config: SourceMarkerConfig) : SourceMarkEventListener { - - companion object { - private val log: Logger = LoggerFactory.getLogger(FailingEndpointIndicator::class.java) - private val FAILING_ENDPOINT_INDICATOR = SourceKey("FAILING_ENDPOINT_INDICATOR") - private val GUTTER_MARK = SourceKey("FAILING_ENDPOINT_INDICATOR_GUTTER_MARK") - const val FAIL_PERCENT = 60.00 //todo: config through LCP - } - - override fun handleEvent(event: SourceMarkEvent) { - if (event.sourceMark !is GuideMark || event.sourceMark.getUserData(FAILING_ENDPOINT_INDICATOR) == true) return - if (event.eventCode == MARK_USER_DATA_UPDATED && event.sourceMark.getUserData(ENDPOINT_ID) != null) { - event.sourceMark.putUserData(FAILING_ENDPOINT_INDICATOR, true) - val artifactQualifiedName = event.sourceMark.artifactQualifiedName - log.info("Tracking endpoint failures for ${artifactQualifiedName.identifier}") - - //found endpoint, subscribe to SLA to track currently failing status - SourceServices.Instance.liveView!!.addLiveViewSubscription( - LiveViewSubscription( - entityIds = listOf(event.sourceMark.getUserData(ENDPOINT_ID)!!), - artifactQualifiedName = artifactQualifiedName, - artifactLocation = LiveSourceLocation(artifactQualifiedName.identifier, -1), - liveViewConfig = LiveViewConfig("FAILING_ENDPOINT_INDICATOR", listOf("endpoint_sla"), -1) - ) - ).onComplete { - if (it.succeeded()) { - val subscriptionId = it.result().subscriptionId!! - vertx.eventBus().consumer(toLiveViewSubscriberAddress(subscriptionId)) { - val viewEvent = Json.decodeValue(it.body().toString(), LiveViewEvent::class.java) - val metricsData = JsonObject(viewEvent.metricsData) - val currentSLA = metricsData.getInteger("percentage").toDouble() / 100.0 - if (currentSLA > FAIL_PERCENT) { - log.debug("Endpoint ${artifactQualifiedName.identifier} is not failing. SLA: $currentSLA%") - removeFailingIndicatorIfNecessary(event.sourceMark) - } else { - log.debug("Endpoint ${artifactQualifiedName.identifier} is currently failing. SLA: $currentSLA%") - addFailingIndicatorIfNecessary(event.sourceMark) - } - } - event.sourceMark.addEventListener { - if (it.eventCode == SourceMarkEventCode.MARK_REMOVED) { - SourceServices.Instance.liveView!!.removeLiveViewSubscription(subscriptionId) - } - } - - //get initial SLA as endpoints that receive no traffic won't produce metric subscriptions - val endTime = ZonedDateTime.now().minusMinutes(1).truncatedTo(ChronoUnit.MINUTES) //exclusive - val startTime = endTime.minusMinutes(2) - val metricsRequest = GetEndpointMetrics( - listOf("endpoint_sla"), - event.sourceMark.getUserData(ENDPOINT_ID)!!, - ZonedDuration(startTime, endTime, SkywalkingClient.DurationStep.MINUTE) - ) - GlobalScope.launch(vertx.dispatcher()) { - val metrics = EndpointMetricsBridge.getMetrics(metricsRequest, vertx) - if (metrics.flatMap { it.values }.filter { (it.value as Int) < FAIL_PERCENT * 100 }.size > 1) { - log.info("Endpoint ${artifactQualifiedName.identifier} is currently failing") - addFailingIndicatorIfNecessary(event.sourceMark) - } - } - } - } - } - } - - private fun addFailingIndicatorIfNecessary(sourceMark: SourceMark) { - val existingGutterMark = sourceMark.getUserData(GUTTER_MARK) - if (existingGutterMark == null) { - log.info("Adding failing indicator gutter mark to ${sourceMark.artifactQualifiedName.identifier}") - ApplicationManager.getApplication().runReadAction { - val gutterMark = creationService.createMethodGutterMark( - sourceMark.sourceFileMarker, - (sourceMark as MethodSourceMark).getPsiElement().nameIdentifier!!, - false - ) - gutterMark.configuration.activateOnMouseHover = false //todo: show tooltip with extra info - gutterMark.configuration.icon = SourceMarkerIcons.exclamationTriangle - gutterMark.apply(true) - sourceMark.putUserData(GUTTER_MARK, gutterMark) - } - } - } - - private fun removeFailingIndicatorIfNecessary(sourceMark: SourceMark) { - val existingGutterMark = sourceMark.getUserData(GUTTER_MARK) - if (existingGutterMark != null) { - log.info("Removing failing indicator gutter mark from ${sourceMark.artifactQualifiedName.identifier}") - existingGutterMark.dispose() - sourceMark.putUserData(GUTTER_MARK, null) - } - } -} diff --git a/plugin/src/main/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml index 1f1d689b9..58fff5a98 100644 --- a/plugin/src/main/resources/META-INF/plugin.xml +++ b/plugin/src/main/resources/META-INF/plugin.xml @@ -30,8 +30,38 @@ serviceImplementation="spp.jetbrains.sourcemarker.console.SourceMarkerConsoleService"/> + + + + + + + + + + + liveplugin.implementation.pluginrunner.AddToClassPathGroovyIntention + LivePlugin + + + liveplugin.implementation.pluginrunner.AddPluginDependencyGroovyIntention + LivePlugin + + + liveplugin.implementation.pluginrunner.AddToClassPathKotlinIntention + LivePlugin + + + liveplugin.implementation.pluginrunner.AddPluginDependencyKotlinIntention + LivePlugin + + + + + + @@ -52,5 +82,14 @@ class="spp.jetbrains.marker.source.mark.SourceMarkPopupAction" text="SourcePortal Popup Action" description="SourcePortal popup action"> + + + + + + + + + diff --git a/plugin/src/main/resources/META-INF/withKotlin.xml b/plugin/src/main/resources/META-INF/withKotlin.xml index ceb8d07db..bb185fc1a 100644 --- a/plugin/src/main/resources/META-INF/withKotlin.xml +++ b/plugin/src/main/resources/META-INF/withKotlin.xml @@ -5,4 +5,7 @@ - \ No newline at end of file + + + + diff --git a/plugin/src/main/resources/logback.xml b/plugin/src/main/resources/logback.xml index 581e2f9d6..e63eff3de 100644 --- a/plugin/src/main/resources/logback.xml +++ b/plugin/src/main/resources/logback.xml @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/plugin/src/main/resources/messages/PluginBundle.properties b/plugin/src/main/resources/messages/PluginBundle.properties index 3729e339b..450ffb4a3 100644 --- a/plugin/src/main/resources/messages/PluginBundle.properties +++ b/plugin/src/main/resources/messages/PluginBundle.properties @@ -9,6 +9,7 @@ root_source_package=Root Source Package version=Version apache_skywalking_settings=Apache SkyWalking Settings plugin_settings=Plugin Settings +portal_settings=Portal Settings last_occurred=Last Occurred threw=Threw @@ -141,4 +142,4 @@ sec_letter=s day=Day verify_host=Verify Host certificate_pins=Certificate Pins -service=Service \ No newline at end of file +service=Service diff --git a/plugin/src/main/resources/messages/PluginBundle_zh.properties b/plugin/src/main/resources/messages/PluginBundle_zh.properties index 5bde37ee6..037da6d41 100644 --- a/plugin/src/main/resources/messages/PluginBundle_zh.properties +++ b/plugin/src/main/resources/messages/PluginBundle_zh.properties @@ -8,6 +8,7 @@ root_source_package=根源包 version=版 apache_skywalking_settings=Apache SkyWalking 设置 plugin_settings=插件设置 +portal_settings=门户设置 last_occurred=上次发生 threw=扔了 @@ -140,4 +141,4 @@ sec_letter=秒 day=日 verify_host=验证主机 certificate_pins=证书别针 -service=营运 \ No newline at end of file +service=营运 diff --git a/settings.gradle b/settings.gradle index a8b940ea1..1cc655989 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,8 +12,10 @@ pluginManagement { } } +include 'commander' +include 'commander:kotlin-compiler-wrapper' include 'marker' include 'marker:jvm-marker' include 'marker:py-marker' include 'monitor' -include 'plugin' \ No newline at end of file +include 'plugin' diff --git a/test/e2e/example-web-app b/test/e2e/example-web-app index ef048628d..b0df6ae30 160000 --- a/test/e2e/example-web-app +++ b/test/e2e/example-web-app @@ -1 +1 @@ -Subproject commit ef048628d53c681b5b5d6faa80786f659c699736 +Subproject commit b0df6ae303b88a645c1354130dbc1d97fc8b43a0