notifications: Only propagate ACTIVATE events to HyperlinkListener
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / XDebugSessionImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.xdebugger.impl;
17
18 import com.intellij.execution.ExecutionManager;
19 import com.intellij.execution.configurations.RunConfiguration;
20 import com.intellij.execution.configurations.RunProfile;
21 import com.intellij.execution.executors.DefaultDebugExecutor;
22 import com.intellij.execution.filters.HyperlinkInfo;
23 import com.intellij.execution.filters.OpenFileHyperlinkInfo;
24 import com.intellij.execution.process.ProcessAdapter;
25 import com.intellij.execution.process.ProcessEvent;
26 import com.intellij.execution.process.ProcessHandler;
27 import com.intellij.execution.runners.ExecutionEnvironment;
28 import com.intellij.execution.ui.ConsoleView;
29 import com.intellij.execution.ui.ConsoleViewContentType;
30 import com.intellij.execution.ui.RunContentDescriptor;
31 import com.intellij.execution.ui.RunnerLayoutUi;
32 import com.intellij.ide.DataManager;
33 import com.intellij.notification.NotificationGroup;
34 import com.intellij.notification.NotificationListener;
35 import com.intellij.openapi.Disposable;
36 import com.intellij.openapi.actionSystem.AnAction;
37 import com.intellij.openapi.application.ApplicationManager;
38 import com.intellij.openapi.application.ReadAction;
39 import com.intellij.openapi.application.Result;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.markup.GutterIconRenderer;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.ui.MessageType;
44 import com.intellij.openapi.util.Comparing;
45 import com.intellij.openapi.util.Computable;
46 import com.intellij.openapi.util.Disposer;
47 import com.intellij.openapi.wm.ToolWindowId;
48 import com.intellij.ui.AppUIUtil;
49 import com.intellij.util.EventDispatcher;
50 import com.intellij.util.SmartList;
51 import com.intellij.util.containers.SmartHashSet;
52 import com.intellij.util.ui.UIUtil;
53 import com.intellij.xdebugger.*;
54 import com.intellij.xdebugger.breakpoints.*;
55 import com.intellij.xdebugger.frame.XExecutionStack;
56 import com.intellij.xdebugger.frame.XStackFrame;
57 import com.intellij.xdebugger.frame.XSuspendContext;
58 import com.intellij.xdebugger.frame.XValueMarkerProvider;
59 import com.intellij.xdebugger.impl.breakpoints.*;
60 import com.intellij.xdebugger.impl.evaluate.XDebuggerEditorLinePainter;
61 import com.intellij.xdebugger.impl.evaluate.quick.common.ValueLookupManager;
62 import com.intellij.xdebugger.impl.frame.XValueMarkers;
63 import com.intellij.xdebugger.impl.frame.XWatchesViewImpl;
64 import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl;
65 import com.intellij.xdebugger.impl.ui.XDebugSessionData;
66 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
67 import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
68 import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
69 import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
70 import gnu.trove.THashMap;
71 import gnu.trove.THashSet;
72 import org.jetbrains.annotations.NotNull;
73 import org.jetbrains.annotations.Nullable;
74
75 import javax.swing.*;
76 import javax.swing.event.HyperlinkEvent;
77 import javax.swing.event.HyperlinkListener;
78 import java.util.*;
79 import java.util.concurrent.atomic.AtomicBoolean;
80
81 /**
82  * @author nik
83  */
84 public class XDebugSessionImpl implements XDebugSession {
85   private static final Logger LOG = Logger.getInstance("#com.intellij.xdebugger.impl.XDebugSessionImpl");
86   public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Debugger messages", ToolWindowId.DEBUG,
87                                                                                                false);
88   private XDebugProcess myDebugProcess;
89   private final Map<XBreakpoint<?>, CustomizedBreakpointPresentation> myRegisteredBreakpoints =
90     new THashMap<>();
91   private final Set<XBreakpoint<?>> myInactiveSlaveBreakpoints = Collections.synchronizedSet(new SmartHashSet<>());
92   private boolean myBreakpointsDisabled;
93   private final XDebuggerManagerImpl myDebuggerManager;
94   private MyBreakpointListener myBreakpointListener;
95   private XSuspendContext mySuspendContext;
96   private XExecutionStack myCurrentExecutionStack;
97   private XStackFrame myCurrentStackFrame;
98   private boolean myIsTopFrame;
99   private volatile XSourcePosition myTopFramePosition;
100   private final AtomicBoolean myPaused = new AtomicBoolean();
101   private MyDependentBreakpointListener myDependentBreakpointListener;
102   private XValueMarkers<?, ?> myValueMarkers;
103   private final String mySessionName;
104   private @Nullable XDebugSessionTab mySessionTab;
105   private final XDebugSessionData mySessionData;
106   private XBreakpoint<?> myActiveNonLineBreakpoint;
107   private final EventDispatcher<XDebugSessionListener> myDispatcher = EventDispatcher.create(XDebugSessionListener.class);
108   private final Project myProject;
109   private final @Nullable ExecutionEnvironment myEnvironment;
110   private final AtomicBoolean myStopped = new AtomicBoolean();
111   private boolean myPauseActionSupported;
112   private final AtomicBoolean myShowTabOnSuspend;
113   private final List<AnAction> myRestartActions = new SmartList<>();
114   private final List<AnAction> myExtraStopActions = new SmartList<>();
115   private final List<AnAction> myExtraActions = new SmartList<>();
116   private ConsoleView myConsoleView;
117   private final Icon myIcon;
118
119   private volatile boolean breakpointsInitialized;
120
121   public XDebugSessionImpl(@NotNull ExecutionEnvironment environment, @NotNull XDebuggerManagerImpl debuggerManager) {
122     this(environment, debuggerManager, environment.getRunProfile().getName(), environment.getRunProfile().getIcon(), false, null);
123   }
124
125   public XDebugSessionImpl(@Nullable ExecutionEnvironment environment,
126                            @NotNull XDebuggerManagerImpl debuggerManager,
127                            @NotNull String sessionName,
128                            @Nullable Icon icon,
129                            boolean showTabOnSuspend,
130                            @Nullable RunContentDescriptor contentToReuse) {
131     myEnvironment = environment;
132     mySessionName = sessionName;
133     myDebuggerManager = debuggerManager;
134     myShowTabOnSuspend = new AtomicBoolean(showTabOnSuspend);
135     myProject = debuggerManager.getProject();
136     ValueLookupManager.getInstance(myProject).startListening();
137     myIcon = icon;
138
139     XDebugSessionData oldSessionData = null;
140     if (contentToReuse == null) {
141       contentToReuse = environment != null ? environment.getContentToReuse() : null;
142     }
143     if (contentToReuse != null) {
144       JComponent component = contentToReuse.getComponent();
145       if (component != null) {
146         oldSessionData = XDebugSessionData.DATA_KEY.getData(DataManager.getInstance().getDataContext(component));
147       }
148     }
149
150     String currentConfigurationName = getConfigurationName();
151     if (oldSessionData == null || !oldSessionData.getConfigurationName().equals(currentConfigurationName)) {
152       oldSessionData = new XDebugSessionData(getWatchExpressions(), currentConfigurationName);
153     }
154     mySessionData = oldSessionData;
155   }
156
157   @Override
158   @NotNull
159   public String getSessionName() {
160     return mySessionName;
161   }
162
163   @Override
164   @NotNull
165   public RunContentDescriptor getRunContentDescriptor() {
166     assertSessionTabInitialized();
167     //noinspection ConstantConditions
168     return mySessionTab.getRunContentDescriptor();
169   }
170
171   private void assertSessionTabInitialized() {
172     if (myShowTabOnSuspend.get()) {
173       LOG.error("Debug tool window isn't shown yet because debug process isn't suspended");
174     }
175     else {
176       LOG.assertTrue(mySessionTab != null, "Debug tool window not initialized yet!");
177     }
178   }
179
180   @Override
181   public void setPauseActionSupported(final boolean isSupported) {
182     myPauseActionSupported = isSupported;
183   }
184
185   @NotNull
186   public List<AnAction> getRestartActions() {
187     return myRestartActions;
188   }
189
190   public void addRestartActions(AnAction... restartActions) {
191     if (restartActions != null) {
192       Collections.addAll(myRestartActions, restartActions);
193     }
194   }
195
196   @NotNull
197   public List<AnAction> getExtraActions() {
198     return myExtraActions;
199   }
200
201   public void addExtraActions(AnAction... extraActions) {
202     if (extraActions != null) {
203       Collections.addAll(myExtraActions, extraActions);
204     }
205   }
206
207   public List<AnAction> getExtraStopActions() {
208     return myExtraStopActions;
209   }
210
211   public void addExtraStopActions(AnAction... extraStopActions) {
212     if (extraStopActions != null) {
213       Collections.addAll(myExtraStopActions, extraStopActions);
214     }
215   }
216
217   @Override
218   public void rebuildViews() {
219     if (!myShowTabOnSuspend.get() && mySessionTab != null) {
220       mySessionTab.rebuildViews();
221     }
222   }
223
224   @Override
225   @Nullable
226   public RunProfile getRunProfile() {
227     return myEnvironment != null ? myEnvironment.getRunProfile() : null;
228   }
229
230   public boolean isPauseActionSupported() {
231     return myPauseActionSupported;
232   }
233
234   @Override
235   @NotNull
236   public Project getProject() {
237     return myDebuggerManager.getProject();
238   }
239
240   @Override
241   @NotNull
242   public XDebugProcess getDebugProcess() {
243     return myDebugProcess;
244   }
245
246   @Override
247   public boolean isSuspended() {
248     return myPaused.get() && mySuspendContext != null;
249   }
250
251   @Override
252   public boolean isPaused() {
253     return myPaused.get();
254   }
255
256   @Override
257   @Nullable
258   public XStackFrame getCurrentStackFrame() {
259     return myCurrentStackFrame;
260   }
261
262   @Override
263   public XSuspendContext getSuspendContext() {
264     return mySuspendContext;
265   }
266
267   @Override
268   @Nullable
269   public XSourcePosition getCurrentPosition() {
270     return myCurrentStackFrame != null ? myCurrentStackFrame.getSourcePosition() : null;
271   }
272
273   @Nullable
274   @Override
275   public XSourcePosition getTopFramePosition() {
276     return myTopFramePosition;
277   }
278
279   XDebugSessionTab init(@NotNull XDebugProcess process, @Nullable RunContentDescriptor contentToReuse) {
280     LOG.assertTrue(myDebugProcess == null);
281     myDebugProcess = process;
282
283     if (myDebugProcess.checkCanInitBreakpoints()) {
284       initBreakpoints();
285     }
286
287     myDebugProcess.getProcessHandler().addProcessListener(new ProcessAdapter() {
288       @Override
289       public void processTerminated(final ProcessEvent event) {
290         stopImpl();
291         myDebugProcess.getProcessHandler().removeProcessListener(this);
292       }
293     });
294     //todo[nik] make 'createConsole()' method return ConsoleView
295     myConsoleView = (ConsoleView)myDebugProcess.createConsole();
296     if (!myShowTabOnSuspend.get()) {
297       initSessionTab(contentToReuse);
298     }
299
300     return mySessionTab;
301   }
302
303   public void reset() {
304     breakpointsInitialized = false;
305   }
306
307   @Override
308   public void initBreakpoints() {
309     ApplicationManager.getApplication().assertReadAccessAllowed();
310     LOG.assertTrue(!breakpointsInitialized);
311     breakpointsInitialized = true;
312
313     XBreakpointManagerImpl breakpointManager = myDebuggerManager.getBreakpointManager();
314     XDependentBreakpointManager dependentBreakpointManager = breakpointManager.getDependentBreakpointManager();
315     disableSlaveBreakpoints(dependentBreakpointManager);
316     processAllBreakpoints(true, false);
317
318     if (myBreakpointListener == null) {
319       myBreakpointListener = new MyBreakpointListener();
320       breakpointManager.addBreakpointListener(myBreakpointListener);
321     }
322     if (myDependentBreakpointListener == null) {
323       myDependentBreakpointListener = new MyDependentBreakpointListener();
324       dependentBreakpointManager.addListener(myDependentBreakpointListener);
325     }
326   }
327
328   @Override
329   public ConsoleView getConsoleView() {
330     return myConsoleView;
331   }
332
333   @Nullable
334   public XDebugSessionTab getSessionTab() {
335     return mySessionTab;
336   }
337
338   @Override
339   public RunnerLayoutUi getUI() {
340     assertSessionTabInitialized();
341     assert mySessionTab != null;
342     return mySessionTab.getUi();
343   }
344
345   private void initSessionTab(@Nullable RunContentDescriptor contentToReuse) {
346     mySessionTab = XDebugSessionTab.create(this, myIcon, myEnvironment, contentToReuse);
347     myDebugProcess.sessionInitialized();
348   }
349
350   public XDebugSessionData getSessionData() {
351     return mySessionData;
352   }
353
354   private void disableSlaveBreakpoints(final XDependentBreakpointManager dependentBreakpointManager) {
355     Set<XBreakpoint<?>> slaveBreakpoints = dependentBreakpointManager.getAllSlaveBreakpoints();
356     if (slaveBreakpoints.isEmpty()) {
357       return;
358     }
359
360     Set<XBreakpointType<?, ?>> breakpointTypes = new THashSet<>();
361     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
362       breakpointTypes.add(getBreakpointTypeClass(handler));
363     }
364     for (XBreakpoint<?> slaveBreakpoint : slaveBreakpoints) {
365       if (breakpointTypes.contains(slaveBreakpoint.getType())) {
366         myInactiveSlaveBreakpoints.add(slaveBreakpoint);
367       }
368     }
369   }
370
371   public void showSessionTab() {
372     RunContentDescriptor descriptor = getRunContentDescriptor();
373     ExecutionManager.getInstance(getProject()).getContentManager()
374       .showRunContent(DefaultDebugExecutor.getDebugExecutorInstance(), descriptor);
375   }
376
377   @Nullable
378   public XValueMarkers<?, ?> getValueMarkers() {
379     if (myValueMarkers == null) {
380       XValueMarkerProvider<?, ?> provider = myDebugProcess.createValueMarkerProvider();
381       if (provider != null) {
382         myValueMarkers = XValueMarkers.createValueMarkers(provider);
383       }
384     }
385     return myValueMarkers;
386   }
387
388   @SuppressWarnings("unchecked") //need to compile under 1.8, please do not remove before checking
389   private static XBreakpointType getBreakpointTypeClass(final XBreakpointHandler handler) {
390     return XDebuggerUtil.getInstance().findBreakpointType(handler.getBreakpointTypeClass());
391   }
392
393   private <B extends XBreakpoint<?>> void processBreakpoints(final XBreakpointHandler<B> handler,
394                                                              boolean register,
395                                                              final boolean temporary) {
396     Collection<? extends B> breakpoints = myDebuggerManager.getBreakpointManager().getBreakpoints(handler.getBreakpointTypeClass());
397     for (B b : breakpoints) {
398       handleBreakpoint(handler, b, register, temporary);
399     }
400   }
401
402   private <B extends XBreakpoint<?>> void handleBreakpoint(final XBreakpointHandler<B> handler, final B b, final boolean register,
403                                                            final boolean temporary) {
404     if (register) {
405       boolean active = ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() -> isBreakpointActive(b));
406       if (active) {
407         synchronized (myRegisteredBreakpoints) {
408           myRegisteredBreakpoints.put(b, new CustomizedBreakpointPresentation());
409         }
410         handler.registerBreakpoint(b);
411       }
412     }
413     else {
414       boolean removed;
415       synchronized (myRegisteredBreakpoints) {
416         removed = myRegisteredBreakpoints.remove(b) != null;
417       }
418       if (removed) {
419         handler.unregisterBreakpoint(b, temporary);
420       }
421     }
422   }
423
424   @Nullable
425   public CustomizedBreakpointPresentation getBreakpointPresentation(@NotNull XBreakpoint<?> breakpoint) {
426     synchronized (myRegisteredBreakpoints) {
427       return myRegisteredBreakpoints.get(breakpoint);
428     }
429   }
430
431   private void processAllHandlers(final XBreakpoint<?> breakpoint, final boolean register) {
432     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
433       processBreakpoint(breakpoint, handler, register);
434     }
435   }
436
437   private <B extends XBreakpoint<?>> void processBreakpoint(final XBreakpoint<?> breakpoint,
438                                                             final XBreakpointHandler<B> handler,
439                                                             boolean register) {
440     XBreakpointType<?, ?> type = breakpoint.getType();
441     if (handler.getBreakpointTypeClass().equals(type.getClass())) {
442       //noinspection unchecked
443       B b = (B)breakpoint;
444       handleBreakpoint(handler, b, register, false);
445     }
446   }
447
448   public boolean isBreakpointActive(@NotNull XBreakpoint<?> b) {
449     ApplicationManager.getApplication().assertReadAccessAllowed();
450     return !areBreakpointsMuted() && b.isEnabled() && !isInactiveSlaveBreakpoint(b) && !((XBreakpointBase)b).isDisposed();
451   }
452
453   @Override
454   public boolean areBreakpointsMuted() {
455     return mySessionData.isBreakpointsMuted();
456   }
457
458   @Override
459   public void addSessionListener(@NotNull XDebugSessionListener listener, @NotNull Disposable parentDisposable) {
460     myDispatcher.addListener(listener, parentDisposable);
461   }
462
463   @Override
464   public void addSessionListener(@NotNull final XDebugSessionListener listener) {
465     myDispatcher.addListener(listener);
466   }
467
468   @Override
469   public void removeSessionListener(@NotNull final XDebugSessionListener listener) {
470     myDispatcher.removeListener(listener);
471   }
472
473   @Override
474   public void setBreakpointMuted(boolean muted) {
475     ApplicationManager.getApplication().assertReadAccessAllowed();
476     if (areBreakpointsMuted() == muted) return;
477     mySessionData.setBreakpointsMuted(muted);
478     processAllBreakpoints(!muted, muted);
479     myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueAllBreakpointsUpdate();
480   }
481
482   @Override
483   public void stepOver(final boolean ignoreBreakpoints) {
484     if (!myDebugProcess.checkCanPerformCommands()) return;
485
486     if (ignoreBreakpoints) {
487       disableBreakpoints();
488     }
489     myDebugProcess.startStepOver(doResume());
490   }
491
492   @Override
493   public void stepInto() {
494     if (!myDebugProcess.checkCanPerformCommands()) return;
495
496     myDebugProcess.startStepInto(doResume());
497   }
498
499   @Override
500   public void stepOut() {
501     if (!myDebugProcess.checkCanPerformCommands()) return;
502
503     myDebugProcess.startStepOut(doResume());
504   }
505
506   @Override
507   public <V extends XSmartStepIntoVariant> void smartStepInto(XSmartStepIntoHandler<V> handler, V variant) {
508     if (!myDebugProcess.checkCanPerformCommands()) return;
509
510     doResume();
511     handler.startStepInto(variant);
512   }
513
514   @Override
515   public void forceStepInto() {
516     if (!myDebugProcess.checkCanPerformCommands()) return;
517
518     myDebugProcess.startForceStepInto(doResume());
519   }
520
521   @Override
522   public void runToPosition(@NotNull final XSourcePosition position, final boolean ignoreBreakpoints) {
523     if (!myDebugProcess.checkCanPerformCommands()) return;
524
525     if (ignoreBreakpoints) {
526       disableBreakpoints();
527     }
528     myDebugProcess.runToPosition(position, doResume());
529   }
530
531   @Override
532   public void pause() {
533     if (!myDebugProcess.checkCanPerformCommands()) return;
534
535     myDebugProcess.startPausing();
536   }
537
538   private void processAllBreakpoints(final boolean register, final boolean temporary) {
539     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
540       processBreakpoints(handler, register, temporary);
541     }
542   }
543
544   private void disableBreakpoints() {
545     myBreakpointsDisabled = true;
546     processAllBreakpoints(false, true);
547   }
548
549   @Override
550   public void resume() {
551     if (!myDebugProcess.checkCanPerformCommands()) return;
552
553     myDebugProcess.resume(doResume());
554   }
555
556   @Nullable
557   private XSuspendContext doResume() {
558     if (!myPaused.getAndSet(false)) {
559       return null;
560     }
561
562     myDispatcher.getMulticaster().beforeSessionResume();
563     XSuspendContext context = mySuspendContext;
564     mySuspendContext = null;
565     myCurrentExecutionStack = null;
566     myCurrentStackFrame = null;
567     myTopFramePosition = null;
568     myActiveNonLineBreakpoint = null;
569     updateExecutionPosition();
570     UIUtil.invokeLaterIfNeeded(() -> {
571       if (mySessionTab != null) {
572         mySessionTab.getUi().clearAttractionBy(XDebuggerUIConstants.LAYOUT_VIEW_BREAKPOINT_CONDITION);
573       }
574     });
575     myDispatcher.getMulticaster().sessionResumed();
576     return context;
577   }
578
579   @Override
580   public void updateExecutionPosition() {
581     // allowed only for the active session
582     if (myDebuggerManager.getCurrentSession() == this) {
583       boolean isTopFrame = isTopFrameSelected();
584       myDebuggerManager.updateExecutionPoint(getCurrentPosition(), !isTopFrame, getPositionIconRenderer(isTopFrame));
585     }
586   }
587
588   public boolean isTopFrameSelected() {
589     return myCurrentExecutionStack != null && myIsTopFrame;
590   }
591
592
593   @Override
594   public void showExecutionPoint() {
595     if (mySuspendContext != null) {
596       XExecutionStack executionStack = mySuspendContext.getActiveExecutionStack();
597       if (executionStack != null) {
598         XStackFrame topFrame = executionStack.getTopFrame();
599         if (topFrame != null) {
600           setCurrentStackFrame(executionStack, topFrame, true);
601           myDebuggerManager.showExecutionPosition();
602         }
603       }
604     }
605   }
606
607   @Override
608   public void setCurrentStackFrame(@NotNull XExecutionStack executionStack, @NotNull XStackFrame frame, boolean isTopFrame) {
609     if (mySuspendContext == null) return;
610
611     boolean frameChanged = myCurrentStackFrame != frame;
612     myCurrentExecutionStack = executionStack;
613     myCurrentStackFrame = frame;
614     myIsTopFrame = isTopFrame;
615     activateSession();
616
617     if (frameChanged) {
618       myDispatcher.getMulticaster().stackFrameChanged();
619     }
620   }
621
622   void activateSession() {
623     myDebuggerManager.setCurrentSession(this);
624     updateExecutionPosition();
625   }
626
627   public XBreakpoint<?> getActiveNonLineBreakpoint() {
628     if (myActiveNonLineBreakpoint != null) {
629       XSourcePosition breakpointPosition = myActiveNonLineBreakpoint.getSourcePosition();
630       XSourcePosition position = getTopFramePosition();
631       if (breakpointPosition == null ||
632           (position != null && !(breakpointPosition.getFile().equals(position.getFile()) && breakpointPosition.getLine() == position.getLine()))) {
633         return myActiveNonLineBreakpoint;
634       }
635     }
636     return null;
637   }
638
639   @Nullable
640   private GutterIconRenderer getPositionIconRenderer(boolean isTopFrame) {
641     if (!isTopFrame) {
642       return null;
643     }
644     XBreakpoint<?> activeNonLineBreakpoint = getActiveNonLineBreakpoint();
645     if (activeNonLineBreakpoint != null) {
646       return ((XBreakpointBase<?, ?, ?>)activeNonLineBreakpoint).createGutterIconRenderer();
647     }
648     if (myCurrentExecutionStack != null) {
649       return myCurrentExecutionStack.getExecutionLineIconRenderer();
650     }
651     return null;
652   }
653
654   @Override
655   public void updateBreakpointPresentation(@NotNull final XLineBreakpoint<?> breakpoint,
656                                            @Nullable final Icon icon,
657                                            @Nullable final String errorMessage) {
658     CustomizedBreakpointPresentation presentation;
659     synchronized (myRegisteredBreakpoints) {
660       presentation = myRegisteredBreakpoints.get(breakpoint);
661       if (presentation == null ||
662           (Comparing.equal(presentation.getIcon(), icon) && Comparing.strEqual(presentation.getErrorMessage(), errorMessage))) {
663         return;
664       }
665
666       presentation.setErrorMessage(errorMessage);
667       presentation.setIcon(icon);
668     }
669     myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueBreakpointUpdate((XLineBreakpointImpl<?>)breakpoint);
670   }
671
672   @Override
673   public boolean breakpointReached(@NotNull final XBreakpoint<?> breakpoint, @NotNull final XSuspendContext suspendContext) {
674     return breakpointReached(breakpoint, null, suspendContext);
675   }
676
677   @Override
678   public boolean breakpointReached(@NotNull final XBreakpoint<?> breakpoint, @Nullable String evaluatedLogExpression,
679                                    @NotNull XSuspendContext suspendContext) {
680     return breakpointReached(breakpoint, evaluatedLogExpression, suspendContext, true);
681   }
682
683   public void breakpointReachedNoProcessing(@NotNull final XBreakpoint<?> breakpoint, @NotNull XSuspendContext suspendContext) {
684     breakpointReached(breakpoint, null, suspendContext, false);
685   }
686
687   private boolean breakpointReached(@NotNull final XBreakpoint<?> breakpoint, @Nullable String evaluatedLogExpression,
688                                    @NotNull XSuspendContext suspendContext, boolean doProcessing) {
689     if (doProcessing) {
690       if (breakpoint.isLogMessage()) {
691         XSourcePosition position = breakpoint.getSourcePosition();
692         OpenFileHyperlinkInfo hyperlinkInfo =
693           position != null ? new OpenFileHyperlinkInfo(myProject, position.getFile(), position.getLine()) : null;
694         printMessage(XDebuggerBundle.message("xbreakpoint.reached.text") + " ", XBreakpointUtil.getShortText(breakpoint), hyperlinkInfo);
695       }
696
697       if (evaluatedLogExpression != null) {
698         printMessage(evaluatedLogExpression, null, null);
699       }
700
701       processDependencies(breakpoint);
702
703       if (breakpoint.getSuspendPolicy() == SuspendPolicy.NONE) {
704         return false;
705       }
706     }
707
708     myActiveNonLineBreakpoint =
709       (!(breakpoint instanceof XLineBreakpoint) || ((XLineBreakpoint)breakpoint).getType().canBeHitInOtherPlaces()) ? breakpoint : null;
710
711     // set this session active on breakpoint, update execution position will be called inside positionReached
712     myDebuggerManager.setCurrentSession(this);
713
714     positionReachedInternal(suspendContext, true);
715
716     if (doProcessing && breakpoint instanceof XLineBreakpoint<?> && ((XLineBreakpoint)breakpoint).isTemporary()) {
717       handleTemporaryBreakpointHit(breakpoint);
718     }
719     return true;
720   }
721
722   private void handleTemporaryBreakpointHit(final XBreakpoint<?> breakpoint) {
723     addSessionListener(new XDebugSessionListener() {
724       private void removeBreakpoint() {
725         XDebuggerUtil.getInstance().removeBreakpoint(myProject, breakpoint);
726         removeSessionListener(this);
727       }
728
729       @Override
730       public void sessionResumed() {
731         removeBreakpoint();
732       }
733
734       @Override
735       public void sessionStopped() {
736         removeBreakpoint();
737       }
738     });
739   }
740
741   public void processDependencies(final XBreakpoint<?> breakpoint) {
742     XDependentBreakpointManager dependentBreakpointManager = myDebuggerManager.getBreakpointManager().getDependentBreakpointManager();
743     if (!dependentBreakpointManager.isMasterOrSlave(breakpoint)) return;
744
745     List<XBreakpoint<?>> breakpoints = dependentBreakpointManager.getSlaveBreakpoints(breakpoint);
746     myInactiveSlaveBreakpoints.removeAll(breakpoints);
747     for (XBreakpoint<?> slaveBreakpoint : breakpoints) {
748       processAllHandlers(slaveBreakpoint, true);
749     }
750
751     if (dependentBreakpointManager.getMasterBreakpoint(breakpoint) != null && !dependentBreakpointManager.isLeaveEnabled(breakpoint)) {
752       boolean added = myInactiveSlaveBreakpoints.add(breakpoint);
753       if (added) {
754         processAllHandlers(breakpoint, false);
755         myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueBreakpointUpdate(breakpoint);
756       }
757     }
758   }
759
760   private void printMessage(final String message, final String hyperLinkText, @Nullable final HyperlinkInfo info) {
761     AppUIUtil.invokeOnEdt(() -> {
762       myConsoleView.print(message, ConsoleViewContentType.SYSTEM_OUTPUT);
763       if (info != null) {
764         myConsoleView.printHyperlink(hyperLinkText, info);
765       }
766       else if (hyperLinkText != null) {
767         myConsoleView.print(hyperLinkText, ConsoleViewContentType.SYSTEM_OUTPUT);
768       }
769       myConsoleView.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT);
770     });
771   }
772
773   public void unsetPaused() {
774     myPaused.set(false);
775   }
776
777   private void positionReachedInternal(@NotNull final XSuspendContext suspendContext, boolean attract) {
778     enableBreakpoints();
779     mySuspendContext = suspendContext;
780     myCurrentExecutionStack = suspendContext.getActiveExecutionStack();
781     myCurrentStackFrame = myCurrentExecutionStack != null ? myCurrentExecutionStack.getTopFrame() : null;
782     myIsTopFrame = true;
783     myTopFramePosition = myCurrentStackFrame != null ? myCurrentStackFrame.getSourcePosition() : null;
784
785     myPaused.set(true);
786
787     updateExecutionPosition();
788
789     final boolean showOnSuspend = myShowTabOnSuspend.compareAndSet(true, false);
790     if (showOnSuspend || attract) {
791       AppUIUtil.invokeLaterIfProjectAlive(myProject, () -> {
792         if (showOnSuspend) {
793           initSessionTab(null);
794           showSessionTab();
795         }
796
797         // user attractions should only be made if event happens independently (e.g. program paused/suspended)
798         // and should not be made when user steps in the code
799         if (attract) {
800           if (mySessionTab == null) {
801             LOG.debug("Cannot request focus because Session Tab is not initialized yet");
802             return;
803           }
804
805           if (XDebuggerSettingManagerImpl.getInstanceImpl().getGeneralSettings().isShowDebuggerOnBreakpoint()) {
806             mySessionTab.toFront(true, this::updateExecutionPosition);
807           }
808
809           if (myTopFramePosition == null) {
810             // if there is no source position available, we should somehow tell the user that session is stopped.
811             // the best way is to show the stack frames.
812             XDebugSessionTab.showFramesView(this);
813           }
814
815           mySessionTab.getUi().attractBy(XDebuggerUIConstants.LAYOUT_VIEW_BREAKPOINT_CONDITION);
816         }
817       });
818     }
819
820     myDispatcher.getMulticaster().sessionPaused();
821   }
822
823   @Override
824   public void positionReached(@NotNull final XSuspendContext suspendContext) {
825     positionReached(suspendContext, false);
826   }
827
828   public void positionReached(@NotNull XSuspendContext suspendContext, boolean attract) {
829     myActiveNonLineBreakpoint = null;
830     positionReachedInternal(suspendContext, attract);
831   }
832
833   @Override
834   public void sessionResumed() {
835     doResume();
836   }
837
838   private void enableBreakpoints() {
839     if (myBreakpointsDisabled) {
840       myBreakpointsDisabled = false;
841       new ReadAction() {
842         @Override
843         protected void run(@NotNull Result result) {
844           processAllBreakpoints(true, false);
845         }
846       }.execute();
847     }
848   }
849
850   @Override
851   public boolean isStopped() {
852     return myStopped.get();
853   }
854
855   private void stopImpl() {
856     if (!myStopped.compareAndSet(false, true)) {
857       return;
858     }
859
860     try {
861       if (breakpointsInitialized) {
862         XBreakpointManagerImpl breakpointManager = myDebuggerManager.getBreakpointManager();
863         if (myBreakpointListener != null) {
864           breakpointManager.removeBreakpointListener(myBreakpointListener);
865         }
866         if (myDependentBreakpointListener != null) {
867           breakpointManager.getDependentBreakpointManager().removeListener(myDependentBreakpointListener);
868         }
869       }
870     }
871     finally {
872       //noinspection unchecked
873       myDebugProcess.stopAsync().done(aVoid -> {
874         if (!myProject.isDisposed()) {
875           myProject.getMessageBus().syncPublisher(XDebuggerManager.TOPIC).processStopped(myDebugProcess);
876         }
877
878         if (mySessionTab != null) {
879           ((XWatchesViewImpl)mySessionTab.getWatchesView()).updateSessionData();
880           mySessionTab.detachFromSession();
881         }
882         else if (myConsoleView != null) {
883           AppUIUtil.invokeOnEdt(() -> Disposer.dispose(myConsoleView));
884         }
885
886         myTopFramePosition = null;
887         myCurrentExecutionStack = null;
888         myCurrentStackFrame = null;
889         mySuspendContext = null;
890
891         updateExecutionPosition();
892
893         if (myValueMarkers != null) {
894           myValueMarkers.clear();
895         }
896         if (XDebuggerSettingManagerImpl.getInstanceImpl().getGeneralSettings().isUnmuteOnStop()) {
897           mySessionData.setBreakpointsMuted(false);
898         }
899         myDebuggerManager.removeSession(this);
900         myDispatcher.getMulticaster().sessionStopped();
901         myProject.putUserData(XDebuggerEditorLinePainter.CACHE, null);
902
903         synchronized (myRegisteredBreakpoints) {
904           myRegisteredBreakpoints.clear();
905         }
906       });
907     }
908   }
909
910   public boolean isInactiveSlaveBreakpoint(final XBreakpoint<?> breakpoint) {
911     return myInactiveSlaveBreakpoints.contains(breakpoint);
912   }
913
914   @Override
915   public void stop() {
916     ProcessHandler processHandler = myDebugProcess.getProcessHandler();
917     if (processHandler.isProcessTerminated() || processHandler.isProcessTerminating()) return;
918
919     if (processHandler.detachIsDefault()) {
920       processHandler.detachProcess();
921     }
922     else {
923       processHandler.destroyProcess();
924     }
925   }
926
927   @Override
928   public void reportError(@NotNull final String message) {
929     reportMessage(message, MessageType.ERROR);
930   }
931
932   @Override
933   public void reportMessage(@NotNull final String message, @NotNull final MessageType type) {
934     reportMessage(message, type, null);
935   }
936
937   @Override
938   public void reportMessage(@NotNull final String message, @NotNull final MessageType type, @Nullable final HyperlinkListener listener) {
939     NotificationListener notificationListener = listener == null ? null : (notification, event) -> {
940       if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
941         listener.hyperlinkUpdate(event);
942       }
943     };
944     NOTIFICATION_GROUP.createNotification("", message, type.toNotificationType(), notificationListener).notify(myProject);
945   }
946
947   private class MyBreakpointListener implements XBreakpointListener<XBreakpoint<?>> {
948     @Override
949     public void breakpointAdded(@NotNull final XBreakpoint<?> breakpoint) {
950       if (!myBreakpointsDisabled) {
951         processAllHandlers(breakpoint, true);
952       }
953     }
954
955     @Override
956     public void breakpointRemoved(@NotNull final XBreakpoint<?> breakpoint) {
957       if (getActiveNonLineBreakpoint() == breakpoint) {
958         myActiveNonLineBreakpoint = null;
959       }
960       processAllHandlers(breakpoint, false);
961     }
962
963     @Override
964     public void breakpointChanged(@NotNull final XBreakpoint<?> breakpoint) {
965       breakpointRemoved(breakpoint);
966       breakpointAdded(breakpoint);
967     }
968   }
969
970   private class MyDependentBreakpointListener implements XDependentBreakpointListener {
971     @Override
972     public void dependencySet(final XBreakpoint<?> slave, final XBreakpoint<?> master) {
973       boolean added = myInactiveSlaveBreakpoints.add(slave);
974       if (added) {
975         processAllHandlers(slave, false);
976       }
977     }
978
979     @Override
980     public void dependencyCleared(final XBreakpoint<?> breakpoint) {
981       boolean removed = myInactiveSlaveBreakpoints.remove(breakpoint);
982       if (removed) {
983         processAllHandlers(breakpoint, true);
984       }
985     }
986   }
987
988   @NotNull
989   private String getConfigurationName() {
990     if (myEnvironment != null) {
991       RunProfile profile = myEnvironment.getRunProfile();
992       if (profile instanceof RunConfiguration) {
993         return ((RunConfiguration)profile).getType().getId();
994       }
995     }
996     return getSessionName();
997   }
998
999   public void setWatchExpressions(@NotNull XExpression[] watchExpressions) {
1000     mySessionData.setWatchExpressions(watchExpressions);
1001     myDebuggerManager.getWatchesManager().setWatches(getConfigurationName(), watchExpressions);
1002   }
1003
1004   XExpression[] getWatchExpressions() {
1005     return myDebuggerManager.getWatchesManager().getWatches(getConfigurationName());
1006   }
1007
1008   @Nullable
1009   public ExecutionEnvironment getExecutionEnvironment() {
1010     return myEnvironment;
1011   }
1012 }