Merge remote-tracking branch 'origin/master'

This commit is contained in:
Alexander Luyblinsky
2017-06-21 14:31:35 +03:00
7 changed files with 126 additions and 36 deletions
@@ -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:
* <pre>
* &lt;extensions defaultExtensionNs="com.intellij"&gt;
* &lt;moduleService serviceInterface="qualified-interface-class-name"
serviceImplementation="qualified-implementation-class-name"/&gt;
* &lt;/extensions&gt;
* </pre>
* Class is loaded and its instance is created lazily when {@link #getService(Module, Class)} method is called for the first time.
* <p/>
* 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 {
@@ -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.
* <p/>
* 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<ModuleExtension> EP_NAME = ExtensionPointName.create("com.intellij.moduleExtension");
@@ -58,9 +58,6 @@ interface SuspendContext<CALL_FRAME : CallFrame> {
val vm: Vm
get() = throw UnsupportedOperationException()
val workerId: String?
get() = null
}
abstract class ContextDependentAsyncResultConsumer<T>(private val context: SuspendContext<*>) : Consumer<T> {
@@ -55,7 +55,7 @@ abstract class SuspendContextManagerBase<T : SuspendContextBase<CALL_FRAME>, CAL
throw IllegalStateException("Expected $context, but another suspend context exists")
}
context.valueManager.markObsolete()
debugListener.resumed()
debugListener.resumed(context.vm)
}
override val context: SuspendContext<CALL_FRAME>?
@@ -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) {
}
/**
@@ -135,7 +135,7 @@ abstract class DebugProcessImpl<C : VmConnection<*>>(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
@@ -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<out XExecutionStack> by lazy {
private val stacks: MutableMap<Vm, ScriptExecutionStack> = Collections.synchronizedMap(LinkedHashMap<Vm, ScriptExecutionStack>())
init {
val mainVm = debugProcess.mainVm
val vmList = debugProcess.collectVMs
if (mainVm != null && !vmList.isEmpty()) {
val list = ArrayList<XExecutionStack>()
// 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<SuspendContextView>().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 {
ExecutionStackView(context, activeStack.viewSupport, null, null, displayName)
}
}
override fun getActiveExecutionStack() = stacks[activeVm]
override fun getActiveExecutionStack() = activeStack
override fun getExecutionStacks(): Array<out XExecutionStack> = stacks
override fun getExecutionStacks(): Array<out XExecutionStack> = stacks.values.toTypedArray()
fun evaluateExpression(expression: String): Promise<String> {
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,12 +154,33 @@ 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(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.ThreadAtBreakpoint) {
displayName: String = "",
isCurrent: Boolean = true)
: ScriptExecutionStack(suspendContext.vm, displayName, getThreadIcon(isCurrent)) {
private var topCallFrameView: CallFrameView? = null
override fun getTopFrame(): CallFrameView? {
@@ -159,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())