4a680f7c598b006118ff47feeee3f668ce9fb806
[idea/community.git] / platform / script-debugger / debugger-ui / src / org / jetbrains / debugger / DebugProcessImpl.java
1 package org.jetbrains.debugger;
2
3 import com.intellij.execution.ExecutionResult;
4 import com.intellij.execution.process.ProcessHandler;
5 import com.intellij.openapi.vfs.VirtualFile;
6 import com.intellij.util.Url;
7 import com.intellij.util.containers.ContainerUtil;
8 import com.intellij.util.io.socketConnection.ConnectionStatus;
9 import com.intellij.util.io.socketConnection.SocketConnectionListener;
10 import com.intellij.xdebugger.XDebugProcess;
11 import com.intellij.xdebugger.XDebugSession;
12 import com.intellij.xdebugger.XExpression;
13 import com.intellij.xdebugger.breakpoints.XBreakpoint;
14 import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
15 import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
16 import com.intellij.xdebugger.frame.XSuspendContext;
17 import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
18 import org.jetbrains.annotations.NotNull;
19 import org.jetbrains.annotations.Nullable;
20 import org.jetbrains.debugger.connection.VmConnection;
21 import org.jetbrains.debugger.frame.SuspendContextImpl;
22
23 import javax.swing.event.HyperlinkListener;
24 import java.util.concurrent.ConcurrentMap;
25 import java.util.concurrent.atomic.AtomicBoolean;
26
27 public abstract class DebugProcessImpl<C extends VmConnection> extends XDebugProcess {
28   protected final AtomicBoolean repeatStepInto = new AtomicBoolean();
29   protected volatile StepAction lastStep;
30   protected volatile CallFrame lastCallFrame;
31   protected volatile boolean isForceStep;
32
33   protected final ConcurrentMap<Url, VirtualFile> urlToFileCache = ContainerUtil.newConcurrentMap();
34
35   protected final C connection;
36
37   private boolean processBreakpointConditionsAtIdeSide;
38
39   private final XDebuggerEditorsProvider editorsProvider;
40   private final XSmartStepIntoHandler<?> smartStepIntoHandler;
41   protected XBreakpointHandler<?>[] breakpointHandlers;
42
43   protected final ExecutionResult executionResult;
44
45   protected DebugProcessImpl(@NotNull XDebugSession session, @NotNull C connection,
46                              @NotNull XDebuggerEditorsProvider editorsProvider,
47                              @Nullable XSmartStepIntoHandler<?> smartStepIntoHandler,
48                              @Nullable ExecutionResult executionResult) {
49     super(session);
50
51     this.executionResult = executionResult;
52     this.connection = connection;
53     this.editorsProvider = editorsProvider;
54     this.smartStepIntoHandler = smartStepIntoHandler;
55
56     connection.addListener(new SocketConnectionListener() {
57       @Override
58       public void statusChanged(@NotNull ConnectionStatus status) {
59         if (status == ConnectionStatus.DISCONNECTED || status == ConnectionStatus.DETACHED) {
60           if (status == ConnectionStatus.DETACHED) {
61             ProcessHandler processHandler = doGetProcessHandler();
62             if (processHandler != null) {
63               processHandler.detachProcess();
64             }
65           }
66           getSession().stop();
67         }
68         else {
69           getSession().rebuildViews();
70         }
71       }
72     });
73   }
74
75   @NotNull
76   public final C getConnection() {
77     return connection;
78   }
79
80   @Override
81   @Nullable
82   public final XSmartStepIntoHandler<?> getSmartStepIntoHandler() {
83     return smartStepIntoHandler;
84   }
85
86   @NotNull
87   @Override
88   public final XBreakpointHandler<?>[] getBreakpointHandlers() {
89     return breakpointHandlers;
90   }
91
92   @Override
93   @NotNull
94   public final XDebuggerEditorsProvider getEditorsProvider() {
95     return editorsProvider;
96   }
97
98   public void setProcessBreakpointConditionsAtIdeSide(final boolean processBreakpointConditionsAtIdeSide) {
99     this.processBreakpointConditionsAtIdeSide = processBreakpointConditionsAtIdeSide;
100   }
101
102   public final Vm getVm() {
103     return connection.getVm();
104   }
105
106   private void updateLastCallFrame() {
107     Vm vm = getVm();
108     if (vm != null) {
109       SuspendContext context = vm.getSuspendContextManager().getContext();
110       if (context != null) {
111         lastCallFrame = context.getTopFrame();
112         return;
113       }
114     }
115
116     lastCallFrame = null;
117   }
118
119   @Override
120   public boolean checkCanPerformCommands() {
121     return getVm() != null;
122   }
123
124   @Override
125   public boolean isValuesCustomSorted() {
126     return true;
127   }
128
129   @Override
130   public void startStepOver() {
131     updateLastCallFrame();
132     continueVm(StepAction.OVER);
133   }
134
135   @Override
136   public void startForceStepInto() {
137     isForceStep = true;
138     startStepInto();
139   }
140
141   @Override
142   public void startStepInto() {
143     updateLastCallFrame();
144     continueVm(StepAction.IN);
145   }
146
147   @Override
148   public void startStepOut() {
149     if (isVmStepOutCorrect()) {
150       lastCallFrame = null;
151     }
152     else {
153       updateLastCallFrame();
154     }
155     continueVm(StepAction.OUT);
156   }
157
158   // some VM (firefox for example) doesn't implement step out correctly, so, we need to fix it
159   protected boolean isVmStepOutCorrect() {
160     return true;
161   }
162
163   protected void continueVm(@NotNull StepAction stepAction) {
164     SuspendContextManager suspendContextManager = getVm().getSuspendContextManager();
165     if (stepAction == StepAction.CONTINUE) {
166       if (suspendContextManager.getContext() == null) {
167         // on resumed we ask session to resume, and session then call our "resume", but we have already resumed, so, we don't need to send "continue" message
168         return;
169       }
170
171       lastStep = null;
172       lastCallFrame = null;
173       urlToFileCache.clear();
174     }
175     else {
176       lastStep = stepAction;
177     }
178     suspendContextManager.continueVm(stepAction, 1);
179   }
180
181   protected final void setOverlay() {
182     getVm().getSuspendContextManager().setOverlayMessage("Paused in debugger");
183   }
184
185   protected final void processBreakpoint(@NotNull final SuspendContext suspendContext,
186                                          @NotNull final XBreakpoint<?> breakpoint,
187                                          @NotNull final SuspendContextImpl xSuspendContext) {
188     XExpression conditionExpression = breakpoint.getConditionExpression();
189     String condition = conditionExpression == null ? null : conditionExpression.getExpression();
190     if (!processBreakpointConditionsAtIdeSide || condition == null) {
191       processBreakpointLogExpressionAndSuspend(breakpoint, xSuspendContext, suspendContext);
192     }
193     else {
194       xSuspendContext.evaluateExpression(condition).done(new ContextDependentAsyncResultConsumer<String>(suspendContext) {
195         @Override
196         public void consume(String evaluationResult, @NotNull Vm vm) {
197           if ("false".equals(evaluationResult)) {
198             resume();
199           }
200           else {
201             processBreakpointLogExpressionAndSuspend(breakpoint, xSuspendContext, suspendContext);
202           }
203         }
204       }).rejected(new ContextDependentAsyncResultConsumer<Throwable>(suspendContext) {
205         @Override
206         public void consume(Throwable failure, @NotNull Vm vm) {
207           processBreakpointLogExpressionAndSuspend(breakpoint, xSuspendContext, suspendContext);
208         }
209       });
210     }
211   }
212
213   private void processBreakpointLogExpressionAndSuspend(@NotNull final XBreakpoint<?> breakpoint,
214                                                         @NotNull final SuspendContextImpl xSuspendContext,
215                                                         @NotNull SuspendContext suspendContext) {
216     XExpression logExpressionObject = breakpoint.getLogExpressionObject();
217     final String logExpression = logExpressionObject == null ? null : logExpressionObject.getExpression();
218
219     if (logExpression == null) {
220       breakpointReached(breakpoint, null, xSuspendContext);
221     }
222     else {
223       xSuspendContext.evaluateExpression(logExpression).done(new ContextDependentAsyncResultConsumer<String>(suspendContext) {
224         @Override
225         public void consume(String logResult, @NotNull Vm vm) {
226           breakpointReached(breakpoint, logResult, xSuspendContext);
227         }
228       }).rejected(new ContextDependentAsyncResultConsumer<Throwable>(suspendContext) {
229         @Override
230         public void consume(Throwable logResult, @NotNull Vm vm) {
231           breakpointReached(breakpoint, "Failed to evaluate expression: " + logExpression, xSuspendContext);
232         }
233       });
234     }
235   }
236
237   private void breakpointReached(@NotNull XBreakpoint<?> breakpoint,
238                                  @Nullable String evaluatedLogExpression,
239                                  @NotNull XSuspendContext suspendContext) {
240     if (getSession().breakpointReached(breakpoint, evaluatedLogExpression, suspendContext)) {
241       setOverlay();
242     }
243     else {
244       resume();
245     }
246   }
247
248   @Override
249   public final void startPausing() {
250     connection.getVm().getSuspendContextManager().suspend().rejected(new RejectErrorReporter(getSession(), "Cannot pause"));
251   }
252
253   @Override
254   public final String getCurrentStateMessage() {
255     return connection.getState().getMessage();
256   }
257
258   @Nullable
259   @Override
260   public final HyperlinkListener getCurrentStateHyperlinkListener() {
261     return getConnection().getState().getMessageLinkListener();
262   }
263
264   @Override
265   protected final ProcessHandler doGetProcessHandler() {
266     return executionResult != null ? executionResult.getProcessHandler() : null;
267   }
268 }