diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineXDebuggerManagerListener.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineXDebuggerManagerListener.kt new file mode 100644 index 00000000000..8ede98a952a --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineXDebuggerManagerListener.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.coroutines + +import com.intellij.debugger.DebuggerInvocationUtil +import com.intellij.debugger.DebuggerManagerEx +import com.intellij.debugger.actions.ThreadDumpAction +import com.intellij.debugger.impl.DebuggerSession +import com.intellij.execution.configurations.JavaParameters +import com.intellij.execution.configurations.RunConfigurationBase +import com.intellij.execution.ui.RunnerLayoutUi +import com.intellij.execution.ui.layout.PlaceInGrid +import com.intellij.execution.ui.layout.impl.RunnerContentUi +import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.registry.Registry +import com.intellij.ui.content.ContentManagerAdapter +import com.intellij.ui.content.ContentManagerEvent +import com.intellij.util.messages.MessageBusConnection +import com.intellij.xdebugger.XDebugProcess +import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.XDebuggerManagerListener +import org.jetbrains.kotlin.psi.UserDataProperty + +class CoroutineXDebuggerManagerListener(val project: Project) : XDebuggerManagerListener { + lateinit var connection: MessageBusConnection + + override fun processStarted(debugProcess: XDebugProcess) { + DebuggerInvocationUtil.swingInvokeLater(project) { + val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession + val ui = session?.xDebugSession?.ui + if (ui != null) + registerCoroutinesPanel(ui, session) + } + } + + override fun processStopped(debugProcess: XDebugProcess) { + connection.disconnect() + project.listenerCreated = false + } + + /** + * Adds panel to XDebugSessionTab + */ + private fun registerCoroutinesPanel(ui: RunnerLayoutUi, session: DebuggerSession) { + val panel = CoroutinesPanel(session.project, session.contextManager) + val content = ui.createContent( + "CoroutinesContent", panel, "Coroutines", // TODO(design) + AllIcons.Debugger.ThreadGroup, null + ) + content.isCloseable = false + ui.addContent(content, 0, PlaceInGrid.left, true) + ui.addListener(object : ContentManagerAdapter() { + override fun selectionChanged(event: ContentManagerEvent) { + if (event.content === content) { + if (content.isSelected) { + panel.setUpdateEnabled(true) + if (panel.isRefreshNeeded) { + panel.rebuildIfVisible(DebuggerSession.Event.CONTEXT) + } + } else { + panel.setUpdateEnabled(false) + } + } + } + }, content) + // add coroutine dump button: due to api problem left toolbar is copied, modified and reset to tab + val runnerContent = (ui.options as RunnerLayoutUiImpl).getData(RunnerContentUi.KEY.name) as RunnerContentUi + val modifiedActions = runnerContent.getActions(true) + val pos = modifiedActions.indexOfLast { it is ThreadDumpAction } + modifiedActions.add(pos + 1, ActionManager.getInstance().getAction("Kotlin.XDebugger.CoroutinesDump")) + ui.options.setLeftToolbar(DefaultActionGroup(modifiedActions), ActionPlaces.DEBUGGER_TOOLBAR) + } + + fun attach() { + connection = project.messageBus.connect() + connection.subscribe(XDebuggerManager.TOPIC, this) + project.listenerCreated = true + } +} + +internal var Project.listenerCreated: Boolean? by UserDataProperty(Key.create("COROUTINES_DEBUG_TAB_CREATE_LISTENER")) + +internal fun isCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines") + +internal fun ?> initializeCoroutineAgent( + params: JavaParameters, + it: String?, + configuration: T +): Boolean { + params.vmParametersList?.add("-javaagent:$it") + params.vmParametersList?.add("-ea") + val project = (configuration as RunConfigurationBase<*>).project + // add listener to put coroutines tab into debugger tab + if (project.listenerCreated != true) { // prevent multiple listeners creation + CoroutineXDebuggerManagerListener(project).attach() + return true + } + return false +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt index 4f71cc60ef6..c5ba8da45b4 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt @@ -4,124 +4,35 @@ */ package org.jetbrains.kotlin.idea.debugger.coroutines -import com.intellij.debugger.DebuggerInvocationUtil -import com.intellij.debugger.DebuggerManagerEx -import com.intellij.debugger.actions.ThreadDumpAction -import com.intellij.debugger.impl.DebuggerSession import com.intellij.execution.RunConfigurationExtension import com.intellij.execution.configurations.DebuggingRunnerData import com.intellij.execution.configurations.JavaParameters import com.intellij.execution.configurations.RunConfigurationBase import com.intellij.execution.configurations.RunnerSettings -import com.intellij.execution.ui.RunnerLayoutUi -import com.intellij.execution.ui.layout.PlaceInGrid -import com.intellij.execution.ui.layout.impl.RunnerContentUi -import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.ActionPlaces -import com.intellij.openapi.actionSystem.DefaultActionGroup -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key -import com.intellij.openapi.util.registry.Registry -import com.intellij.ui.content.ContentManagerAdapter -import com.intellij.ui.content.ContentManagerEvent -import com.intellij.util.messages.MessageBusConnection -import com.intellij.xdebugger.XDebugProcess -import com.intellij.xdebugger.XDebuggerManager -import com.intellij.xdebugger.XDebuggerManagerListener -import org.jetbrains.kotlin.psi.UserDataProperty +import com.intellij.openapi.diagnostic.Logger /** * Installs coroutines debug agent and coroutines tab if `kotlinx.coroutines.debug` dependency is found */ @Suppress("IncompatibleAPI") class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() { - private var Project.listenerCreated: Boolean? by UserDataProperty(Key.create("COROUTINES_DEBUG_TAB_CREATE_LISTENER")) + private val log = Logger.getInstance(this::class.java) - override fun isApplicableFor(configuration: RunConfigurationBase<*>): Boolean { - return Registry.`is`("kotlin.debugger.coroutines") - } + override fun isApplicableFor(configuration: RunConfigurationBase<*>) = isCoroutineDebuggerEnabled() override fun ?> updateJavaParameters( configuration: T, params: JavaParameters?, runnerSettings: RunnerSettings? ) { - if (!Registry.`is`("kotlin.debugger.coroutines")) return - if (runnerSettings is DebuggingRunnerData && params != null - && params.classPath != null - && params.classPath.pathList.isNotEmpty() - ) { - params.classPath.pathList.forEach { - if (!it.contains("kotlinx-coroutines-debug")) return@forEach - // if debug library is included into project, add agent which installs probes - params.vmParametersList?.add("-javaagent:$it") - params.vmParametersList?.add("-ea") - val project = (configuration as RunConfigurationBase<*>).project - // add listener to put coroutines tab into debugger tab - if (project.listenerCreated != true) { // prevent multiple listeners creation - val connection = project.messageBus.connect() - connection.subscribe( - XDebuggerManager.TOPIC, createListener(project, connection) - ) - project.listenerCreated = true - return - } - } - - } - } - - private fun createListener(project: Project, connection: MessageBusConnection): XDebuggerManagerListener { - return object : XDebuggerManagerListener { - override fun processStarted(debugProcess: XDebugProcess) { - DebuggerInvocationUtil.swingInvokeLater(project) { - val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession - val ui = session?.xDebugSession?.ui - if (ui != null) - registerCoroutinesPanel(ui, session) - } - } - - override fun processStopped(debugProcess: XDebugProcess) { - connection.disconnect() - project.listenerCreated = false + if (!isCoroutineDebuggerEnabled()) return + if (runnerSettings is DebuggingRunnerData) { + try { + val kotlinxCoroutinesClassPathLib = params?.classPath?.pathList?.first { it.contains("kotlinx-coroutines-debug") } + initializeCoroutineAgent(params!!, kotlinxCoroutinesClassPathLib, configuration) + } catch (e: NoSuchElementException) { + log.warn("'kotlinx-coroutines-debug' not found in classpath. Coroutine debugger disabled.") } } } - - /** - * Adds panel to XDebugSessionTab - */ - private fun registerCoroutinesPanel(ui: RunnerLayoutUi, session: DebuggerSession) { - val panel = CoroutinesPanel(session.project, session.contextManager) - val content = ui.createContent( - "CoroutinesContent", panel, "Coroutines", // TODO(design) - AllIcons.Debugger.ThreadGroup, null - ) - content.isCloseable = false - ui.addContent(content, 0, PlaceInGrid.left, true) - ui.addListener(object : ContentManagerAdapter() { - override fun selectionChanged(event: ContentManagerEvent) { - if (event.content === content) { - if (content.isSelected) { - panel.setUpdateEnabled(true) - if (panel.isRefreshNeeded) { - panel.rebuildIfVisible(DebuggerSession.Event.CONTEXT) - } - } else { - panel.setUpdateEnabled(false) - } - } - } - }, content) - // add coroutine dump button: due to api problem left toolbar is copied, modified and reset to tab - val runnerContent = (ui.options as RunnerLayoutUiImpl).getData(RunnerContentUi.KEY.name) as RunnerContentUi - val modifiedActions = runnerContent.getActions(true) - val pos = modifiedActions.indexOfLast { it is ThreadDumpAction } - modifiedActions.add(pos + 1, ActionManager.getInstance().getAction("Kotlin.XDebugger.CoroutinesDump")) - ui.options.setLeftToolbar(DefaultActionGroup(modifiedActions), ActionPlaces.DEBUGGER_TOOLBAR) - } - } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt.193 b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt.193 index 411f2b645ae..bb196287791 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt.193 +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutinesDebugConfigurationExtension.kt.193 @@ -4,124 +4,35 @@ */ package org.jetbrains.kotlin.idea.debugger.coroutines -import com.intellij.debugger.DebuggerInvocationUtil -import com.intellij.debugger.DebuggerManagerEx -import com.intellij.debugger.actions.ThreadDumpAction -import com.intellij.debugger.impl.DebuggerSession import com.intellij.execution.RunConfigurationExtension import com.intellij.execution.configurations.DebuggingRunnerData import com.intellij.execution.configurations.JavaParameters import com.intellij.execution.configurations.RunConfigurationBase import com.intellij.execution.configurations.RunnerSettings -import com.intellij.execution.ui.RunnerLayoutUi -import com.intellij.execution.ui.layout.PlaceInGrid -import com.intellij.execution.ui.layout.impl.RunnerContentUi -import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.ActionPlaces -import com.intellij.openapi.actionSystem.DefaultActionGroup -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key -import com.intellij.openapi.util.registry.Registry -import com.intellij.ui.content.ContentManagerAdapter -import com.intellij.ui.content.ContentManagerEvent -import com.intellij.util.messages.MessageBusConnection -import com.intellij.xdebugger.XDebugProcess -import com.intellij.xdebugger.XDebuggerManager -import com.intellij.xdebugger.XDebuggerManagerListener -import org.jetbrains.kotlin.psi.UserDataProperty +import com.intellij.openapi.diagnostic.Logger /** * Installs coroutines debug agent and coroutines tab if `kotlinx.coroutines.debug` dependency is found */ @Suppress("IncompatibleAPI") class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() { - private var Project.listenerCreated: Boolean? by UserDataProperty(Key.create("COROUTINES_DEBUG_TAB_CREATE_LISTENER")) + private val log = Logger.getInstance(this::class.java) - override fun isApplicableFor(configuration: RunConfigurationBase<*>): Boolean { - return Registry.`is`("kotlin.debugger.coroutines") - } + override fun isApplicableFor(configuration: RunConfigurationBase<*>) = isCoroutineDebuggerEnabled() override fun ?> updateJavaParameters( configuration: T, params: JavaParameters, runnerSettings: RunnerSettings? ) { - if (!Registry.`is`("kotlin.debugger.coroutines")) return - if (runnerSettings is DebuggingRunnerData - && params.classPath != null - && params.classPath.pathList.isNotEmpty() - ) { - params.classPath.pathList.forEach { - if (!it.contains("kotlinx-coroutines-debug")) return@forEach - // if debug library is included into project, add agent which installs probes - params.vmParametersList?.add("-javaagent:$it") - params.vmParametersList?.add("-ea") - val project = (configuration as RunConfigurationBase<*>).project - // add listener to put coroutines tab into debugger tab - if (project.listenerCreated != true) { // prevent multiple listeners creation - val connection = project.messageBus.connect() - connection.subscribe( - XDebuggerManager.TOPIC, createListener(project, connection) - ) - project.listenerCreated = true - return - } - } - - } - } - - private fun createListener(project: Project, connection: MessageBusConnection): XDebuggerManagerListener { - return object : XDebuggerManagerListener { - override fun processStarted(debugProcess: XDebugProcess) { - DebuggerInvocationUtil.swingInvokeLater(project) { - val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession - val ui = session?.xDebugSession?.ui - if (ui != null) - registerCoroutinesPanel(ui, session) - } - } - - override fun processStopped(debugProcess: XDebugProcess) { - connection.disconnect() - project.listenerCreated = false + if (!isCoroutineDebuggerEnabled()) return + if (runnerSettings is DebuggingRunnerData) { + try { + val kotlinxCoroutinesClassPathLib = params?.classPath?.pathList?.first { it.contains("kotlinx-coroutines-debug") } + initializeCoroutineAgent(params!!, kotlinxCoroutinesClassPathLib, configuration) + } catch (e: NoSuchElementException) { + log.warn("'kotlinx-coroutines-debug' not found in classpath. Coroutine debugger disabled.") } } } - - /** - * Adds panel to XDebugSessionTab - */ - private fun registerCoroutinesPanel(ui: RunnerLayoutUi, session: DebuggerSession) { - val panel = CoroutinesPanel(session.project, session.contextManager) - val content = ui.createContent( - "CoroutinesContent", panel, "Coroutines", // TODO(design) - AllIcons.Debugger.ThreadGroup, null - ) - content.isCloseable = false - ui.addContent(content, 0, PlaceInGrid.left, true) - ui.addListener(object : ContentManagerAdapter() { - override fun selectionChanged(event: ContentManagerEvent) { - if (event.content === content) { - if (content.isSelected) { - panel.setUpdateEnabled(true) - if (panel.isRefreshNeeded) { - panel.rebuildIfVisible(DebuggerSession.Event.CONTEXT) - } - } else { - panel.setUpdateEnabled(false) - } - } - } - }, content) - // add coroutine dump button: due to api problem left toolbar is copied, modified and reset to tab - val runnerContent = (ui.options as RunnerLayoutUiImpl).getData(RunnerContentUi.KEY.name) as RunnerContentUi - val modifiedActions = runnerContent.getActions(true) - val pos = modifiedActions.indexOfLast { it is ThreadDumpAction } - modifiedActions.add(pos + 1, ActionManager.getInstance().getAction("Kotlin.XDebugger.CoroutinesDump")) - ui.options.setLeftToolbar(DefaultActionGroup(modifiedActions), ActionPlaces.DEBUGGER_TOOLBAR) - } - } \ No newline at end of file