Skip to content

Commit

Permalink
removed pluginApiGroovy and pluginApiKotlin scopes (because it seems …
Browse files Browse the repository at this point in the history
…that the new gradle plugin for IJ modifies test compilation and runtime classpath, so the scopes don't work anymore 😒); made plugin-api-kotlin independent of any groovy code so that kotlin can be compiled before groovy
  • Loading branch information
dkandalov committed Aug 29, 2024
1 parent 86b6e08 commit bc1cdc8
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 33 deletions.
28 changes: 8 additions & 20 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,35 +77,24 @@ dependencies {
'Copy GDSL file into standardDsls folder'()

sourceSets {
// Keep Groovy and Kotlin API source code in separate source sets, otherwise
// compilation fails because of inter-dependencies between Kotlin and Groovy files which confuse compiler,
// even though overall dependencies are unidirectional: pluginApiKotlin -> pluginApiGroovy -> main.

main {
// Keep Kotlin and Groovy API source code in separate folders for clarity.
java { srcDir "src/main" }
resources { srcDir "resources" }
resources { srcDir "plugin-examples" }
}
pluginApiGroovy {
groovy { srcDir "src/plugin-api-groovy" }
compileClasspath = main.output +
configurations.compileClasspath + configurations.runtimeClasspath +
configurations.pluginApiGroovyCompileClasspath + configurations.pluginApiGroovyRuntimeClasspath
}
pluginApiKotlin {
kotlin { srcDir "src/plugin-api-kotlin" }
compileClasspath = main.output + pluginApiGroovy.output +
configurations.compileClasspath + configurations.pluginApiKotlinCompileClasspath
resources { srcDir "resources" }
resources { srcDir "plugin-examples" }
}
test {
groovy { srcDir "src/test" }
kotlin { srcDir "src/test" }
compileClasspath = main.output + pluginApiGroovy.output + pluginApiKotlin.output +
configurations.testCompileClasspath + configurations.pluginApiGroovyCompileClasspath
runtimeClasspath = test.output + main.output + pluginApiGroovy.output + pluginApiKotlin.output +
configurations.testRuntimeClasspath + configurations.pluginApiGroovyRuntimeClasspath
}
}
// Make Groovy compilation depend on Kotlin as described here
// https://docs.gradle.org/current/userguide/building_java_projects.html#sub:compile_deps_jvm_lang
tasks.named('compileGroovy') {
classpath += files(sourceSets.main.kotlin.classesDirectory)
}

kotlin {
jvmToolchain(21)
Expand All @@ -119,7 +108,6 @@ compileKotlin {
}

jar {
from sourceSets.pluginApiGroovy.output, sourceSets.pluginApiKotlin.output
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

Expand Down
84 changes: 84 additions & 0 deletions src/plugin-api-kotlin/liveplugin/implementation/console.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package liveplugin.implementation

import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.filters.ConsoleInputFilterProvider
import com.intellij.execution.filters.ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS
import com.intellij.execution.filters.InputFilter
import com.intellij.execution.filters.TextConsoleBuilderFactory
import com.intellij.execution.ui.*
import com.intellij.execution.ui.ConsoleViewContentType.NORMAL_OUTPUT
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.LoadingOrder
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import liveplugin.implementation.common.IdeUtil
import liveplugin.runOnEdt
import java.awt.BorderLayout
import java.util.concurrent.atomic.AtomicReference
import javax.swing.JPanel

fun registerConsoleListener(disposable: Disposable, callback: (String) -> String?) {
registerConsoleFilter(disposable) { consoleText ->
callback(consoleText)
null // no filtering of console output
}
}

fun registerConsoleFilter(disposable: Disposable, callback: (String) -> String?) {
registerConsoleFilter(disposable) { text, contentType ->
val newConsoleText = callback(text)
if (newConsoleText == null) null
else listOf(Pair(newConsoleText, contentType))
}
}

fun registerConsoleFilter(disposable: Disposable, inputFilter: InputFilter) {
val extensionPoint = ApplicationManager.getApplication().extensionArea.getExtensionPoint(INPUT_FILTER_PROVIDERS)
extensionPoint.registerExtension(consoleFilterProviderFor(inputFilter), LoadingOrder.FIRST, disposable)
}

private fun consoleFilterProviderFor(inputFilter: InputFilter) =
ConsoleInputFilterProvider { arrayOf(inputFilter) }

fun showInConsole(
message: String,
consoleTitle: String = "",
project: Project,
contentType: ConsoleViewContentType = NORMAL_OUTPUT
): ConsoleView {
val result = AtomicReference<ConsoleView>(null)
val titleRef = AtomicReference(consoleTitle)

runOnEdt {
val consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(project).console
consoleView.print(message, contentType)

val toolbarActions = DefaultActionGroup()
val consoleComponent = MyConsolePanel(consoleView, toolbarActions)
val descriptor = object : RunContentDescriptor(consoleView, null, consoleComponent, titleRef.get()) {
override fun isContentReuseProhibited() = true
override fun getIcon() = AllIcons.Nodes.Plugin
}
val executor = DefaultRunExecutor.getRunExecutorInstance()
toolbarActions.add(com.intellij.execution.ui.actions.CloseAction(executor, descriptor, project))
consoleView.createConsoleActions().forEach { toolbarActions.add(it) }

RunContentManager.getInstance(project).showRunContent(executor, descriptor)
result.set(consoleView)
}
return result.get()
}

private class MyConsolePanel(consoleView: ExecutionConsole, toolbarActions: ActionGroup) : JPanel(BorderLayout()) {
init {
val toolbarPanel = JPanel(BorderLayout())
toolbarPanel.add(ActionManager.getInstance().createActionToolbar(IdeUtil.livePluginActionPlace, toolbarActions, false).component)
add(toolbarPanel, BorderLayout.WEST)
add(consoleView.component, BorderLayout.CENTER)
}
}
19 changes: 19 additions & 0 deletions src/plugin-api-kotlin/liveplugin/implementation/inspections.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package liveplugin.implementation

import com.intellij.codeInspection.InspectionProfileEntry
import com.intellij.codeInspection.InspectionWrapperUtil
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.profile.codeInspection.InspectionProjectProfileManager


fun registerInspectionIn(project: Project, disposable: Disposable = project, inspection: InspectionProfileEntry) {
val projectProfile = InspectionProjectProfileManager.getInstance(project).currentProfile
projectProfile.addTool(project, InspectionWrapperUtil.wrapTool(inspection), emptyMap())
projectProfile.enableTool(inspection.shortName, project)

Disposer.register(disposable) {
projectProfile.removeTool(InspectionWrapperUtil.wrapTool(inspection))
}
}
48 changes: 36 additions & 12 deletions src/plugin-api-kotlin/liveplugin/plugin-util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@
package liveplugin

import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.IntentionManager
import com.intellij.codeInspection.InspectionProfileEntry
import com.intellij.execution.ui.ConsoleView
import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.execution.ui.ConsoleViewContentType.NORMAL_OUTPUT
import com.intellij.ide.BrowserUtil
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.notification.NotificationType.INFORMATION
import com.intellij.notification.Notifications
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy.DEFAULT
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.impl.HTMLEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import liveplugin.PluginUtil.openUrlInEditor
import liveplugin.implementation.Console
import liveplugin.implementation.Editors
import liveplugin.implementation.pluginrunner.kotlin.LivePluginScript
import liveplugin.implementation.registerInspectionIn
import liveplugin.implementation.showInConsole
import java.awt.Component

/**
Expand All @@ -46,15 +52,21 @@ fun show(
groupDisplayId: String = "Live Plugin",
notificationAction: NotificationAction? = null
) {
PluginUtil.show(message, title, notificationType, groupDisplayId, notificationAction)
runLaterOnEdt {
val notification = Notification(groupDisplayId, title, message.toString().ifBlank { "[empty message]" }, notificationType)
if (notificationAction != null) {
notification.addAction(notificationAction)
}
ApplicationManager.getApplication().messageBus.syncPublisher(Notifications.TOPIC).notify(notification)
}
}

fun Project.showInConsole(
message: Any?,
message: String,
consoleTitle: String = "",
contentType: ConsoleViewContentType = Console.guessContentTypeOf(message)
contentType: ConsoleViewContentType = NORMAL_OUTPUT
): ConsoleView =
Console.showInConsole(message, consoleTitle, this, contentType)
showInConsole(message, consoleTitle, this, contentType)

fun Document.executeCommand(project: Project, description: String? = null, callback: Document.() -> Unit) {
runOnEdtWithWriteLock {
Expand All @@ -63,11 +75,22 @@ fun Document.executeCommand(project: Project, description: String? = null, callb
}
}

fun LivePluginScript.registerIntention(intention: IntentionAction): IntentionAction =
PluginUtil.registerIntention(pluginDisposable, intention)
fun LivePluginScript.registerIntention(intention: IntentionAction): IntentionAction {
runOnEdtWithWriteLock {
IntentionManager.getInstance().addAction(intention)
pluginDisposable.whenDisposed {
IntentionManager.getInstance().unregisterIntention(intention)
}
}
return intention
}

fun LivePluginScript.registerInspection(inspection: InspectionProfileEntry) {
PluginUtil.registerInspection(pluginDisposable, inspection)
runOnEdtWithWriteLock {
registerProjectOpenListener(pluginDisposable) { project ->
registerInspectionIn(project, pluginDisposable.registerParent(project), inspection)
}
}
}

fun openInBrowser(url: String) =
Expand All @@ -77,7 +100,8 @@ fun Project.openInIdeBrowser(url: String, title: String = "") =
HTMLEditorProvider.openEditor(this, title, url, null)

fun Project.openInEditor(filePath: String) {
openUrlInEditor("file://${filePath}", this)
val virtualFile = VirtualFileManager.getInstance().refreshAndFindFileByUrl("file://${filePath}") ?: return
FileEditorManager.getInstance(this).openFile(virtualFile, true, true)
}

val logger: Logger = Logger.getInstance("LivePlugin")
Expand All @@ -104,7 +128,7 @@ val VirtualFile.document: Document?
get() = FileDocumentManager.getInstance().getDocument(this)

val Project.currentEditor: Editor?
get() = Editors.currentEditorIn(this)
get() = FileEditorManagerEx.getInstanceEx(this).selectedTextEditor

val Project.currentFile: VirtualFile?
get() = FileEditorManagerEx.getInstanceEx(this).currentFile
Expand Down
2 changes: 1 addition & 1 deletion src/test/liveplugin/PluginUtilTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class PluginUtilTest {

asString(new IllegalStateException("message")).split(/\n/).toList().with {
assert get(0) == "java.lang.IllegalStateException: message"
assert get(1) == "\tat java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)"
assert get(1).startsWith("\tat java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance")
}
}
}

0 comments on commit bc1cdc8

Please sign in to comment.