From b5fb0faed8d6b25473a5b87108c2c3d82dfb5537 Mon Sep 17 00:00:00 2001 From: Jean Rouge Date: Mon, 12 Jun 2017 18:01:01 -0700 Subject: [PATCH] Improving the expression evaluator * 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 to fix that we just ignore the next "breakpoint reached" event occuring in an Erlang process right after evaluating an expression if it's the same breakpoint, in the same process. Better ideas welcome! * checking whether the evaluation resulted in an error, and nicely formatting it in if that's the case --- .../debugger/node/ErlangProcessSnapshot.java | 7 +++ .../debugger/xdebug/ErlangXDebugProcess.java | 58 ++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) 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);