From 377aae0af00132b9d0ac844128401a60d09f460c Mon Sep 17 00:00:00 2001 From: Konstantin Ulitin Date: Mon, 19 Jun 2017 20:18:46 +0300 Subject: [PATCH 1/3] JS debugger: proper icon for active execution stack --- .../debugger-ui/src/SuspendContextView.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt index 183adc41ffad..a04703052fb6 100644 --- a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt +++ b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt @@ -71,7 +71,7 @@ abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProces RunningThreadExecutionStackView(displayName) } else { - ExecutionStackView(context, activeStack.viewSupport, null, null, displayName) + InactiveAtBreakpointExecutionStackView(displayName) } } @@ -105,12 +105,17 @@ class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(dis override fun getTopFrame(): XStackFrame? = null } -// icon ThreadCurrent would be preferred for active thread, but it won't be updated on stack change +class InactiveAtBreakpointExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadAtBreakpoint) { + override fun getTopFrame(): XStackFrame? = null + + override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {} +} + class ExecutionStackView(val suspendContext: SuspendContext<*>, internal val viewSupport: DebuggerViewSupport, private val topFrameScript: Script?, private val topFrameSourceInfo: SourceInfo? = null, - displayName: String = "") : XExecutionStack(displayName, AllIcons.Debugger.ThreadAtBreakpoint) { + displayName: String = "") : XExecutionStack(displayName, AllIcons.Debugger.ThreadCurrent) { private var topCallFrameView: CallFrameView? = null override fun getTopFrame(): CallFrameView? { From 3e9cc5b8292bc6ed5a5107b7af0179687f2277ba Mon Sep 17 00:00:00 2001 From: Konstantin Ulitin Date: Wed, 21 Jun 2017 14:13:30 +0300 Subject: [PATCH 2/3] debug web workers the same way as node child process allow several paused threads, choosing active, stepping, resuming --- .../backend/src/SuspendContext.kt | 3 - .../backend/src/SuspendContextManagerBase.kt | 2 +- .../debugger/DebugEventListener.java | 3 +- .../debugger-ui/src/DebugProcessImpl.kt | 2 +- .../debugger-ui/src/SuspendContextView.kt | 129 ++++++++++++++---- 5 files changed, 104 insertions(+), 35 deletions(-) diff --git a/platform/script-debugger/backend/src/SuspendContext.kt b/platform/script-debugger/backend/src/SuspendContext.kt index 1c9cb81129eb..39fc56e98e95 100755 --- a/platform/script-debugger/backend/src/SuspendContext.kt +++ b/platform/script-debugger/backend/src/SuspendContext.kt @@ -58,9 +58,6 @@ interface SuspendContext { val vm: Vm get() = throw UnsupportedOperationException() - - val workerId: String? - get() = null } abstract class ContextDependentAsyncResultConsumer(private val context: SuspendContext<*>) : Consumer { diff --git a/platform/script-debugger/backend/src/SuspendContextManagerBase.kt b/platform/script-debugger/backend/src/SuspendContextManagerBase.kt index 992a02cdf92a..7df95c55e5ab 100644 --- a/platform/script-debugger/backend/src/SuspendContextManagerBase.kt +++ b/platform/script-debugger/backend/src/SuspendContextManagerBase.kt @@ -55,7 +55,7 @@ abstract class SuspendContextManagerBase, CAL throw IllegalStateException("Expected $context, but another suspend context exists") } context.valueManager.markObsolete() - debugListener.resumed() + debugListener.resumed(context.vm) } override val context: SuspendContext? diff --git a/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java b/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java index 764091b57961..c32bf65f9410 100755 --- a/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java +++ b/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java @@ -32,8 +32,9 @@ public interface DebugEventListener extends EventListener { /** * Reports the virtual machine has resumed. This can happen * asynchronously, due to a user action in the browser (without explicitly resuming the VM through + * @param vm */ - default void resumed() { + default void resumed(Vm vm) { } /** diff --git a/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt index 620d31075949..88ddcb3973b9 100644 --- a/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt +++ b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt @@ -135,7 +135,7 @@ abstract class DebugProcessImpl>(session: XDebugSession, } val XSuspendContext?.vm: Vm - get() = (this as? SuspendContextView)?.activeExecutionStack?.suspendContext?.vm ?: mainVm!! + get() = (this as? SuspendContextView)?.activeVm ?: mainVm!! override final fun startForceStepInto(context: XSuspendContext?) { isForceStep = true diff --git a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt index a04703052fb6..a81a30980381 100644 --- a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt +++ b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt @@ -16,6 +16,7 @@ package org.jetbrains.debugger import com.intellij.icons.AllIcons +import com.intellij.openapi.diagnostic.logger import com.intellij.ui.ColoredTextContainer import com.intellij.ui.SimpleTextAttributes import com.intellij.util.ui.UIUtil @@ -30,8 +31,6 @@ import org.jetbrains.debugger.frame.CallFrameView import org.jetbrains.debugger.values.StringValue import java.util.* -const val MAIN_LOOP_NAME = "main loop" - /** * Debugging several VMs simultaneously should be similar to debugging multi-threaded Java application when breakpoints suspend only one thread. * 1. When thread is paused and another thread reaches breakpoint, show notification about it with possibility to switch thread. @@ -41,46 +40,48 @@ const val MAIN_LOOP_NAME = "main loop" * 4. Stepping/releasing updates current thread icon and clears frame, but doesn't switch thread. To release other threads, user needs to * select them firstly. */ -abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess, protected val activeStack: ExecutionStackView) : XSuspendContext() { +abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess, + activeStack: ExecutionStackView, + @Volatile var activeVm: Vm) + : XSuspendContext() { - protected open val stacks: Array by lazy { + private val stacks: MutableMap = Collections.synchronizedMap(LinkedHashMap()) + + init { val mainVm = debugProcess.mainVm val vmList = debugProcess.collectVMs if (mainVm != null && !vmList.isEmpty()) { - val list = ArrayList() - // main vm should go first - vmList.mapNotNullTo(list) { + vmList.forEach { val context = it.suspendContextManager.context - if (context == activeStack.suspendContext) { - activeStack - } - else { - createStackView(context, it.presentableName) - } + + val stack: ScriptExecutionStack = + if (context == null) { + RunningThreadExecutionStackView(it) + } + else if (context == activeStack.suspendContext) { + activeStack + } + else { + logger().error("Paused VM was lost.") + InactiveAtBreakpointExecutionStackView(it) + } + stacks[it] = stack } - list.toTypedArray() } else { - arrayOf(activeStack) + stacks[activeVm] = activeStack } } - private fun createStackView(context: SuspendContext<*>?, displayName: String): XExecutionStack { - return if (context == null) { - RunningThreadExecutionStackView(displayName) - } - else { - InactiveAtBreakpointExecutionStackView(displayName) - } - } + override fun getActiveExecutionStack() = stacks[activeVm] - override fun getActiveExecutionStack() = activeStack - - override fun getExecutionStacks(): Array = stacks + override fun getExecutionStacks(): Array = stacks.values.toTypedArray() fun evaluateExpression(expression: String): Promise { + val activeStack = stacks[activeVm]!! val frame = activeStack.topFrame ?: return rejectedPromise("Top frame is null") + if (frame !is CallFrameView) return rejectedPromise("Can't evaluate on non-paused thread") return evaluateExpression(frame.callFrame.evaluateContext, expression) } @@ -94,9 +95,57 @@ abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProces resolvedPromise(value.valueString!!) } } + + fun pauseInactiveThread(inactiveThread: ExecutionStackView) { + stacks[inactiveThread.vm] = inactiveThread + } + + fun hasPausedThreads(): Boolean { + return stacks.values.any { it is ExecutionStackView } + } + + fun resume(vm: Vm) { + val prevStack = stacks[vm] + if (prevStack is ExecutionStackView) { + stacks[vm] = RunningThreadExecutionStackView(prevStack.vm) + } + } + + fun resumeCurrentThread() { + resume(activeVm) + } + + fun setActiveThread(selectedStackFrame: XStackFrame?): Boolean { + if (selectedStackFrame !is CallFrameView) return false + + var selectedVm: Vm? = null + for ((key, value) in stacks) { + if (value is ExecutionStackView && value.topFrame?.vm == selectedStackFrame.vm) { + selectedVm = key + break + } + } + + val selectedVmStack = stacks[selectedVm] + if (selectedVm != null && selectedVmStack is ExecutionStackView) { + activeVm = selectedVm + stacks[selectedVm] = selectedVmStack.copyWithIsCurrent(true) + + stacks.keys.forEach { + val stack = stacks[it] + if (it != selectedVm && stack is ExecutionStackView) { + stacks[it] = stack.copyWithIsCurrent(false) + } + } + + return stacks[selectedVm] !== selectedVmStack + } + + return false + } } -class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadRunning) { +class RunningThreadExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadRunning) { override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) { // add dependency to DebuggerBundle? container?.errorOccurred("Frames not available for unsuspended thread") @@ -105,17 +154,33 @@ class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(dis override fun getTopFrame(): XStackFrame? = null } -class InactiveAtBreakpointExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadAtBreakpoint) { +class InactiveAtBreakpointExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadAtBreakpoint) { override fun getTopFrame(): XStackFrame? = null override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {} } +abstract class ScriptExecutionStack(val vm: Vm, displayName: String, icon: javax.swing.Icon): XExecutionStack(displayName, icon) { + override fun hashCode(): Int { + return vm.hashCode() + } + + override fun equals(other: Any?): Boolean { + return other is ScriptExecutionStack && other.vm == vm + } +} + +// TODO should be AllIcons.Debugger.ThreadCurrent, but because of strange logic to add non-equal XExecutionStacks we can't update icon. +private fun getThreadIcon(isCurrent: Boolean) = AllIcons.Debugger.ThreadAtBreakpoint + class ExecutionStackView(val suspendContext: SuspendContext<*>, internal val viewSupport: DebuggerViewSupport, private val topFrameScript: Script?, private val topFrameSourceInfo: SourceInfo? = null, - displayName: String = "") : XExecutionStack(displayName, AllIcons.Debugger.ThreadCurrent) { + displayName: String = "", + isCurrent: Boolean = true) + : ScriptExecutionStack(suspendContext.vm, displayName, getThreadIcon(isCurrent)) { + private var topCallFrameView: CallFrameView? = null override fun getTopFrame(): CallFrameView? { @@ -164,6 +229,12 @@ class ExecutionStackView(val suspendContext: SuspendContext<*>, container.addStackFrames(result, true) } } + + fun copyWithIsCurrent(isCurrent: Boolean): ExecutionStackView { + if (icon == getThreadIcon(isCurrent)) return this + + return ExecutionStackView(suspendContext, viewSupport, topFrameScript, topFrameSourceInfo, displayName, isCurrent) + } } private val PREFIX_ATTRIBUTES = SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, UIUtil.getInactiveTextColor()) From ff516de802f2c8179523650d68d310fbe3b75042 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 21 Jun 2017 13:18:33 +0200 Subject: [PATCH 3/3] platform: added javadoc for ModuleExtension and moduleService --- .../openapi/module/ModuleServiceManager.java | 12 ++++++++++++ .../com/intellij/openapi/roots/ModuleExtension.java | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java b/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java index ad7e3b7265e6..59e3027ea779 100644 --- a/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java +++ b/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java @@ -23,6 +23,18 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** + * Provide a module service if you need to can be used to store data associated with a module. Its implementation should be registered in plugin.xml: + *
+ * <extensions defaultExtensionNs="com.intellij">
+ *   <moduleService serviceInterface="qualified-interface-class-name"
+                    serviceImplementation="qualified-implementation-class-name"/>
+ * </extensions>
+ * 
+ * Class is loaded and its instance is created lazily when {@link #getService(Module, Class)} method is called for the first time. + *

+ * If the service implementation class implements {@link com.intellij.openapi.components.PersistentStateComponent} interface its state will + * be persisted in the module configuration file. + * * @author yole */ public class ModuleServiceManager { diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java b/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java index 7f5a2319066c..83af5e35cde3 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java @@ -22,7 +22,11 @@ import org.jdom.Element; import org.jetbrains.annotations.NotNull; /** - * Implement {@link com.intellij.openapi.components.PersistentStateComponent} to be serializable. + * Extend this class to provide additional module-level properties which can be edited in Project Structure dialog. For ordinary module-level + * properties use {@link com.intellij.openapi.module.ModuleServiceManager module service} instead. + *

+ * If the inheritor implements {@link com.intellij.openapi.components.PersistentStateComponent} its state will be persisted in the module + * configuration file. */ public abstract class ModuleExtension implements Disposable { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.moduleExtension");