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