diff --git a/src/org/intellij/erlang/debugger/node/ErlangProcessSnapshot.java b/src/org/intellij/erlang/debugger/node/ErlangProcessSnapshot.java index 9c85203ee..84a09b562 100644 --- a/src/org/intellij/erlang/debugger/node/ErlangProcessSnapshot.java +++ b/src/org/intellij/erlang/debugger/node/ErlangProcessSnapshot.java @@ -81,4 +81,11 @@ public String getExitReason() { public List getStack() { return myStack; } + + public boolean isSameBreakpoint(@NotNull ErlangProcessSnapshot snapshot) { + return myPid.equals(snapshot.getPid()) + && myBreakModule != null + && myBreakModule.equals(snapshot.getBreakModule()) + && myBreakLine == snapshot.getBreakLine(); + } } diff --git a/src/org/intellij/erlang/debugger/xdebug/ErlangXDebugProcess.java b/src/org/intellij/erlang/debugger/xdebug/ErlangXDebugProcess.java index 1ebd92712..f6614d1aa 100644 --- a/src/org/intellij/erlang/debugger/xdebug/ErlangXDebugProcess.java +++ b/src/org/intellij/erlang/debugger/xdebug/ErlangXDebugProcess.java @@ -16,8 +16,10 @@ package org.intellij.erlang.debugger.xdebug; +import com.ericsson.otp.erlang.OtpErlangAtom; import com.ericsson.otp.erlang.OtpErlangObject; import com.ericsson.otp.erlang.OtpErlangPid; +import com.ericsson.otp.erlang.OtpErlangTuple; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.OSProcessHandler; @@ -72,7 +74,6 @@ import static org.intellij.erlang.debugger.ErlangDebuggerLog.LOG; public class ErlangXDebugProcess extends XDebugProcess implements ErlangDebuggerEventListener { - private final XDebugSession mySession; private final ExecutionEnvironment myExecutionEnvironment; private final ErlangRunningState myRunningState; private final ErlangDebuggerNode myDebuggerNode; @@ -83,11 +84,17 @@ public class ErlangXDebugProcess extends XDebugProcess implements ErlangDebugger private ConcurrentHashMap> myPositionToLineBreakpointMap = new ConcurrentHashMap<>(); private XDebuggerEvaluator.XEvaluationCallback myEvalCallback = null; + // right after evaluating an expression, the erlang debugger will trigger a new "breakpoint reached" event + // unfortunately, that means this plugin will try to concurrently render the new state of the bindings as + // well as the result from the evaluation, which Intellij doesn't seem to handle too well, resulting in + // the debugging session losing track of what the current frame is... so we just ignore the next "breakpoint + // reached" event after evaluating an expression if it's the same breakpoint - better ideas welcome! + private ErlangProcessSnapshot myLastBreakpointReached = null; + private boolean myIgnoreNextBreakpointIfSame = false; public ErlangXDebugProcess(@NotNull XDebugSession session, ExecutionEnvironment env) throws ExecutionException { //TODO add debug build targets and make sure the project is built using them. super(session); - mySession = session; session.setPauseActionSupported(false); @@ -124,18 +131,46 @@ public ErlangDebugLocationResolver getLocationResolver() { public synchronized void evaluateExpression(@NotNull String expression, @NotNull XDebuggerEvaluator.XEvaluationCallback callback, @NotNull ErlangTraceElement traceElement) { - // need to pause the debugging session otherwise the callback might get invalidated - mySession.pause(); myEvalCallback = callback; + // see the comment about myIgnoreNextBreakpointIfSame + myIgnoreNextBreakpointIfSame = true; myDebuggerNode.evaluate(expression, traceElement); } @Override public synchronized void handleEvaluationResponse(OtpErlangObject response) { if (myEvalCallback != null) { - myEvalCallback.evaluated(ErlangXValueFactory.create(response)); - mySession.resume(); + String error = maybeExtractErrorFromEvaluationResponse(response); + + if (error == null) { + myEvalCallback.evaluated(ErlangXValueFactory.create(response)); + } + else { + myEvalCallback.errorOccurred(error); + } + } + } + + // Parses the response from an evaluation and determines whether it's an error + // response or not; if it is an error, formats it as a displayable string, if + // it's not, just returns null + private static String maybeExtractErrorFromEvaluationResponse(OtpErlangObject response) { + // is it a parsing error? + if (response instanceof OtpErlangAtom && ((OtpErlangAtom) response).atomValue().equals("Parse error")) { + return "Parse error"; } + + // is it an uncaught exception? + if (response instanceof OtpErlangTuple) { + OtpErlangObject[] elements = ((OtpErlangTuple) response).elements(); + if (elements.length == 2 + && elements[0] instanceof OtpErlangAtom + && ((OtpErlangAtom) elements[0]).atomValue().equals("EXIT")) { + return "Uncaught exception: " + elements[1]; + } + } + + return null; } @Override @@ -177,9 +212,18 @@ public void breakpointIsSet(String module, int line) { } @Override - public void breakpointReached(final OtpErlangPid pid, List snapshots) { + public synchronized void breakpointReached(final OtpErlangPid pid, List snapshots) { ErlangProcessSnapshot processInBreakpoint = ContainerUtil.find(snapshots, erlangProcessSnapshot -> erlangProcessSnapshot.getPid().equals(pid)); assert processInBreakpoint != null; + + boolean ignoreIfSame = myIgnoreNextBreakpointIfSame; + myIgnoreNextBreakpointIfSame = false; + if (ignoreIfSame && myLastBreakpointReached.isSameBreakpoint(processInBreakpoint)) { + // see the comment about myLastBreakpointReached + return; + } + + myLastBreakpointReached = processInBreakpoint; ErlangSourcePosition breakPosition = ErlangSourcePosition.create(myLocationResolver, processInBreakpoint); XLineBreakpoint breakpoint = getLineBreakpoint(breakPosition); ErlangSuspendContext suspendContext = new ErlangSuspendContext(this, pid, snapshots);