diff --git a/components/ide/jetbrains/backend-plugin/gradle.properties b/components/ide/jetbrains/backend-plugin/gradle.properties
index 2cbce2c7b77a77..846a70cb13f697 100644
--- a/components/ide/jetbrains/backend-plugin/gradle.properties
+++ b/components/ide/jetbrains/backend-plugin/gradle.properties
@@ -10,7 +10,7 @@ platformType=IU
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=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe
+platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe, org.jetbrains.plugins.yaml
# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
kotlin.stdlib.default.dependency=false
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodProjectManager.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodProjectManager.kt
index bff55823fdbc3f..6e7b6f71320bb2 100644
--- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodProjectManager.kt
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodProjectManager.kt
@@ -5,19 +5,31 @@
package io.gitpod.jetbrains.remote
import com.intellij.ProjectTopics
+import com.intellij.analysis.AnalysisScope
+import com.intellij.codeInspection.actions.RunInspectionIntention
+import com.intellij.codeInspection.ex.InspectionManagerEx
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
+import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.ModuleListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.profile.codeInspection.InspectionProfileManager
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiManager
import com.intellij.util.application
+import io.gitpod.jetbrains.remote.inspections.GitpodConfigInspection
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
+import org.jetbrains.yaml.psi.YAMLFile
+import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
@@ -29,6 +41,34 @@ class GitpodProjectManager(
configureSdks()
}
+ init {
+ application.invokeLater {
+ try {
+ runInspection()
+ } catch (ex: Exception) {
+ thisLogger().error("Failed to run inspection", ex)
+ }
+ }
+ }
+
+ private fun runInspection() {
+ val psiFile = getGitpodYamlPsiFile(project) ?: return
+ val profile = InspectionProfileManager.getInstance(project).currentProfile
+ val inspectionName = GitpodConfigInspection::class.java.simpleName
+ val tool = profile.getInspectionTool(inspectionName, psiFile) ?: return
+ val manager = InspectionManagerEx.getInstance(project) as InspectionManagerEx
+ val scope = AnalysisScope(psiFile)
+ DumbService.getInstance(project).smartInvokeLater {
+ RunInspectionIntention.rerunInspection(tool, manager, scope, psiFile)
+ }
+ }
+
+ private fun getGitpodYamlPsiFile(project: Project): PsiFile? {
+ val basePath = project.basePath ?: return null
+ val vfile = VfsUtil.findFile(Paths.get(basePath, gitpodYamlFile), true) ?: return null
+ return PsiManager.getInstance(project).findFile(vfile) as? YAMLFile ?: return null
+ }
+
/**
* It is a workaround for https://youtrack.jetbrains.com/issue/GTW-88
*/
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/inspections/GitpodConfigInspection.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/inspections/GitpodConfigInspection.kt
new file mode 100644
index 00000000000000..a49197a5683162
--- /dev/null
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/inspections/GitpodConfigInspection.kt
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License-AGPL.txt in the project root for license information.
+
+package io.gitpod.jetbrains.remote.inspections
+
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.diagnostic.VMOptions
+import com.intellij.openapi.util.BuildNumber
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.PsiFile
+import io.gitpod.jetbrains.remote.quickfixes.AddVMOptionsQuickFix
+import io.gitpod.jetbrains.remote.quickfixes.ReplaceVMOptionsQuickFix
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.defaultXmxMiB
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.getJetBrainsProductName
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
+import org.jetbrains.yaml.YAMLUtil
+import org.jetbrains.yaml.psi.YAMLFile
+import org.jetbrains.yaml.psi.YAMLKeyValue
+
+class GitpodConfigInspection : LocalInspectionTool() {
+
+ private val runtimeXmxMiB = Runtime.getRuntime().maxMemory().shr(20)
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : PsiElementVisitor() {
+ override fun visitFile(file: PsiFile) {
+ if (file.name != gitpodYamlFile || file !is YAMLFile) return
+ val productCode = BuildNumber.currentVersion().productCode
+ val productName = getJetBrainsProductName(productCode) ?: return
+ val keyValue = YAMLUtil.getQualifiedKeyInFile(file, YamlKey.jetbrains, productName, YamlKey.vmOptions)
+ if (keyValue == null) {
+ val description = "IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but not configured in $gitpodYamlFile"
+ val quickFix = AddVMOptionsQuickFix(productName, runtimeXmxMiB)
+ holder.registerProblem(file, description, quickFix)
+ return
+ }
+ val configuredXmxMiB = getUserConfiguredXmxValue(keyValue)
+ val quickFix = ReplaceVMOptionsQuickFix(runtimeXmxMiB)
+ if (configuredXmxMiB == null && runtimeXmxMiB != defaultXmxMiB) {
+ val description = "IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but not configured in $gitpodYamlFile"
+ holder.registerProblem(keyValue, description, quickFix)
+ } else if (configuredXmxMiB != null && runtimeXmxMiB != configuredXmxMiB) {
+ val description = "IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but -Xmx${configuredXmxMiB}m configured in $gitpodYamlFile"
+ holder.registerProblem(keyValue, description, quickFix)
+ }
+ }
+ }
+ }
+
+ private fun getUserConfiguredXmxValue(vmOptionsKeyValue: YAMLKeyValue): Long? {
+ val vmOptions = vmOptionsKeyValue.valueText.trim().split("\\s".toRegex())
+ // the rightmost option is the one to take effect
+ val finalXmx = vmOptions.lastOrNull { it.startsWith("-Xmx") } ?: return null
+ val xmxValue = finalXmx.substringAfter("-Xmx")
+ return try {
+ VMOptions.parseMemoryOption(xmxValue).shr(20)
+ } catch (e: IllegalArgumentException) {
+ // ignore invalid user configuration
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/AddVMOptionsQuickFix.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/AddVMOptionsQuickFix.kt
new file mode 100644
index 00000000000000..7a5e290ec285b6
--- /dev/null
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/AddVMOptionsQuickFix.kt
@@ -0,0 +1,64 @@
+// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License-AGPL.txt in the project root for license information.
+
+package io.gitpod.jetbrains.remote.quickfixes
+
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.openapi.diagnostic.thisLogger
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
+import com.intellij.psi.codeStyle.CodeStyleManager
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.IncorrectOperationException
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
+import org.jetbrains.yaml.YAMLElementGenerator
+import org.jetbrains.yaml.psi.YAMLFile
+import org.jetbrains.yaml.psi.YAMLKeyValue
+
+class AddVMOptionsQuickFix(private val productName: String, private val xmxValueMiB: Long) : LocalQuickFix {
+
+ override fun getName() = "Add -Xmx${xmxValueMiB}m to $gitpodYamlFile"
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val psiFile = descriptor.psiElement as? YAMLFile ?: return
+ val document = psiFile.viewProvider.document ?: return
+ val generator = YAMLElementGenerator.getInstance(project)
+ val jetbrainsKeyValue = findOrCreateYamlKeyValue(psiFile, YamlKey.jetbrains, "", generator) ?: return
+ val productKeyValue = findOrCreateYamlKeyValue(jetbrainsKeyValue, productName, "", generator) ?: return
+ findOrCreateYamlKeyValue(productKeyValue, YamlKey.vmOptions, "-Xmx${xmxValueMiB}m", generator)
+ PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document)
+ try {
+ CodeStyleManager.getInstance(project).reformat(jetbrainsKeyValue)
+ } catch (e: IncorrectOperationException) {
+ thisLogger().warn("AddVMOptionsQuickFix reformat failed", e)
+ }
+ }
+
+ private fun findOrCreateYamlKeyValue(
+ parent: PsiElement,
+ keyText: String,
+ valueText: String,
+ generator: YAMLElementGenerator
+ ): PsiElement? {
+ var element = findElementByYamlKeyText(parent, keyText)
+ return if (element == null) {
+ element = generator.createYamlKeyValue(keyText, valueText)
+ parent.add(generator.createEol())
+ parent.add(element) ?: return null
+ } else {
+ element
+ }
+ }
+
+ private fun findElementByYamlKeyText(rootElement: PsiElement, keyText: String): PsiElement? {
+ return PsiTreeUtil.collectElements(rootElement) {
+ it is YAMLKeyValue && it.keyText == keyText
+ }.firstOrNull()
+ }
+}
\ No newline at end of file
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ApplyVMOptionsQuickFix.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ApplyVMOptionsQuickFix.kt
new file mode 100644
index 00000000000000..63616cdfc25294
--- /dev/null
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ApplyVMOptionsQuickFix.kt
@@ -0,0 +1,23 @@
+// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License-AGPL.txt in the project root for license information.
+
+package io.gitpod.jetbrains.remote.quickfixes
+
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.diagnostic.VMOptions
+import com.intellij.openapi.project.Project
+
+class ApplyVMOptionsQuickFix(private val quickFixName: String, private val xmxValueMiB: Long) : LocalQuickFix {
+
+ override fun getName() = quickFixName
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ if (VMOptions.canWriteOptions()) {
+ VMOptions.setOption(VMOptions.MemoryKind.HEAP, xmxValueMiB.toInt())
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ReplaceVMOptionsQuickFix.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ReplaceVMOptionsQuickFix.kt
new file mode 100644
index 00000000000000..028c2dc2c770a1
--- /dev/null
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/quickfixes/ReplaceVMOptionsQuickFix.kt
@@ -0,0 +1,36 @@
+// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License-AGPL.txt in the project root for license information.
+
+package io.gitpod.jetbrains.remote.quickfixes
+
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.openapi.project.Project
+import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
+import org.jetbrains.yaml.YAMLElementGenerator
+import org.jetbrains.yaml.psi.YAMLKeyValue
+
+class ReplaceVMOptionsQuickFix(private val xmxValueMiB: Long) : LocalQuickFix {
+
+ override fun getName() = "Set Xmx to ${xmxValueMiB}m"
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val vmOptionsKeyValue = descriptor.psiElement as? YAMLKeyValue ?: return
+ if (vmOptionsKeyValue.keyText != YamlKey.vmOptions) return
+ val vmOptions = vmOptionsKeyValue.valueText.trim().split("\\s".toRegex())
+ val xmxUpdated = "-Xmx${xmxValueMiB}m"
+ val xmxOptions = vmOptions
+ .filter { it.startsWith("-Xmx") }
+ .map { xmxUpdated }
+ .ifEmpty { listOf(xmxUpdated) }
+ val nonXmxOptions = vmOptions
+ .filter { !it.startsWith("-Xmx") }
+ val newVmOptions = (xmxOptions + nonXmxOptions).toSortedSet().joinToString(" ")
+ val generator = YAMLElementGenerator.getInstance(project)
+ val psiElementUpdated = generator.createYamlKeyValue(YamlKey.vmOptions, newVmOptions)
+ vmOptionsKeyValue.replace(psiElementUpdated)
+ }
+}
\ No newline at end of file
diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/utils/GitpodConfig.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/utils/GitpodConfig.kt
new file mode 100644
index 00000000000000..faf5a287cdcb2f
--- /dev/null
+++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/utils/GitpodConfig.kt
@@ -0,0 +1,34 @@
+// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License-AGPL.txt in the project root for license information.
+
+package io.gitpod.jetbrains.remote.utils
+
+/**
+ * Constants and util functions for Gitpod config spec
+ */
+object GitpodConfig {
+
+ // FIXME: get from env var
+ const val defaultXmxMiB = 2048L
+ const val gitpodYamlFile = ".gitpod.yml"
+
+ object YamlKey {
+ const val jetbrains = "jetbrains"
+ const val vmOptions = "vmoptions"
+ }
+
+ /**
+ * map JetBrains IDE productCode to YAML key for .gitpod.yml
+ */
+ fun getJetBrainsProductName(productCode: String): String? {
+ return when (productCode) {
+ "IC" -> "intellij"
+ "IU" -> "intellij"
+ "PS" -> "phpstorm"
+ "PY" -> "pycharm"
+ "GO" -> "goland"
+ else -> null
+ }
+ }
+}
diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml b/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml
index bbb6f3991a9cdc..b8ad5a2666cccd 100644
--- a/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml
+++ b/components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml
@@ -19,6 +19,7 @@
Example configuration:
+
+ jetbrains:
+ intellij:
+ vmoptions: -Xmx4g
+
+More information: https://www.gitpod.io/docs/references/gitpod-yml#jetbrainsproductvmoptions
+ + diff --git a/components/ide/jetbrains/backend-plugin/src/main/resources/messages/GitpodBundle.properties b/components/ide/jetbrains/backend-plugin/src/main/resources/messages/GitpodBundle.properties new file mode 100644 index 00000000000000..02a647d8960d40 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/src/main/resources/messages/GitpodBundle.properties @@ -0,0 +1,2 @@ +inspections.group.name=Gitpod +inspections.gitpod.schema.validation.name=Incorrect -Xmx config \ No newline at end of file