From 097f540a75f825b858b9d3a08ba22c112cdb0e0e Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Tue, 29 Jul 2025 09:44:00 +0100 Subject: [PATCH 1/7] lsp4ij: Add Regal support Signed-off-by: Charlie Egan --- .gitignore | 1 + build.gradle.kts | 3 +- plugin/src/main/resources/META-INF/plugin.xml | 1 + .../lsp/RegalLanguageServerFactory.kt | 21 +++++ .../lsp/RegalStreamConnectionProvider.kt | 85 +++++++++++++++++++ .../settings/OpaOptionsConfigurable.kt | 11 +++ .../project/settings/OpaProjectSettings.kt | 12 +++ .../opa/project/settings/OpaSettingsState.kt | 2 + src/main/resources/META-INF/opa-core.xml | 15 +++- 9 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt diff --git a/.gitignore b/.gitignore index b2379a59..4d9d4273 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/ gen/ deps .intellijPlatform +.kotlin # MacOS files .DS_Store diff --git a/build.gradle.kts b/build.gradle.kts index 8bf0a84d..25bd5002 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,7 +84,8 @@ allprojects { intellijPlatform { create(ideType, ideVersion) val pluginList = mutableListOf( - "PsiViewer:$psiViewerPluginVersion" + "PsiViewer:$psiViewerPluginVersion", + "com.redhat.devtools.lsp4ij:0.14.2" ) plugins(pluginList) diff --git a/plugin/src/main/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml index 376c7c35..caebbe88 100644 --- a/plugin/src/main/resources/META-INF/plugin.xml +++ b/plugin/src/main/resources/META-INF/plugin.xml @@ -24,6 +24,7 @@ on how to target different products --> com.intellij.modules.platform com.intellij.modules.lang + com.redhat.devtools.lsp4ij diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt new file mode 100644 index 00000000..6448f782 --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt @@ -0,0 +1,21 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +// Implementation based on LSP4IJ Developer Guide: +// https://github.com/redhat-developer/lsp4ij/blob/035c4fd82cc7603ef3a346be0d9585d4e41564b0/docs/dap/DeveloperGuide.md + +package org.openpolicyagent.ideaplugin.lsp + +import com.intellij.openapi.project.Project +import com.intellij.openapi.diagnostic.Logger +import com.redhat.devtools.lsp4ij.LanguageServerFactory +import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider +import org.jetbrains.annotations.NotNull + +class RegalLanguageServerFactory : LanguageServerFactory { + override fun createConnectionProvider(@NotNull project: Project): StreamConnectionProvider { + return RegalStreamConnectionProvider(project) + } +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt new file mode 100644 index 00000000..b38b4c0f --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt @@ -0,0 +1,85 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +// Implementation based on LSP4IJ Developer Guide: +// https://github.com/redhat-developer/lsp4ij/blob/035c4fd82cc7603ef3a346be0d9585d4e41564b0/docs/dap/DeveloperGuide.md + +package org.openpolicyagent.ideaplugin.lsp + +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType +import com.intellij.openapi.project.Project +import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider +import org.openpolicyagent.ideaplugin.opa.project.settings.OpaProjectSettings + +class RegalStreamConnectionProvider(private val project: Project) : OSProcessStreamConnectionProvider() { + init { + val commandLine = GeneralCommandLine() + val regalPath = findRegalExecutable() + if (regalPath == null) { + showRegalNotFoundNotification() + commandLine.exePath = "regal" + } else { + commandLine.exePath = regalPath + } + val settings = OpaProjectSettings.getInstance(project) + commandLine.addParameter("language-server") + if (settings.regalVerboseLogging) { + commandLine.addParameter("--verbose") + } + commandLine.workDirectory = project.basePath?.let { java.io.File(it) } + + super.setCommandLine(commandLine) + } + + private fun findRegalExecutable(): String? { + val settings = OpaProjectSettings.getInstance(project) + val configuredPath = settings.regalPath.trim() + if (configuredPath.isNotEmpty()) { + val file = java.io.File(configuredPath) + if (file.exists() && file.canExecute()) { + return configuredPath + } + } + + val pathExecutable = findExecutableInPath("regal") + if (pathExecutable != null) { + return pathExecutable + } + + return null + } + + private fun findExecutableInPath(executable: String): String? { + val pathEnv = System.getenv("PATH") ?: return null + val pathSeparator = System.getProperty("path.separator") + + for (dir in pathEnv.split(pathSeparator)) { + val file = java.io.File(dir, executable) + if (file.exists() && file.canExecute()) { + return file.absolutePath + } + } + + return null + } + + private fun showRegalNotFoundNotification() { + val notificationGroup = NotificationGroupManager.getInstance() + .getNotificationGroup("OPA Plugin") + + if (notificationGroup != null) { + val notification = notificationGroup.createNotification( + "Regal Language Server Not Found", + "Regal binary not found. Install Regal to enable advanced IDE features like code completion and diagnostics.
" + + "Install Regal or configure the path in Settings → Languages & Frameworks → Open Policy Agent", + NotificationType.WARNING + ).setImportant(true) + + notification.notify(project) + } + } +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaOptionsConfigurable.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaOptionsConfigurable.kt index 552e3a51..50a849c4 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaOptionsConfigurable.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaOptionsConfigurable.kt @@ -12,6 +12,7 @@ import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.bindText import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.bindSelected /** * UI for the opa setting options. @@ -29,6 +30,16 @@ class OpaOptionsConfigurable(private val project: Project) : .bindText(settings::opaCheckOptions) .align(AlignX.FILL) } + row("Regal path:") { + textField() + .bindText(settings::regalPath) + .align(AlignX.FILL) + .comment("Path to Regal binary (leave empty to use PATH)") + } + row { + checkBox("Enable Regal verbose logging") + .bindSelected(settings::regalVerboseLogging) + } } } } diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt index 54f15177..40a30bb4 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt @@ -20,6 +20,18 @@ class OpaProjectSettings(val project: Project) : SimplePersistentStateComponent< state.opaCheckOptions = value } + var regalPath + get() = state.regalPath + set(value) { + state.regalPath = value + } + + var regalVerboseLogging + get() = state.regalVerboseLogging + set(value) { + state.regalVerboseLogging = value + } + companion object { @JvmStatic val defaultOpaCheckOptions = "--strict" diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt index a159594d..1ff3394b 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt @@ -9,4 +9,6 @@ import com.intellij.openapi.components.BaseState class OpaSettingsState: BaseState() { var opaCheckOptions = OpaProjectSettings.defaultOpaCheckOptions + var regalPath = "" + var regalVerboseLogging = false } \ No newline at end of file diff --git a/src/main/resources/META-INF/opa-core.xml b/src/main/resources/META-INF/opa-core.xml index 4c5b4dac..4553dcdf 100644 --- a/src/main/resources/META-INF/opa-core.xml +++ b/src/main/resources/META-INF/opa-core.xml @@ -53,7 +53,20 @@ displayName="Open Policy Agent" instance="org.openpolicyagent.ideaplugin.opa.project.settings.OpaOptionsConfigurable"/> - + + + + + + + + + Regal Language Server for Open Policy Agent Rego files + + + From b91d5ce9f5c35251e33c23aee59f63fc465a6f85 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Tue, 29 Jul 2025 11:02:47 +0100 Subject: [PATCH 2/7] lsp4ij: Show Debug lens Signed-off-by: Charlie Egan --- .../ideaplugin/lsp/RegalStreamConnectionProvider.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt index b38b4c0f..5f8b8fd2 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt @@ -12,6 +12,7 @@ import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider import org.openpolicyagent.ideaplugin.opa.project.settings.OpaProjectSettings @@ -82,4 +83,9 @@ class RegalStreamConnectionProvider(private val project: Project) : OSProcessStr notification.notify(project) } } + + override fun getInitializationOptions(rootUri: VirtualFile?): Any? { + val initOptions = mapOf("enableDebugCodelens" to true) + return initOptions + } } From 284ac2e9617c39594799f535ebd0cd53ca863083 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Tue, 29 Jul 2025 11:49:28 +0100 Subject: [PATCH 3/7] lsp4ij: Add DAP support for Regal Signed-off-by: Charlie Egan --- .../dap/RegalDebugAdapterDescriptor.kt | 119 ++++++++++++++++++ .../dap/RegalDebugAdapterDescriptorFactory.kt | 36 ++++++ .../ideaplugin/lsp/RegalLanguageClient.kt | 38 ++++++ .../lsp/RegalLanguageServerFactory.kt | 5 + .../lsp/RegalStreamConnectionProvider.kt | 3 +- src/main/resources/META-INF/opa-core.xml | 7 ++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt new file mode 100644 index 00000000..a7f5e8b0 --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt @@ -0,0 +1,119 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.dap + +import com.intellij.execution.ExecutionException +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.process.ProcessHandlerFactory +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor +import com.redhat.devtools.lsp4ij.dap.DebugMode +import com.redhat.devtools.lsp4ij.dap.descriptors.ServerReadyConfig +import com.redhat.devtools.lsp4ij.dap.definitions.DebugAdapterServerDefinition +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.configurations.RunConfigurationOptions +import com.intellij.openapi.fileTypes.FileType +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable +import org.openpolicyagent.ideaplugin.opa.project.settings.OpaProjectSettings +import java.io.File + +class RegalDebugAdapterDescriptor( + @NotNull options: RunConfigurationOptions, + @NotNull environment: ExecutionEnvironment, + @Nullable serverDefinition: DebugAdapterServerDefinition? +) : DebugAdapterDescriptor(options, environment, serverDefinition) { + + private val project: Project = environment.project + + companion object { + private val LOG = Logger.getInstance(RegalDebugAdapterDescriptor::class.java) + } + + @Throws(ExecutionException::class) + override fun startServer(): ProcessHandler { + val commandLine = GeneralCommandLine() + val regalPath = findRegalExecutable() + + if (regalPath == null) { + throw ExecutionException("Regal executable not found. Please install Regal or configure the path in Settings → Languages & Frameworks → Open Policy Agent") + } + + commandLine.exePath = regalPath + commandLine.addParameter("debug") + + val settings = OpaProjectSettings.getInstance(project) + if (settings.regalVerboseLogging) { + commandLine.addParameter("--verbose") + } + + commandLine.workDirectory = project.basePath?.let { File(it) } + + LOG.info("Starting Regal debug adapter with command: ${commandLine.commandLineString}") + + return ProcessHandlerFactory.getInstance().createProcessHandler(commandLine) + } + + override fun getDapParameters(): Map { + // Default DAP parameters that can be overridden by debug configuration + return mapOf( + "command" to "eval", + "query" to "data", + "bundlePaths" to listOf(project.basePath ?: "."), + "input" to emptyMap(), + "inputPath" to "", + "stopOnEntry" to true, + "enablePrint" to true + ) + } + + override fun getServerReadyConfig(@NotNull debugMode: DebugMode): ServerReadyConfig { + // Return server ready configuration with a timeout for DAP server startup + return ServerReadyConfig(5000) // 5 second timeout + } + + @Nullable + override fun getFileType(): FileType? { + // Return null to support all file types for now + return null + } + + private fun findRegalExecutable(): String? { + val settings = OpaProjectSettings.getInstance(project) + val configuredPath = settings.regalPath.trim() + + if (configuredPath.isNotEmpty()) { + val file = File(configuredPath) + if (file.exists() && file.canExecute()) { + return configuredPath + } + } + + // Try to find regal in PATH + val pathExecutable = findExecutableInPath("regal") + if (pathExecutable != null) { + return pathExecutable + } + + return null + } + + private fun findExecutableInPath(executable: String): String? { + val pathEnv = System.getenv("PATH") ?: return null + val pathSeparator = System.getProperty("path.separator") + + for (dir in pathEnv.split(pathSeparator)) { + val file = File(dir, executable) + if (file.exists() && file.canExecute()) { + return file.absolutePath + } + } + + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt new file mode 100644 index 00000000..368c3968 --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt @@ -0,0 +1,36 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.dap + +import com.intellij.openapi.project.Project +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.vfs.VirtualFile +import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor +import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptorFactory +import com.redhat.devtools.lsp4ij.dap.definitions.DebugAdapterServerDefinition +import com.redhat.devtools.lsp4ij.dap.configurations.DAPRunConfigurationOptions +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable + +class RegalDebugAdapterDescriptorFactory : DebugAdapterDescriptorFactory() { + + override fun getServerDefinition(): DebugAdapterServerDefinition { + // This will be set by LSP4IJ framework when the factory is registered + return super.getServerDefinition() + } + + override fun isDebuggableFile(file: VirtualFile, project: Project): Boolean { + // Check if file is a Rego file that can be debugged + return file.extension == "rego" + } + + override fun createDebugAdapterDescriptor( + @NotNull options: DAPRunConfigurationOptions, + @NotNull environment: ExecutionEnvironment + ): DebugAdapterDescriptor { + return RegalDebugAdapterDescriptor(options, environment, getServerDefinition()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt new file mode 100644 index 00000000..d69377af --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt @@ -0,0 +1,38 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.lsp + +import com.google.gson.JsonObject +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.redhat.devtools.lsp4ij.client.LanguageClientImpl +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment +import java.util.concurrent.CompletableFuture + +@JsonSegment("regal") +interface RegalLanguageClientExtensions { + @JsonRequest("startDebugging") + fun startDebugging(params: JsonObject): CompletableFuture +} + +class RegalLanguageClient(project: Project) : LanguageClientImpl(project), RegalLanguageClientExtensions { + companion object { + private val LOG = Logger.getInstance(RegalLanguageClient::class.java) + } + + override fun startDebugging(params: JsonObject): CompletableFuture { + LOG.info("RegalLanguageClient: startDebugging called with params: $params") + + // Debugging is now handled via DAP integration + // This LSP method is kept for compatibility but redirects to DAP + val response = JsonObject() + response.addProperty("status", "redirected") + response.addProperty("message", "Debug requests are now handled via DAP. Use IntelliJ's Debug menu to start debugging.") + + return CompletableFuture.completedFuture(response) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt index 6448f782..65a49fb2 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageServerFactory.kt @@ -11,6 +11,7 @@ package org.openpolicyagent.ideaplugin.lsp import com.intellij.openapi.project.Project import com.intellij.openapi.diagnostic.Logger import com.redhat.devtools.lsp4ij.LanguageServerFactory +import com.redhat.devtools.lsp4ij.client.LanguageClientImpl import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider import org.jetbrains.annotations.NotNull @@ -18,4 +19,8 @@ class RegalLanguageServerFactory : LanguageServerFactory { override fun createConnectionProvider(@NotNull project: Project): StreamConnectionProvider { return RegalStreamConnectionProvider(project) } + + override fun createLanguageClient(@NotNull project: Project): LanguageClientImpl { + return RegalLanguageClient(project) + } } diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt index 5f8b8fd2..a3b9e8b9 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt @@ -85,7 +85,6 @@ class RegalStreamConnectionProvider(private val project: Project) : OSProcessStr } override fun getInitializationOptions(rootUri: VirtualFile?): Any? { - val initOptions = mapOf("enableDebugCodelens" to true) - return initOptions + return emptyMap() } } diff --git a/src/main/resources/META-INF/opa-core.xml b/src/main/resources/META-INF/opa-core.xml index 4553dcdf..7ba9d65c 100644 --- a/src/main/resources/META-INF/opa-core.xml +++ b/src/main/resources/META-INF/opa-core.xml @@ -66,6 +66,13 @@ Regal Language Server for Open Policy Agent Rego files + + + + Regal Debug Adapter for debugging Rego policies + From c5e7bc298482040d94e68d80c83d24b763404580 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Wed, 30 Jul 2025 10:43:53 +0100 Subject: [PATCH 4/7] lsp4ij: Add folding, signature help, + doc symbols Signed-off-by: Charlie Egan --- src/main/resources/META-INF/opa-core.xml | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/resources/META-INF/opa-core.xml b/src/main/resources/META-INF/opa-core.xml index 7ba9d65c..e732fd7f 100644 --- a/src/main/resources/META-INF/opa-core.xml +++ b/src/main/resources/META-INF/opa-core.xml @@ -40,6 +40,32 @@ + + + + + + + + + + + + + Date: Wed, 30 Jul 2025 11:18:01 +0100 Subject: [PATCH 5/7] lsp4ij: Formatting and tidying Signed-off-by: Charlie Egan --- plugin/src/main/resources/META-INF/plugin.xml | 2 +- .../dap/RegalDebugAdapterDescriptor.kt | 71 +++++-------------- .../dap/RegalDebugAdapterDescriptorFactory.kt | 2 +- .../ideaplugin/lsp/RegalLanguageClient.kt | 6 +- .../lsp/RegalStreamConnectionProvider.kt | 1 + .../project/settings/OpaProjectSettings.kt | 2 +- .../opa/project/settings/OpaSettingsState.kt | 4 +- .../ideaplugin/util/RegalExecutableUtil.kt | 47 ++++++++++++ src/main/resources/META-INF/opa-core.xml | 4 +- 9 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 src/main/kotlin/org/openpolicyagent/ideaplugin/util/RegalExecutableUtil.kt diff --git a/plugin/src/main/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml index caebbe88..54fa875e 100644 --- a/plugin/src/main/resources/META-INF/plugin.xml +++ b/plugin/src/main/resources/META-INF/plugin.xml @@ -36,4 +36,4 @@ - \ No newline at end of file + diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt index a7f5e8b0..19054c81 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptor.kt @@ -7,20 +7,22 @@ package org.openpolicyagent.ideaplugin.dap import com.intellij.execution.ExecutionException import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.configurations.RunConfigurationOptions import com.intellij.execution.process.ProcessHandler import com.intellij.execution.process.ProcessHandlerFactory +import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.project.Project -import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor import com.redhat.devtools.lsp4ij.dap.DebugMode -import com.redhat.devtools.lsp4ij.dap.descriptors.ServerReadyConfig import com.redhat.devtools.lsp4ij.dap.definitions.DebugAdapterServerDefinition -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.execution.configurations.RunConfigurationOptions -import com.intellij.openapi.fileTypes.FileType +import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor +import com.redhat.devtools.lsp4ij.dap.descriptors.ServerReadyConfig import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.Nullable +import org.openpolicyagent.ideaplugin.lang.RegoFileType import org.openpolicyagent.ideaplugin.opa.project.settings.OpaProjectSettings +import org.openpolicyagent.ideaplugin.util.RegalExecutableUtil import java.io.File class RegalDebugAdapterDescriptor( @@ -28,9 +30,9 @@ class RegalDebugAdapterDescriptor( @NotNull environment: ExecutionEnvironment, @Nullable serverDefinition: DebugAdapterServerDefinition? ) : DebugAdapterDescriptor(options, environment, serverDefinition) { - + private val project: Project = environment.project - + companion object { private val LOG = Logger.getInstance(RegalDebugAdapterDescriptor::class.java) } @@ -38,29 +40,29 @@ class RegalDebugAdapterDescriptor( @Throws(ExecutionException::class) override fun startServer(): ProcessHandler { val commandLine = GeneralCommandLine() - val regalPath = findRegalExecutable() - + val regalPath = RegalExecutableUtil.findRegalExecutable(project) + if (regalPath == null) { throw ExecutionException("Regal executable not found. Please install Regal or configure the path in Settings → Languages & Frameworks → Open Policy Agent") } - + commandLine.exePath = regalPath commandLine.addParameter("debug") - + val settings = OpaProjectSettings.getInstance(project) if (settings.regalVerboseLogging) { commandLine.addParameter("--verbose") } - + commandLine.workDirectory = project.basePath?.let { File(it) } - + LOG.info("Starting Regal debug adapter with command: ${commandLine.commandLineString}") - + return ProcessHandlerFactory.getInstance().createProcessHandler(commandLine) } override fun getDapParameters(): Map { - // Default DAP parameters that can be overridden by debug configuration + // TODO: Set input and inputPath from project input.json return mapOf( "command" to "eval", "query" to "data", @@ -73,47 +75,12 @@ class RegalDebugAdapterDescriptor( } override fun getServerReadyConfig(@NotNull debugMode: DebugMode): ServerReadyConfig { - // Return server ready configuration with a timeout for DAP server startup return ServerReadyConfig(5000) // 5 second timeout } @Nullable override fun getFileType(): FileType? { - // Return null to support all file types for now - return null - } - - private fun findRegalExecutable(): String? { - val settings = OpaProjectSettings.getInstance(project) - val configuredPath = settings.regalPath.trim() - - if (configuredPath.isNotEmpty()) { - val file = File(configuredPath) - if (file.exists() && file.canExecute()) { - return configuredPath - } - } - - // Try to find regal in PATH - val pathExecutable = findExecutableInPath("regal") - if (pathExecutable != null) { - return pathExecutable - } - - return null + return RegoFileType } - private fun findExecutableInPath(executable: String): String? { - val pathEnv = System.getenv("PATH") ?: return null - val pathSeparator = System.getProperty("path.separator") - - for (dir in pathEnv.split(pathSeparator)) { - val file = File(dir, executable) - if (file.exists() && file.canExecute()) { - return file.absolutePath - } - } - - return null - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt index 368c3968..7fd2e1ce 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/dap/RegalDebugAdapterDescriptorFactory.kt @@ -33,4 +33,4 @@ class RegalDebugAdapterDescriptorFactory : DebugAdapterDescriptorFactory() { ): DebugAdapterDescriptor { return RegalDebugAdapterDescriptor(options, environment, getServerDefinition()) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt index d69377af..ce846dc4 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt @@ -26,13 +26,13 @@ class RegalLanguageClient(project: Project) : LanguageClientImpl(project), Regal override fun startDebugging(params: JsonObject): CompletableFuture { LOG.info("RegalLanguageClient: startDebugging called with params: $params") - + // Debugging is now handled via DAP integration // This LSP method is kept for compatibility but redirects to DAP val response = JsonObject() response.addProperty("status", "redirected") response.addProperty("message", "Debug requests are now handled via DAP. Use IntelliJ's Debug menu to start debugging.") - + return CompletableFuture.completedFuture(response) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt index a3b9e8b9..20a4d5c0 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalStreamConnectionProvider.kt @@ -85,6 +85,7 @@ class RegalStreamConnectionProvider(private val project: Project) : OSProcessStr } override fun getInitializationOptions(rootUri: VirtualFile?): Any? { + // Placeholder for future client feature customization (e.g. inline eval, etc.) return emptyMap() } } diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt index 40a30bb4..cbeceaf4 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaProjectSettings.kt @@ -41,4 +41,4 @@ class OpaProjectSettings(val project: Project) : SimplePersistentStateComponent< return project.service() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt index 1ff3394b..5fcaf67a 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/opa/project/settings/OpaSettingsState.kt @@ -10,5 +10,5 @@ import com.intellij.openapi.components.BaseState class OpaSettingsState: BaseState() { var opaCheckOptions = OpaProjectSettings.defaultOpaCheckOptions var regalPath = "" - var regalVerboseLogging = false -} \ No newline at end of file + var regalVerboseLogging = false // Applies to both Language Server and DAP server +} diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/util/RegalExecutableUtil.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/util/RegalExecutableUtil.kt new file mode 100644 index 00000000..b4b2032f --- /dev/null +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/util/RegalExecutableUtil.kt @@ -0,0 +1,47 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.util + +import com.intellij.openapi.project.Project +import org.openpolicyagent.ideaplugin.opa.project.settings.OpaProjectSettings +import java.io.File + +object RegalExecutableUtil { + + fun findRegalExecutable(project: Project): String? { + val settings = OpaProjectSettings.getInstance(project) + val configuredPath = settings.regalPath.trim() + + if (configuredPath.isNotEmpty()) { + val file = File(configuredPath) + if (file.exists() && file.canExecute()) { + return configuredPath + } + } + + // Try to find regal in PATH + val pathExecutable = findExecutableInPath("regal") + if (pathExecutable != null) { + return pathExecutable + } + + return null + } + + private fun findExecutableInPath(executable: String): String? { + val pathEnv = System.getenv("PATH") ?: return null + val pathSeparator = System.getProperty("path.separator") + + for (dir in pathEnv.split(pathSeparator)) { + val file = File(dir, executable) + if (file.exists() && file.canExecute()) { + return file.absolutePath + } + } + + return null + } +} diff --git a/src/main/resources/META-INF/opa-core.xml b/src/main/resources/META-INF/opa-core.xml index e732fd7f..c019fdbb 100644 --- a/src/main/resources/META-INF/opa-core.xml +++ b/src/main/resources/META-INF/opa-core.xml @@ -42,7 +42,7 @@ - Regal Language Server for Open Policy Agent Rego files - + Date: Wed, 30 Jul 2025 11:24:35 +0100 Subject: [PATCH 6/7] lsp4ji: remove debugging message handling Signed-off-by: Charlie Egan --- .../ideaplugin/lsp/RegalLanguageClient.kt | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt index ce846dc4..5b685b6b 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/lsp/RegalLanguageClient.kt @@ -5,34 +5,31 @@ package org.openpolicyagent.ideaplugin.lsp -import com.google.gson.JsonObject import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.redhat.devtools.lsp4ij.client.LanguageClientImpl -import org.eclipse.lsp4j.jsonrpc.services.JsonRequest -import org.eclipse.lsp4j.jsonrpc.services.JsonSegment -import java.util.concurrent.CompletableFuture -@JsonSegment("regal") -interface RegalLanguageClientExtensions { - @JsonRequest("startDebugging") - fun startDebugging(params: JsonObject): CompletableFuture -} - -class RegalLanguageClient(project: Project) : LanguageClientImpl(project), RegalLanguageClientExtensions { +class RegalLanguageClient(project: Project) : LanguageClientImpl(project) { companion object { private val LOG = Logger.getInstance(RegalLanguageClient::class.java) } - override fun startDebugging(params: JsonObject): CompletableFuture { - LOG.info("RegalLanguageClient: startDebugging called with params: $params") - - // Debugging is now handled via DAP integration - // This LSP method is kept for compatibility but redirects to DAP - val response = JsonObject() - response.addProperty("status", "redirected") - response.addProperty("message", "Debug requests are now handled via DAP. Use IntelliJ's Debug menu to start debugging.") - - return CompletableFuture.completedFuture(response) - } + /** + * Custom client message handling can be added here for future Regal-specific features. + * + * To add custom message handling: + * 1. Create an interface extending JsonSegment: + * @JsonSegment("regal") + * interface RegalLanguageClientExtensions { + * @JsonRequest("customMessage") + * fun customMessage(params: JsonObject): CompletableFuture + * } + * 2. Make this class implement the interface: + * class RegalLanguageClient(project: Project) : LanguageClientImpl(project), RegalLanguageClientExtensions + * 3. Implement the custom message handler methods: + * override fun customMessage(params: JsonObject): CompletableFuture { + * // Handle custom message + * return CompletableFuture.completedFuture(JsonObject()) + * } + */ } From 0a4bfa16ca18ba40c5abc0b4a1a76a7f53775b14 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Wed, 30 Jul 2025 11:35:51 +0100 Subject: [PATCH 7/7] lsp4ij: README updates Signed-off-by: Charlie Egan --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b813434e..507a506b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -# Opa IntelliJ plugin -A plugin for [IntelliJ](https://www.jetbrains.com/idea/) that provides support for [Open Policy Agent](https://www.openpolicyagent.org/) +# OPA IntelliJ Plugin +A plugin for [IntelliJ](https://www.jetbrains.com/idea/) that provides support for [Open Policy Agent](https://www.openpolicyagent.org/). Main features are: -* highlighting +* Highlighting * `opa eval` run configuration * `opa test` run configuration +* Regal linter and language server support (diagnostics, code completions, code folding, signature help, document symbols, debugging) # Compatibility @@ -17,18 +18,21 @@ The plugin is compatible with all IntelliJ-based IDEs starting from the version | Other features | + | + | -Plugin has been tested against OPA `0.28.0`, but should work with more recent versions. +Plugin has been tested against OPA `1.6.0`, but should work with more recent versions. # Installation OPA binary must be in the path. Installation instructions for OPA can be found [here](https://www.openpolicyagent.org/docs/latest/#running-opa). -## from Jetbrains repository +For enhanced IDE features (diagnostics, code completions, etc.), Regal must also be installed. +Regal releases are available [here](https://github.com/StyraInc/regal/releases). + +## From JetBrains Repository Go to `Settings / Preferences / Plugins` menu. Then, search `opa` in the `Marketplace` tab and install the plugin. ![Step 3](docs/user/img/3_install_opa_plugin.png) -## from source +## From Source You can build the project from source and then install it. Build instructions are available [here](docs/devel/setup_development_env.md). # Documentation