package com.jetbrains.lang.dart.ide.runner.server.vmService;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XSuspendContext;
import com.intellij.xdebugger.frame.XValue;
import com.jetbrains.lang.dart.ide.runner.server.vmService.frame.DartVmServiceSuspendContext;
import com.jetbrains.lang.dart.ide.runner.server.vmService.frame.DartVmServiceValue;
import org.dartlang.vm.service.VmServiceListener;
import org.dartlang.vm.service.element.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DartVmServiceListener implements VmServiceListener {
private static final Logger LOG = Logger.getInstance(DartVmServiceListener.class.getName());
@NotNull private final DartVmServiceDebugProcess myDebugProcess;
@NotNull private final DartVmServiceBreakpointHandler myBreakpointHandler;
@Nullable private XSourcePosition myLatestSourcePosition;
public DartVmServiceListener(@NotNull final DartVmServiceDebugProcess debugProcess,
@NotNull final DartVmServiceBreakpointHandler breakpointHandler) {
myDebugProcess = debugProcess;
myBreakpointHandler = breakpointHandler;
}
@Override
public void connectionOpened() {
}
@Override
public void received(@NotNull final String streamId, @NotNull final Event event) {
switch (event.getKind()) {
case BreakpointAdded:
// TODO Respond to breakpoints added by the observatory.
// myBreakpointHandler.vmBreakpointAdded(null, event.getIsolate().getId(), event.getBreakpoint());
break;
case BreakpointRemoved:
break;
case BreakpointResolved:
myBreakpointHandler.breakpointResolved(event.getBreakpoint());
break;
case Extension:
break;
case GC:
break;
case Inspect:
break;
case IsolateExit:
myDebugProcess.isolateExit(event.getIsolate());
break;
case IsolateReload:
break;
case IsolateRunnable:
break;
case IsolateStart:
break;
case IsolateUpdate:
break;
case None:
break;
case PauseBreakpoint:
case PauseException:
case PauseInterrupted:
myDebugProcess.isolateSuspended(event.getIsolate());
ApplicationManager.getApplication().executeOnPooledThread(() -> {
final ElementList<Breakpoint> breakpoints = event.getKind() == EventKind.PauseBreakpoint ? event.getPauseBreakpoints() : null;
final InstanceRef exception = event.getKind() == EventKind.PauseException ? event.getException() : null;
onIsolatePaused(event.getIsolate(), breakpoints, exception, event.getTopFrame(), event.getAtAsyncSuspension());
});
break;
case PausePostRequest:
// We get this event after an isolate reload call, when pause after reload has been requested.
// This adds the "supports.pausePostRequest" capability.
myDebugProcess.getVmServiceWrapper().restoreBreakpointsForIsolate(event.getIsolate().getId(),
() -> myDebugProcess.getVmServiceWrapper()
.resumeIsolate(event.getIsolate().getId(), null));
break;
case PauseExit:
break;
case PauseStart:
myDebugProcess.getVmServiceWrapper().handleIsolate(event.getIsolate(), true);
break;
case Resume:
myDebugProcess.isolateResumed(event.getIsolate());
break;
case ServiceExtensionAdded:
break;
case VMUpdate:
break;
case WriteEvent:
myDebugProcess.handleWriteEvent(event.getBytes());
break;
case Unknown:
break;
}
}
@Override
public void connectionClosed() {
if (myDebugProcess.isRemoteDebug()) {
myDebugProcess.getSession().stop();
}
}
void onIsolatePaused(@NotNull final IsolateRef isolateRef,
@Nullable final ElementList<Breakpoint> vmBreakpoints,
@Nullable final InstanceRef exception,
@Nullable final Frame vmTopFrame,
boolean atAsyncSuspension) {
if (vmTopFrame == null) {
myDebugProcess.getSession().positionReached(new XSuspendContext() {
});
return;
}
final DartVmServiceSuspendContext suspendContext =
new DartVmServiceSuspendContext(myDebugProcess, isolateRef, vmTopFrame, exception, atAsyncSuspension);
final XStackFrame xTopFrame = suspendContext.getActiveExecutionStack().getTopFrame();
final XSourcePosition sourcePosition = xTopFrame == null ? null : xTopFrame.getSourcePosition();
if (vmBreakpoints == null || vmBreakpoints.isEmpty()) {
final StepOption latestStep = myDebugProcess.getVmServiceWrapper().getLatestStep();
if (latestStep == StepOption.Over && equalSourcePositions(myLatestSourcePosition, sourcePosition)) {
// continue stepping to change current line
myDebugProcess.getVmServiceWrapper().resumeIsolate(isolateRef.getId(), latestStep);
}
else {
myLatestSourcePosition = sourcePosition;
myDebugProcess.getSession().positionReached(suspendContext);
}
}
else {
if (vmBreakpoints.size() > 1) {
// Shouldn't happen. IDE doesn't allow to set 2 breakpoints on one line.
LOG.error(vmBreakpoints.size() + " breakpoints hit in one shot.");
}
// Remove any temporary (run to cursor) breakpoints.
myBreakpointHandler.removeTemporaryBreakpoints(isolateRef.getId());
final XLineBreakpoint<XBreakpointProperties> xBreakpoint = myBreakpointHandler.getXBreakpoint(vmBreakpoints.get(0));
if (xBreakpoint == null) {
// breakpoint could be set in the Observatory
myLatestSourcePosition = sourcePosition;
myDebugProcess.getSession().positionReached(suspendContext);
return;
}
if ("false".equals(evaluateExpression(isolateRef.getId(), vmTopFrame, xBreakpoint.getConditionExpression()))) {
myDebugProcess.getVmServiceWrapper().resumeIsolate(isolateRef.getId(), null);
return;
}
myLatestSourcePosition = sourcePosition;
final String logExpression = evaluateExpression(isolateRef.getId(), vmTopFrame, xBreakpoint.getLogExpressionObject());
final boolean suspend = myDebugProcess.getSession().breakpointReached(xBreakpoint, logExpression, suspendContext);
if (!suspend) {
myDebugProcess.getVmServiceWrapper().resumeIsolate(isolateRef.getId(), null);
}
}
}
private static boolean equalSourcePositions(@Nullable final XSourcePosition position1, @Nullable final XSourcePosition position2) {
return position1 != null &&
position2 != null &&
position1.getFile().equals(position2.getFile()) &&
position1.getLine() == position2.getLine();
}
@Nullable
private String evaluateExpression(final @NotNull String isolateId,
final @Nullable Frame vmTopFrame,
final @Nullable XExpression xExpression) {
final String evalText = xExpression == null ? null : xExpression.getExpression();
if (vmTopFrame == null || StringUtil.isEmptyOrSpaces(evalText)) return null;
final Ref<String> evalResult = new Ref<>();
final Semaphore semaphore = new Semaphore();
semaphore.down();
myDebugProcess.getVmServiceWrapper().evaluateInFrame(isolateId, vmTopFrame, evalText, new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull final XValue result) {
if (result instanceof DartVmServiceValue) {
evalResult.set(getSimpleStringPresentation(((DartVmServiceValue)result).getInstanceRef()));
}
semaphore.up();
}
@Override
public void errorOccurred(@NotNull final String errorMessage) {
evalResult.set("Failed to evaluate log expression [" + evalText + "]: " + errorMessage);
semaphore.up();
}
});
semaphore.waitFor(1000);
return evalResult.get();
}
@NotNull
private static String getSimpleStringPresentation(@NotNull final InstanceRef instanceRef) {
// getValueAsString() is provided for the instance kinds: Null, Bool, Double, Int, String (value may be truncated), Float32x4, Float64x2, Int32x4, StackTrace
switch (instanceRef.getKind()) {
case Null:
case Bool:
case Double:
case Int:
case String:
case Float32x4:
case Float64x2:
case Int32x4:
case StackTrace:
return instanceRef.getValueAsString();
default:
return "Instance of " + instanceRef.getClassRef().getName();
}
}
}